diff --git a/.eslintrc b/.eslintrc index 7398fd8f225..684a3a6707b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -24,6 +24,7 @@ "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-empty-function": "off", "consistent-return": "off", + "class-methods-use-this": "off", "func-names": "off", "no-console": ["error", { "allow": ["warn", "error"] }], "no-param-reassign": "off", diff --git a/package.json b/package.json index 3d8d5606a5d..618885fce13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockchain-wallet-v4", - "version": "4.82.3", + "version": "4.82.5", "license": "AGPL-3.0-or-later", "private": true, "author": { diff --git a/packages/blockchain-wallet-v4-frontend/src/assets/locales/en.json b/packages/blockchain-wallet-v4-frontend/src/assets/locales/en.json index fd6f5fe4428..5348dfa0962 100644 --- a/packages/blockchain-wallet-v4-frontend/src/assets/locales/en.json +++ b/packages/blockchain-wallet-v4-frontend/src/assets/locales/en.json @@ -1016,7 +1016,6 @@ "modals.active-rewards.withdrawal.total-balance-message": "Requesting a withdrawal from your Active Rewards Account will send your total balance to your Bitcoin Trading Account.", "modals.active-rewards.withdrawalrequested.activerewardsbutton": "Go to {coin} Active Rewards", "modals.active-rewards.withdrawalrequested.description": "Your withdrawal will be executed once this week's strategy is complete.", - "modals.active-rewards.withdrawalrequested.historybutton": "See Transfer Details", "modals.active-rewards.withdrawalrequested.title": "Withdrawal Requested", "modals.activeRewards.balancedropdown.button": "Show in {currency}", "modals.addbitcoinwallet.button": "Create New Bitcoin Wallet", diff --git a/packages/blockchain-wallet-v4-frontend/src/components/Form/AmountFieldInput/index.tsx b/packages/blockchain-wallet-v4-frontend/src/components/Form/AmountFieldInput/index.tsx index 76e7e92aa7c..38edcded113 100644 --- a/packages/blockchain-wallet-v4-frontend/src/components/Form/AmountFieldInput/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/components/Form/AmountFieldInput/index.tsx @@ -6,6 +6,7 @@ import styled from 'styled-components' import Currencies from '@core/exchange/currencies' import { Icon, Text } from 'blockchain-info-components' import { AmountTextBox } from 'components/Exchange' +import { useCoinConfig } from 'hooks' import { formatTextAmount } from 'services/forms' const AmountContainer = styled.div` @@ -63,6 +64,8 @@ const AmountFieldInput: React.FC = ({ return formatTextAmount(value, allValues && allValues.fix === 'FIAT') } + const { coinfig } = window.coins[coin || 'BTC'] + const resizeSymbol = (isFiat, inputNode, fontSizeRatio, fontSizeNumber) => { if (Number(fontSizeRatio) > 0) { setRatio(fontSizeRatio > 1 ? 1 : fontSizeRatio) @@ -118,7 +121,7 @@ const AmountFieldInput: React.FC = ({ {fix === 'CRYPTO' && ( - {coin} + {coinfig.displaySymbol} )} {showToggle ? ( @@ -144,7 +147,7 @@ const AmountFieldInput: React.FC = ({ weight={500} data-e2e='sendQuoteAmount' > - {fix === 'FIAT' && coin} {quote} {fix === 'CRYPTO' && fiatCurrency} + {fix === 'FIAT' && coinfig.displaySymbol} {quote} {fix === 'CRYPTO' && fiatCurrency} ) : null} diff --git a/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxCoinAddresses/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxCoinAddresses/selectors.ts index 41ccf2501c5..42989b9986a 100644 --- a/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxCoinAddresses/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxCoinAddresses/selectors.ts @@ -2,10 +2,10 @@ import { concat, curry, reduce, sequence } from 'ramda' import { Exchange, Remote } from '@core' -import { getBalance } from '@core/redux/data/coins/selectors' import { ADDRESS_TYPES } from '@core/redux/payment/btc/utils' import { EarnAccountBalanceResponseType } from '@core/types' import { selectors } from 'data' +import { getCoinNonCustodialBalance } from 'data/balances/selectors' export const getData = ( state, @@ -72,7 +72,7 @@ export const getData = ( const toSelfCustodyDropdown = (balance) => [ { label: buildSelfCustodyDisplay(balance), - value: { balance, label: 'Private Key', type: ADDRESS_TYPES.ACCOUNT } + value: { balance, coin, label: 'Private Key', type: ADDRESS_TYPES.ACCOUNT } } ] const toInterestDropdown = (account) => @@ -104,7 +104,9 @@ export const getData = ( .map(toGroup('Custodial Wallet')) : Remote.of([]), includeSelfCustody - ? getBalance(coin, state).map(toSelfCustodyDropdown).map(toGroup('Private Key')) + ? getCoinNonCustodialBalance(coin)(state) + .map(toSelfCustodyDropdown) + .map(toGroup('Private Key')) : Remote.of([]), includeInterest ? selectors.components.interest diff --git a/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxXlmAddresses/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxXlmAddresses/selectors.ts index 6cce6eb201b..4dec860b460 100644 --- a/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxXlmAddresses/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/components/Form/SelectBoxXlmAddresses/selectors.ts @@ -28,7 +28,7 @@ export const getData = ( if (has('balance', wallet)) { const xlmDisplay = Exchange.displayCoinToCoin({ coin: 'XLM', - value: wallet.balnace + value: wallet.balance }) return `${wallet.label} (${xlmDisplay})` } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/actions.ts b/packages/blockchain-wallet-v4-frontend/src/data/actions.ts index 5a7a06ffd10..4bcfafabf01 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/actions.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/actions.ts @@ -13,6 +13,7 @@ import * as form from './form/actions' import { actions as goals } from './goals/slice' import { actions as logs } from './logs/slice' import * as middleware from './middleware/actions' +import * as activities from './middleware/webSocket/activities/actions' import * as ws from './middleware/webSocket/coins/actions' import { actions as misc } from './misc/slice' import { actions as modals } from './modals/slice' @@ -24,6 +25,7 @@ import { actions as signup } from './signup/slice' import * as wallet from './wallet/actions' export { + activities, alerts, analytics, auth, diff --git a/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.ts index 1f9cde6cc1e..c515bad62a4 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.ts @@ -4,7 +4,9 @@ import { startSubmit, stopSubmit } from 'redux-form' import { all, call, fork, put, select, take } from 'redux-saga/effects' import { coreSelectors } from '@core' +import { APIType } from '@core/network/api' import { CountryScope, WalletOptionsType } from '@core/types' +import { sha256 } from '@core/walletCrypto' import { actions, actionTypes, selectors } from 'data' import { ClientErrorProperties } from 'data/analytics/types/errors' import { fetchBalances } from 'data/balances/sagas' @@ -42,7 +44,7 @@ import { ProductAuthOptions } from './types' -export default ({ api, coreSagas, networks }) => { +export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; networks: any }) => { const logLocation = 'auth/sagas' const { createExchangeUser, createUser } = profileSagas({ api, @@ -242,6 +244,48 @@ export default ({ api, coreSagas, networks }) => { } } + const authWalletPubkeyService = function* ({ + guid, + sharedKey + }: { + guid: string + sharedKey: string + }) { + try { + const sharedKeyHash = sha256(sharedKey).toString('hex') + const response = yield call(api.authWalletPubkeyService, { guid, sharedKeyHash }) + return response.success + } catch (e) { + return false + } + } + + const subscribeToUnifiedBalances = function* () { + const guid = yield select(selectors.core.wallet.getGuid) + const sharedKey = yield select(selectors.core.wallet.getSharedKey) + + const guidHash = sha256(guid).toString('hex') + const sharedKeyHash = sha256(sharedKey).toString('hex') + + const auth = yield call(authWalletPubkeyService, { guid, sharedKey }) + if (!auth) return + try { + yield put(actions.core.data.coins.getSubscriptionsLoading()) + const subscriptions: ReturnType = yield call( + api.getSubscriptions, + { guidHash, sharedKeyHash } + ) + yield put(actions.core.data.coins.getSubscriptionsSuccess(subscriptions)) + if (subscriptions.currencies.length === 0) { + yield put(actions.core.data.coins.initializeSubscriptions()) + } else { + yield put(actions.core.data.coins.fetchUnifiedBalances()) + } + } catch (e) { + yield put(actions.core.data.coins.getSubscriptionsFailure()) + } + } + const checkWalletDerivationsLegitimacy = function* () { const accounts = yield call(coreSagas.wallet.getAccountsWithIncompleteDerivations) @@ -325,7 +369,6 @@ export default ({ api, coreSagas, networks }) => { yield call(coreSagas.kvStore.xlm.fetchMetadataXlm, askSecondPasswordEnhancer) yield call(coreSagas.kvStore.bch.fetchMetadataBch) yield call(coreSagas.data.xlm.fetchLedgerDetails) - yield call(coreSagas.data.xlm.fetchData) yield call(authNabu, firstLogin) if (product === ProductAuthOptions.EXCHANGE && (recovery || !firstLogin)) { @@ -334,7 +377,7 @@ export default ({ api, coreSagas, networks }) => { ) } const guid = yield select(selectors.core.wallet.getGuid) - if (firstLogin && !isAccountReset && !recovery) { + if (firstLogin && !isAccountReset && !recovery && country) { // create nabu user yield call(createUser) yield call(api.setUserInitialAddress, country, state) @@ -390,6 +433,7 @@ export default ({ api, coreSagas, networks }) => { } else { yield put(actions.router.push('/home')) } + yield call(subscribeToUnifiedBalances) yield call(fetchBalances) yield call(saveGoals, firstLogin) // We run goals in accountResetSaga in this case @@ -400,7 +444,8 @@ export default ({ api, coreSagas, networks }) => { yield call(upgradeAddressLabelsSaga) yield put(actions.auth.startLogoutTimer()) yield call(startCoinWebsockets) - + // TODO: temp, do we want this here? + yield put(actions.activities.startSocket()) // store guid and email in cache for future login yield put(actions.cache.guidEntered(guid)) if (email) { @@ -521,6 +566,7 @@ export default ({ api, coreSagas, networks }) => { session, sharedKey }) + // Check which unification flow we're running // to determine what we want to do after authing user const magicLinkData: AuthMagicLink = yield select(S.getMagicLinkData) @@ -548,6 +594,7 @@ export default ({ api, coreSagas, networks }) => { yield call(loginRoutineSaga, {}) break } + yield put( actions.analytics.trackEvent({ key: Analytics.LOGIN_SIGNED_IN, diff --git a/packages/blockchain-wallet-v4-frontend/src/data/balances/sagas.js b/packages/blockchain-wallet-v4-frontend/src/data/balances/sagas.js index 7b596d07479..590ff5ba882 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/balances/sagas.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/balances/sagas.js @@ -1,5 +1,5 @@ import { pathOr } from 'ramda' -import { fork, join, put, select, take } from 'redux-saga/effects' +import { put, select, take } from 'redux-saga/effects' import { Remote } from '@core' import { actions, actionTypes, selectors } from 'data' @@ -10,49 +10,10 @@ export const balancePath = ['payload', 'info', 'final_balance'] export const fetchBalances = function* () { yield put(actions.core.data.bch.fetchData()) yield put(actions.core.data.btc.fetchData()) - yield put(actions.core.data.xlm.fetchData()) yield put(actions.core.data.eth.fetchData()) - yield put(actions.core.data.eth.fetchErc20Data()) + yield put(actions.core.data.coins.fetchUnifiedBalances()) yield put(actions.components.refresh.refreshRates()) yield put(actions.custodial.fetchRecentSwapTxs()) - yield put(actions.core.data.coins.fetchData()) -} - -export const getEthBalance = function* () { - try { - const ethBalanceR = yield select(selectors.core.data.eth.getBalance) - if (!Remote.Success.is(ethBalanceR)) { - const ethData = yield take([ - actionTypes.core.data.eth.FETCH_ETH_DATA_SUCCESS, - actionTypes.core.data.eth.FETCH_ETH_DATA_FAILURE - ]) - return pathOr(0, balancePath, ethData) - } - return ethBalanceR.getOrElse(0) - } catch (e) { - yield put(actions.logs.logErrorMessage(logLocation, 'getEthBalance', e)) - } -} - -export const getErc20Balance = function* (token) { - try { - const erc20BalanceR = yield select(selectors.core.data.eth.getErc20Balance, token) - if (!Remote.Success.is(erc20BalanceR)) { - yield put(actions.core.data.eth.fetchErc20Data(token)) - const erc20Data = yield take([ - (action) => - action.type === actionTypes.core.data.eth.FETCH_ERC20_TOKEN_DATA_SUCCESS && - action.payload.token === token, - (action) => - action.type === actionTypes.core.data.eth.FETCH_ERC20_TOKEN_DATA_FAILURE && - action.payload.token === token - ]) - return pathOr(0, balancePath, erc20Data) - } - return erc20BalanceR.getOrElse(0) - } catch (e) { - yield put(actions.logs.logErrorMessage(logLocation, 'getErc20Balance', e)) - } } export const getBtcBalance = function* () { @@ -86,29 +47,3 @@ export const getBchBalance = function* () { yield put(actions.logs.logErrorMessage(logLocation, 'getBchBalance', e)) } } - -export const getXlmBalance = function* () { - try { - const xlmBalanceR = yield select(selectors.core.data.xlm.getTotalBalance) - if (!Remote.Success.is(xlmBalanceR)) { - const xlmData = yield take(actionTypes.core.data.xlm.FETCH_DATA_SUCCESS) - return pathOr(0, balancePath, xlmData) - } - return xlmBalanceR.getOrElse(0) - } catch (e) { - yield put(actions.logs.logErrorMessage(logLocation, 'getXlmBalance', e)) - } -} - -export const waitForAllBalances = function* () { - const btcT = yield fork(getBtcBalance) - const bchT = yield fork(getBchBalance) - const ethT = yield fork(getEthBalance) - const xlmT = yield fork(getXlmBalance) - const btc = yield join(btcT) - const bch = yield join(bchT) - const eth = yield join(ethT) - const xlm = yield join(xlmT) - - return { bch, btc, eth, xlm } -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/balances/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/data/balances/selectors.ts index f524d1cbbd4..3180ad4b8b1 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/balances/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/balances/selectors.ts @@ -16,11 +16,10 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ import BigNumber from 'bignumber.js' -import { add, curry, flatten, lift, map, pathOr, reduce, reject } from 'ramda' +import { add, curry, lift, map, reject } from 'ramda' import { coreSelectors, Exchange, Remote } from '@core' import { fiatToString } from '@core/exchange/utils' -import { getBalance } from '@core/redux/data/coins/selectors' import { BSBalancesType, BSBalanceType, @@ -36,6 +35,7 @@ import { WalletFiatType } from '@core/types' import { createDeepEqualSelector } from '@core/utils' +import { selectors } from 'data' import { PartialClientErrorProperties } from 'data/analytics/types/errors' import { DEFAULT_BS_BALANCE } from 'data/components/buySell/model' import { convertBaseToStandard } from 'data/components/exchange/services' @@ -50,14 +50,6 @@ import { } from '../components/selectors' import * as custodialSelectors from '../custodial/selectors' import * as routerSelectors from '../router/selectors' -// these selectors need to be external to this file to avoid hoisting/initialization errors -import { - __getBchNonCustodialBalance, - __getBtcNonCustodialBalance, - __getErc20NonCustodialBalance, - __getEthNonCustodialBalance, - __getXlmNonCustodialBalance -} from './selectors.utils' // these functions are private as denoted by the '__' prefix but must be at the top of this // file to avoid function hoisting issues. they also cannot be included in the external @@ -216,31 +208,22 @@ export const getCoinTotalBalance = ( coin: CoinType | FiatType ): ((state: RootState) => RemoteDataType) => { switch (coin) { - case 'BCH': - return __getBchTotalBalance - case 'BTC': - return __getBtcTotalBalance - case 'ETH': - return __getEthTotalBalance - case 'XLM': - return __getXlmTotalBalance case 'EUR': case 'GBP': case 'USD': case 'ARS': return getFiatCurrencyBalance(coin) default: - switch (true) { - case coreSelectors.data.coins.getDynamicSelfCustodyCoins().includes(coin): - return __getDynamicSelfCustodyTotalBalance(coin) - case coreSelectors.data.coins.getCustodialCoins().includes(coin): - return getCoinCustodialBalance(coin) - case !!window.coins[coin].coinfig.type.erc20Address: - return __getErc20TotalBalance(coin) - default: - // coin not supported by wallet, just return 0 value - return () => Remote.Success(0) - } + return createDeepEqualSelector( + [getCoinNonCustodialBalance(coin), getCoinCustodialBalance(coin)], + (balancesR, custodialBalanceR) => { + const custodialBalance = custodialBalanceR.getOrElse(0) + + return Remote.of( + new BigNumber(balancesR.getOrElse(new BigNumber(0))).plus(custodialBalance).toNumber() + ) + } + ) } } @@ -359,19 +342,24 @@ export const getTotalWalletBalancesSorted = createDeepEqualSelector( export const getCoinNonCustodialBalance = ( coin: CoinType ): ((state: RootState) => RemoteDataType) => { - switch (true) { - case coin === 'BTC': - return __getBtcNonCustodialBalance - case coin === 'BCH': - return __getBchNonCustodialBalance - case coin === 'ETH': - return __getEthNonCustodialBalance - case coin === 'XLM': - return __getXlmNonCustodialBalance - case !!window.coins[coin].coinfig.type.erc20Address: - return __getErc20NonCustodialBalance(coin) - default: - throw Error(`Coin "${coin}" not supported by getCoinNonCustodialBalance`) + return (state: RootState) => { + return createDeepEqualSelector( + [selectors.core.data.coins.getUnifiedBalances], + (unifiedBalancesR) => { + let balance = new BigNumber(0) + const transform = (unifiedBalances: ExtractSuccess) => { + unifiedBalances + .filter(({ ticker }) => ticker === coin) + .forEach(({ amount }) => { + balance = balance.plus(amount ? amount.amount : 0) + }) + + return balance + } + + return lift(transform)(unifiedBalancesR) + } + )(state) } } @@ -388,103 +376,3 @@ export const getCoinBalancesTypeSeparated = (coin) => ]) } ) - -// -// INTERNAL SELECTORS, DO NOT USE OUTSIDE OF THIS FILE! -// - -// returns BTC total balance -const __getBtcTotalBalance = createDeepEqualSelector( - [ - coreSelectors.wallet.getSpendableContext, - coreSelectors.data.btc.getAddresses, - getCoinCustodialBalance('BTC') - ], - (context, addressesR, custodialBalanceR) => { - const contextToBalances = ( - context, - balances, - custodialBalance: ExtractSuccess - ): Array => { - const walletBalances: Array = flatten(context).map((a) => - pathOr(0, [a, 'final_balance'], balances) - ) - return walletBalances.concat(custodialBalance) - } - const balancesR = lift(contextToBalances)(Remote.of(context), addressesR, custodialBalanceR) - return balancesR.map(reduce(add, 0)) - } -) - -// returns BCH total balance -const __getBchTotalBalance = createDeepEqualSelector( - [ - coreSelectors.kvStore.bch.getSpendableContext, - coreSelectors.data.bch.getAddresses, - getCoinCustodialBalance('BCH') - ], - (context, addressesR, custodialBalanceR) => { - const contextToBalances = ( - context, - balances, - custodialBalance: ExtractSuccess - ) => { - const walletBalances: Array = context.map((a) => - pathOr(0, [a, 'final_balance'], balances) - ) - return walletBalances.concat(custodialBalance) - } - const balancesR = lift(contextToBalances)(Remote.of(context), addressesR, custodialBalanceR) - return balancesR.map(reduce(add, 0)) - } -) - -// returns ETH total balance -const __getEthTotalBalance = createDeepEqualSelector( - [getCoinNonCustodialBalance('ETH'), getCoinCustodialBalance('ETH')], - (balancesR, custodialBalanceR) => { - const custodialBalance = custodialBalanceR.getOrElse(0) - - return Remote.of( - new BigNumber(balancesR.getOrElse(new BigNumber(0))).plus(custodialBalance).toNumber() - ) - } -) - -// given an erc20 coin, returns its total balances -const __getErc20TotalBalance = (coin: CoinType) => - createDeepEqualSelector( - [getCoinNonCustodialBalance(coin), getCoinCustodialBalance(coin)], - (balanceR, custodialBalanceR) => { - const custodialBalance = custodialBalanceR.getOrElse(0) - - return Remote.of( - new BigNumber(balanceR.getOrElse(new BigNumber(0))).plus(custodialBalance).toNumber() - ) - } - ) - -// returns XLM total balances -const __getXlmTotalBalance = createDeepEqualSelector( - [getCoinNonCustodialBalance('XLM'), getCoinCustodialBalance('XLM')], - (balanceR, custodialBalanceR) => { - const custodialBalance = custodialBalanceR.getOrElse(0) - - return Remote.of( - new BigNumber(balanceR.getOrElse(new BigNumber(0))).plus(custodialBalance).toNumber() - ) - } -) - -// given a dynamic self custody coin, returns its total balances -const __getDynamicSelfCustodyTotalBalance = (coin: CoinType) => - createDeepEqualSelector( - [getBalance(coin), getCoinCustodialBalance(coin)], - (balanceR, custodialBalanceR) => { - const custodialBalance = custodialBalanceR.getOrElse(0) - - return Remote.of( - new BigNumber(balanceR.getOrElse(new BigNumber(0))).plus(custodialBalance).toNumber() - ) - } - ) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/balances/selectors.utils.ts b/packages/blockchain-wallet-v4-frontend/src/data/balances/selectors.utils.ts deleted file mode 100644 index 0255a46b71b..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/balances/selectors.utils.ts +++ /dev/null @@ -1,76 +0,0 @@ -import BigNumber from 'bignumber.js' -import { add, lift, pathOr, reduce } from 'ramda' - -import { coreSelectors, Remote } from '@core' -import { RemoteDataType } from '@core/remote/types' -import { createDeepEqualSelector } from '@core/utils' - -// gets BTC non-custodial balance which includes hd accounts excluding imported addresses -export const __getBtcNonCustodialBalance = createDeepEqualSelector( - [coreSelectors.common.btc.getActiveHDAccounts], - (accountsR) => { - const getBalances = (balances) => { - const walletBalances: Array = balances.map((account) => - pathOr(0, ['info', 'final_balance'], account) - ) - return walletBalances - } - const balancesR = lift(getBalances)(accountsR) - - return balancesR.map(reduce(add, 0)) - } -) - -// gets BCH non-custodial balance which includes hd accounts excluding imported addresses -export const __getBchNonCustodialBalance = createDeepEqualSelector( - [coreSelectors.common.bch.getActiveHDAccounts], - (accountsR) => { - const getBalances = (balances) => { - const walletBalances: Array = balances.map((account) => - pathOr(0, ['info', 'final_balance'], account) - ) - return walletBalances - } - const balancesR = lift(getBalances)(accountsR) - - return balancesR.map(reduce(add, 0)) - } -) - -// gets ETH non-custodial balance -export const __getEthNonCustodialBalance = createDeepEqualSelector( - [coreSelectors.kvStore.eth.getContext, coreSelectors.data.eth.getAddresses], - (context, addressesR) => { - const contextToBalances = (context, balances) => - context.map((a) => pathOr(0, [a, 'balance'], balances)) - - const balancesR: RemoteDataType> = lift( - contextToBalances - )(Remote.of(context), addressesR) - return balancesR.map((b) => { - return new BigNumber(b.getOrElse('0')) - }) - } -) - -// gets XLM non-custodial balance -export const __getXlmNonCustodialBalance = createDeepEqualSelector( - [ - (state) => - coreSelectors.kvStore.xlm - .getDefaultAccountId(state) - .map((accountId) => coreSelectors.data.xlm.getBalance(state, accountId).getOrElse(0)) - ], - (balanceR) => { - return Remote.of(new BigNumber(balanceR.getOrElse(0))) - } -) - -// given an ERC20 coin, returns its non-custodial balance -export const __getErc20NonCustodialBalance = (coin) => - createDeepEqualSelector( - [(state) => coreSelectors.data.eth.getErc20Balance(state, coin)], - (balanceR) => { - return Remote.of(new BigNumber(balanceR.getOrElse(0))) - } - ) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.classes.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.classes.ts new file mode 100644 index 00000000000..f85dd13dfb3 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.classes.ts @@ -0,0 +1,46 @@ +/* eslint-disable max-classes-per-file */ +import { CallEffect, SelectEffect } from 'redux-saga/effects' + +import { APIType } from '@core/network/api' +import { CoinType, RemoteDataType } from '@core/types' +import { RootState } from 'data/rootReducer' +import { RequestExtrasType, SwapAccountType } from 'data/types' + +export enum CoinAccountTypeEnum { + CUSTODIAL = 'CUSTODIAL', + DYNAMIC_SELF_CUSTODY = 'DYNAMIC_SELF_CUSTODY', + ERC20 = 'ERC20', + NON_CUSTODIAL = 'NON_CUSTODIAL' +} + +export class AccountTypeClass { + api: APIType + + networks: any + + constructor(api: APIType, networks) { + this.api = api + this.networks = networks + } + + getNextReceiveAddress: ( + coin: CoinType, + index?: number + ) => Generator< + CallEffect | SelectEffect, + { address?: string; extras?: RequestExtrasType } | undefined, + any + > + + getAccounts: (state: RootState, ownProps: any) => RemoteDataType +} + +export class NonCustodialAccountTypeClass extends AccountTypeClass { + getDefaultAccount: (coin: string) => Generator + + getOrUpdateProvisionalPayment: ( + coreSagas: any, + paymentR: any, + coin: string + ) => Generator +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.custodial.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.custodial.ts new file mode 100644 index 00000000000..28b72da4fce --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.custodial.ts @@ -0,0 +1,64 @@ +import { call } from '@redux-saga/core/effects' +import { lift } from 'ramda' + +import { APIType } from '@core/network/api' +import { BSBalanceType, CoinType, ExtractSuccess } from '@core/types' +import { createDeepEqualSelector } from '@core/utils' +import { selectors } from 'data' +import { RequestExtrasType, SwapAccountType } from 'data/types' + +import { generateTradingAccount } from '../utils' +import { AccountTypeClass } from './accountTypes.classes' + +export class CustodialAccountType implements AccountTypeClass { + api: APIType + + networks: any + + constructor(api: APIType, networks: any) { + this.api = api + this.networks = networks + } + + *getNextReceiveAddress(coin: CoinType) { + const custodial: ReturnType = yield call( + this.api.getBSPaymentAccount, + coin + ) + let { address } = custodial + const extras = {} as RequestExtrasType + if (window.coins[coin].coinfig.type.isMemoBased && address.split(':')[1]) { + // eslint-disable-next-line prefer-destructuring + extras.Memo = address.split(':')[1] + // eslint-disable-next-line prefer-destructuring + address = address.split(':')[0] + } + + return { address, extras } + } + + getAccounts = createDeepEqualSelector( + [ + (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts + (state, ownProps) => ownProps // selector config + ], + (sbBalanceR, ownProps) => { + const transform = (sbBalance: ExtractSuccess) => { + const { coin } = ownProps + const { coinfig } = window.coins[coin] + let accounts: SwapAccountType[] = [] + + // add trading accounts if requested + if (ownProps?.tradingAccounts && coinfig.products.includes('CustodialWalletBalance')) { + accounts = accounts.concat( + generateTradingAccount(coin as CoinType, sbBalance as BSBalanceType) + ) + } + + return accounts + } + + return lift(transform)(sbBalanceR) + } + ) +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.dynamicSelfCustody.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.dynamicSelfCustody.ts new file mode 100644 index 00000000000..38d89cfecfc --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.dynamicSelfCustody.ts @@ -0,0 +1,68 @@ +import { call, select } from '@redux-saga/core/effects' +import { lift } from 'ramda' + +import { APIType } from '@core/network/api' +import { getPubKey } from '@core/redux/data/self-custody/sagas' +import { BSBalanceType, CoinType } from '@core/types' +import { createDeepEqualSelector } from '@core/utils' +import { selectors } from 'data' +import { SwapAccountType } from 'data/types' +import { promptForSecondPassword } from 'services/sagas' + +import { generateSelfCustodyAccount, generateTradingAccount } from '../utils' +import { AccountTypeClass } from './accountTypes.classes' + +export class DynamicSelfCustodyAccountType implements AccountTypeClass { + api: APIType + + networks: any + + constructor(api: APIType, networks: any) { + this.api = api + this.networks = networks + } + + *getNextReceiveAddress(coin: CoinType) { + if (coin === 'STX') { + const password = yield call(promptForSecondPassword) + const pubKeys = yield call(getPubKey, coin, password) + const { results }: ReturnType = yield call( + this.api.deriveAddress, + coin, + pubKeys + ) + const result = results.find(({ default: isDefault }) => isDefault) + return { address: result?.address } + } + const address = selectors.core.kvStore.eth + .getDefaultAddress(yield select()) + .getOrFail(`Failed to get ETH receive address`) + + return { address } + } + + getAccounts = createDeepEqualSelector( + [ + (state, ownProps) => selectors.balances.getCoinNonCustodialBalance(ownProps.coin)(state), + (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts + (state, ownProps) => ownProps // selector config + ], + (balanceR, sbBalanceR, ownProps) => { + const { coin } = ownProps + const { coinfig } = window.coins[coin] + let accounts: SwapAccountType[] = [] + + const transform = (balance, sbBalance) => { + accounts = accounts.concat(generateSelfCustodyAccount(coin, balance)) + + // add trading accounts if requested + if (ownProps?.tradingAccounts && coinfig.products.includes('CustodialWalletBalance')) { + accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) + } + return accounts + } + + return lift(transform)(balanceR, sbBalanceR) + } + ) +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.erc20.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.erc20.ts new file mode 100644 index 00000000000..71b1cd6a3ec --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.erc20.ts @@ -0,0 +1,85 @@ +import { head, lift } from 'ramda' +import { select } from 'redux-saga/effects' + +import { coreSelectors } from '@core' +import { APIType } from '@core/network/api' +import { BSBalanceType, ExtractSuccess, PaymentValue } from '@core/types' +import { createDeepEqualSelector } from '@core/utils' +import { selectors } from 'data' +import { SwapAccountType, SwapBaseCounterTypes } from 'data/types' + +import { generateTradingAccount } from '../utils' +import { NonCustodialAccountTypeClass } from './accountTypes.classes' + +export class ERC20AccountType implements NonCustodialAccountTypeClass { + api: APIType + + networks: any + + constructor(api: APIType, networks: any) { + this.api = api + this.networks = networks + } + + *getNextReceiveAddress(coin: string) { + const address = selectors.core.kvStore.eth + .getDefaultAddress(yield select()) + .getOrFail(`Failed to get ${coin} receive address.`) + + return { address } + } + + *getDefaultAccount(coin: string) { + const erc20AccountR = yield select(selectors.core.common.eth.getErc20AccountBalances, coin) + return erc20AccountR.map(head) + } + + *getOrUpdateProvisionalPayment(coreSagas, networks, paymentR) { + return yield coreSagas.payment.eth.create({ + network: networks.eth, + payment: paymentR.getOrElse({}) + }) + } + + getAccounts = createDeepEqualSelector( + [ + coreSelectors.kvStore.eth.getDefaultAddress, + (state, { coin }) => selectors.balances.getCoinNonCustodialBalance(coin)(state), // non-custodial metadata + (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts + (state, ownProps) => ownProps // selector config + ], + (ethAddressR, erc20BalanceR, sbBalanceR, ownProps) => { + const transform = ( + ethAddress, + erc20Balance, + sbBalance: ExtractSuccess + ) => { + const { coin } = ownProps + const { coinfig } = window.coins[coin] + let accounts: SwapAccountType[] = [] + + // add non-custodial accounts if requested + if (ownProps?.nonCustodialAccounts) { + accounts = accounts.concat([ + { + address: ethAddress, + balance: erc20Balance, + baseCoin: 'ETH', + coin, + label: 'Private Key Wallet', + type: SwapBaseCounterTypes.ACCOUNT + } + ]) + } + + // add trading accounts if requested + if (ownProps?.tradingAccounts && coinfig.products.includes('CustodialWalletBalance')) { + accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) + } + return accounts + } + + return lift(transform)(ethAddressR, erc20BalanceR, sbBalanceR) + } + ) +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.nonCustodial.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.nonCustodial.ts new file mode 100644 index 00000000000..c293a17e1cf --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/accountTypes/accountTypes.nonCustodial.ts @@ -0,0 +1,357 @@ +import BigNumber from 'bignumber.js' +import { add, head, lift, nth, path, prop, propEq } from 'ramda' +import { select } from 'redux-saga/effects' + +import { coreSelectors, Remote, utils } from '@core' +import { APIType } from '@core/network/api' +import { ADDRESS_TYPES } from '@core/redux/payment/btc/utils' +import { BSBalanceType, CoinType, ExtractSuccess, PaymentValue } from '@core/types' +import { createDeepEqualSelector } from '@core/utils' +import { selectors } from 'data' +import { convertStandardToBase } from 'data/components/exchange/services' +import { SwapAccountType } from 'data/types' + +import { CoinAccountSelectorType } from '../types' +import { generateInterestAccount, generateTradingAccount } from '../utils' +import { NonCustodialAccountTypeClass } from './accountTypes.classes' + +const { formatAddr, isCashAddr, toCashAddr } = utils.bch + +export class NonCustodialAccountType implements NonCustodialAccountTypeClass { + api: APIType + + networks: any + + constructor(api: APIType, networks: any) { + this.api = api + this.networks = networks + } + + *getNextReceiveAddress(coin: string, index?: number) { + let address = '' + + switch (coin) { + case 'BCH': { + const state = yield select() + const defaultAccountIndex = + index || (yield select(selectors.core.kvStore.bch.getDefaultAccountIndex)).getOrFail() + const nextAddress = selectors.core.common.bch + .getNextAvailableReceiveAddress(this.networks.bch, defaultAccountIndex, state) + .getOrFail('Failed to get BCH receive address') + + address = isCashAddr(nextAddress) ? formatAddr(nextAddress) : toCashAddr(nextAddress) + break + } + case 'BTC': { + const state = yield select() + const defaultAccountIndex = + index || (yield select(selectors.core.wallet.getDefaultAccountIndex)) + const defaultDerivation = selectors.core.common.btc.getAccountDefaultDerivation( + defaultAccountIndex, + state + ) + address = selectors.core.common.btc + .getNextAvailableReceiveAddress( + this.networks.btc, + defaultAccountIndex, + defaultDerivation, + state + ) + .getOrFail('Failed to get BTC receive address') + break + } + case 'ETH': + address = selectors.core.kvStore.eth + .getDefaultAddress(yield select()) + .getOrFail(`Failed to get ETH receive address`) + break + case 'XLM': + address = selectors.core.kvStore.xlm + .getDefaultAccountId(yield select()) + .getOrFail(`Failed to get XLM receive address`) + break + default: + } + + return { address } + } + + *getDefaultAccount(coin: string) { + switch (coin) { + case 'BCH': + const bchAccountsR = yield select(selectors.core.common.bch.getAccountsBalances) + const bchDefaultIndex = (yield select( + selectors.core.kvStore.bch.getDefaultAccountIndex + )).getOrElse(0) + return bchAccountsR.map(nth(bchDefaultIndex)) + case 'BTC': + const btcAccountsR = yield select(selectors.core.common.btc.getAccountsBalances) + const btcDefaultIndex = yield select(selectors.core.wallet.getDefaultAccountIndex) + return btcAccountsR.map(nth(btcDefaultIndex)) + case 'ETH': + const ethAccountR = yield select(selectors.core.common.eth.getAccountBalances) + return ethAccountR.map(head) + case 'XLM': + return (yield select(selectors.core.common.xlm.getAccountBalances)).map(head) + default: + } + } + + *getOrUpdateProvisionalPayment(coreSagas, paymentR, coin: string) { + return yield coreSagas.payment[coin.toLowerCase()].create({ + network: this.networks[coin.toLowerCase()], + payment: paymentR.getOrElse({}) + }) + } + + getAccounts = (state, ownProps) => { + switch (ownProps.coin) { + case 'BCH': { + return createDeepEqualSelector( + [ + coreSelectors.wallet.getHDAccounts, // non-custodial accounts + coreSelectors.data.bch.getAddresses, // non-custodial xpub info + coreSelectors.kvStore.bch.getAccounts, // non-custodial metadata info + coreSelectors.common.bch.getActiveAddresses, // imported addresses + (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts + (state, ownProps) => ownProps // selector config + ], + (bchAccounts, bchDataR, bchMetadataR, importedAddressesR, sbBalanceR, ownProps) => { + const transform = ( + bchData, + bchMetadata, + importedAddresses, + sbBalance: ExtractSuccess + ) => { + const { coin } = ownProps + let accounts: SwapAccountType[] = [] + // add non-custodial accounts if requested + if (ownProps?.nonCustodialAccounts) { + accounts = accounts.concat( + bchAccounts + .map((acc) => { + const index = prop('index', acc) + // this is using hdAccount with new segwit structure + // need to get legacy xPub from derivations object similar to btc selector + const xpub = prop( + 'xpub', + prop('derivations', acc).find((derr) => derr.type === 'legacy') + ) + const data = prop(xpub, bchData) + const metadata = bchMetadata[index] + return { + accountIndex: prop('index', acc), + address: index, + archived: prop('archived', metadata) || false, + balance: prop('final_balance', data), + baseCoin: coin, + coin, + label: prop('label', metadata) || xpub, + type: ADDRESS_TYPES.ACCOUNT + } + }) + .filter(propEq('archived', false)) + ) + } + + // add imported addresses if requested + if (ownProps?.importedAddresses) { + accounts = accounts.concat( + importedAddresses.map((importedAcc) => ({ + address: importedAcc.addr, + balance: importedAcc.info.final_balance, + baseCoin: coin, + coin, + label: importedAcc.label || importedAcc.addr, + type: ADDRESS_TYPES.LEGACY + })) + ) + } + + // add trading accounts if requested + if (ownProps?.tradingAccounts) { + accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) + } + + return accounts + } + + return lift(transform)(bchDataR, bchMetadataR, importedAddressesR, sbBalanceR) + } + )(state, ownProps) + } + case 'BTC': { + return createDeepEqualSelector( + [ + coreSelectors.wallet.getHDAccounts, // non-custodial accounts + coreSelectors.data.btc.getAddresses, // non-custodial xpub info + coreSelectors.common.btc.getActiveAddresses, // imported addresses + (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts + (state, { coin }) => selectors.balances.getCoinInterestBalance(coin, state), // custodial accounts + (state, ownProps): CoinAccountSelectorType & { coin: CoinType } => ownProps // selector config + ], + (btcAccounts, btcDataR, importedAddressesR, sbBalanceR, interestBalanceR, ownProps) => { + const transform = ( + btcData, + importedAddresses, + sbBalance: ExtractSuccess, + interestBalance: ExtractSuccess + ) => { + const { coin } = ownProps + let accounts: SwapAccountType[] = [] + // add non-custodial accounts if requested + if (ownProps?.nonCustodialAccounts) { + // each account has a derivations object with legacy xpub and segwit xpub + // need to extract each xpub for balance + const xpubArray = (acc) => + prop('derivations', acc).map((derr) => prop('xpub', derr)) + const xpubBalance = (acc) => + xpubArray(acc).map((xpub) => + prop('final_balance', prop(xpub, btcData)) + ) + accounts = accounts.concat( + btcAccounts + .map((acc) => ({ + accountIndex: prop('index', acc), + address: prop('index', acc), + archived: prop('archived', acc) || false, + balance: xpubBalance(acc).reduce(add, 0), + baseCoin: coin, + coin, + label: prop('label', acc) || prop('xpub', acc), + type: ADDRESS_TYPES.ACCOUNT + })) + .filter(propEq('archived', false)) + ) + } + + // add imported addresses if requested + if (ownProps?.importedAddresses) { + accounts = accounts.concat( + importedAddresses.map((importedAcc) => ({ + address: importedAcc.addr, + balance: importedAcc.info.final_balance, + baseCoin: coin, + coin, + label: importedAcc.label || importedAcc.addr, + type: ADDRESS_TYPES.LEGACY + })) + ) + } + + // add trading accounts if requested + if (ownProps?.tradingAccounts) { + accounts = accounts.concat(generateTradingAccount(coin, sbBalance)) + } + + // add interest accounts if requested + if (ownProps?.interestAccounts) { + accounts = accounts.concat(generateInterestAccount(coin, interestBalance)) + } + return accounts + } + + return lift(transform)(btcDataR, importedAddressesR, sbBalanceR, interestBalanceR) + } + )(state, ownProps) + } + case 'ETH': { + return createDeepEqualSelector( + [ + coreSelectors.data.eth.getAddresses, // non-custodial accounts + coreSelectors.kvStore.eth.getAccounts, // non-custodial metadata + (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts + (state, ownProps) => ownProps // selector config + ], + (ethDataR, ethMetadataR, sbBalanceR, ownProps) => { + const transform = ( + ethData, + ethMetadata, + sbBalance: ExtractSuccess + ) => { + const { coin } = ownProps + let accounts: SwapAccountType[] = [] + + // add non-custodial accounts if requested + if (ownProps?.nonCustodialAccounts) { + accounts = accounts.concat( + ethMetadata.map((acc) => { + const address = prop('addr', acc) + const data = prop(address, ethData) + + return { + address, + balance: prop('balance', data), + baseCoin: coin, + coin, + label: prop('label', acc) || address, + type: ADDRESS_TYPES.ACCOUNT + } + }) + ) + } + + // add trading accounts if requested + if (ownProps?.tradingAccounts) { + accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) + } + + return accounts + } + + return lift(transform)(ethDataR, ethMetadataR, sbBalanceR) + } + )(state, ownProps) + } + case 'XLM': { + return createDeepEqualSelector( + [ + coreSelectors.kvStore.xlm.getAccounts, // non-custodial metadata + (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts + (state, ownProps) => ownProps // selector config + ], + (xlmMetadataR, sbBalanceR, ownProps) => { + const transform = (xlmMetadata, sbBalance: ExtractSuccess) => { + const { coin } = ownProps + let accounts: SwapAccountType[] = [] + + // add non-custodial accounts if requested + if (ownProps?.nonCustodialAccounts) { + accounts = accounts.concat( + xlmMetadata + .map((acc) => { + const address = prop('publicKey', acc) + const balance = coreSelectors.data.coins + .getCoinUnifiedBalance('XLM')(state) + .getOrElse(new BigNumber(0)) + return { + address, + archived: prop('archived', acc) || false, + balance, + baseCoin: coin, + coin, + label: prop('label', acc) || address, + type: ADDRESS_TYPES.ACCOUNT + } + }) + .filter(propEq('archived', false)) + ) + } + + // add trading accounts if requested + if (ownProps?.tradingAccounts) { + accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) + } + + return accounts + } + + return lift(transform)(xlmMetadataR, sbBalanceR) + } + )(state, ownProps) + } + default: + return Remote.Failure('') + } + } +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas.ts new file mode 100644 index 00000000000..dbb5457931f --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas.ts @@ -0,0 +1,60 @@ +import { CoinType, PaymentValue, RemoteDataType } from '@core/types' +import { RequestExtrasType } from 'data/types' + +import { + CoinAccountTypeEnum, + NonCustodialAccountTypeClass +} from './accountTypes/accountTypes.classes' +import { CustodialAccountType } from './accountTypes/accountTypes.custodial' +import { DynamicSelfCustodyAccountType } from './accountTypes/accountTypes.dynamicSelfCustody' +import { ERC20AccountType } from './accountTypes/accountTypes.erc20' +import { NonCustodialAccountType } from './accountTypes/accountTypes.nonCustodial' +import { getKey } from './selectors' + +export default ({ api, coreSagas, networks }) => { + const accounts = { + CUSTODIAL: new CustodialAccountType(api, networks), + DYNAMIC_SELF_CUSTODY: new DynamicSelfCustodyAccountType(api, networks), + ERC20: new ERC20AccountType(api, networks), + NON_CUSTODIAL: new NonCustodialAccountType(api, networks) + } + // gets the default account/address for requested coin + const getDefaultAccountForCoin = function* (coin: CoinType) { + const accountType = getKey(coin) + const defaultAccountR = yield ( + accounts[accountType] as NonCustodialAccountTypeClass + ).getDefaultAccount(coin) + + return defaultAccountR.getOrFail('Failed to find default account') + } + + // gets the next receive address for requested coin + // account based currencies will just return the account address + const getNextReceiveAddressForCoin = function* ( + coin: CoinType, + coinAccountType: CoinAccountTypeEnum, + index?: number + // @ts-ignore + ): { address: string; extras?: RequestExtrasType } { + return yield accounts[coinAccountType].getNextReceiveAddress(coin, index) + } + + // gets or updates a provisional payment for a coin + // provisional payments are mutable payment objects used to build a transaction + // over an extended period of time (e.g. as user goes through interest/swap/sell flows) + const getOrUpdateProvisionalPaymentForCoin = function* ( + coin: CoinType, + paymentR: RemoteDataType + ) { + const accountType = getKey(coin) + return yield ( + accounts[accountType] as NonCustodialAccountTypeClass + ).getOrUpdateProvisionalPayment(coreSagas, paymentR, coin) + } + + return { + getDefaultAccountForCoin, + getNextReceiveAddressForCoin, + getOrUpdateProvisionalPaymentForCoin + } +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/ars.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/ars.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/bch.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/bch.ts deleted file mode 100644 index c5c35784452..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/bch.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { nth } from 'ramda' -import { select } from 'redux-saga/effects' - -import { utils } from '@core' -import { PaymentValue } from '@core/redux/payment/types' -import { selectors } from 'data' - -const { formatAddr, isCashAddr, toCashAddr } = utils.bch - -// retrieves default account/address -export const getDefaultAccount = function* () { - const bchAccountsR = yield select(selectors.core.common.bch.getAccountsBalances) - const bchDefaultIndex = (yield select( - selectors.core.kvStore.bch.getDefaultAccountIndex - )).getOrElse(0) - return bchAccountsR.map(nth(bchDefaultIndex)) -} - -// retrieves the next receive address -export const getNextReceiveAddress = function* (_, networks, index) { - const state = yield select() - const defaultAccountIndex = - index || (yield select(selectors.core.kvStore.bch.getDefaultAccountIndex)).getOrFail() - const nextAddress = selectors.core.common.bch - .getNextAvailableReceiveAddress(networks.bch, defaultAccountIndex, state) - .getOrFail('Failed to get BCH receive address') - - return isCashAddr(nextAddress) ? formatAddr(nextAddress) : toCashAddr(nextAddress) -} - -// gets or updates a provisional payment -export const getOrUpdateProvisionalPayment = function* (coreSagas, networks, paymentR) { - return yield coreSagas.payment.bch.create({ - network: networks.bch, - payment: paymentR.getOrElse({}) - }) -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/btc.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/btc.ts deleted file mode 100644 index 0b785cbe123..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/btc.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { nth } from 'ramda' -import { select } from 'redux-saga/effects' - -import { PaymentValue } from '@core/redux/payment/types' -import { selectors } from 'data' - -// retrieves default account/address -export const getDefaultAccount = function* () { - const btcAccountsR = yield select(selectors.core.common.btc.getAccountsBalances) - const btcDefaultIndex = yield select(selectors.core.wallet.getDefaultAccountIndex) - return btcAccountsR.map(nth(btcDefaultIndex)) -} - -// retrieves the next receive address -export const getNextReceiveAddress = function* (_, networks, index) { - const state = yield select() - const defaultAccountIndex = index || (yield select(selectors.core.wallet.getDefaultAccountIndex)) - - const defaultDerivation = selectors.core.common.btc.getAccountDefaultDerivation( - defaultAccountIndex, - state - ) - - return selectors.core.common.btc - .getNextAvailableReceiveAddress(networks.btc, defaultAccountIndex, defaultDerivation, state) - .getOrFail('Failed to get BTC receive address') -} - -// gets or updates a provisional payment -export const getOrUpdateProvisionalPayment = function* (coreSagas, networks, paymentR) { - return yield coreSagas.payment.btc.create({ - network: networks.btc, - payment: paymentR.getOrElse({}) - }) -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/custodial.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/custodial.ts deleted file mode 100644 index 044f2cd02b4..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/custodial.ts +++ /dev/null @@ -1,6 +0,0 @@ -// retrieves default account/address -export const getDefaultAccount = function* () { - // const btcAccountsR = yield select(selectors.core.common.btc.getAccountsBalances) - // const btcDefaultIndex = yield select(selectors.core.wallet.getDefaultAccountIndex) - // return btcAccountsR.map(nth(btcDefaultIndex)) -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/erc20.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/erc20.ts deleted file mode 100644 index 495ed38f852..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/erc20.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { head } from 'ramda' -import { select } from 'redux-saga/effects' - -import { CoinType, PaymentValue } from '@core/types' -import { selectors } from 'data' - -// retrieves default account/address -export const getDefaultAccount = function* (coin: CoinType) { - const erc20AccountR = yield select(selectors.core.common.eth.getErc20AccountBalances, coin) - return erc20AccountR.map(head) -} - -// retrieves the next receive address -export const getNextReceiveAddress = function* (coin: CoinType) { - return selectors.core.kvStore.eth - .getDefaultAddress(yield select()) - .getOrFail(`Failed to get ${coin} receive address`) -} - -// gets or updates a provisional payment -export const getOrUpdateProvisionalPayment = function* (coreSagas, networks, paymentR) { - return yield coreSagas.payment.eth.create({ - network: networks.eth, - payment: paymentR.getOrElse({}) - }) -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/eth.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/eth.ts deleted file mode 100644 index 8d55ed2b489..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/eth.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { head } from 'ramda' -import { select } from 'redux-saga/effects' - -import { PaymentValue } from '@core/redux/payment/types' -import { selectors } from 'data' - -// retrieves default account/address -export const getDefaultAccount = function* () { - const ethAccountR = yield select(selectors.core.common.eth.getAccountBalances) - return ethAccountR.map(head) -} - -// retrieves the next receive address -export const getNextReceiveAddress = function* () { - return selectors.core.kvStore.eth - .getDefaultAddress(yield select()) - .getOrFail(`Failed to get ETH receive address`) -} - -// gets or updates a provisional payment -export const getOrUpdateProvisionalPayment = function* (coreSagas, networks, paymentR) { - return yield coreSagas.payment.eth.create({ - network: networks.eth, - payment: paymentR.getOrElse({}) - }) -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/eur.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/eur.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/gbp.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/gbp.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/self-custody.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/self-custody.ts deleted file mode 100644 index 63b9f7b0a54..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/self-custody.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { call } from 'redux-saga/effects' - -import { APIType } from '@core/network/api' -import { getPubKey } from '@core/redux/data/self-custody/sagas' -import { CoinType } from '@core/types' -import { promptForSecondPassword } from 'services/sagas' - -// retrieves the next receive address -export const getNextReceiveAddress = function* (coin: CoinType, networks, index, api: APIType) { - const password = yield call(promptForSecondPassword) - const pubKeys = yield call(getPubKey, password) - const { results }: ReturnType = yield call( - api.deriveAddress, - coin, - pubKeys - ) - const address = results.find(({ default: isDefault }) => isDefault) - return address?.address -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/usd.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/usd.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/xlm.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/xlm.ts deleted file mode 100644 index 11759215e66..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/coins/xlm.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { head } from 'ramda' -import { select } from 'redux-saga/effects' - -import { PaymentValue } from '@core/redux/payment/types' -import { selectors } from 'data' - -// retrieves default account/address -export const getDefaultAccount = function* () { - return (yield select(selectors.core.common.xlm.getAccountBalances)).map(head) -} - -// retrieves the next receive address -export const getNextReceiveAddress = function* () { - return selectors.core.kvStore.xlm - .getDefaultAccountId(yield select()) - .getOrFail(`Failed to get XLM receive address`) -} - -// gets or updates a provisional payment -export const getOrUpdateProvisionalPayment = function* (coreSagas, networks, paymentR) { - return yield coreSagas.payment.xlm.create({ - payment: paymentR.getOrElse({}) - }) -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/index.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/index.ts deleted file mode 100644 index d6f76b495b2..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/sagas/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { CoinType, PaymentType, PaymentValue, RemoteDataType } from '@core/types' -import { selectors } from 'data' - -import * as BCH from './coins/bch' -import * as BTC from './coins/btc' -import * as ERC20 from './coins/erc20' -import * as ETH from './coins/eth' -import * as SELF_CUSTODY from './coins/self-custody' -// import * as EUR from './coins/eur' -// import * as GBP from './coins/gbp' -// import * as USD from './coins/usd' -import * as XLM from './coins/xlm' - -const getSaga = (coin: CoinType) => { - if (selectors.core.data.coins.getErc20Coins().includes(coin)) { - return 'ERC20' - } - if (selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coin)) { - return 'SELF_CUSTODY' - } - return coin -} - -// create a function map of all coins -const coinSagas = { - BCH, - BTC, - ERC20, - ETH, - SELF_CUSTODY, - // EUR, - // GBP, - // USD, - XLM -} - -// -// for now this is a dumping ground for both sagas and util functions (not generator functions) -// that require coin specific logic to execute. perhaps in the future we split these out further -// - -export default ({ api, coreSagas, networks }) => { - // gets the default account/address for requested coin - const getDefaultAccountForCoin = function* (coin: CoinType): Generator { - const saga = getSaga(coin) - const defaultAccountR = yield coinSagas[saga]?.getDefaultAccount(coin) - // @ts-ignore - return defaultAccountR.getOrFail('Failed to find default account') - } - - // gets the next receive address for requested coin - // account based currencies will just return the account address - const getNextReceiveAddressForCoin = function* ( - coin: CoinType, - index?: number - ): Generator { - const saga = getSaga(coin) - return yield coinSagas[saga]?.getNextReceiveAddress(coin, networks, index, api) - } - - // gets or updates a provisional payment for a coin - // provisional payments are mutable payment objects used to build a transaction - // over an extended period of time (e.g. as user goes through interest/swap/sell flows) - const getOrUpdateProvisionalPaymentForCoin = function* ( - coin: CoinType, - paymentR: RemoteDataType - ): Generator { - const saga = getSaga(coin) - return yield coinSagas[saga]?.getOrUpdateProvisionalPayment( - coreSagas, - networks, - paymentR - ) as PaymentType - } - - return { - getDefaultAccountForCoin, - getNextReceiveAddressForCoin, - getOrUpdateProvisionalPaymentForCoin - } -} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/index.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors.ts similarity index 64% rename from packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/index.ts rename to packages/blockchain-wallet-v4-frontend/src/data/coins/selectors.ts index 3d210c929e0..8ce3fbffe1b 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/index.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors.ts @@ -1,58 +1,37 @@ import { any, isEmpty, isNil, map, values } from 'ramda' import { Remote } from '@core' -import { CoinfigType, CoinType, InvitationsType, RemoteDataType } from '@core/types' +import { CoinType, InvitationsType, RemoteDataType } from '@core/types' import { selectors } from 'data' import { CoinAccountSelectorType } from 'data/coins/types' import { SwapAccountType } from 'data/components/swap/types' import { RootState } from 'data/rootReducer' -import * as ARS from './coins/ars' -import * as BCH from './coins/bch' -import * as BTC from './coins/btc' -import * as CUSTODIAL from './coins/custodial' -import * as DYNAMIC_SELF_CUSTODY from './coins/dynamic-self-custody' -import * as ERC20 from './coins/erc20' -import * as ETH from './coins/eth' -import * as EUR from './coins/eur' -import * as GBP from './coins/gbp' -import * as USD from './coins/usd' -import * as XLM from './coins/xlm' - -// create a function map of all coins -const coinSelectors = { - ARS, - BCH, - BTC, - CUSTODIAL, - DYNAMIC_SELF_CUSTODY, - ERC20, - ETH, - EUR, - GBP, - USD, - XLM -} +import { CustodialAccountType } from './accountTypes/accountTypes.custodial' +import { DynamicSelfCustodyAccountType } from './accountTypes/accountTypes.dynamicSelfCustody' +import { ERC20AccountType } from './accountTypes/accountTypes.erc20' +import { NonCustodialAccountType } from './accountTypes/accountTypes.nonCustodial' -// internal util get locate correct selectors -const __getSelector = (coinfig: CoinfigType) => { - if (selectors.core.data.coins.getErc20Coins().includes(coinfig.symbol)) { +export const getKey = (coin: CoinType) => { + if (selectors.core.data.coins.getErc20Coins().includes(coin)) { return 'ERC20' } - if (selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coinfig.symbol)) { + if (selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coin)) { return 'DYNAMIC_SELF_CUSTODY' } - if (selectors.core.data.coins.getCustodialCoins().includes(coinfig.symbol)) { - return 'CUSTODIAL' - } - return coinfig.symbol + return 'NON_CUSTODIAL' } // retrieves introduction text for coin on its transaction page const getIntroductionText = (coin: string) => { - const { coinfig } = window.coins[coin] - const selector = __getSelector(coinfig) - return coinSelectors[selector]?.getTransactionPageHeaderText(coinfig.symbol) + return '' +} + +const accountTypes = { + CUSTODIAL: new CustodialAccountType({} as any, {} as any), + DYNAMIC_SELF_CUSTODY: new DynamicSelfCustodyAccountType({} as any, {} as any), + ERC20: new ERC20AccountType({} as any, {} as any), + NON_CUSTODIAL: new NonCustodialAccountType({} as any, {} as any) } // generic selector that should be used by all features to request their desired @@ -66,10 +45,8 @@ const getCoinAccounts = (state: RootState, ownProps: CoinAccountSelectorType) => isEmpty(coinList) || isNil(coinList) ? Remote.of({}) : coinList.reduce((accounts, coin) => { - const { coinfig } = window.coins[coin] - const selector = __getSelector(coinfig) - // eslint-disable-next-line - accounts[coin] = coinSelectors[selector]?.getAccounts(state, { coin, ...ownProps }) + const accountType = getKey(coin) + accounts[coin] = accountTypes[accountType].getAccounts(state, { coin, ...ownProps }) return accounts }, {}) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/ars.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/ars.tsx deleted file mode 100644 index b4bfe3892cc..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/ars.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/bch.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/bch.tsx deleted file mode 100644 index a9891801064..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/bch.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' -import { lift, prop, propEq } from 'ramda' - -import { coreSelectors } from '@core' -import { BSBalanceType } from '@core/network/api/buySell/types' -import { ADDRESS_TYPES } from '@core/redux/payment/btc/utils' -import { ExtractSuccess } from '@core/remote/types' -import { createDeepEqualSelector } from '@core/utils' -import { selectors } from 'data' -import { generateTradingAccount } from 'data/coins/utils' -import { SwapAccountType } from 'data/types' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) - -// main selector for all BCH account types -// accepts a CoinAccountSelectorType config object -export const getAccounts = createDeepEqualSelector( - [ - coreSelectors.wallet.getHDAccounts, // non-custodial accounts - coreSelectors.data.bch.getAddresses, // non-custodial xpub info - coreSelectors.kvStore.bch.getAccounts, // non-custodial metadata info - coreSelectors.common.bch.getActiveAddresses, // imported addresses - (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts - (state, ownProps) => ownProps // selector config - ], - (bchAccounts, bchDataR, bchMetadataR, importedAddressesR, sbBalanceR, ownProps) => { - const transform = ( - bchData, - bchMetadata, - importedAddresses, - sbBalance: ExtractSuccess - ) => { - const { coin } = ownProps - let accounts: SwapAccountType[] = [] - // add non-custodial accounts if requested - if (ownProps?.nonCustodialAccounts) { - accounts = accounts.concat( - bchAccounts - .map((acc) => { - const index = prop('index', acc) - // this is using hdAccount with new segwit structure - // need to get legacy xPub from derivations object similar to btc selector - const xpub = prop( - 'xpub', - prop('derivations', acc).find((derr) => derr.type === 'legacy') - ) - const data = prop(xpub, bchData) - const metadata = bchMetadata[index] - return { - accountIndex: prop('index', acc), - address: index, - archived: prop('archived', metadata) || false, - balance: prop('final_balance', data), - baseCoin: coin, - coin, - label: prop('label', metadata) || xpub, - type: ADDRESS_TYPES.ACCOUNT - } - }) - .filter(propEq('archived', false)) - ) - } - - // add imported addresses if requested - if (ownProps?.importedAddresses) { - accounts = accounts.concat( - importedAddresses.map((importedAcc) => ({ - address: importedAcc.addr, - balance: importedAcc.info.final_balance, - baseCoin: coin, - coin, - label: importedAcc.label || importedAcc.addr, - type: ADDRESS_TYPES.LEGACY - })) - ) - } - - // add trading accounts if requested - if (ownProps?.tradingAccounts) { - accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) - } - - return accounts - } - - return lift(transform)(bchDataR, bchMetadataR, importedAddressesR, sbBalanceR) - } -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/btc.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/btc.tsx deleted file mode 100644 index 6f0db3ff0ff..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/btc.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' -import { add, lift, prop, propEq } from 'ramda' - -import { coreSelectors } from '@core' -import { ADDRESS_TYPES } from '@core/redux/payment/btc/utils' -import { ExtractSuccess } from '@core/remote/types' -import { CoinType } from '@core/types' -import { createDeepEqualSelector } from '@core/utils' -import { selectors } from 'data' -import { CoinAccountSelectorType } from 'data/coins/types' -import { generateInterestAccount, generateTradingAccount } from 'data/coins/utils' -import { SwapAccountType } from 'data/types' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) - -// main selector for all BTC account types -// accepts a CoinAccountSelectorType config object -export const getAccounts = createDeepEqualSelector( - [ - coreSelectors.wallet.getHDAccounts, // non-custodial accounts - coreSelectors.data.btc.getAddresses, // non-custodial xpub info - coreSelectors.common.btc.getActiveAddresses, // imported addresses - (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts - (state, { coin }) => selectors.balances.getCoinInterestBalance(coin, state), // custodial accounts - (state, ownProps): CoinAccountSelectorType & { coin: CoinType } => ownProps // selector config - ], - (btcAccounts, btcDataR, importedAddressesR, sbBalanceR, interestBalanceR, ownProps) => { - const transform = ( - btcData, - importedAddresses, - sbBalance: ExtractSuccess, - interestBalance: ExtractSuccess - ) => { - const { coin } = ownProps - let accounts: SwapAccountType[] = [] - // add non-custodial accounts if requested - if (ownProps?.nonCustodialAccounts) { - // each account has a derivations object with legacy xpub and segwit xpub - // need to extract each xpub for balance - const xpubArray = (acc) => prop('derivations', acc).map((derr) => prop('xpub', derr)) - const xpubBalance = (acc) => - xpubArray(acc).map((xpub) => prop('final_balance', prop(xpub, btcData))) - accounts = accounts.concat( - btcAccounts - .map((acc) => ({ - accountIndex: prop('index', acc), - address: prop('index', acc), - archived: prop('archived', acc) || false, - balance: xpubBalance(acc).reduce(add, 0), - baseCoin: coin, - coin, - label: prop('label', acc) || prop('xpub', acc), - type: ADDRESS_TYPES.ACCOUNT - })) - .filter(propEq('archived', false)) - ) - } - - // add imported addresses if requested - if (ownProps?.importedAddresses) { - accounts = accounts.concat( - importedAddresses.map((importedAcc) => ({ - address: importedAcc.addr, - balance: importedAcc.info.final_balance, - baseCoin: coin, - coin, - label: importedAcc.label || importedAcc.addr, - type: ADDRESS_TYPES.LEGACY - })) - ) - } - - // add trading accounts if requested - if (ownProps?.tradingAccounts) { - accounts = accounts.concat(generateTradingAccount(coin, sbBalance)) - } - - // add interest accounts if requested - if (ownProps?.interestAccounts) { - accounts = accounts.concat(generateInterestAccount(coin, interestBalance)) - } - return accounts - } - - return lift(transform)(btcDataR, importedAddressesR, sbBalanceR, interestBalanceR) - } -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/custodial.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/custodial.ts deleted file mode 100644 index 728edb0cd1a..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/custodial.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { lift } from 'ramda' - -import { BSBalanceType } from '@core/network/api/buySell/types' -import { ExtractSuccess } from '@core/remote/types' -import { CoinType } from '@core/types' -import { createDeepEqualSelector } from '@core/utils' -import { selectors } from 'data' -import { generateTradingAccount } from 'data/coins/utils' -import { SwapAccountType } from 'data/types' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => null - -// main selector for all CUSTODIAL account types -// accepts a CoinAccountSelectorType config object -// NOT IMPLEMENTED FOR COIN: non-custodial accounts, imported addresses/accounts -export const getAccounts = createDeepEqualSelector( - [ - (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts - (state, ownProps) => ownProps // selector config - ], - (sbBalanceR, ownProps) => { - const transform = (sbBalance: ExtractSuccess) => { - const { coin } = ownProps - const { coinfig } = window.coins[coin] - let accounts: SwapAccountType[] = [] - - // add trading accounts if requested - if (ownProps?.tradingAccounts && coinfig.products.includes('CustodialWalletBalance')) { - accounts = accounts.concat( - generateTradingAccount(coin as CoinType, sbBalance as BSBalanceType) - ) - } - - return accounts - } - - return lift(transform)(sbBalanceR) - } -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/dynamic-self-custody.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/dynamic-self-custody.ts deleted file mode 100644 index 9a9624e5b7f..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/dynamic-self-custody.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { lift } from 'ramda' - -import { getBalance } from '@core/redux/data/coins/selectors' -import { BSBalanceType } from '@core/types' -import { createDeepEqualSelector } from '@core/utils' -import { selectors } from 'data' -import { generateSelfCustodyAccount, generateTradingAccount } from 'data/coins/utils' -import { SwapAccountType } from 'data/types' - -export const getTransactionPageHeaderText = () => null - -// main selector for all DYNAMIC-SELF-CUSTODY account types -// accepts a coin string -export const getAccounts = createDeepEqualSelector( - [ - (state, ownProps) => getBalance(ownProps.coin, state), - (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts - (state, ownProps) => ownProps // selector config - ], - (balanceR, sbBalanceR, ownProps) => { - const { coin } = ownProps - const { coinfig } = window.coins[coin] - let accounts: SwapAccountType[] = [] - - const transform = (balance, sbBalance) => { - accounts = accounts.concat(generateSelfCustodyAccount(coin, balance)) - - // add trading accounts if requested - if (ownProps?.tradingAccounts && coinfig.products.includes('CustodialWalletBalance')) { - accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) - } - return accounts - } - - return lift(transform)(balanceR, sbBalanceR) - } -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/erc20.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/erc20.tsx deleted file mode 100644 index cae287c5c12..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/erc20.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' -import { lift } from 'ramda' - -import { coreSelectors } from '@core' -import { BSBalanceType } from '@core/network/api/buySell/types' -import { ExtractSuccess } from '@core/remote/types' -import { createDeepEqualSelector } from '@core/utils' -import { selectors } from 'data' -import { generateTradingAccount } from 'data/coins/utils' -import { SwapAccountType, SwapBaseCounterTypes } from 'data/types' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = (coin) => { - switch (coin) { - case 'AAVE': - return ( - - ) - case 'PAX': - return ( - - ) - case 'USDT': - return ( - - ) - case 'WDGLD': - return ( - - ) - case 'YFI': - return ( - - ) - default: - return null - } -} - -// main selector for all ERC20 account types -// accepts a CoinAccountSelectorType config object -// NOT IMPLEMENTED: imported addresses/accounts -export const getAccounts = createDeepEqualSelector( - [ - coreSelectors.kvStore.eth.getDefaultAddress, - (state, { coin }) => coreSelectors.data.eth.getErc20Balance(state, coin), // non-custodial metadata - (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts - (state, ownProps) => ownProps // selector config - ], - (ethAddressR, erc20BalanceR, sbBalanceR, ownProps) => { - const transform = (ethAddress, erc20Balance, sbBalance: ExtractSuccess) => { - const { coin } = ownProps - const { coinfig } = window.coins[coin] - let accounts: SwapAccountType[] = [] - - // add non-custodial accounts if requested - if (ownProps?.nonCustodialAccounts) { - accounts = accounts.concat([ - { - address: ethAddress, - balance: erc20Balance, - baseCoin: 'ETH', - coin, - label: 'Private Key Wallet', - type: SwapBaseCounterTypes.ACCOUNT - } - ]) - } - - // add trading accounts if requested - if (ownProps?.tradingAccounts && coinfig.products.includes('CustodialWalletBalance')) { - accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) - } - return accounts - } - - return lift(transform)(ethAddressR, erc20BalanceR, sbBalanceR) - } -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/eth.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/eth.tsx deleted file mode 100644 index 8dd3550b711..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/eth.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' -import { lift, prop } from 'ramda' - -import { coreSelectors } from '@core' -import { BSBalanceType } from '@core/network/api/buySell/types' -import { ADDRESS_TYPES } from '@core/redux/payment/btc/utils' -import { ExtractSuccess } from '@core/remote/types' -import { createDeepEqualSelector } from '@core/utils' -import { selectors } from 'data' -import { generateTradingAccount } from 'data/coins/utils' -import { SwapAccountType } from 'data/types' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) - -// main selector for all ETH account types -// accepts a CoinAccountSelectorType config object -// NOT IMPLEMENTED: imported addresses/accounts -export const getAccounts = createDeepEqualSelector( - [ - coreSelectors.data.eth.getAddresses, // non-custodial accounts - coreSelectors.kvStore.eth.getAccounts, // non-custodial metadata - (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts - (state, ownProps) => ownProps // selector config - ], - (ethDataR, ethMetadataR, sbBalanceR, ownProps) => { - const transform = (ethData, ethMetadata, sbBalance: ExtractSuccess) => { - const { coin } = ownProps - let accounts: SwapAccountType[] = [] - - // add non-custodial accounts if requested - if (ownProps?.nonCustodialAccounts) { - accounts = accounts.concat( - ethMetadata.map((acc) => { - const address = prop('addr', acc) - const data = prop(address, ethData) - - return { - address, - balance: prop('balance', data), - baseCoin: coin, - coin, - label: prop('label', acc) || address, - type: ADDRESS_TYPES.ACCOUNT - } - }) - ) - } - - // add trading accounts if requested - if (ownProps?.tradingAccounts) { - accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) - } - - return accounts - } - - return lift(transform)(ethDataR, ethMetadataR, sbBalanceR) - } -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/eur.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/eur.tsx deleted file mode 100644 index dd1a264f9b9..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/eur.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/gbp.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/gbp.tsx deleted file mode 100644 index d99ab1019e0..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/gbp.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/usd.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/usd.tsx deleted file mode 100644 index b333a9695c9..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/usd.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/xlm.tsx b/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/xlm.tsx deleted file mode 100644 index 5ae10dff007..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/selectors/coins/xlm.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' -import { lift, path, prop, propEq } from 'ramda' - -import { coreSelectors } from '@core' -import { BSBalanceType } from '@core/network/api/buySell/types' -import { ADDRESS_TYPES } from '@core/redux/payment/btc/utils' -import { ExtractSuccess } from '@core/remote/types' -import { createDeepEqualSelector } from '@core/utils' -import { selectors } from 'data' -import { generateTradingAccount } from 'data/coins/utils' -import { convertStandardToBase } from 'data/components/exchange/services' -import { SwapAccountType } from 'data/types' - -// retrieves introduction text for coin on its transaction page -export const getTransactionPageHeaderText = () => ( - -) - -// main selector for all XLM account types -// accepts a CoinAccountSelectorType config object -// NOT IMPLEMENTED FOR COIN: imported addresses/accounts -export const getAccounts = createDeepEqualSelector( - [ - coreSelectors.data.xlm.getAccounts, // non-custodial accounts - coreSelectors.kvStore.xlm.getAccounts, // non-custodial metadata - (state, { coin }) => selectors.balances.getCoinTradingBalance(coin, state), // custodial accounts - (state, ownProps) => ownProps // selector config - ], - (xlmData, xlmMetadataR, sbBalanceR, ownProps) => { - const transform = (xlmMetadata, sbBalance: ExtractSuccess) => { - const { coin } = ownProps - let accounts: SwapAccountType[] = [] - - // add non-custodial accounts if requested - if (ownProps?.nonCustodialAccounts) { - accounts = accounts.concat( - xlmMetadata - .map((acc) => { - const address = prop('publicKey', acc) - const account = prop(address, xlmData) - const noAccount = path(['error', 'message'], account) === 'Not Found' - const balance = convertStandardToBase( - coin, - account - // @ts-ignore - .map(coreSelectors.data.xlm.selectBalanceFromAccount) - .getOrElse(0) - ) - return { - address, - archived: prop('archived', acc) || false, - balance, - baseCoin: coin, - coin, - label: prop('label', acc) || address, - noAccount, - type: ADDRESS_TYPES.ACCOUNT - } - }) - .filter(propEq('archived', false)) - ) - } - - // add trading accounts if requested - if (ownProps?.tradingAccounts) { - accounts = accounts.concat(generateTradingAccount(coin, sbBalance as BSBalanceType)) - } - - return accounts - } - - return lift(transform)(xlmMetadataR, sbBalanceR) - } -) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/types.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/types.ts index 52198727b56..fcbee25e318 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/types.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/types.ts @@ -1,3 +1,6 @@ +import { PubkeyServiceSubscriptions } from '@core/network/api/coins/types' +import { RemoteDataType } from '@core/types' + export type CoinAccountSelectorType = { coins?: Array importedAddresses?: boolean @@ -5,3 +8,7 @@ export type CoinAccountSelectorType = { nonCustodialAccounts?: boolean tradingAccounts?: boolean } + +export type CoinsStateType = { + subscriptions: RemoteDataType +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/coins/utils.ts b/packages/blockchain-wallet-v4-frontend/src/data/coins/utils.ts index d390c117d70..25eb6b4dc05 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/coins/utils.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/coins/utils.ts @@ -1,4 +1,4 @@ -import { BSBalanceType, CoinType, EarnBalanceType } from '@core/types' +import { BSBalanceType, CoinfigType, CoinType, EarnBalanceType } from '@core/types' import { convertStandardToBase } from 'data/components/exchange/services' import { SwapAccountType, SwapBaseCounterTypes } from 'data/types' @@ -57,3 +57,11 @@ export const generateProvisionalPaymentAmount = ( return convertStandardToBase(coin, amount) } + +export const getSymbolWithParentChain = (coinfig: CoinfigType) => { + if (coinfig.symbol.includes('.') && coinfig.type.parentChain) { + return `${coinfig.displaySymbol} - ${coinfig.type.parentChain}` + } + + return coinfig.displaySymbol +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/model.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/model.ts index 68c2d424a63..1fda4645bdd 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/model.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/model.ts @@ -40,8 +40,6 @@ export const CARD_ORDER_POLLING = { export const LIMIT = { max: '10000', min: '500' } as Limits export const LIMIT_FACTOR = 100 // we get 10000 from API -export const SDD_TIER = 3 - export enum BS_ERROR { APPLE_PAY_INFO_NOT_FOUND = 'APPLE_PAY_INFO_NOT_FOUND', APPLE_PAY_SESSION_NOT_SUPPORTED = 'APPLE_PAY_SESSION_NOT_SUPPORTED', @@ -64,6 +62,7 @@ export enum BS_ERROR { ORDER_NOT_FOUND = 'ORDER_NOT_FOUND', ORDER_VALUE_CHANGED = 'ORDER_VALUE_CHANGED', ORDER_VERIFICATION_TIMED_OUT = 'ORDER_VERIFICATION_TIMED_OUT', + ORDER_VERIFICATION_UNEXPECTED_ERROR = 'ORDER_VERIFICATION_UNEXPECTED_ERROR', RETRYING_TO_GET_AUTH_URL = 'RETRYING_TO_GET_AUTH_URL', UNHANDLED_PAYMENT_STATE = 'UNHANDLED_PAYMENT_STATE', USER_CANCELLED_APPLE_PAY = 'USER_CANCELLED_APPLE_PAY', @@ -104,6 +103,7 @@ export enum ORDER_ERROR_CODE { CARD_PAYMENT_INSUFFICIENT_FUNDS = 'CARD_PAYMENT_INSUFFICIENT_FUNDS', CARD_PAYMENT_NOT_SUPPORTED = 'CARD_PAYMENT_NOT_SUPPORTED', + ORDER_CVV_UPDATE_ERROR = 'ORDER_CVV_UPDATE_ERROR', ORDER_FAILED_AFTER_POLL = 'ORDER_FAILED_AFTER_POLL' // TODO implement later // "BANK_TRANSFER_ACCOUNT_ALREADY_LINKED" diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/sagas.ts index 71e362a16b9..d0caf987d16 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/sagas.ts @@ -73,15 +73,16 @@ import { GOOGLE_PAY_MERCHANT_ID, isFiatCurrencySupported, ORDER_ERROR_CODE, - ORDER_POLLING, - SDD_TIER + ORDER_POLLING } from './model' import { createBuyQuoteLoopAndWaitForFirstResult } from './sagas/createBuyQuoteLoopAndWaitForFirstResult' import { updateCardCvvAndPollOrder } from './sagas/updateCardCvvAndPollOrder' import * as S from './selectors' +import { getIsSddFlow } from './selectors/getIsSddFlow' import { actions as A } from './slice' import * as T from './types' import { getDirection, getEnterAmountStepType, getQuoteRefreshConfig, reversePair } from './utils' +import * as SddFlow from './utils/sddFlow' export const logLocation = 'components/buySell/sagas' @@ -602,8 +603,8 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne const asyncOrderConfirmCheck = function* (orderId) { try { - // When isAsync is true on the confirm request this call can return the "ux" nabu error but the request is still - // techniclly not an http error (ex. 400, 500) and so we need to catch it and manually return the order info "dataFields" + // When isAsync is true on the confirm request this call can return the "ux" NABU error but the request is still + // technically not an http error (ex. 400, 500) and so we need to catch it and manually return the order info "dataFields" const order: ReturnType = yield call(api.getBSOrder, orderId) if (order.state === 'FINISHED' || order.state === 'FAILED' || order.state === 'CANCELED') { return order @@ -631,12 +632,19 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne throw new Error(BS_ERROR.ORDER_VERIFICATION_TIMED_OUT) } - const orderConfirmCheck = function* (orderId) { - const order: ReturnType = yield call(api.getBSOrder, orderId) - if (order.state === 'FINISHED' || order.state === 'FAILED' || order.state === 'CANCELED') { - return order - } + const orderConfirmCheck = function* (orderId: string) { + try { + const order: ReturnType = yield call(api.getBSOrder, orderId) + if (order.state === 'FINISHED' || order.state === 'FAILED' || order.state === 'CANCELED') { + return order + } + } catch (e) { + return { + error: e, + type: BS_ERROR.ORDER_VERIFICATION_UNEXPECTED_ERROR + } + } throw new Error(BS_ERROR.ORDER_VERIFICATION_TIMED_OUT) } @@ -650,6 +658,10 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne const confirmedOrder = yield retry(RETRY_AMOUNT, SECONDS, orderConfirmCheck, payload.id) yield put(actions.form.stopSubmit(FORM_BS_CHECKOUT_CONFIRM)) + if (confirmedOrder.type === BS_ERROR.ORDER_VERIFICATION_UNEXPECTED_ERROR) { + throw confirmedOrder.error + } + if (confirmedOrder.paymentError) { throw new Error(confirmedOrder.paymentError) } @@ -1012,15 +1024,18 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne } try { - // Inside the polling, if the order is finished, we set the order success and set the step to ORDER_SUMMARY yield call(confirmOrderPoll, A.confirmOrderPoll(confirmedOrder), CARD_ORDER_POLLING) } catch (e) { - // Exhausted the retry attempts, so just show the order summary with the order we have - yield put(A.confirmOrderSuccess(confirmedOrder)) + const error = errorHandler(e) + if (error === BS_ERROR.ORDER_VERIFICATION_TIMED_OUT) { + yield put(A.confirmOrderSuccess(confirmedOrder)) - yield put(cacheActions.removeLastUsedAmount({ pair: confirmedOrder.pair })) + yield put(cacheActions.removeLastUsedAmount({ pair: confirmedOrder.pair })) - yield put(A.setStep({ step: 'ORDER_SUMMARY' })) + yield put(A.setStep({ step: 'ORDER_SUMMARY' })) + } else { + throw e + } } } else if ( confirmedOrder.attributes?.everypay || @@ -1206,12 +1221,6 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne try { yield call(waitForUserData) - yield call(fetchSDDVerified) - const isUserTier2 = yield call(isTier2) - const sddVerified = S.isUserSddVerified(yield select()).getOrElse(false) - const loadCards = isUserTier2 || sddVerified - - if (!loadCards) return yield put(A.fetchCardsSuccess([])) if (!payload) yield put(A.fetchCardsLoading()) useNewPaymentProviders = (yield select( @@ -1390,28 +1399,18 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne S.getFiatCurrency(yield select()) || (yield select(selectors.core.settings.getCurrency)).getOrElse('USD') - const userSDDTierR = S.getUserSddEligibleTier(yield select()) - if (!Remote.Success.is(userSDDTierR)) { - yield call(fetchSDDEligible) - } const state = yield select() const currentUserTier = selectors.modules.profile .getCurrentTier(state) .getOrFail('User has no tier') - const userSDDEligibleTier = S.getUserSddEligibleTier(state).getOrElse(1) - // only fetch non-eligible payment methods if user is not tier 2 - const includeNonEligibleMethods = currentUserTier === 2 - // if user is SDD tier 3 eligible, fetch limits for tier 3 - // else let endpoint return default current tier limits for current tier of user - // double check if user is tier 2 and in case user is ignore this property - const includeTierLimits = - userSDDEligibleTier === SDD_TIER && currentUserTier !== 2 ? SDD_TIER : undefined + + // Present all possible (but necessarily eligible) payment methods to SDD users + const includeEligibleOnlyPaymentMethods = currentUserTier === 2 let paymentMethods = yield call( api.getBSPaymentMethods, payload || fallbackFiatCurrency, - includeNonEligibleMethods, - includeTierLimits + includeEligibleOnlyPaymentMethods ) // 🚨👋 temporarily remove ACH from user payment methods if they are not t2 @@ -1665,37 +1664,17 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne const pair = S.getBSPair(yield select()) const swapAccount = S.getSwapAccount(yield select()) if (!pair) throw new Error(BS_ERROR.NO_PAIR_SELECTED) - const isUserTier2 = yield call(isTier2) - if (!isUserTier2) { - switch (method.type) { - // https://blockc.slack.com/archives/GT1JZ1ZN2/p1596546978351100?thread_ts=1596541628.345800&cid=GT1JZ1ZN2 - // REMOVE THIS WHEN BACKEND CAN HANDLE PENDING 'FUNDS' ORDERS - // 👇-------------------------------------------------------- - case BSPaymentTypes.BANK_ACCOUNT: - case BSPaymentTypes.USER_CARD: - return yield put( - A.setStep({ - step: 'KYC_REQUIRED' - }) - ) - // REMOVE THIS WHEN BACKEND CAN HANDLE PENDING 'FUNDS' ORDERS - // 👆-------------------------------------------------------- - case BSPaymentTypes.PAYMENT_CARD: - // ADD THIS WHEN BACKEND CAN HANDLE PENDING 'FUNDS' ORDERS - // 👇----------------------------------------------------- - // const methodType = - // method.type === BSPaymentTypes.BANK_ACCOUNT ? BSPaymentTypes.FUNDS : method.type - // return yield put(A.createOrder(undefined, methodType)) - // 👆------------------------------------------------------ - - return yield put(A.createOrder({ paymentType: method.type })) - default: - return - } + const isSddFlow = getIsSddFlow(yield select()).getOrElse(false) + const isAllowedPaymentType = SddFlow.isAllowedPaymentType(method, mobilePaymentMethod) + if (isSddFlow && !isAllowedPaymentType) { + return yield put( + A.setStep({ + step: 'KYC_REQUIRED' + }) + ) } - // User is Tier 2 switch (method.type) { case BSPaymentTypes.BANK_ACCOUNT: return yield put( @@ -1971,6 +1950,11 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne ) { yield cancel() } + + // In case that transaction is pending and waiting for 3DS response we do not need to poll to the end of poll cycle + if (order.attributes?.cardCassy?.paymentState === 'WAITING_FOR_3DS_RESPONSE') { + break + } yield delay(2000) } @@ -1983,6 +1967,8 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne } else { yield put(A.createOrderFailure(ORDER_ERROR_CODE.ORDER_FAILED_AFTER_POLL)) } + // return back to ORDER_SUMMARY and show the error + yield put(A.setStep({ step: 'ORDER_SUMMARY' })) } } @@ -2309,8 +2295,9 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne yield put(A.cvvStatusLoading()) yield call(api.updateCardCvv, payload) yield put(A.cvvStatusSuccess()) - } catch (e) { - yield put(A.cvvStatusFailure()) + } catch (error) { + const errorPayload = isNabuError(error) ? error : ORDER_ERROR_CODE.ORDER_CVV_UPDATE_ERROR + yield put(A.cvvStatusFailure(errorPayload)) } } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/selectors/getIsSddFlow.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/selectors/getIsSddFlow.ts new file mode 100644 index 00000000000..96a138d9472 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/selectors/getIsSddFlow.ts @@ -0,0 +1,9 @@ +import { createSelector } from 'reselect' + +import { getUserData } from 'data/modules/profile/selectors' + +import * as SddFlow from '../utils/sddFlow' + +export const getIsSddFlow = createSelector([getUserData], (userDataR) => + userDataR.map(SddFlow.isSddUser) +) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/slice.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/slice.ts index e65ad81fe95..90d5f7892ee 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/slice.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/slice.ts @@ -44,6 +44,7 @@ import { StepActionsPayload, SwapAccountType } from 'data/types' +import { NabuError } from 'services/errors' import { getCoinFromPair, getFiatFromPair } from './model' import { BSCardSuccessRateType, BuySellState } from './types' @@ -166,8 +167,8 @@ const buySellSlice = createSlice({ state.order = Remote.Success(action.payload) state.pendingOrder = action.payload }, - cvvStatusFailure: (state) => { - state.cvvStatus = Remote.Failure('The code entered is either invalid or expired. Try Again.') + cvvStatusFailure: (state, action: PayloadAction) => { + state.cvvStatus = Remote.Failure(action.payload) }, cvvStatusLoading: (state) => { state.cvvStatus = Remote.Loading diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/types.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/types.ts index b9722e60bae..c818b7760b6 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/types.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/types.ts @@ -32,6 +32,7 @@ import { PartialClientErrorProperties } from 'data/analytics/types/errors' import type { CountryType } from 'data/components/identityVerification/types' import type { RecurringBuyPeriods } from 'data/components/recurringBuy/types' import type { SwapAccountType, SwapBaseCounterTypes } from 'data/components/swap/types' +import { NabuError } from 'services/errors' import { BankDWStepType, PlaidSettlementErrorReasons } from '../brokerage/types' @@ -189,7 +190,7 @@ export type BuySellState = { checkoutDotComApiKey?: string crossBorderLimits: RemoteDataType cryptoCurrency?: CoinType - cvvStatus: RemoteDataType + cvvStatus: RemoteDataType displayBack: boolean fiatCurrency?: FiatType fiatEligible: RemoteDataType diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/utils/sddFlow.test.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/utils/sddFlow.test.ts new file mode 100644 index 00000000000..83f4c445e1a --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/utils/sddFlow.test.ts @@ -0,0 +1,74 @@ +import { + BSPaymentMethodType, + BSPaymentTypes, + MobilePaymentType +} from '@core/network/api/buySell/types' +import { Tiers, UserDataType } from 'data/modules/profile/types' + +import * as SddFlow from './sddFlow' + +describe('SddFlow', () => { + describe('isSddUser', () => { + const makeUserDataStub = (currentTier: Tiers['current']) => { + const tiers: Tiers = { + current: currentTier, + next: 2, + selected: 2 + } + + return { + tiers + } as UserDataType + } + + describe('when user current tier is 1', () => { + it('should return true', () => { + const userDataStub = makeUserDataStub(1) + + expect(SddFlow.isSddUser(userDataStub)).toBe(true) + }) + }) + + describe('otherwise', () => { + it.each([0, 2] as const)('should return false %#', (tier) => { + const userDataStub = makeUserDataStub(tier) + + expect(SddFlow.isSddUser(userDataStub)).toBe(false) + }) + }) + }) + + describe('isAllowedPaymentType', () => { + const makePaymentMethodStub = (type: BSPaymentTypes) => + ({ + type + } as BSPaymentMethodType) + + describe('when payment method is card and it is not mobile payment', () => { + it('should return true', () => { + const paymentStub = makePaymentMethodStub(BSPaymentTypes.PAYMENT_CARD) + + expect(SddFlow.isAllowedPaymentType(paymentStub, undefined)).toBe(true) + }) + }) + + describe('when payment method is card and it is mobile payment', () => { + it('should return false', () => { + const paymentStub = makePaymentMethodStub(BSPaymentTypes.PAYMENT_CARD) + + expect(SddFlow.isAllowedPaymentType(paymentStub, MobilePaymentType.APPLE_PAY)).toBe(false) + }) + }) + + describe('when payment method is not card', () => { + it.each([BSPaymentTypes.BANK_TRANSFER, BSPaymentTypes.FUNDS])( + 'should return false %#', + (paymentType) => { + const paymentStub = makePaymentMethodStub(paymentType) + + expect(SddFlow.isAllowedPaymentType(paymentStub, undefined)).toBe(false) + } + ) + }) + }) +}) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/utils/sddFlow.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/utils/sddFlow.ts new file mode 100644 index 00000000000..bcab7abfc03 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/buySell/utils/sddFlow.ts @@ -0,0 +1,28 @@ +import { + BSPaymentMethodType, + BSPaymentTypes, + MobilePaymentType +} from '@core/network/api/buySell/types' +import { UserDataType } from 'data/modules/profile/types' + +/** + * Back-End allows only T1 SDD eligible+verified and T2 users to access Buy/Sell flows. + * Thus, for purpose of these flows, T1 users are SDD users. + */ +const TIER = 1 + +export const isSddUser = (userData: UserDataType) => { + const currentTier = userData.tiers.current + + return currentTier === TIER +} + +export const isAllowedPaymentType = ( + method: BSPaymentMethodType, + mobilePaymentType?: MobilePaymentType +) => { + const isCardPayment = method.type === BSPaymentTypes.PAYMENT_CARD + const isMobilePayment = !!mobilePaymentType + + return isCardPayment && !isMobilePayment +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/coinTransactions/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/coinTransactions/sagas.ts index 17b80838d64..9a4f0993a72 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/coinTransactions/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/coinTransactions/sagas.ts @@ -9,7 +9,9 @@ export const logLocation = 'components/coinTransactions/sagas' export default () => { const initialized = function* (action: ReturnType) { try { - yield put(actions.core.data.coins.fetchTransactions(action.payload.coin, true)) + yield put( + actions.core.data.coins.fetchTransactions({ coin: action.payload.coin, reset: true }) + ) } catch (e) { yield put(actions.logs.logErrorMessage(logLocation, 'initialized', e)) } @@ -17,7 +19,7 @@ export default () => { const loadMore = function* (action: ReturnType) { try { - yield put(actions.core.data.coins.fetchTransactions(action.payload.coin)) + yield put(actions.core.data.coins.fetchTransactions({ coin: action.payload.coin })) } catch (e) { yield put(actions.logs.logErrorMessage(logLocation, 'loadMore', e)) } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/interest/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/interest/sagas.ts index 4e762aee839..7f6075768d8 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/interest/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/interest/sagas.ts @@ -25,6 +25,7 @@ import { } from '@core/types' import { errorHandler, errorHandlerCode } from '@core/utils' import { actions, selectors } from 'data' +import { CoinAccountTypeEnum } from 'data/coins/accountTypes/accountTypes.classes' import coinSagas from 'data/coins/sagas' import { generateProvisionalPaymentAmount } from 'data/coins/utils' import profileSagas from 'data/modules/profile/sagas' @@ -1214,8 +1215,17 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne origin }) } else { - const receiveAddress = yield call(getNextReceiveAddressForCoin, coin) - yield call(api.initiateInterestWithdrawal, withdrawalAmountBase, coin, receiveAddress) + const receiveAddress: ReturnType = yield call( + getNextReceiveAddressForCoin, + coin, + CoinAccountTypeEnum.NON_CUSTODIAL + ) + yield call( + api.initiateInterestWithdrawal, + withdrawalAmountBase, + coin, + receiveAddress.address + ) } // notify success diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/nfts/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/nfts/sagas.ts index 1bdf7561f13..06fcf8e56a0 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/nfts/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/nfts/sagas.ts @@ -419,8 +419,7 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas; network value: amtToWrap }) yield call(executeWrapEth, signer, amount, wrapFees) - yield put(actions.core.data.eth.fetchData()) - yield put(actions.core.data.eth.fetchErc20Data()) + yield put(actions.core.data.coins.fetchUnifiedBalances()) } yield put(A.setOrderFlowStep({ step: NftOrderStepEnum.STATUS })) @@ -1146,8 +1145,7 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas; network value: action.payload.amtToWrap }) yield call(executeWrapEth, signer, amount, action.payload.wrapFees) - yield put(actions.core.data.eth.fetchData()) - yield put(actions.core.data.eth.fetchErc20Data()) + yield put(actions.core.data.coins.fetchUnifiedBalances()) } yield put(A.setOrderFlowStep({ step: NftOrderStepEnum.STATUS })) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/refresh/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/refresh/sagas.ts index 16e9a45f54a..a6b0a2a0ba0 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/refresh/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/refresh/sagas.ts @@ -23,7 +23,7 @@ export default () => { } const refreshCoinTransactions = function* (coin) { - yield put(actions.core.data.coins.fetchTransactions(coin, true)) + yield put(actions.core.data.coins.fetchTransactions({ coin, reset: true })) } const refreshXlmTransactions = function* () { @@ -42,18 +42,11 @@ export default () => { yield put(actions.core.data.bch.fetchData()) yield put(actions.core.data.btc.fetchData()) yield put(actions.core.data.eth.fetchData()) - yield put(actions.core.data.xlm.fetchData()) - yield put(actions.core.data.eth.fetchErc20Data()) yield put(actions.components.interest.fetchRewardsBalance()) yield put(actions.components.interest.fetchStakingBalance()) yield put(actions.components.interest.fetchActiveRewardsBalance()) yield put(actions.components.buySell.fetchBalance({})) yield put(actions.components.buySell.fetchOrders()) - // TODO: SELF_CUSTODY, remove - const stxEligibility = selectors.coins.getStxSelfCustodyAvailability(yield select()) - if (stxEligibility) { - yield put(actions.core.data.coins.fetchData()) - } // Prices (new approach) yield put(actions.prices.fetchCoinPrices()) // Rates diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/request/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/request/sagas.ts index e652c0253ed..769914e2ae4 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/request/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/request/sagas.ts @@ -1,12 +1,15 @@ -import { call, CallEffect, put, PutEffect, SelectEffect } from 'redux-saga/effects' +import { call, CallEffect, put, PutEffect, select, SelectEffect } from 'redux-saga/effects' import { APIType } from '@core/network/api' import { errorHandler } from '@core/utils' +import { actions } from 'data' +import { CoinAccountTypeEnum } from 'data/coins/accountTypes/accountTypes.classes' import coinSagas from '../../coins/sagas' import profileSagas from '../../modules/profile/sagas' import { SwapBaseCounterTypes } from '../swap/types' import * as A from './actions' +import * as S from './selectors' import { RequestExtrasType } from './types' import { generateKey } from './utils' @@ -23,33 +26,35 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne action: ReturnType ): Generator { const key = generateKey(action.payload.account) + yield call(waitForUserData) try { yield put(A.getNextAddressLoading(key)) let address - const extras: RequestExtrasType = {} + let extras: RequestExtrasType = {} const { account } = action.payload + const { coinfig } = window.coins[account.coin] + const subscriptions = S.getSubscriptions(yield select()) + const isSubscribed = subscriptions.data.currencies.some((c) => c.ticker === account.baseCoin) + if (!isSubscribed) { + yield put(actions.core.data.coins.subscribe(account.baseCoin)) + } switch (account.type) { case SwapBaseCounterTypes.ACCOUNT: - const { accountIndex, coin } = account - address = yield call(getNextReceiveAddressForCoin, coin, accountIndex) - break case SwapBaseCounterTypes.CUSTODIAL: - yield call(waitForUserData) - const custodial: ReturnType = yield call( - // @ts-ignore - api.getBSPaymentAccount, - account.coin - ) - address = custodial.address - if (window.coins[account.coin].coinfig.type.isMemoBased && address.split(':')[1]) { - // eslint-disable-next-line prefer-destructuring - extras.Memo = address.split(':')[1] - // eslint-disable-next-line prefer-destructuring - address = address.split(':')[0] - } + const { accountIndex, coin } = account + const accountType = + account.type === SwapBaseCounterTypes.ACCOUNT + ? coinfig.products.includes('DynamicSelfCustody') + ? CoinAccountTypeEnum.DYNAMIC_SELF_CUSTODY + : coinfig.type.name === 'ERC20' + ? CoinAccountTypeEnum.ERC20 + : CoinAccountTypeEnum.NON_CUSTODIAL + : CoinAccountTypeEnum.CUSTODIAL + const response = yield call(getNextReceiveAddressForCoin, coin, accountType, accountIndex) + address = response.address + extras = response.extras break - // SwapAccountType only supports ACCOUNT and CUSTODIAL? // @ts-ignore case 'LEGACY': address = account.address diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/request/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/request/selectors.ts index 0b898413349..c696cbb62f8 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/request/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/request/selectors.ts @@ -6,3 +6,5 @@ import { SwapAccountType } from '../swap/types' export const getNextAddress = (state: RootState, account: SwapAccountType) => { return state.components.request[`${account.coin} ${account.label}`] || Remote.NotAsked } + +export const getSubscriptions = (state: RootState) => state.dataPath.coins.subscriptions diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/sagas.ts index 04d2d8fa6a9..0ab0627d2d8 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/sagas.ts @@ -1,11 +1,13 @@ import { SEND_FORM } from 'blockchain-wallet-v4-frontend/src/modals/SendCrypto/model' import { SendFormType } from 'blockchain-wallet-v4-frontend/src/modals/SendCrypto/types' +import * as ethers from 'ethers' import { call, delay, put, select } from 'redux-saga/effects' import secp256k1 from 'secp256k1' import { convertCoinToCoin, convertFiatToCoin } from '@core/exchange' import { APIType } from '@core/network/api' -import { BuildTxIntentType, BuildTxResponseType } from '@core/network/api/coin/types' +import { BuildTxIntentType, BuildTxResponseType } from '@core/network/api/coins/types' +import { getCoinNetworksAndTypes } from '@core/redux/data/coins/selectors' import { getPrivKey, getPubKey } from '@core/redux/data/self-custody/sagas' import { FiatType, WalletAccountEnum } from '@core/types' import { errorHandler } from '@core/utils' @@ -20,6 +22,14 @@ import * as S from './selectors' import { actions as A } from './slice' import { SendCryptoStepType } from './types' +const getNetwork = (coin: string) => { + if (coin.includes('.')) { + return coin.split('.')[1] + } + + return coin +} + export default ({ api }: { api: APIType }) => { const initializeSend = function* () { const totalBalanceR = yield select(selectors.balances.getTotalWalletBalanceNotFormatted) @@ -44,18 +54,18 @@ export default ({ api }: { api: APIType }) => { if (account.type === SwapBaseCounterTypes.ACCOUNT) { const password = yield call(promptForSecondPassword) - const pubKey = yield call(getPubKey, password) + const pubKey = yield call(getPubKey, coin, password) const guid = yield select(selectors.core.wallet.getGuid) const [uuid] = yield call(api.generateUUIDs, 1) - const tx: ReturnType = yield call(api.buildTx, { + const tx: ReturnType = yield call(api.buildTx, coin, { id: { guid, uuid }, intent: { amount: baseCryptoAmt, - currency: coin, + currency: 'native', destination, extraData: { memo @@ -69,7 +79,8 @@ export default ({ api }: { api: APIType }) => { } ], type: 'PAYMENT' - } as BuildTxIntentType + } as BuildTxIntentType, + network: getNetwork(coin) }) yield put(A.buildTxSuccess(tx)) @@ -202,8 +213,8 @@ export default ({ api }: { api: APIType }) => { } } - const signTx = function* (prebuildTx: BuildTxResponseType, password: string) { - const privateKey = yield call(getPrivKey, password) + const signTx = function* (coin: string, prebuildTx: BuildTxResponseType, password: string) { + const privateKey = yield call(getPrivKey, coin, password) if (!privateKey) throw new Error('Could not derive private key') @@ -211,7 +222,10 @@ export default ({ api }: { api: APIType }) => { const signedPreImages = prebuildTx.preImages.map((preImage) => { // @ts-ignore const { recovery, signature } = secp256k1.sign( - Buffer.from(preImage.preImage, 'hex'), + Buffer.from( + preImage.preImage.startsWith('0x') ? preImage.preImage.substr(2) : preImage.preImage, + 'hex' + ), Buffer.from(privateKey, 'hex') ) @@ -255,7 +269,7 @@ export default ({ api }: { api: APIType }) => { 'No prebuildTx' ) as BuildTxResponseType prebuildTxFee = prebuildTx.summary.absoluteFeeEstimate - const signedTx: BuildTxResponseType = yield call(signTx, prebuildTx, password) + const signedTx: BuildTxResponseType = yield call(signTx, coin, prebuildTx, password) const pushedTx: ReturnType = yield call( api.pushTx, coin, @@ -264,7 +278,8 @@ export default ({ api }: { api: APIType }) => { { guid, uuid - } + }, + getNetwork(coin) ) const value = convertCoinToCoin({ @@ -280,7 +295,7 @@ export default ({ api }: { api: APIType }) => { }) ) yield delay(2000) - yield put(actions.core.data.coins.fetchTransactions(coin, true)) + yield put(actions.core.data.coins.fetchTransactions({ coin, reset: true })) } else { throw new Error('Failed to submit transaction.') } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/slice.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/slice.ts index 5115860fe43..7b0dae04b17 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/slice.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/slice.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { BuildTxFeeType, BuildTxResponseType } from '@core/network/api/coin/types' +import { BuildTxFeeType, BuildTxResponseType } from '@core/network/api/coins/types' import Remote from '@core/remote' import { CrossBorderLimits, diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/types.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/types.ts index 5f814be72fd..b47c409bed7 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/types.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/sendCrypto/types.ts @@ -1,4 +1,4 @@ -import { BuildTxResponseType } from '@core/network/api/coin/types' +import { BuildTxResponseType } from '@core/network/api/coins/types' import { CrossBorderLimits, RemoteDataType, diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/sendXlm/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/sendXlm/sagas.ts index 687c0dc48eb..9e00114e18c 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/sendXlm/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/sendXlm/sagas.ts @@ -315,7 +315,6 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas: any; ne } else { payment = yield call(payment.publish) } - yield put(actions.core.data.xlm.fetchData()) yield put(A.paymentUpdatedSuccess(value)) const { description } = value if (description) yield put(actions.core.kvStore.xlm.setTxNotesXlm(value.txId, description)) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/swap/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/swap/sagas.ts index 430054b797c..16fb3ecec2f 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/swap/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/swap/sagas.ts @@ -467,11 +467,12 @@ export default ({ api, coreSagas, networks }: { api: APIType; coreSagas; network } const showModal = function* ({ payload }: ReturnType) { - const { baseCurrency, counterCurrency, origin } = payload + const { baseCurrency, counterCurrency, fromType, origin } = payload yield put( actions.modals.showModal(ModalName.SWAP_MODAL, { baseCurrency, counterCurrency, + fromType, origin }) ) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/swap/slice.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/swap/slice.ts index 836c14208ae..9bad84d0010 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/swap/slice.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/swap/slice.ts @@ -9,7 +9,8 @@ import { PaymentValue, SwapOrderType, SwapQuoteStateType, - SwapUserLimitsType + SwapUserLimitsType, + WalletAccountSuperAppType } from '@core/types' import { ModalOriginType } from 'data/modals/types' @@ -149,6 +150,7 @@ const swapSlice = createSlice({ action: PayloadAction<{ baseCurrency?: CoinType counterCurrency?: CoinType + fromType?: WalletAccountSuperAppType origin: ModalOriginType }> ) => {}, diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/utils/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/utils/sagas.ts index c5d5ad4fa59..3354e23389c 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/utils/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/utils/sagas.ts @@ -1,9 +1,8 @@ import * as ethers from 'ethers' -import { equals, identity, is, isEmpty, prop } from 'ramda' -import { call, select } from 'redux-saga/effects' +import { equals, is, prop } from 'ramda' +import { call } from 'redux-saga/effects' -import { utils } from '@core' -import { selectors } from 'data' +import { CoinAccountTypeEnum } from 'data/coins/accountTypes/accountTypes.classes' import coinSagas from '../../coins/sagas' @@ -14,7 +13,6 @@ export const selectReceiveAddress = function* (source, networks, api, coreSagas) networks }) - const appState = yield select(identity) const coin = prop('coin', source) const address = prop('address', source) const { coinfig } = window.coins[coin] @@ -23,45 +21,13 @@ export const selectReceiveAddress = function* (source, networks, api, coreSagas) return ethers.utils.getAddress(address) } - if (equals('XLM', coin) && is(String, address)) return address - - if (equals('BCH', coin)) { - const bchReceiveAddress = selectors.core.common.bch.getNextAvailableReceiveAddress( - networks.bch, - address, - appState - ) - - if (isEmpty(bchReceiveAddress.getOrElse(''))) { - throw new Error('Could not generate bitcoin cash receive address') - } - - return utils.bch.toCashAddr(bchReceiveAddress.getOrElse('')) - } - - if (equals('BTC', coin)) { - const defaultDerivation = selectors.core.common.btc.getAccountDefaultDerivation( - address, - appState - ) - - const btcReceiveAddress = selectors.core.common.btc.getNextAvailableReceiveAddress( - networks.btc, - address, - defaultDerivation, - appState - ) - - if (isEmpty(btcReceiveAddress.getOrElse(''))) { - throw new Error('Could not generate return bitcoin receive address') - } - - return btcReceiveAddress.getOrElse('') - } - try { - const address = yield call(getNextReceiveAddressForCoin, coin) - return address + const address: ReturnType = yield call( + getNextReceiveAddressForCoin, + CoinAccountTypeEnum.NON_CUSTODIAL, + coin + ) + return address.address } catch (e) { throw new Error('Could not generate receive address') } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/components/utils/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/data/components/utils/selectors.ts index b7e1510f92d..09359f09676 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/components/utils/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/components/utils/selectors.ts @@ -1,12 +1,6 @@ -import { lift, mapObjIndexed, values } from 'ramda' +import { lift, mapObjIndexed, uniq, values } from 'ramda' -import { - AccountTokensBalancesResponseType, - BSPaymentTypes, - CoinfigType, - ExtractSuccess, - SwapOrderType -} from '@core/types' +import { BSPaymentTypes, CoinfigType, ExtractSuccess, SwapOrderType } from '@core/types' import { selectors } from 'data' import { RootState } from 'data/rootReducer' import { UserDataType } from 'data/types' @@ -16,45 +10,27 @@ import { getOutputFromPair } from '../swap/model' // eslint-disable-next-line import/prefer-default-export export const getCoinsWithBalanceOrMethod = (state: RootState) => { const sbMethodsR = selectors.components.buySell.getBSPaymentMethods(state) - // TODO: SELF_CUSTODY, remove this - const stxEligibility = selectors.coins.getStxSelfCustodyAvailability(state) - // TODO, check all custodial features + const unifiedBalancesR = selectors.core.data.coins.getUnifiedBalances(state) const sbBalancesR = selectors.components.buySell.getBSBalances(state) - const erc20sR = selectors.core.data.eth.getErc20AccountTokenBalances(state) const recentSwapTxs = selectors.custodial.getRecentSwapTxs(state).getOrElse([] as SwapOrderType[]) const custodials = selectors.core.data.coins.getCustodialCoins() const userData = selectors.modules.profile.getUserData(state).getOrElse({} as UserDataType) const fiatCurrencies = userData.currencies?.userFiatCurrencies || [] - // TODO: SELF_CUSTODY - const selfCustodials = stxEligibility ? ['STX'] : [] const transform = ( + unifiedBalances: ExtractSuccess, paymentMethods: ExtractSuccess, - sbBalances: ExtractSuccess, - erc20s: AccountTokensBalancesResponseType['tokenAccounts'] + sbBalances: ExtractSuccess ) => { - const custodialErc20s = Object.keys(sbBalances).filter( - (coin) => window.coins[coin] && window.coins[coin].coinfig.type.erc20Address - ) + const custodialErc20s = Object.keys(sbBalances) const coinsInRecentSwaps = recentSwapTxs.map((tx) => getOutputFromPair(tx.pair)) + const coinsInUnifiedBalances = uniq(unifiedBalances.map(({ ticker }) => ticker)) + const coinOrder = [ ...new Set([ ...fiatCurrencies, - 'BTC', - 'ETH', - 'BCH', - 'XLM', - ...selfCustodials, + ...coinsInUnifiedBalances, ...custodials, - ...(erc20s - .map(({ tokenHash }) => { - return Object.keys(window.coins).find( - (coin) => - window.coins[coin].coinfig.type?.erc20Address?.toLowerCase() === - tokenHash.toLowerCase() - ) - }) - .filter(Boolean) as string[]), ...custodialErc20s, ...coinsInRecentSwaps ]) @@ -77,7 +53,7 @@ export const getCoinsWithBalanceOrMethod = (state: RootState) => { ) } - return lift(transform)(sbMethodsR, sbBalancesR, erc20sR) + return lift(transform)(unifiedBalancesR, sbMethodsR, sbBalancesR) } export default getCoinsWithBalanceOrMethod diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagaRegister.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagaRegister.js index c221d05487c..584b452dd15 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagaRegister.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagaRegister.js @@ -2,7 +2,7 @@ import { fork } from 'redux-saga/effects' import webSocket from './webSocket/sagaRegister' -export default ({ api, coinsSocket, ratesSocket }) => +export default ({ activitiesSocket, api, coinsSocket, ratesSocket }) => function* middlewareSaga() { - yield fork(webSocket({ api, coinsSocket, ratesSocket })) + yield fork(webSocket({ activitiesSocket, api, coinsSocket, ratesSocket })) } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagas.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagas.js index 4eab26a2094..5dfba1a6866 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagas.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/sagas.js @@ -1,7 +1,8 @@ import webSocket from './webSocket/sagas' -export default ({ api, coinsSocket, ratesSocket }) => ({ +export default ({ activitiesSocket, api, coinsSocket, ratesSocket }) => ({ webSocket: webSocket({ + activitiesSocket, api, coinsSocket, ratesSocket diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actionTypes.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actionTypes.js index 10836d46837..bdd72f3bd5b 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actionTypes.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actionTypes.js @@ -1,5 +1,6 @@ +import * as activities from './activities/actionTypes' import * as coins from './coins/actionTypes' import * as rates from './rates/actionTypes' import * as xlm from './xlm/actionTypes' -export { coins, rates, xlm } +export { activities, coins, rates, xlm } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actions.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actions.js index 511867f27ee..dd33f346127 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actions.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/actions.js @@ -1,5 +1,6 @@ +import * as activities from './activities/actions' import * as coins from './coins/actions' import * as rates from './rates/actions' import * as xlm from './xlm/actions' -export { coins, rates, xlm } +export { activities, coins, rates, xlm } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/actionTypes.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/actionTypes.js new file mode 100644 index 00000000000..380181abfe0 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/actionTypes.js @@ -0,0 +1,9 @@ +export const START_SOCKET = '@CORE.ACTIVITIES_WEBSOCKET_START' +export const AUTH_SOCKET = '@CORE.ACTIVITIES_WEBSOCKET_AUTH' +export const STOP_SOCKET = '@CORE.ACTIVITIES_WEBSOCKET_STOP' + +export const OPEN_SOCKET = '@CORE.ACTIVITIES_WEBSOCKET_OPEN' +export const MESSAGE_SOCKET = '@CORE.ACTIVITIES_WEBSOCKET_MESSAGE' +export const CLOSE_SOCKET = '@CORE.ACTIVITIES_WEBSOCKET_CLOSE' + +export const RESEND_MESSAGE_SOCKET = '@CORE.RESEND_MESSAGE_SOCKET' diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/actions.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/actions.js new file mode 100644 index 00000000000..4ec702585b2 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/actions.js @@ -0,0 +1,11 @@ +import * as AT from './actionTypes' + +export const startSocket = () => ({ type: AT.START_SOCKET }) +export const authSocket = () => ({ type: AT.AUTH_SOCKET }) +export const stopSocket = () => ({ type: AT.STOP_SOCKET }) + +export const openSocket = () => ({ type: AT.OPEN_SOCKET }) +export const messageSocket = (payload) => ({ payload, type: AT.MESSAGE_SOCKET }) +export const closeSocket = () => ({ type: AT.CLOSE_SOCKET }) + +export const resendMessageSocket = () => ({ type: AT.RESEND_MESSAGE_SOCKET }) diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/sagaRegister.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/sagaRegister.js new file mode 100644 index 00000000000..1648839f3d2 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/sagaRegister.js @@ -0,0 +1,15 @@ +import { takeEvery } from 'redux-saga/effects' + +import * as AT from './actionTypes' +import sagas from './sagas' + +export default ({ activitiesSocket, api }) => { + const activitiesSocketSagas = sagas({ api, socket: activitiesSocket }) + return function* activitiesSocketSaga() { + yield takeEvery(AT.OPEN_SOCKET, activitiesSocketSagas.onOpen) + yield takeEvery(AT.AUTH_SOCKET, activitiesSocketSagas.onAuth) + yield takeEvery(AT.MESSAGE_SOCKET, activitiesSocketSagas.onMessage) + yield takeEvery(AT.CLOSE_SOCKET, activitiesSocketSagas.onClose) + yield takeEvery(AT.RESEND_MESSAGE_SOCKET, activitiesSocketSagas.resendMessageSocket) + } +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/sagas.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/sagas.js new file mode 100644 index 00000000000..04ab480dcec --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/activities/sagas.js @@ -0,0 +1,65 @@ +import { prop } from 'ramda' +import { call, put, select } from 'redux-saga/effects' + +import { sha256 } from '@core/walletCrypto' +import { actions, selectors } from 'data' + +export default ({ socket }) => { + const authenticateSocket = function* () { + const guid = yield select(selectors.core.wallet.getGuid) + const sharedKey = yield select(selectors.core.wallet.getSharedKey) + + const guidHash = sha256(guid).toString('hex') + const sharedKeyHash = sha256(sharedKey).toString('hex') + + const authParams = { + action: 'subscribe', + auth: { + guidHash, + sharedKeyHash + }, + channel: 'activity', + params: { + // remove hardcoding timezone here?? + acceptLanguage: 'en-GB;q=1.0, en', + fiatCurrency: 'GBP', + timezoneIana: 'Europe/London' + } + } + + socket.send(JSON.stringify(authParams)) + } + + const onOpen = function* () { + yield call(authenticateSocket) + } + + const onAuth = function* () {} + + const onMessage = function* (action) { + // let pubkeyData = [] + // pubkeyData.push(action.payload.data) + // console.log(action.payload.data.pubKey, action.payload.data.network) + // console.log(pubkeyData) + } + + const resendMessageSocket = function* (action) {} + + const onClose = function* () { + yield put( + actions.logs.logErrorMessage( + 'middleware/webSocket/activities/sagas', + 'onClose', + `websocket closed at ${Date.now()}` + ) + ) + } + + return { + onAuth, + onClose, + onMessage, + onOpen, + resendMessageSocket + } +} diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagaRegister.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagaRegister.js index fd6b64decf8..a79e851cd1b 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagaRegister.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagaRegister.js @@ -1,12 +1,14 @@ import { fork } from 'redux-saga/effects' +import activities from './activities/sagaRegister' import coins from './coins/sagaRegister' import rates from './rates/sagaRegister' import xlm from './xlm/sagaRegister' -export default ({ api, coinsSocket, ratesSocket }) => +export default ({ activitiesSocket, api, coinsSocket, ratesSocket }) => function* webSocketSaga() { yield fork(xlm()) + yield fork(activities({ activitiesSocket, api })) yield fork(rates({ api, ratesSocket })) yield fork(coins({ api, coinsSocket })) } diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagas.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagas.js index bac12cad907..c62ff614618 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagas.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/sagas.js @@ -1,8 +1,10 @@ +import activities from './activities/sagas' import coins from './coins/sagas' import rates from './rates/sagas' import xlm from './xlm/sagas' -export default ({ api, coinsSocket, ratesSocket }) => ({ +export default ({ activitiesSocket, api, coinsSocket, ratesSocket }) => ({ + activities: activities({ activitiesSocket, api }), rates: rates({ api, ratesSocket }), sds: coins({ api, coinsSocket }), xlm: xlm() diff --git a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/xlm/sagas.js b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/xlm/sagas.js index 0a51fcc5e57..4eff4fdf3f1 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/xlm/sagas.js +++ b/packages/blockchain-wallet-v4-frontend/src/data/middleware/webSocket/xlm/sagas.js @@ -16,7 +16,6 @@ export default () => { if (tx.source_account !== accountId) { yield put(actions.alerts.displaySuccess(T.PAYMENT_RECEIVED_XLM)) } - yield put(actions.core.data.xlm.fetchData()) const pathname = yield select(selectors.router.getPathname) if (includes(pathname, ['/coins/XLM', '/home'])) yield call(addWalletTransaction, tx) } catch (e) { diff --git a/packages/blockchain-wallet-v4-frontend/src/data/prices/sagas.ts b/packages/blockchain-wallet-v4-frontend/src/data/prices/sagas.ts index 9fabe3f188e..7fdd1fd1064 100755 --- a/packages/blockchain-wallet-v4-frontend/src/data/prices/sagas.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/prices/sagas.ts @@ -2,7 +2,7 @@ import { getUnixTime, subDays } from 'date-fns' import { call, put, select } from 'redux-saga/effects' import { APIType } from '@core/network/api' -import { IndexMultiResponseType } from '@core/network/api/coin/types' +import { IndexMultiResponseType } from '@core/network/api/coins/types' import { errorCodeAndMessage } from '@core/utils' import { selectors } from 'data' import { PartialClientErrorProperties } from 'data/analytics/types/errors' diff --git a/packages/blockchain-wallet-v4-frontend/src/data/rootSaga.ts b/packages/blockchain-wallet-v4-frontend/src/data/rootSaga.ts index d65a66c46be..77a920f36ed 100644 --- a/packages/blockchain-wallet-v4-frontend/src/data/rootSaga.ts +++ b/packages/blockchain-wallet-v4-frontend/src/data/rootSaga.ts @@ -22,7 +22,14 @@ import session from './session/sagaRegister' import signup from './signup/sagaRegister' import wallet from './wallet/sagaRegister' -export default function* rootSaga({ api, coinsSocket, networks, options, ratesSocket }) { +export default function* rootSaga({ + activitiesSocket, + api, + coinsSocket, + networks, + options, + ratesSocket +}) { const coreSagas = coreSagasFactory({ api, networks, options }) const { initAppLanguage, logAppConsoleWarning } = miscSagas() @@ -42,8 +49,9 @@ export default function* rootSaga({ api, coinsSocket, networks, options, ratesSo fork(preferences()), fork(prices({ api })), fork(goals({ api, coreSagas, networks })), + fork(middleware({ activitiesSocket, api, coinsSocket, ratesSocket })), fork(wallet({ coreSagas })), - fork(middleware({ api, coinsSocket, ratesSocket })), + fork(middleware({ activitiesSocket, api, coinsSocket, ratesSocket })), fork(coreRootSagaFactory({ api, networks, options })), fork(router()), fork(session({ api })), diff --git a/packages/blockchain-wallet-v4-frontend/src/hooks/useCoinTransactionsQuery/useCoinTransactionsQuery.types.ts b/packages/blockchain-wallet-v4-frontend/src/hooks/useCoinTransactionsQuery/useCoinTransactionsQuery.types.ts index f89a48f1876..8bcf4326a2b 100644 --- a/packages/blockchain-wallet-v4-frontend/src/hooks/useCoinTransactionsQuery/useCoinTransactionsQuery.types.ts +++ b/packages/blockchain-wallet-v4-frontend/src/hooks/useCoinTransactionsQuery/useCoinTransactionsQuery.types.ts @@ -1,4 +1,4 @@ -import { IngestedSelfCustodyType } from '@core/network/api/coin/types' +import { ActivityResponseType, IngestedSelfCustodyType } from '@core/network/api/coins/types' import { BSOrderType, BSTransactionType, @@ -15,6 +15,7 @@ type TransactionItem = | ProcessedTxType | FiatBSAndSwapTransactionType | IngestedSelfCustodyType + | ActivityResponseType['activity'][0] type CoinTransactionsQueryHookProps = { coin: CoinType diff --git a/packages/blockchain-wallet-v4-frontend/src/hooks/useWalletsForCoin/useWalletsForCoin.ts b/packages/blockchain-wallet-v4-frontend/src/hooks/useWalletsForCoin/useWalletsForCoin.ts index e872a38f17d..15d50a04fa5 100644 --- a/packages/blockchain-wallet-v4-frontend/src/hooks/useWalletsForCoin/useWalletsForCoin.ts +++ b/packages/blockchain-wallet-v4-frontend/src/hooks/useWalletsForCoin/useWalletsForCoin.ts @@ -26,6 +26,11 @@ export const useWalletsForCoin: WalletsForCoinHook = ({ coin }) => { [coin] ) + const isDynamicSelfCustodyCoin = useMemo( + () => selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coin), + [coin] + ) + const isBTC = coin === 'BTC' const isBCH = coin === 'BCH' const isETH = coin === 'ETH' @@ -80,6 +85,15 @@ export const useWalletsForCoin: WalletsForCoinHook = ({ coin }) => { }) } + if (isDynamicSelfCustodyCoin) { + return getCoinAddressData(state, { + coin, + includeCustodial: isCustodialCoin, + includeInterest: true, + includeSelfCustody: true + }) + } + if (isCustodialCoin) { return getCoinAddressData(state, { coin, diff --git a/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuLeft/Balances/WalletBalance/Balance/index.tsx b/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuLeft/Balances/WalletBalance/Balance/index.tsx index 56e34522ce6..71791f7dc83 100644 --- a/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuLeft/Balances/WalletBalance/Balance/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuLeft/Balances/WalletBalance/Balance/index.tsx @@ -1,6 +1,5 @@ import React from 'react' import { connect, ConnectedProps } from 'react-redux' -import { toLower } from 'ramda' import { bindActionCreators } from 'redux' import { CoinType, ExtractSuccess } from '@core/types' @@ -13,14 +12,7 @@ import Success from './template.success' class Balance extends React.PureComponent { handleRefresh = () => { - const { coin } = this.props - const { coinfig } = window.coins[coin] - if (coinfig.type.erc20Address) { - this.props.ethActions.fetchErc20Data(coin) - } else { - const coinLower = toLower(coin) - this.props[`${coinLower}Actions`].fetchData() - } + this.props.coinsActions.fetchUnifiedBalances() } render() { @@ -49,11 +41,7 @@ const mapStateToProps = (state, ownProps) => ({ }) const mapDispatchToProps = (dispatch) => ({ - bchActions: bindActionCreators(actions.core.data.bch, dispatch), - btcActions: bindActionCreators(actions.core.data.btc, dispatch), - ethActions: bindActionCreators(actions.core.data.eth, dispatch), - stxActions: bindActionCreators(actions.core.data.stx, dispatch), - xlmActions: bindActionCreators(actions.core.data.xlm, dispatch) + coinsActions: bindActionCreators(actions.core.data.coins, dispatch) }) const connector = connect(mapStateToProps, mapDispatchToProps) diff --git a/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuTop/Header.tsx b/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuTop/Header.tsx index 21c6a5d9f1e..229294757bd 100644 --- a/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuTop/Header.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/layouts/Wallet/MenuTop/Header.tsx @@ -103,6 +103,16 @@ const Header = (props: OwnProps) => { e2e: 'earnLink', isNew: true, text: + }, + { + dest: '/defi', + e2e: 'defiTest', + text: + }, + { + dest: '/accounts', + e2e: 'accountsTest', + text: } ] diff --git a/packages/blockchain-wallet-v4-frontend/src/middleware/index.js b/packages/blockchain-wallet-v4-frontend/src/middleware/index.js index 5dad97549ee..3ebbb02a21d 100644 --- a/packages/blockchain-wallet-v4-frontend/src/middleware/index.js +++ b/packages/blockchain-wallet-v4-frontend/src/middleware/index.js @@ -1,6 +1,7 @@ import analyticsMiddleware from './analyticsMiddleware' import streamingXlm from './streamingXlm' +import webSocketActivities from './webSocketActivities' import webSocketCoins from './webSocketCoins' import webSocketRates from './webSocketRates' -export { analyticsMiddleware, streamingXlm, webSocketCoins, webSocketRates } +export { analyticsMiddleware, streamingXlm, webSocketActivities, webSocketCoins, webSocketRates } diff --git a/packages/blockchain-wallet-v4-frontend/src/middleware/webSocketActivities.js b/packages/blockchain-wallet-v4-frontend/src/middleware/webSocketActivities.js new file mode 100644 index 00000000000..d46043527f8 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/middleware/webSocketActivities.js @@ -0,0 +1,42 @@ +import { compose } from 'ramda' + +import { actions, actionTypes } from 'data' + +export const fallbackInterval = 5000 +const fallbackIntervalPID = null + +const socket = (socket) => (store) => { + return (next) => (action) => { + const { type } = action + + if (type === actionTypes.middleware.webSocket.activities.START_SOCKET) { + clearInterval(fallbackIntervalPID) + socket.connect( + // onOpen + compose(store.dispatch, actions.middleware.webSocket.activities.openSocket), + // onMessage + compose(store.dispatch, actions.middleware.webSocket.activities.messageSocket), + // onClose + compose(store.dispatch, actions.middleware.webSocket.activities.closeSocket), + // onError + // eslint-disable-next-line no-console + (e) => console.error('Failed to connect to websocket', e), + // fallback + () => { + // fallbackIntervalPID = setInterval( + // compose(store.dispatch, actions.middleware.webSocket.activities.restFallback), + // fallbackInterval + // ) + } + ) + } + + if (type === actionTypes.middleware.webSocket.activities.STOP_SOCKET) { + socket.close() + } + + return next(action) + } +} + +export default socket diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/AddCardVgs/AddCardVgs.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/AddCardVgs/AddCardVgs.tsx index 0bd30f249fd..1d507909774 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/AddCardVgs/AddCardVgs.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/AddCardVgs/AddCardVgs.tsx @@ -41,6 +41,7 @@ const AddCardVgs: VgsComponent = ({ handleClose }) => { // FIXME: due to a recent change on the backend `country` is no longer supported in favor of `countryCode` // but not supported everywhere yet so need to keep `country` for now. address.countryCode = address.country + address.state = address.state || '' ref.current?.contentWindow?.postMessage( { messageData: address, method: 'billingAddress' }, diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/index.tsx index 9dd919aa77e..70c07e72838 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/index.tsx @@ -23,8 +23,6 @@ class CheckoutConfirm extends PureComponent { componentDidMount() { this.props.sendActions.getLockRule() if (!Remote.Success.is(this.props.data)) { - this.props.buySellActions.fetchSDDEligibility() - this.props.buySellActions.fetchSDDVerified() this.props.buySellActions.fetchCards(false) this.props.brokerageActions.fetchBankTransferAccounts() } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/selectors.ts index a4af1d73b3e..56402e9fb9b 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/selectors.ts @@ -3,6 +3,7 @@ import { createSelector } from 'reselect' import { ExtractSuccess } from '@core/types' import { selectors } from 'data' +import { getIsSddFlow } from 'data/components/buySell/selectors/getIsSddFlow' import { RootState } from 'data/rootReducer' import * as QuoteSummaryViewModel from './models/quoteSummaryViewModel' @@ -17,12 +18,8 @@ export const getData = (state: RootState) => { const quoteSummaryViewModelR = selectQuoteSummaryViewModel(state) const sbBalancesR = selectors.components.buySell.getBSBalances(state) - const sddEligibleR = selectors.components.buySell.getSddEligible(state) - const userSDDTierR = selectors.components.buySell.getUserSddEligibleTier(state) - const isUserSddVerifiedR = selectors.components.buySell.isUserSddVerified(state) const cardsR = selectors.components.buySell.getBSCards(state) - - const userDataR = selectors.modules.profile.getUserData(state) + const isSddFlowR = getIsSddFlow(state) const withdrawLockCheckR = selectors.components.send.getWithdrawLockCheckRule(state) @@ -33,34 +30,26 @@ export const getData = (state: RootState) => { bankAccounts: ExtractSuccess, quoteSummaryViewModel: ExtractSuccess, sbBalances: ExtractSuccess, - userData: ExtractSuccess, withdrawLockCheck: ExtractSuccess, - sddEligible: ExtractSuccess, - userSDDTier: ExtractSuccess, - isUserSddVerified: ExtractSuccess, cards: ExtractSuccess, - order: ExtractSuccess + order: ExtractSuccess, + isSddFlow: ExtractSuccess ) => ({ bankAccounts, cards, - isSddFlow: sddEligible.eligible || userSDDTier === 3, - isUserSddVerified, + isSddFlow, order, quoteSummaryViewModel, sbBalances, - userData, withdrawLockCheck }) )( bankAccountsR, quoteSummaryViewModelR, sbBalancesR, - userDataR, withdrawLockCheckR, - sddEligibleR, - userSDDTierR, - isUserSddVerifiedR, cardsR, - orderR + orderR, + isSddFlowR ) } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/template.success.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/template.success.tsx index a9c5b0a938c..e1b69c5122c 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/template.success.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CheckoutConfirm/template.success.tsx @@ -24,8 +24,7 @@ import { BankTransferAccountType, BrokerageModalOriginType, ModalName, - RecurringBuyPeriods, - UserDataType + RecurringBuyPeriods } from 'data/types' import { useDefer3rdPartyScript, useSardineContext } from 'hooks' import { isNabuError } from 'services/errors' @@ -192,6 +191,7 @@ const Success: React.FC & Props> = (p key: Analytics.BUY_CHECKOUT_SCREEN_VIEWED, properties: {} }) + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { @@ -219,37 +219,24 @@ const Success: React.FC & Props> = (p properties: {} }) - const { bankAccounts, cards, isSddFlow, isUserSddVerified, sbBalances, userData } = - props.data.getOrElse({ - isSddFlow: false, - userData: { tiers: { current: 0 } } as UserDataType - } as SuccessStateType) + const { bankAccounts, cards, isSddFlow, sbBalances } = props.data.getOrElse({ + isSddFlow: false + } as SuccessStateType) - const userTier = userData?.tiers?.current const inputCurrency = props.order.inputCurrency as WalletFiatType - // check for SDD flow and direct to add card - if (isSddFlow && props.order.paymentType === BSPaymentTypes.PAYMENT_CARD) { - if (isUserSddVerified) { - // user has to have at least one active card - if (cards && cards.length > 0 && cards[0].state === 'ACTIVE') { - const card = cards[0] - return props.buySellActions.confirmOrder({ - order: props.order, - paymentMethodId: card.id - }) - } - return props.buySellActions.setStep({ - step: 'DETERMINE_CARD_PROVIDER' + + if (isSddFlow) { + // user has to have at least one active card + if (cards && cards.length > 0 && cards[0].state === 'ACTIVE') { + const card = cards[0] + return props.buySellActions.confirmOrder({ + order: props.order, + paymentMethodId: card.id }) } - return props.buySellActions.setStep({ - step: 'KYC_REQUIRED' - }) - } - if (userTier < 2) { return props.buySellActions.setStep({ - step: 'KYC_REQUIRED' + step: 'DETERMINE_CARD_PROVIDER' }) } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/CryptoSelector/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/CryptoSelector/index.tsx index 95abd214250..418744fc379 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/CryptoSelector/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/CryptoSelector/index.tsx @@ -9,6 +9,7 @@ import { Icon, Image, TabMenu, TabMenuItem, Text } from 'blockchain-info-compone import { FlyoutWrapper } from 'components/Flyout' import { model } from 'data' import { getCoinFromPair, getFiatFromPair } from 'data/components/buySell/model' +import * as SddFlow from 'data/components/buySell/utils/sddFlow' import { Analytics, ModalName, SwapAccountType } from 'data/types' import { Props as OwnProps, SuccessStateType } from '../index' @@ -101,26 +102,13 @@ class CryptoSelector extends React.Component & Prop } handleBuy = (pair: BSPairType) => { - const currentTier = this.props.userData?.tiers?.current ?? 0 - this.props.analyticsActions.trackEvent({ key: Analytics.BUY_ASSET_SELECTED, properties: {} }) - // if first time user, send to verify email step which is required future SDD check - if (!this.props.emailVerified && currentTier !== 2 && currentTier !== 1) { - return this.props.buySellActions.setStep({ - cryptoCurrency: getCoinFromPair(pair.pair), - fiatCurrency: getFiatFromPair(pair.pair), - orderType: this.state.orderType, - pair, - step: 'VERIFY_EMAIL' - }) - } - // if SDD user has already placed on order, force them to Gold upgrade - if (currentTier === 3 && this.props.sbOrders?.length > 0) { + if (SddFlow.isSddUser(this.props.userData) && this.props.sbOrders?.length > 0) { return this.props.buySellActions.setStep({ step: 'UPGRADE_TO_GOLD' }) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/index.tsx index 68d7fd3c04c..07033af530b 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/index.tsx @@ -28,7 +28,6 @@ const CryptoSelection: React.FC = memo((props) => { (currentCurrencyIsInSupportedFiat ? props.walletCurrency : 'USD') props.buySellActions.fetchPairs({ currency }) props.buySellActions.fetchFiatEligible(props.walletCurrency) - props.buySellActions.fetchSDDEligibility() props.buySellActions.fetchBSOrders() } }, []) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/selectors.ts index 6997cf7bf83..dbf19f165cf 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/CryptoSelection/selectors.ts @@ -12,7 +12,6 @@ export const getData = (state) => { ) as BSCheckoutFormValuesType const emailVerifiedR = selectors.core.settings.getEmailVerified(state) const sbOrdersR = selectors.components.buySell.getBSOrders(state) - const sddEligibleR = selectors.components.buySell.getSddEligible(state) // checks orderType on state for the 'SELL' button on top of activity feed const stateOrderType = selectors.components.buySell.getOrderType(state) const pairsR = selectors.components.buySell.getBSPairs(state) @@ -26,7 +25,6 @@ export const getData = (state) => { pairs: ExtractSuccess, userData: ExtractSuccess, sbOrders: ExtractSuccess, - sddEligible: ExtractSuccess, walletCurrency: FiatType ) => ({ eligibility, @@ -37,11 +35,10 @@ export const getData = (state) => { orderType: formValues ? formValues.orderType : stateOrderType || 'BUY', pairs, sbOrders, - sddEligible, userData, walletCurrency }) - )(eligibilityR, emailVerifiedR, pairsR, userDataR, sbOrdersR, sddEligibleR, walletCurrencyR) + )(eligibilityR, emailVerifiedR, pairsR, userDataR, sbOrdersR, walletCurrencyR) } export default getData diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/ActionButton/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/ActionButton/index.tsx index 3a3f00e9f11..df58aaf9618 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/ActionButton/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/ActionButton/index.tsx @@ -10,14 +10,12 @@ type Props = { invalid: boolean isAmountInBounds: boolean isDailyLimitExceeded: boolean - isSufficientEthForErc20: boolean submitting: boolean } & OwnProps & SuccessStateType const ActionButton: React.FC = (props) => { const disabled = props.invalid || props.submitting || !props.isAmountInBounds - const disableInsufficientEth = props.isSufficientEthForErc20 const dailyLimitExceeded = props.isDailyLimitExceeded switch (props.userData.kycState) { @@ -93,7 +91,7 @@ const ActionButton: React.FC = (props) => { nature='primary' type='submit' fullwidth - disabled={disabled || disableInsufficientEth || dailyLimitExceeded} + disabled={disabled || dailyLimitExceeded} > {props.submitting ? ( diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/index.tsx index 0f5ab5a5b1e..6b280d5d74e 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/index.tsx @@ -15,7 +15,6 @@ import { FlyoutOopsError } from 'components/Flyout/Errors' import { GenericNabuErrorFlyout } from 'components/GenericNabuErrorFlyout' import { actions, model, selectors } from 'data' import { PartialClientErrorProperties } from 'data/analytics/types/errors' -import { getValidPaymentMethod } from 'data/components/buySell/model' import { RootState } from 'data/rootReducer' import { Analytics, BSCheckoutFormValuesType, ModalName, RecurringBuyPeriods } from 'data/types' import { useRemote } from 'hooks' @@ -54,9 +53,7 @@ const Checkout = (props: Props) => { const handleSubmit = () => { if (!data) return - // if the user is < tier 2 go to kyc but save order info - // if the user is tier 2 try to submit order, let BE fail - const { hasPaymentAccount, isSddFlow, userData } = data + const { hasPaymentAccount, isSddFlow } = data const buySellGoal = find(propEq('name', 'buySell'), goals) @@ -79,22 +76,7 @@ const Checkout = (props: Props) => { }) if (isSddFlow) { - const currentTier = userData?.tiers?.current ?? 0 - - if (currentTier === 2 || currentTier === 1) { - // user in SDD but already completed eligibility check, continue to payment - props.buySellActions.createOrder({ paymentType: BSPaymentTypes.PAYMENT_CARD }) - } else { - // user in SDD but needs to confirm KYC and SDD eligibility - props.identityVerificationActions.verifyIdentity({ - checkSddEligibility: true, - needMoreInfo: false, - onCompletionCallback: () => - props.buySellActions.createOrder({ paymentType: BSPaymentTypes.PAYMENT_CARD }), - origin: 'BuySell', - tier: 2 - }) - } + props.buySellActions.createOrder({ paymentType: BSPaymentTypes.PAYMENT_CARD }) } else if (!method) { const nextStep = hasPaymentAccount ? 'LINKED_PAYMENT_ACCOUNTS' : 'PAYMENT_METHODS' props.buySellActions.setStep({ @@ -104,8 +86,6 @@ const Checkout = (props: Props) => { pair: props.pair, step: nextStep }) - } else if (userData.tiers.current < 2) { - props.buySellActions.createOrder({ paymentMethodId: getValidPaymentMethod(method.type) }) } else if (formValues && method) { switch (method.type) { case BSPaymentTypes.PAYMENT_CARD: @@ -178,7 +158,6 @@ const Checkout = (props: Props) => { } if (!data) { - props.buySellActions.fetchSDDEligibility() props.brokerageActions.fetchBankTransferAccounts() } // we fetch limits as part of home banners logic at that point we had only fiatCurrency diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/selectors.ts index 6dcd99d28f9..c07f93548fc 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/selectors.ts @@ -2,6 +2,7 @@ import { lift } from 'ramda' import { BSPaymentTypes, CrossBorderLimits, ExtractSuccess } from '@core/types' import { model, selectors } from 'data' +import { getIsSddFlow } from 'data/components/buySell/selectors/getIsSddFlow' import { RootState } from 'data/rootReducer' const { FORM_BS_CHECKOUT } = model.components.buySell @@ -13,8 +14,6 @@ const getData = (state: RootState) => { const ratesR = selectors.core.data.misc.getRatesSelector(coin, state) const sbBalancesR = selectors.components.buySell.getBSBalances(state) const userDataR = selectors.modules.profile.getUserData(state) - const sddEligibleR = selectors.components.buySell.getSddEligible(state) - const userSDDTierR = selectors.components.buySell.getUserSddEligibleTier(state) const sddLimitR = selectors.components.buySell.getUserLimit(state, BSPaymentTypes.PAYMENT_CARD) const cardsR = selectors.components.buySell.getBSCards(state) || [] const bankTransferAccounts = selectors.components.brokerage @@ -22,6 +21,7 @@ const getData = (state: RootState) => { .getOrElse([]) const limitsR = selectors.components.buySell.getLimits(state) const hasFiatBalance = selectors.components.buySell.hasFiatBalances(state) + const isSddFlowR = getIsSddFlow(state) const isRecurringBuy = selectors.core.walletOptions .getFeatureFlagRecurringBuys(state) @@ -38,10 +38,9 @@ const getData = (state: RootState) => { rates: ExtractSuccess, sbBalances: ExtractSuccess, userData: ExtractSuccess, - sddEligible: ExtractSuccess, sddLimit: ExtractSuccess, - userSDDTier: ExtractSuccess, - products: ExtractSuccess + products: ExtractSuccess, + isSddFlow: ExtractSuccess ) => ({ bankTransferAccounts, cards, @@ -50,17 +49,16 @@ const getData = (state: RootState) => { hasFiatBalance, hasPaymentAccount: hasFiatBalance || cards.length > 0 || bankTransferAccounts.length > 0, isRecurringBuy, - isSddFlow: sddEligible.eligible || userSDDTier === 3, + isSddFlow, limits: limitsR.getOrElse(undefined), payment: paymentR.getOrElse(undefined), products, rates, sbBalances, - sddEligible, sddLimit, userData }) - )(cardsR, ratesR, sbBalancesR, userDataR, sddEligibleR, sddLimitR, userSDDTierR, productsR) + )(cardsR, ratesR, sbBalancesR, userDataR, sddLimitR, productsR, isSddFlowR) } export default getData diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/template.success.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/template.success.tsx index 4250e9900ce..e703a9830b2 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/template.success.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/Checkout/template.success.tsx @@ -541,7 +541,6 @@ const Success: React.FC & Props> = (props) => { {!showLimitError && !showError && !isPaymentMethodBlocked && ( diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/index.tsx index 11375104bcd..0d9a7ec085f 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/EnterAmount/index.tsx @@ -43,7 +43,6 @@ const EnterAmount = (props: Props) => { }) props.brokerageActions.fetchBankTransferAccounts() props.buySellActions.fetchCards(false) - props.buySellActions.fetchSDDEligibility() props.buySellActions.fetchOrders() } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/OrderSummary/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/OrderSummary/index.tsx index 26e8268d16a..7c36a4c041e 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/OrderSummary/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/OrderSummary/index.tsx @@ -92,7 +92,6 @@ class OrderSummaryContainer extends PureComponent { NotAsked: () => , Success: (val) => { const { interestEligible, interestRates, order } = val - const { state } = order const currencySymbol = getSymbol(getCounterCurrency(order)) const [recurringBuy] = val.recurringBuyList.filter((rb) => { return rb.id === order.recurringBuyId @@ -150,6 +149,17 @@ class OrderSummaryContainer extends PureComponent { } } + if ( + order.state === 'PENDING_CONFIRMATION' && + order.attributes?.cardCassy?.paymentState !== 'SETTLED' && + order.attributes?.needCvv + ) { + // In case that it's in PENDING_CONFIRMATION state we need to and need tp update CVV we have t show modal + this.props.buySellActions.setStep({ + step: 'UPDATE_SECURITY_CODE' + }) + } + const handleCompleteButton = () => { if ( order.attributes?.cardProvider?.cardAcquirerName === 'EVERYPAY' || @@ -179,7 +189,7 @@ class OrderSummaryContainer extends PureComponent { } } - return state === 'FAILED' || state === 'CANCELED' || !order.paymentType ? ( + return order.state === 'FAILED' || order.state === 'CANCELED' || !order.paymentType ? ( { interestEligible={interestEligible} interestRates={interestRates} lockTime={val.lockTime} - orderState={state} + orderState={order.state} orderType={getOrderType(order) as OrderType} outputCurrency={order.outputCurrency} paymentState={order.attributes?.everypay?.paymentState || null} diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/index.tsx index 39a3da35a41..5f348f9cc1e 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/index.tsx @@ -4,7 +4,6 @@ import { find, isEmpty, pathOr, propEq, propOr } from 'ramda' import { bindActionCreators } from 'redux' import { - BSPaymentTypes, CrossBorderLimitsPayload, ExtractSuccess, FiatType, @@ -15,7 +14,6 @@ import { FlyoutOopsError } from 'components/Flyout/Errors' import { GenericNabuErrorFlyout } from 'components/GenericNabuErrorFlyout' import { actions, model, selectors } from 'data' import { PartialClientErrorProperties } from 'data/analytics/types/errors' -import { getValidPaymentMethod } from 'data/components/buySell/model' import { RootState } from 'data/rootReducer' import { Analytics, @@ -65,97 +63,16 @@ const Checkout = (props: Props) => { properties: {} }) - // if the user is < tier 2 go to kyc but save order info - // if the user is tier 2 try to submit order, let BE fail - const { hasPaymentAccount, isSddFlow, userData } = data - const buySellGoal = find(propEq('name', 'buySell'), goals) - const id = propOr('', 'id', buySellGoal) - if (!isEmpty(id)) { props.deleteGoal(String(id)) } - const method = props.method || props.defaultMethod - - // TODO: sell - // need to do kyc check - // SELL - if (formValues?.orderType === OrderType.SELL) { - return props.buySellActions.setStep({ - sellOrderType: props.swapAccount?.type, - step: 'PREVIEW_SELL' - }) - } - - // BUY - if (isSddFlow) { - const currentTier = userData?.tiers?.current ?? 0 - - if (currentTier === 2 || currentTier === 1) { - // user in SDD but already completed eligibility check, continue to payment - props.buySellActions.createOrder({ paymentType: BSPaymentTypes.PAYMENT_CARD }) - } else { - // user in SDD but needs to confirm KYC and SDD eligibility - props.identityVerificationActions.verifyIdentity({ - checkSddEligibility: true, - needMoreInfo: false, - onCompletionCallback: () => - props.buySellActions.createOrder({ paymentType: BSPaymentTypes.PAYMENT_CARD }), - origin: 'BuySell', - tier: 2 - }) - } - } else if (!method) { - const nextStep = hasPaymentAccount ? 'LINKED_PAYMENT_ACCOUNTS' : 'PAYMENT_METHODS' - props.buySellActions.setStep({ - cryptoCurrency: props.cryptoCurrency, - fiatCurrency: props.fiatCurrency, - order: props.order, - pair: props.pair, - step: nextStep - }) - } else if (userData.tiers.current < 2) { - props.buySellActions.createOrder({ paymentMethodId: getValidPaymentMethod(method.type) }) - } else if (formValues && method) { - switch (method.type) { - case BSPaymentTypes.PAYMENT_CARD: - if (props.mobilePaymentMethod) { - props.buySellActions.createOrder({ - mobilePaymentMethod: props.mobilePaymentMethod, - paymentMethodId: method.id, - paymentType: BSPaymentTypes.PAYMENT_CARD - }) - - break - } - - props.buySellActions.setStep({ - step: 'DETERMINE_CARD_PROVIDER' - }) - break - case BSPaymentTypes.USER_CARD: - props.buySellActions.createOrder({ - paymentMethodId: method.id, - paymentType: BSPaymentTypes.PAYMENT_CARD - }) - break - case BSPaymentTypes.FUNDS: - props.buySellActions.createOrder({ paymentType: BSPaymentTypes.FUNDS }) - break - case BSPaymentTypes.BANK_TRANSFER: - props.buySellActions.createOrder({ - paymentMethodId: method.id, - paymentType: BSPaymentTypes.BANK_TRANSFER - }) - break - case BSPaymentTypes.BANK_ACCOUNT: - break - default: - break - } - } + return props.buySellActions.setStep({ + sellOrderType: props.swapAccount?.type, + step: 'PREVIEW_SELL' + }) } const errorCallback = () => { @@ -190,7 +107,6 @@ const Checkout = (props: Props) => { } if (!data) { - props.buySellActions.fetchSDDEligibility() props.brokerageActions.fetchBankTransferAccounts() } // we fetch limits as part of home banners logic at that point we had only fiatCurrency diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/selectors.ts index eba24a887ec..293f117142a 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/Checkout/selectors.ts @@ -2,6 +2,7 @@ import { lift } from 'ramda' import { BSPaymentTypes, CrossBorderLimits, ExtractSuccess } from '@core/types' import { model, selectors } from 'data' +import { getIsSddFlow } from 'data/components/buySell/selectors/getIsSddFlow' import { RootState } from 'data/rootReducer' import { OwnProps } from '.' @@ -20,8 +21,6 @@ const getData = (state: RootState, ownProps: OwnProps) => { const ratesR = selectors.core.data.misc.getRatesSelector(coin, state) const sbBalancesR = selectors.components.buySell.getBSBalances(state) const userDataR = selectors.modules.profile.getUserData(state) - const sddEligibleR = selectors.components.buySell.getSddEligible(state) - const userSDDTierR = selectors.components.buySell.getUserSddEligibleTier(state) const sddLimitR = selectors.components.buySell.getUserLimit(state, BSPaymentTypes.PAYMENT_CARD) const cardsR = selectors.components.buySell.getBSCards(state) || [] const bankTransferAccounts = selectors.components.brokerage @@ -29,6 +28,7 @@ const getData = (state: RootState, ownProps: OwnProps) => { .getOrElse([]) const limitsR = selectors.components.buySell.getLimits(state) const hasFiatBalance = selectors.components.buySell.hasFiatBalances(state) + const isSddFlowR = getIsSddFlow(state) const isRecurringBuy = selectors.core.walletOptions .getFeatureFlagRecurringBuys(state) @@ -46,10 +46,9 @@ const getData = (state: RootState, ownProps: OwnProps) => { rates: ExtractSuccess, sbBalances: ExtractSuccess, userData: ExtractSuccess, - sddEligible: ExtractSuccess, sddLimit: ExtractSuccess, - userSDDTier: ExtractSuccess, - products: ExtractSuccess + products: ExtractSuccess, + isSddFlow: ExtractSuccess ) => ({ bankTransferAccounts, cards, @@ -58,28 +57,17 @@ const getData = (state: RootState, ownProps: OwnProps) => { hasFiatBalance, hasPaymentAccount: hasFiatBalance || cards.length > 0 || bankTransferAccounts.length > 0, isRecurringBuy, - isSddFlow: sddEligible.eligible || userSDDTier === 3, + isSddFlow, limits: limitsR.getOrElse(undefined), payment: paymentR.getOrElse(undefined), products, quote, rates, sbBalances, - sddEligible, sddLimit, userData }) - )( - cardsR, - quoteR, - ratesR, - sbBalancesR, - userDataR, - sddEligibleR, - sddLimitR, - userSDDTierR, - productsR - ) + )(cardsR, quoteR, ratesR, sbBalancesR, userDataR, sddLimitR, productsR, isSddFlowR) } export default getData diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/index.tsx index 11375104bcd..0d9a7ec085f 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/SellEnterAmount/index.tsx @@ -43,7 +43,6 @@ const EnterAmount = (props: Props) => { }) props.brokerageActions.fetchBankTransferAccounts() props.buySellActions.fetchCards(false) - props.buySellActions.fetchSDDEligibility() props.buySellActions.fetchOrders() } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerEverypay/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerEverypay/index.tsx index 12fe8df32e9..25fd8137c15 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerEverypay/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerEverypay/index.tsx @@ -89,7 +89,7 @@ const ThreeDSHandlerEverypay = (props: Props) => { ) if (order.hasError && order.error) { - renderError(order.error) + return renderError(order.error) } if (order.isLoading) { @@ -115,7 +115,7 @@ const ThreeDSHandlerEverypay = (props: Props) => { } if (!paymentLink) { - renderError(CARD_ERROR_CODE.CREATE_FAILED) + return renderError(CARD_ERROR_CODE.CREATE_FAILED) } return ( diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerFakeCardAcquirer/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerFakeCardAcquirer/index.tsx index 64d58eeb772..ea7a615345f 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerFakeCardAcquirer/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/ThreeDSHandlerFakeCardAcquirer/index.tsx @@ -89,7 +89,7 @@ const ThreeDSHandlerFakeCardAcquirer = (props: Props) => { ) if (order.hasError && order.error) { - renderError(order.error) + return renderError(order.error) } if (order.isLoading) { @@ -115,7 +115,7 @@ const ThreeDSHandlerFakeCardAcquirer = (props: Props) => { } if (!paymentLink) { - renderError(CARD_ERROR_CODE.CREATE_FAILED) + return renderError(CARD_ERROR_CODE.CREATE_FAILED) } return ( diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/UpdateSecurityCode/UpdateSecurityCode.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/UpdateSecurityCode/UpdateSecurityCode.tsx index 82ca39a7575..8798d8f38b4 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/UpdateSecurityCode/UpdateSecurityCode.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/UpdateSecurityCode/UpdateSecurityCode.tsx @@ -18,16 +18,20 @@ import { } from '@blockchain-com/constellation' import { Card } from 'components/Card' +import { FlyoutOopsError } from 'components/Flyout/Errors' import { FlyoutContainer, FlyoutContent, FlyoutFooter, FlyoutHeader } from 'components/Flyout/Layout' +import { GenericNabuErrorFlyout } from 'components/GenericNabuErrorFlyout' import { StandardRow } from 'components/Rows' import { actions, selectors } from 'data' import { useRemote } from 'hooks' +import { isNabuError } from 'services/errors' +import Loading from '../template.loading' import { UpdateSecurityCodeComponent } from './types' const UpdateSecurityCode: UpdateSecurityCodeComponent = ({ backToEnterAmount }) => { @@ -35,6 +39,7 @@ const UpdateSecurityCode: UpdateSecurityCodeComponent = ({ backToEnterAmount }) const [cvv, setCvv] = useState(null) const { data: order } = useRemote(selectors.components.buySell.getBSOrder) const { + error, hasData: cvvHasData, hasError: cvvHasError, isLoading: cvvLoading @@ -43,22 +48,44 @@ const UpdateSecurityCode: UpdateSecurityCodeComponent = ({ backToEnterAmount }) const updateCvv = () => { const paymentId = order?.attributes?.paymentId || order?.depositPaymentId - if (cvv && paymentId) { + if (cvv && paymentId && !cvvHasData) { dispatch(actions.components.buySell.updateCardCvvAndPollOrder({ cvv, paymentId })) } } + const errorCallback = () => { + backToEnterAmount() + } + useEffect(() => { return () => { dispatch(actions.components.buySell.cvvStatusReset()) } }, []) + if (error) { + if (isNabuError(error)) { + return + } + + return ( + + ) + } + if (!method) { backToEnterAmount() return null } + if (cvvHasData) { + return + } + return ( @@ -92,7 +119,7 @@ const UpdateSecurityCode: UpdateSecurityCodeComponent = ({ backToEnterAmount }) @@ -108,24 +135,8 @@ const UpdateSecurityCode: UpdateSecurityCodeComponent = ({ backToEnterAmount }) autoComplete='cc-csc' aria-label='Card Security Code' aria-placeholder='CVC' - postfix={ - cvvHasData ? ( - - ) : ( - - ) - } + postfix={} onChange={(e) => setCvv(e.currentTarget.value)} - helperText={ - cvvHasError ? ( - - ) : ( - <> - ) - } /> diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/index.tsx deleted file mode 100644 index 92c13a1badb..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/index.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { PureComponent } from 'react' -import { connect, ConnectedProps } from 'react-redux' -import { bindActionCreators, Dispatch } from 'redux' - -import { actions, model, selectors } from 'data' -import { getEnterAmountStepType } from 'data/components/buySell/utils' -import { RootState } from 'data/rootReducer' -import { BSVerifyEmailFormValuesType } from 'data/types' - -import Loading from '../template.loading' -import { getData } from './selectors' -import Success from './template.success' - -const { FORM_BS_CHANGE_EMAIL } = model.components.buySell - -class VerifyEmail extends PureComponent { - componentDidMount() { - this.props.buySellActions.fetchSDDEligibility() - } - - componentDidUpdate(prevProps: Props) { - if ( - prevProps.isEmailVerified !== this.props.isEmailVerified && - this.props.isEmailVerified && - this.props.cryptoCurrency && - this.props.fiatCurrency && - this.props.pair - ) { - this.props.buySellActions.setStep({ - cryptoCurrency: this.props.cryptoCurrency, - fiatCurrency: this.props.fiatCurrency, - orderType: this.props.orderType, - pair: this.props.pair, - step: getEnterAmountStepType(this.props.orderType) - }) - } - } - - handleSubmit = () => { - const { formValues, identityVerificationActions } = this.props - if (formValues) { - identityVerificationActions.updateEmail({ email: formValues.email }) - } - } - - onResendEmail = (email: string) => { - const { securityCenterActions } = this.props - securityCenterActions.resendVerifyEmail(email, 'VERIFICATION') - } - - render() { - return this.props.data.cata({ - Failure: () => null, - Loading: () => , - NotAsked: () => , - Success: (val) => ( - - ) - }) - } -} - -const mapStateToProps = (state: RootState) => ({ - cryptoCurrency: selectors.components.buySell.getCryptoCurrency(state), - data: getData(state), - fiatCurrency: selectors.components.buySell.getFiatCurrency(state), - formValues: selectors.form.getFormValues(FORM_BS_CHANGE_EMAIL)(state) as - | BSVerifyEmailFormValuesType - | undefined, - isEmailVerified: selectors.core.settings.getEmailVerified(state).getOrElse(false), - orderType: selectors.components.buySell.getOrderType(state), - pair: selectors.components.buySell.getBSPair(state) -}) - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - buySellActions: bindActionCreators(actions.components.buySell, dispatch), - formActions: bindActionCreators(actions.form, dispatch), - identityVerificationActions: bindActionCreators( - actions.components.identityVerification, - dispatch - ), - profileActions: bindActionCreators(actions.modules.profile, dispatch), - securityCenterActions: bindActionCreators(actions.modules.securityCenter, dispatch), - settingsActions: bindActionCreators(actions.core.settings, dispatch) -}) - -const connector = connect(mapStateToProps, mapDispatchToProps) - -export type OwnProps = { - handleClose: () => void -} -export type Props = OwnProps & ConnectedProps -export type LinkDispatchPropsType = ReturnType -export default connector(VerifyEmail) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/selectors.ts deleted file mode 100644 index 9efa11126fc..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/selectors.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Remote } from '@core' -import { model, selectors } from 'data' -import { RootState } from 'data/rootReducer' - -const { FORM_BS_CHANGE_EMAIL } = model.components.buySell - -export const getData = (state: RootState) => { - const formErrors = selectors.form.getFormSyncErrors(FORM_BS_CHANGE_EMAIL)(state) - - const emailAuth = selectors.signup.getRegisterEmail(state) - const emailFromSettings = selectors.core.settings.getEmail(state).getOrElse(null) - const isUserSddEligibleR = selectors.components.buySell.isUserSddEligible(state) - - // compare if user changed email from auth - const email = emailFromSettings && emailAuth !== emailFromSettings ? emailFromSettings : emailAuth - - const isUserSddEligible = isUserSddEligibleR.getOrElse(false) - - return Remote.Success({ - email, - formErrors, - isUserSddEligible - }) -} diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/template.success.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/template.success.tsx deleted file mode 100644 index 1d1e63c317f..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/VerifyEmail/template.success.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import React, { useState } from 'react' -import { FormattedMessage } from 'react-intl' -import { Field, InjectedFormProps, reduxForm } from 'redux-form' -import styled, { DefaultTheme } from 'styled-components' - -import { Button, HeartbeatLoader, Icon, Link, Text } from 'blockchain-info-components' -import { FlyoutWrapper } from 'components/Flyout' -import Form from 'components/Form/Form' -import FormGroup from 'components/Form/FormGroup' -import FormItem from 'components/Form/FormItem' -import TextBox from 'components/Form/TextBox' -import { model } from 'data' -import { required, validEmail } from 'services/forms' - -import { Props as OwnProps } from '.' - -const { FORM_BS_CHANGE_EMAIL } = model.components.buySell - -const Wrapper = styled.div` - width: 100%; - height: 100%; - display: flex; - flex-direction: column; -` - -const IconWrapper = styled.div<{ color: keyof DefaultTheme }>` - display: flex; - background: ${(props) => props.theme[props.color]}; - height: 40px; - width: 40px; - justify-content: center; - align-items: center; - border-radius: 50%; - margin-right: 20px; -` - -const CloseContainer = styled.div` - display: flex; - align-items: right; - justify-content: flex-end; -` -const ContentWrapper = styled.div` - display: flex; - text-align: center; - align-items: center; - flex-direction: column; - padding: 0 40px; - flex: 1; - justify-content: center; -` - -const Content = styled.div` - display: flex; - text-align: center; - align-items: center; - flex-direction: column; - height: 250px; - width: 100%; -` - -const ResendContiner = styled.span` - display: inline; - padding: 40px; - text-align: center; -` -const CustomForm = styled(Form)` - display: flex; - flex-direction: column; - margin-top: 16px; -` - -export const Label = styled.label` - font-size: 16px; - font-weight: 400; - margin-bottom: 12px; - display: block; - text-align: left; -` - -const Template: React.FC & Props> = (props) => { - const [changeEmail, setChangeEmail] = useState(false) - - return ( - - - - - - - - - - - - - - {changeEmail ? ( - - ) : ( - - )} - - - {changeEmail ? ( - - - - - - - - - - ) : ( - <> - - - - setChangeEmail(true)} - size='16px' - style={{ marginTop: '40px' }} - weight={500} - data-e2e='changeVerifyEmail' - color='blue600' - > - - - - )} - - - {!changeEmail && ( - - - - - - props.resendEmail(props.email)} - size='16px' - style={{ marginTop: '40px' }} - weight={500} - data-e2e='resendVerifyEmail' - color='blue600' - > - - - - )} - - ) -} - -type Props = OwnProps & { - email: string - resendEmail: (email: string) => void -} - -export default reduxForm<{}, Props>({ - destroyOnUnmount: false, - form: FORM_BS_CHANGE_EMAIL -})(Template) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/index.tsx index c8049e49c7b..cc869585433 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/index.tsx @@ -31,7 +31,6 @@ import { import ModalEnhancer from 'providers/ModalEnhancer' import { Loading as StdLoading, LoadingTextEnum } from '../components' -import Rejected from '../components/Rejected' import { ModalPropsType } from '../types' import AddCardCheckoutDotCom from './AddCardCheckoutDotCom' import AddCardVgs from './AddCardVgs' @@ -52,14 +51,12 @@ import getData from './selectors' import SellEnterAmount from './SellEnterAmount' import SellOrderSummary from './SellOrderSummary' import Loading from './template.loading' -import Pending from './template.pending' import ThreeDSHandlerCheckoutDotCom from './ThreeDSHandlerCheckoutDotCom' import ThreeDSHandlerEverypay from './ThreeDSHandlerEverypay' import ThreeDSHandlerFakeCardAcquirer from './ThreeDSHandlerFakeCardAcquirer' import ThreeDSHandlerStripe from './ThreeDSHandlerStripe' import UpdateSecurityCode from './UpdateSecurityCode' import UpgradeToGold from './UpgradeToGold' -import VerifyEmail from './VerifyEmail' const { FORM_BS_CHECKOUT, FORMS_BS_BILLING_ADDRESS } = model.components.buySell @@ -155,196 +152,160 @@ class BuySell extends PureComponent { ), - Success: (val) => { - const { userData } = val - const { kycState } = userData - const isUserRejectedOrExpired = kycState === 'REJECTED' || kycState === 'EXPIRED' - const isUserPending = kycState === 'UNDER_REVIEW' || kycState === 'PENDING' - - return isUserRejectedOrExpired ? ( - - - - ) : isUserPending ? ( - - { - this.props.profileActions.fetchUserDataLoading() - this.props.profileActions.fetchUser() - }} - /> - - ) : ( - - {this.props.step === 'ENTER_AMOUNT' && ( - - - - )} - {this.props.step === 'SELL_ENTER_AMOUNT' && ( - - - - )} - {this.props.step === 'UPDATE_SECURITY_CODE' && ( - - - - )} - {this.props.step === 'CRYPTO_SELECTION' && ( - - - - )} - {this.props.step === 'PAYMENT_METHODS' && ( - - - - )} - {this.props.step === 'LINKED_PAYMENT_ACCOUNTS' && ( - - - - )} - {this.props.step === 'DETERMINE_CARD_PROVIDER' && ( - - - - )} - {this.props.step === 'ADD_CARD_VGS' && ( - - - - )} - {this.props.step === 'ADD_CARD_CHECKOUTDOTCOM' && ( - - - - )} - {this.props.step === '3DS_HANDLER_FAKE_CARD_ACQUIRER' && ( - - - - )} - {this.props.step === '3DS_HANDLER_EVERYPAY' && ( - - - - )} - {this.props.step === '3DS_HANDLER_CHECKOUTDOTCOM' && ( - - - - )} - {this.props.step === '3DS_HANDLER_STRIPE' && ( - - - - )} - {this.props.step === 'BILLING_ADDRESS' && ( - - - - )} - {this.props.step === 'AUTHORIZE_PAYMENT' && ( - - - - )} - {this.props.step === 'CHECKOUT_CONFIRM' && ( - - - - )} - {this.props.step === 'ORDER_SUMMARY' && ( - - - - )} - {/* + Success: () => ( + + {this.props.step === 'ENTER_AMOUNT' && ( + + + + )} + {this.props.step === 'SELL_ENTER_AMOUNT' && ( + + + + )} + {this.props.step === 'UPDATE_SECURITY_CODE' && ( + + + + )} + {this.props.step === 'CRYPTO_SELECTION' && ( + + + + )} + {this.props.step === 'PAYMENT_METHODS' && ( + + + + )} + {this.props.step === 'LINKED_PAYMENT_ACCOUNTS' && ( + + + + )} + {this.props.step === 'DETERMINE_CARD_PROVIDER' && ( + + + + )} + {this.props.step === 'ADD_CARD_VGS' && ( + + + + )} + {this.props.step === 'ADD_CARD_CHECKOUTDOTCOM' && ( + + + + )} + {this.props.step === '3DS_HANDLER_FAKE_CARD_ACQUIRER' && ( + + + + )} + {this.props.step === '3DS_HANDLER_EVERYPAY' && ( + + + + )} + {this.props.step === '3DS_HANDLER_CHECKOUTDOTCOM' && ( + + + + )} + {this.props.step === '3DS_HANDLER_STRIPE' && ( + + + + )} + {this.props.step === 'BILLING_ADDRESS' && ( + + + + )} + {this.props.step === 'AUTHORIZE_PAYMENT' && ( + + + + )} + {this.props.step === 'CHECKOUT_CONFIRM' && ( + + + + )} + {this.props.step === 'ORDER_SUMMARY' && ( + + + + )} + {/* used for sell only now, eventually buy as well TODO: use swap2 quote for buy AND sell */} - {this.props.step === 'PREVIEW_SELL' && ( - - - - )} - {this.props.step === 'SELL_ORDER_SUMMARY' && ( - - - - )} - {this.props.step === 'BANK_WIRE_DETAILS' && ( - - - - )} - {this.props.step === 'OPEN_BANKING_CONNECT' && ( - - - - )} - {this.props.step === 'KYC_REQUIRED' && ( - - - - )} - {this.props.step === 'VERIFY_EMAIL' && ( - - - - )} - {this.props.step === 'UPGRADE_TO_GOLD' && ( - - - - )} - {this.props.step === 'FREQUENCY' && ( - - - - )} - {this.props.step === 'LOADING' && ( - - - - )} - {/** Only Plaid errors for now */} - {this.props.step === 'PAYMENT_ACCOUNT_ERROR' && ( - - - - )} - - ) - } + {this.props.step === 'PREVIEW_SELL' && ( + + + + )} + {this.props.step === 'SELL_ORDER_SUMMARY' && ( + + + + )} + {this.props.step === 'BANK_WIRE_DETAILS' && ( + + + + )} + {this.props.step === 'OPEN_BANKING_CONNECT' && ( + + + + )} + {this.props.step === 'KYC_REQUIRED' && ( + + + + )} + {this.props.step === 'UPGRADE_TO_GOLD' && ( + + + + )} + {this.props.step === 'FREQUENCY' && ( + + + + )} + {this.props.step === 'LOADING' && ( + + + + )} + {/** Only Plaid errors for now */} + {this.props.step === 'PAYMENT_ACCOUNT_ERROR' && ( + + + + )} + + ) }) } } @@ -456,7 +417,7 @@ type LinkStatePropsType = order?: BSOrderType orderType: BSOrderActionType pair: BSPairType - step: 'ENTER_AMOUNT' | 'SELL_ENTER_AMOUNT' | 'VERIFY_EMAIL' + step: 'ENTER_AMOUNT' | 'SELL_ENTER_AMOUNT' } | { orderType: BSOrderActionType diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/template.pending.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/template.pending.tsx deleted file mode 100644 index dc4b5600d32..00000000000 --- a/packages/blockchain-wallet-v4-frontend/src/modals/BuySell/template.pending.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react' -import { FormattedMessage } from 'react-intl' -import styled from 'styled-components' - -import { BlockchainLoader, Button, Icon, Text } from 'blockchain-info-components' -import { FlyoutWrapper } from 'components/Flyout' - -const Top = styled(FlyoutWrapper)` - padding-bottom: 0px; - position: relative; - height: 100%; -` -const Container = styled.div` - width: 100%; - height: 100%; -` -const MainText = styled(Text)` - display: flex; - align-items: center; - justify-content: space-between; -` -const Subcontent = styled(Text)` - margin: 38px 0 46px 0; - display: flex; - justify-content: center; -` -const Title = styled(Text)` - margin: 16px 0; - text-align: left; -` - -const Pending: React.FC<{ - handleClose: () => void - handleRefresh: () => void -}> = (props) => { - return ( - - - - - - - - <FormattedMessage - id='modals.simplebuy.underreview' - defaultMessage='ID Verification Pending' - /> - - - <FormattedMessage - id='scenes.exchange.getstarted.status.pending.description' - defaultMessage='We are currently reviewing your application. Hang tight! In just a few minutes you will be all set to trade cryptocurrency. You should receive an update within 5 minutes.' - /> - - - - - - - - ) -} - -export default Pending diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/EthWalletBalances.selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/EthWalletBalances.selectors.ts index f86b7c6ddb0..debeab7737d 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/EthWalletBalances.selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/EthWalletBalances.selectors.ts @@ -1,51 +1,31 @@ import { lift } from 'ramda' import { convertCoinToFiat } from '@core/exchange' -import { AccountTokensBalancesResponseType } from '@core/types' +import { ExtractSuccess, RatesType } from '@core/types' import { createDeepEqualSelector } from '@core/utils' import { selectors } from 'data' -import { RootState } from 'data/rootReducer' export const getData = createDeepEqualSelector( - [ - selectors.balances.getCoinNonCustodialBalance('ETH'), - selectors.core.settings.getCurrency, - selectors.core.data.eth.getErc20AccountTokenBalances, - // @ts-ignore - (state) => selectors.core.data.coins.getRates('ETH', state), - (state) => state - ], - (ethBalanceR, currencyR, erc20BalancesR, ethRatesR, state: RootState) => { - const transform = ( - ethBalance, - currency, - ethRates, - erc20Balances: AccountTokensBalancesResponseType['tokenAccounts'] - ) => { + [selectors.core.data.coins.getUnifiedBalances, selectors.core.settings.getCurrency], + (unifiedBalancesR, currencyR) => { + const transform = (unifiedBalances: ExtractSuccess, currency) => { + const ethBalance = unifiedBalances.find(({ ticker }) => ticker === 'ETH') + const ethBalanceAmt = ethBalance?.amount?.amount || 0 + const ethRates: RatesType = { price: ethBalance?.price || 0, timestamp: 0, volume24h: 0 } let total = Number( - convertCoinToFiat({ coin: 'ETH', currency, rates: ethRates, value: ethBalance }) + convertCoinToFiat({ coin: 'ETH', currency, rates: ethRates, value: ethBalanceAmt }) ) - erc20Balances - .filter((curr) => !!window.coins[curr.tokenSymbol]) - .map((curr) => { - const symbol = Object.keys(window.coins).find( - (coin) => - window.coins[coin].coinfig.type?.erc20Address?.toLowerCase() === - curr.tokenHash.toLowerCase() - ) - if (!symbol) return - - const transform2 = (rates) => { - if (!rates.price) return 0 - - total += Number( - convertCoinToFiat({ coin: symbol, currency, rates, value: curr.balance }) - ) - } - - const ratesR = selectors.core.data.coins.getRates(symbol, state) - return lift(transform2)(ratesR) + const erc20Balances = unifiedBalances + .filter((balance) => { + return !!window.coins[balance.ticker] }) + .filter((balance) => !!window.coins[balance.ticker].coinfig.type.erc20Address) + + erc20Balances.forEach((erc20Balance) => { + const value = erc20Balance.amount?.amount || 0 + const rates: RatesType = { price: erc20Balance.price, timestamp: 0, volume24h: 0 } + total += Number(convertCoinToFiat({ coin: erc20Balance.ticker, currency, rates, value })) + }) return { currency, @@ -55,6 +35,6 @@ export const getData = createDeepEqualSelector( } } - return lift(transform)(ethBalanceR, currencyR, ethRatesR, erc20BalancesR) + return lift(transform)(unifiedBalancesR, currencyR) } ) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/index.tsx index 0202e3375bf..b466a19de07 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/EthWalletBalances/index.tsx @@ -48,9 +48,7 @@ class EthWalletBalance extends PureComponent { } refresh = () => { - this.props.fetchErc20Balances() - this.props.ethActions.fetchDataLoading() - this.props.ethActions.fetchData() + this.props.coinsActions.fetchUnifiedBalances() } render() { @@ -188,35 +186,30 @@ class EthWalletBalance extends PureComponent { {erc20Balances - .filter(({ tokenSymbol }) => !!window.coins[tokenSymbol]) - .map(({ balance, tokenSymbol }) => ( - + .filter(({ ticker }) => !!window.coins[ticker]) + .map(({ amount, ticker }) => ( + - + - {window.coins[tokenSymbol].coinfig.name} + {window.coins[ticker].coinfig.name} - {window.coins[tokenSymbol].coinfig.displaySymbol} Private Key Wallet + {window.coins[ticker].coinfig.displaySymbol} Private Key Wallet - - {balance} + + {amount} - - {balance} + + {amount} @@ -257,8 +250,7 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ buySellActions: bindActionCreators(actions.components.buySell, dispatch), - ethActions: bindActionCreators(actions.core.data.eth, dispatch), - fetchErc20Balances: bindActionCreators(actions.core.data.eth.fetchErc20Data, dispatch) + coinsActions: bindActionCreators(actions.core.data.coins, dispatch) }) const connector = connect(mapStateToProps, mapDispatchToProps) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/selectors.js b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/selectors.js index 57206254856..321c6d1a8c7 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/selectors.js +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/selectors.js @@ -16,7 +16,7 @@ const getData = createDeepEqualSelector( (state, coin) => { const { coinfig } = window.coins[coin] return coinfig.type.erc20Address - ? selectors.core.data.eth.getErc20CurrentBalance(state, coin) + ? selectors.core.data.coins.getCoinUnifiedBalance(coin)(state) : selectors.core.data.eth.getCurrentBalance(state) }, (state) => selectors.core.common.eth.getErc20AccountBalances(state, 'PAX').map(head), @@ -46,7 +46,6 @@ const getData = createDeepEqualSelector( const priorityFee = path(['fees', 'priority'], payment) const minFee = path(['fees', 'limits', 'min'], payment) const maxFee = path(['fees', 'limits', 'max'], payment) - const isSufficientEthForErc20 = path(['isSufficientEthForErc20'], payment) const isRetryAttempt = path(['isRetryAttempt'], payment) const minFeeRequiredForRetry = path(['minFeeRequiredForRetry'], payment) const isContractChecked = Remote.Success.is(isContractR) @@ -88,7 +87,6 @@ const getData = createDeepEqualSelector( isContractChecked, isMnemonicVerified, isRetryAttempt, - isSufficientEthForErc20, maxFee, minFee, minFeeRequiredForRetry, diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/template.success.js b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/template.success.js index b1c373e5433..52c50d60d08 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/template.success.js +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Eth/SendEth/FirstStep/template.success.js @@ -80,7 +80,6 @@ const FirstStep = (props) => { isContractChecked, isMnemonicVerified, isRetryAttempt, - isSufficientEthForErc20, minFeeRequiredForRetry, priorityFee, pristine, @@ -91,7 +90,6 @@ const FirstStep = (props) => { verifyIdentity } = props const isFromCustody = from && from.type === 'CUSTODIAL' - const disableDueToLowEth = coin !== 'ETH' && !isSufficientEthForErc20 && !isFromCustody const disableRetryAttempt = isRetryAttempt && new BigNumber(fee).isLessThanOrEqualTo(minFeeRequiredForRetry) const disableCustodySend = isFromCustody && !isMnemonicVerified @@ -285,7 +283,6 @@ const FirstStep = (props) => { ) : null} {disableRetryAttempt && } - {disableDueToLowEth && } {isFromCustody && !isMnemonicVerified ? : null} {isFromCustody && !isEmpty(sendLimits) && @@ -304,7 +301,6 @@ const FirstStep = (props) => { invalid || !amount || !isContractChecked || - disableDueToLowEth || disableRetryAttempt || disableCustodySend || Remote.Loading.is(balanceStatus) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Interest/DepositForm/template.success.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/Interest/DepositForm/template.success.tsx index 5f3fe9182e7..75713def4eb 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Interest/DepositForm/template.success.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Interest/DepositForm/template.success.tsx @@ -44,7 +44,6 @@ import { CustomField, CustomForm, CustomFormLabel, - ErrorText, FiatMaxContainer, FORM_NAME, GreyBlueCartridge, @@ -95,7 +94,6 @@ const DepositForm: React.FC & Props> interestLimits, interestRates, invalid, - payment, rates, rewardsEDDDepositLimits, submitting, @@ -128,12 +126,6 @@ const DepositForm: React.FC & Props> const depositAmountError = typeof formErrors.depositAmount === 'string' && formErrors.depositAmount const isErc20 = !!window.coins[coin].coinfig.type.erc20Address - const insufficientEth = - payment && - isErc20 && - !!window.coins[coin].coinfig.type.erc20Address && - // @ts-ignore - !payment.isSufficientEthForErc20 const showEDDDepositLimit = checkIsAmountUnderDepositLimit(rewardsEDDDepositLimits, coin, depositAmountFiat) && @@ -230,58 +222,46 @@ const DepositForm: React.FC & Props> rate: interestRates[coin] }} />{' '} - {!insufficientEth && ( - <> - {' '} - - formActions.change( - FORM_NAME, - 'depositAmount', - displayCoin ? earnDepositLimits.maxCoin : earnDepositLimits.maxFiat - ) - } - > - {displayCoin ? ( - - {earnDepositLimits.maxCoin}{' '} - - ) : ( - - {maxDepositFiat}{' '} - - )} - {' '} - - - - - - )} + <> + {' '} + + formActions.change( + FORM_NAME, + 'depositAmount', + displayCoin ? earnDepositLimits.maxCoin : earnDepositLimits.maxFiat + ) + } + > + {displayCoin ? ( + + {earnDepositLimits.maxCoin}{' '} + + ) : ( + + {maxDepositFiat}{' '} + + )} + {' '} + + + + + - {isErc20 && insufficientEth && ( - - - - - )} & Props> coin={coin} component={NumberBox} data-e2e='depositAmount' - // @ts-ignore - disabled={insufficientEth} displayCoin={displayCoin} name='depositAmount' validate={[required, minDepositAmount, maxDepositAmount]} @@ -620,7 +598,7 @@ const DepositForm: React.FC & Props> diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/Confirm/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/Confirm/index.tsx index 08b8b3f4a22..5e0444d3fc0 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/Confirm/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/Confirm/index.tsx @@ -87,6 +87,7 @@ const Confirm: React.FC & Props> = (props) => { rates, walletCurrency }) + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return ( @@ -212,7 +213,7 @@ const Confirm: React.FC & Props> = (props) => { - {tx.rawTx?.payload.payload.memo.content ? ( + {tx.rawTx?.payload.payload?.memo.content ? (
diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/EnterAmount/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/EnterAmount/index.tsx index 81dc65038e1..9e3cddb8482 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/EnterAmount/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/EnterAmount/index.tsx @@ -27,6 +27,7 @@ import { selectors } from 'data' import { convertBaseToStandard } from 'data/components/exchange/services' import { SendCryptoStepType } from 'data/components/sendCrypto/types' import { SwapBaseCounterTypes } from 'data/types' +import { useCoinConfig } from 'hooks' import { getEffectiveLimit, getEffectivePeriod } from 'services/custodial' import { media } from 'services/styles' import { hexToRgb } from 'utils/helpers' @@ -107,6 +108,7 @@ const SendEnterAmount: React.FC & Props> = (props) const { amount, fix, selectedAccount, to } = formValues const { coin, type } = selectedAccount const isAccount = type === SwapBaseCounterTypes.ACCOUNT + const { data: coinfig } = useCoinConfig({ coin }) const max = Number(convertCoinToCoin({ coin, value: selectedAccount.balance })) const min = minR.getOrElse(0) @@ -180,7 +182,7 @@ const SendEnterAmount: React.FC & Props> = (props) {': '} - {selectedAccount.label} ({max} {coin}) + {selectedAccount.label} ({max} {coinfig?.displaySymbol || coin}) {' '} @@ -286,7 +288,7 @@ const SendEnterAmount: React.FC & Props> = (props) size='14px' style={{ marginTop: '4px', textAlign: 'right' }} > - {isAccount ? max : maxMinusFee} {coin} + {isAccount ? max : maxMinusFee} {coinfig?.displaySymbol}
@@ -418,7 +420,7 @@ const SendEnterAmount: React.FC & Props> = (props) defaultMessage='The max you can send from this wallet is {coin} {amount}. Buy {coin} {amount} now to send this amount.' values={{ amount: max, - coin + coin: coinfig?.displaySymbol || coin }} /> @@ -433,7 +435,7 @@ const SendEnterAmount: React.FC & Props> = (props) amount: fix === 'FIAT' ? `${Currencies[walletCurrency].units[walletCurrency].symbol}${min}` - : `${coin}${min}` + : `${coinfig?.displaySymbol || coin}${min}` }} /> @@ -452,7 +454,7 @@ const SendEnterAmount: React.FC & Props> = (props) origin: 'Send' }) } - buyAmount={`${coin} ${amount}`} + buyAmount={`${coinfig?.displaySymbol || coin} ${amount}`} coin={coin} /> )} diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/types.ts b/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/types.ts index 21cbba022fc..02968130b02 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/types.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/SendCrypto/types.ts @@ -1,4 +1,4 @@ -import { BuildTxFeeType } from '@core/network/api/coin/types' +import { BuildTxFeeType } from '@core/network/api/coins/types' import { SwapAccountType } from 'data/types' export type SendFormType = { diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/index.tsx index 8aa76296d57..781e442863d 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/index.tsx @@ -5,6 +5,7 @@ import AutoSizer from 'react-virtualized-auto-sizer' import { FixedSizeList as List } from 'react-window' import { equals } from 'ramda' +import { WalletAccountSuperAppType } from '@core/types' import { Text } from 'blockchain-info-components' import { FlyoutContainer, @@ -149,6 +150,7 @@ const mapStateToProps = (state: RootState, ownProps: OwnProps) => ({ const connector = connect(mapStateToProps) export type OwnProps = BaseProps & { + fromType?: WalletAccountSuperAppType handleClose: () => void side: 'BASE' | 'COUNTER' } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/selectors.ts index 191d77d6be5..83986ba22c6 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Swap/CoinSelection/selectors.ts @@ -1,6 +1,6 @@ import { uniq } from 'ramda' -import { CoinType } from '@core/types' +import { CoinType, WalletAccountSuperAppEnum } from '@core/types' import { selectors } from 'data' import { SWAP_ACCOUNTS_SELECTOR } from 'data/coins/model/swap' import { getCoinAccounts } from 'data/coins/selectors' @@ -14,7 +14,7 @@ import { RootState } from 'data/rootReducer' import { OwnProps } from '.' -export const getData = (state: RootState, { side }: OwnProps) => { +export const getData = (state: RootState, { fromType, side }: OwnProps) => { const coins = selectors.components.swap.getCoins() const accounts = getCoinAccounts(state, { coins, ...SWAP_ACCOUNTS_SELECTOR }) const pairs = selectors.components.swap.getPairs(state).getOrElse([]) @@ -60,6 +60,20 @@ export const getData = (state: RootState, { side }: OwnProps) => { return !(account.type === SwapBaseCounterTypes.CUSTODIAL && !custodialEligibility) } + // TODO temp placeholder name + const _checkFromTypeSuperApp = (account: SwapAccountType) => { + if (side === 'BASE') { + if (fromType === WalletAccountSuperAppEnum.DEFI) { + return account.type === SwapBaseCounterTypes.ACCOUNT + } + if (fromType === WalletAccountSuperAppEnum.ACCOUNT) { + return account.type === SwapBaseCounterTypes.CUSTODIAL + } + } else { + return true + } + } + const sideF = side === 'BASE' ? getInputFromPair : getOutputFromPair let coinsForSide = uniq(pairs.map(sideF)) @@ -92,9 +106,19 @@ export const getData = (state: RootState, { side }: OwnProps) => { const hideCustodialToAccount = _checkBaseCustodial(account) const isBaseAccountZero = _checkBaseAccountZero(account) const isCustodialEligible = _checkCustodialEligibility(account) - - return !isBaseAccountZero && !isCoinSelected && !hideCustodialToAccount && isCustodialEligible + const showDefiOrAccount = _checkFromTypeSuperApp(account) + + return ( + !isBaseAccountZero && + !isCoinSelected && + !hideCustodialToAccount && + isCustodialEligible && + showDefiOrAccount + ) }) - return { accounts: accountsForSide, filteredAccounts } + return { + accounts: accountsForSide, + filteredAccounts + } } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Swap/EnterAmount/Checkout/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/Swap/EnterAmount/Checkout/index.tsx index 8b78f8c857a..9aa38bcfc01 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Swap/EnterAmount/Checkout/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Swap/EnterAmount/Checkout/index.tsx @@ -454,7 +454,7 @@ const Checkout: React.FC & Props> = (props) => { jumbo fullwidth style={{ marginTop: '24px' }} - disabled={props.invalid || isQuoteFailed || disableInsufficientEth} + disabled={props.invalid || isQuoteFailed} > @@ -615,17 +615,6 @@ const Checkout: React.FC & Props> = (props) => { })} )} - {disableInsufficientEth && ( - - - - )} ) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/index.tsx b/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/index.tsx index 326cdbe95b5..040008ea55f 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/index.tsx @@ -106,7 +106,12 @@ const mapDispatchToProps = (dispatch: Dispatch, { coin }: OwnProps) => { const { erc20Address, parentChain = coin } = window.coins[coin].coinfig.type const coinCode = parentChain.toLowerCase() return { - clearTransactions: () => dispatch(actions.core.data[coinCode].clearTransactionHistory()), + clearTransactions: () => { + if (selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coin)) { + return dispatch(actions.core.data.coins.clearTransactionHistory({ coin })) + } + return dispatch(actions.core.data[coinCode].clearTransactionHistory()) + }, fetchTransactions: (address, startDate, endDate) => { if (erc20Address) { return dispatch( @@ -114,6 +119,10 @@ const mapDispatchToProps = (dispatch: Dispatch, { coin }: OwnProps) => { ) } + if (selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coin)) { + return dispatch(actions.core.data.coins.fetchTransactionHistory({ coin })) + } + return dispatch( actions.core.data[coinCode].fetchTransactionHistory(address, startDate, endDate) ) diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/selectors.ts b/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/selectors.ts index 5e6823bd520..021a980bd03 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/selectors.ts +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Transactions/DownloadTransactions/selectors.ts @@ -33,6 +33,7 @@ const getErc20Data = createSelector( return [reportHeaders].concat(transformedData) } return { + // @ts-ignore csvData: dataR.map(transform).getOrElse([]) } } @@ -40,6 +41,7 @@ const getErc20Data = createSelector( const getEthData = createSelector( [selectors.core.data.eth.getTransactionHistory], + // @ts-ignore (dataR: selectors.core.data.eth.getTransactionHistory) => { const transform = (data) => { const transformedData = map((tx) => formatTxData(tx, 'ETH'), data) @@ -105,6 +107,18 @@ const getXlmData = createSelector([selectors.core.data.xlm.getTransactionHistory } }) +const getSelfCustodyData = createSelector( + [(state, coin) => selectors.core.data.coins.getTransactionHistory(coin, state)], + (dataR) => { + const transform = (data) => { + return data + } + return { + csvData: dataR.map(transform).getOrElse([]) + } + } +) + export const getData = (state, coin) => { switch (coin) { case 'BTC': @@ -116,6 +130,9 @@ export const getData = (state, coin) => { case 'XLM': return getXlmData(state) default: + if (selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coin)) { + return getSelfCustodyData(state, coin) + } return getErc20Data(state, coin) } } diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/FirstStep/selectors.js b/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/FirstStep/selectors.js index 1fed8a06daf..0f3b2d59bc6 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/FirstStep/selectors.js +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/FirstStep/selectors.js @@ -9,7 +9,7 @@ const getData = createDeepEqualSelector( selectors.components.sendXlm.getPayment, selectors.components.sendXlm.getCheckDestination, selectors.components.sendXlm.getIsDestinationExchange, - selectors.core.data.xlm.getTotalBalance, + selectors.core.data.coins.getCoinUnifiedBalance('XLM'), selectors.core.settings.getCurrency, selectors.core.wallet.isMnemonicVerified, selectors.form.getFormValues(model.components.sendXlm.FORM), diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/SecondStep/index.js b/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/SecondStep/index.js index 1c8e1404efc..ae15a7c9797 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/SecondStep/index.js +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/SecondStep/index.js @@ -17,7 +17,7 @@ class SecondStepContainer extends React.PureComponent { render() { const { actions, data } = this.props return data.cata({ - Failure: (message) => {message}, + Failure: (message) => {JSON.stringify(message)}, Loading: () => , NotAsked: () => , Success: (value) => ( diff --git a/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/index.js b/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/index.js index 5ac4f8420e5..a3cdcac2d6f 100644 --- a/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/index.js +++ b/packages/blockchain-wallet-v4-frontend/src/modals/Xlm/SendXlm/index.js @@ -44,7 +44,7 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ actions: bindActionCreators(actions.components.sendXlm, dispatch), - fetchData: bindActionCreators(actions.core.data.xlm.fetchData, dispatch) + fetchData: () => {} }) const enhance = compose( diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/ChartBalancePanel/ChartBalancePanel.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/ChartBalancePanel/ChartBalancePanel.tsx index 996d818c52f..fd3c5872fe9 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/ChartBalancePanel/ChartBalancePanel.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/ChartBalancePanel/ChartBalancePanel.tsx @@ -42,7 +42,7 @@ export const ChartBalancePanel: ChartBalancePanelComponent = ({ id='chart-view.chart-balance-panel.price' defaultMessage='{coinCode} Price' values={{ - coinCode + coinCode: window.coins[coinCode].coinfig.displaySymbol }} /> diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/CoinHeader.stories.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/CoinHeader.stories.tsx index f5d964e922d..323b60b32bf 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/CoinHeader.stories.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/CoinHeader.stories.tsx @@ -13,15 +13,13 @@ const Template: ComponentStory = (args) => { +export const CoinHeader: CoinHeaderComponent = ({ coinCode, coinDescription, coinfig }) => { return ( @@ -13,11 +13,11 @@ export const CoinHeader: CoinHeaderComponent = ({ coinCode, coinDescription, coi - {coinName} + {coinfig?.name} - {coinCode} + {coinfig?.displaySymbol} diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/types.ts b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/types.ts index 53b1be5a497..214a6f2e2e1 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/types.ts +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinHeader/types.ts @@ -1,9 +1,11 @@ import { FC } from 'react' +import { CoinfigType } from '@core/types' + export type CoinHeaderProps = { coinCode: string coinDescription?: string - coinName: string + coinfig?: CoinfigType } export type CoinHeaderComponent = FC diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/CoinPage.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/CoinPage.tsx index 60c88d1d9bd..d3caa6dc36d 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/CoinPage.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/CoinPage.tsx @@ -41,7 +41,11 @@ export const CoinPage: CoinPageComponent = ({ {activity} - + {alertCard} {holdings} {wallets} {recurringBuys} {promoCard} diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/hooks/useActivityFeed/useActivityFeed.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/hooks/useActivityFeed/useActivityFeed.tsx index b5764e43019..e8f45eed829 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/hooks/useActivityFeed/useActivityFeed.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/CoinPage/hooks/useActivityFeed/useActivityFeed.tsx @@ -123,7 +123,7 @@ const useActivityFeed: ActivityFeedHook = ({ coin }) => { /> - {!isLoadingAddressData && ( + {!isLoadingAddressData && filterOptions.length > 1 && ( { if (isLoading) return ( } coinCode={coin} coinTotal={} @@ -109,6 +110,7 @@ export const useHoldingsCard: HoldingsCardHook = ({ coin }) => { if (!rates || !coinBalance) { return ( { return ( = ({ computedMatch }) => { chartTabs={tabsNode} about={acoutSection} chart={chart} - header={} + header={} chartBalancePanel={chartBalancePanel} recurringBuys={recurringBuyPanel} holdings={holdingsCard} diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/HoldingsCard.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/HoldingsCard.tsx index 5eab86226b8..30a3a38c0b3 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/HoldingsCard.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/HoldingsCard.tsx @@ -8,7 +8,13 @@ import { Padding } from 'components/Padding' import { HoldingsCardComponent } from './types' -export const HoldingsCard: HoldingsCardComponent = ({ actions, coinCode, coinTotal, total }) => { +export const HoldingsCard: HoldingsCardComponent = ({ + actions, + coinCode, + coinTotal, + coinfig, + total +}) => { return ( @@ -18,7 +24,7 @@ export const HoldingsCard: HoldingsCardComponent = ({ actions, coinCode, coinTot id='scenes.coin.holdings_card.total' defaultMessage='Your Total {coinCode}' values={{ - coinCode + coinCode: coinfig?.displaySymbol || coinCode }} /> diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/types.ts b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/types.ts index 36df4b09022..bb0172328af 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/types.ts +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/HoldingsCard/types.ts @@ -1,9 +1,12 @@ import { FC, ReactElement, ReactNode } from 'react' +import { CoinfigType } from '@core/types' + export type HoldingsCardProps = { actions?: ReactElement[] coinCode: ReactNode coinTotal: ReactNode + coinfig?: CoinfigType total: ReactNode } diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/TransactionListItem/TransactionListItem.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/TransactionListItem/TransactionListItem.tsx index dc523d99e72..8867a04e431 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/TransactionListItem/TransactionListItem.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/CoinPage/components/TransactionListItem/TransactionListItem.tsx @@ -1,6 +1,6 @@ import React from 'react' +import UnifiedActivityTx from 'blockchain-wallet-v4-frontend/src/scenes/Transactions/UnifiedActivityTx' -import { Remote } from '@core' import { FiatBSAndSwapTransactionType } from '@core/types' import { useCoinConfig, useCurrency } from 'hooks' @@ -33,7 +33,9 @@ const TransactionListItem: TransactionListItemComponent = ({ coin, transaction } ) : 'pair' in transaction ? ( ) : 'movements' in transaction ? ( - + + ) : 'detail' in transaction ? ( + ) : ( { - const { buySellActions, interestActions } = props + const { buySellActions, fetchUnifiedActivityWS, interestActions } = props const { data, error, isLoading, isNotAsked } = useRemote(getData) useEffect(() => { @@ -148,6 +148,7 @@ const Banners = (props: Props) => { const mapDispatchToProps = (dispatch: Dispatch) => ({ buySellActions: bindActionCreators(actions.components.buySell, dispatch), + fetchUnifiedActivityWS: bindActionCreators(actions.activities, dispatch), interestActions: bindActionCreators(actions.components.interest, dispatch) }) diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/CoinBalance/index.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/CoinBalance/index.tsx index ab24110ef4f..9d63782ff68 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/CoinBalance/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/CoinBalance/index.tsx @@ -1,37 +1,22 @@ import React from 'react' import { connect, ConnectedProps } from 'react-redux' -import { toLower } from 'ramda' import { bindActionCreators } from 'redux' import { SkeletonRectangle } from 'blockchain-info-components' -import { actions, selectors } from 'data' +import { actions } from 'data' import { getData } from './selectors' import Error from './template.error' import Success from './template.success' class CoinBalance extends React.PureComponent { - handleRefresh = (e?: KeyboardEvent) => { - if (e) { - e.preventDefault() - } - const { coin } = this.props - const { coinfig } = window.coins[coin] - if (selectors.core.data.coins.getDynamicSelfCustodyCoins().includes(coin)) { - this.props.coinActions.fetchData('', [coin]) - } else if (coinfig.type.erc20Address) { - this.props.ethActions.fetchErc20Data(coin) - } else { - const coinLower = toLower(coin) - this.props[`${coinLower}Actions`].fetchData() - } - } - render() { const { coin, data } = this.props return data.cata({ - Failure: () => this.handleRefresh(e)} />, + Failure: () => ( + this.props.coinsActions.fetchUnifiedBalances()} /> + ), Loading: () => , NotAsked: () => , Success: (value) => @@ -44,12 +29,7 @@ const mapStateToProps = (state, ownProps: OwnProps) => ({ }) const mapDispatchToProps = (dispatch) => ({ - bchActions: bindActionCreators(actions.core.data.bch, dispatch), - btcActions: bindActionCreators(actions.core.data.btc, dispatch), - coinActions: bindActionCreators(actions.core.data.coins, dispatch), - ethActions: bindActionCreators(actions.core.data.eth, dispatch), - stxActions: bindActionCreators(actions.core.data.stx, dispatch), - xlmActions: bindActionCreators(actions.core.data.xlm, dispatch) + coinsActions: bindActionCreators(actions.core.data.coins, dispatch) }) const connector = connect(mapStateToProps, mapDispatchToProps) diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/index.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/index.tsx index 83811de20d3..3b3d43f933a 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/index.tsx +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/Home/Holdings/index.tsx @@ -2,11 +2,13 @@ import React from 'react' import { FormattedMessage } from 'react-intl' import { connect, ConnectedProps } from 'react-redux' import { LinkContainer } from 'react-router-bootstrap' +import { IconRefresh } from '@blockchain-com/icons' import { bindActionCreators } from 'redux' import styled from 'styled-components' import { ExtractSuccess } from '@core/remote/types' -import { Icon, Link, Text } from 'blockchain-info-components' +import { Icon, Link, SkeletonCircle, SkeletonRectangle, Text } from 'blockchain-info-components' +import { Flex } from 'components/Flex' import { StandardRow } from 'components/Rows' import { actions, selectors } from 'data' import { media } from 'services/styles' @@ -65,10 +67,31 @@ const HoldingsTableContainer = (props: Props) => ( {props.data.cata({ - Failure: (e) => ( - - {e?.toString() || 'Failed to load balances'} - + Failure: () => ( + <> + + + + {' '} + props.actions.refreshClicked()} + cursor='pointer' + color='blue600' + size='14px' + weight={600} + // @ts-ignore + role='button' + > + + + + + + ), Loading: () => , NotAsked: () => , diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/Settings/Addresses/Xlm/selectors.js b/packages/blockchain-wallet-v4-frontend/src/scenes/Settings/Addresses/Xlm/selectors.js index 652066a5da9..ebb1453c905 100644 --- a/packages/blockchain-wallet-v4-frontend/src/scenes/Settings/Addresses/Xlm/selectors.js +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/Settings/Addresses/Xlm/selectors.js @@ -3,7 +3,7 @@ import { selectors } from 'data' export const getData = (state, props) => { const accountId = selectors.core.kvStore.xlm.getDefaultAccountId(state).getOrElse('') - const amount = selectors.core.data.xlm.getAccountBalance(accountId, state).getOrElse(0) + const amount = selectors.core.data.coins.getCoinUnifiedBalance('XLM')(state).getOrElse(0) return { addressInfo: { diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/TestAccountBalances/index.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/TestAccountBalances/index.tsx new file mode 100644 index 00000000000..a5a9b8c1b7e --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/TestAccountBalances/index.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { connect, ConnectedProps } from 'react-redux' +import BigNumber from 'bignumber.js' + +import { CoinType } from '@core/types' +import { selectors } from 'data' + +import CustodialTestBalances from './template' + +const AccountTestBalances: React.FC = (props: Props) => { + return + // return props.data.cata({ + // Failure: () => <>Failure, + // Loading: () => <>Loading, + // NotAsked: () => <>Not Asked, + // Success: (val) => <>val + // }) +} + +const mapStateToProps = (state) => ({}) + +const connector = connect(mapStateToProps) + +export type OwnProps = { + coin: CoinType +} + +type Props = ConnectedProps & OwnProps + +export default connector(AccountTestBalances) diff --git a/packages/blockchain-wallet-v4-frontend/src/scenes/TestAccountBalances/template.tsx b/packages/blockchain-wallet-v4-frontend/src/scenes/TestAccountBalances/template.tsx new file mode 100644 index 00000000000..96c2d172e85 --- /dev/null +++ b/packages/blockchain-wallet-v4-frontend/src/scenes/TestAccountBalances/template.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { FormattedMessage } from 'react-intl' +import { useDispatch } from 'react-redux' +import { Button } from '@blockchain-com/constellation' +import styled from 'styled-components' + +import { WalletAccountSuperAppEnum } from '@core/types' +import { actions, selectors } from 'data' + +const CustodialTestBalances = (props) => { + // console.log(props, 'ncTestBalanceProps') + const dispatch = useDispatch() + + return ( + <> +