From 193464aff251d56f1c6350ff1092b9ea03827acd Mon Sep 17 00:00:00 2001 From: Velenir Date: Mon, 12 Oct 2020 11:49:51 +0300 Subject: [PATCH 1/9] WalletConnect for xDAI (#1493) * provide rpc url for xDAI * add xDAI rpc input * hack to get around Invalid message on blockNumber calls --- src/api/wallet/WalletApi.ts | 1 + src/components/Settings/WalletConnect.tsx | 16 +++++++++++++++- src/utils/walletconnectOptions.ts | 13 +++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/api/wallet/WalletApi.ts b/src/api/wallet/WalletApi.ts index 382be51e3..269dfb2c5 100644 --- a/src/api/wallet/WalletApi.ts +++ b/src/api/wallet/WalletApi.ts @@ -338,6 +338,7 @@ export class WalletApiImpl implements WalletApi { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore provider.handleReadRequests = async function (payload: unknown): Promise { + if (payload && typeof payload === 'object' && 'skipCache' in payload) delete payload['skipCache'] // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (!this.http) { diff --git a/src/components/Settings/WalletConnect.tsx b/src/components/Settings/WalletConnect.tsx index d01bc0244..58270db76 100644 --- a/src/components/Settings/WalletConnect.tsx +++ b/src/components/Settings/WalletConnect.tsx @@ -25,9 +25,11 @@ const WCSettingsSchema = Joi.object({ rpc: Joi.object({ mainnet: RPCSchema, rinkeby: RPCSchema, + xDAI: RPCSchema, }).empty({ mainnet: '', rinkeby: '', + xDAI: '', }), }) .oxor('infuraId', 'rpc') @@ -192,7 +194,7 @@ export const WCSettings: React.FC = ({ register, errors }) => {

Here you can set the InfuraId or RPC URL that will be used for connecting - the WalletConnect provider to Mainnet and/or Rinkeby. It is also possible to set a custom WalletConnect{' '} + the WalletConnect provider to Mainnet, Rinkeby and/or xDAI. It is also possible to set a custom WalletConnect{' '} Bridge URL to use instead of the default one.

@@ -240,6 +242,18 @@ export const WCSettings: React.FC = ({ register, errors }) => { RINKEBY + + + + xDAI + + diff --git a/src/utils/walletconnectOptions.ts b/src/utils/walletconnectOptions.ts index 3bb0572be..77b749e88 100644 --- a/src/utils/walletconnectOptions.ts +++ b/src/utils/walletconnectOptions.ts @@ -8,9 +8,14 @@ export interface WCOptions { rpc?: { mainnet?: string rinkeby?: string + xDAI?: string } } +const defaultRPC = { + [Network.xDAI]: 'https://rpc.xdaichain.com/', +} + export const setCustomWCOptions = (options: WCOptions): boolean => { const optionsStr = JSON.stringify(options) const oldStr = localStorage.getItem(STORAGE_KEY_CUSTOM_WC_OPTIONS) @@ -32,11 +37,12 @@ export const getWCOptionsFromStorage = (): WCOptions => { const mapStoredRpc = (rpc?: WCOptions['rpc']): IRPCMap | undefined => { if (!rpc) return - const { mainnet, rinkeby } = rpc + const { mainnet, rinkeby, xDAI } = rpc const rpcMap = {} if (mainnet) rpcMap[Network.Mainnet] = mainnet if (rinkeby) rpcMap[Network.Rinkeby] = rinkeby + if (xDAI) rpcMap[Network.xDAI] = xDAI return rpcMap } @@ -46,6 +52,9 @@ export const generateWCOptions = (): IWalletConnectProviderOptions => { return { infuraId: infuraId || INFURA_ID, bridge: bridge || WALLET_CONNECT_BRIDGE, - rpc: mapStoredRpc(rpc), + rpc: { + ...defaultRPC, + ...mapStoredRpc(rpc), + }, } } From 538804d1cc33287bf7f9413888bbce8b1865b5c1 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 12 Oct 2020 15:04:37 +0200 Subject: [PATCH 2/9] userWallet network smushed fix (#1520) --- src/components/UserWallet/UserWallet.styled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/UserWallet/UserWallet.styled.ts b/src/components/UserWallet/UserWallet.styled.ts index f73396b20..8fa4f77f4 100644 --- a/src/components/UserWallet/UserWallet.styled.ts +++ b/src/components/UserWallet/UserWallet.styled.ts @@ -254,7 +254,7 @@ export const NetworkTitle = styled.div` font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.3rem; - top: -0.6rem; + top: -0.8rem; white-space: nowrap; ` From 735c6aeed3899009dc5b0aee40316d10e31ecc1d Mon Sep 17 00:00:00 2001 From: Velenir Date: Tue, 13 Oct 2020 09:59:24 +0300 Subject: [PATCH 3/9] Deterministic sorting ++ (#1517) * Refactor and allow to filter in networks with no TCR * Refactor map creation * Return filter flag Co-authored-by: Velenir * Fix issue accessing Map Co-authored-by: Velenir * Filter tokenList * Improve fetch token details * Refactor fetch into it's own function * Simplify logic * Add label * Provide a deterministic sorting * simplify async logic in _fetchTokenDetails * create a one-pass sorting function * emphasize that sort mutates * fix switch fallthrough Co-authored-by: Anxo Rodriguez --- src/services/factories/tokenList.ts | 95 +++++++++++++++-------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/src/services/factories/tokenList.ts b/src/services/factories/tokenList.ts index 7522b61c4..92393b834 100644 --- a/src/services/factories/tokenList.ts +++ b/src/services/factories/tokenList.ts @@ -173,57 +173,69 @@ export function getTokensFactory(factoryParams: { tokensConfig: TokenDetails[], ): Promise { const tokensConfigMap = new Map(tokensConfig.map((t) => [t.address, t])) - const tokenDetailsPromises: (Promise | TokenDetails)[] = [] - addressToIdMap.forEach((id, tokenAddress) => { + const tokenDetailsPromises: Promise[] = [] + + async function _fillToken(id: number, tokenAddress: string): Promise { // Resolve the details using the config, otherwise fetch the token - const token: undefined | Promise = tokensConfigMap.has(tokenAddress) - ? Promise.resolve(tokensConfigMap.get(tokenAddress)) - : _fetchToken(networkId, id, tokenAddress) + const token = tokensConfigMap.get(tokenAddress) || (await _fetchToken(networkId, id, tokenAddress)) if (token) { - // Add a label for convenience - token.then((token) => { - if (token) { - token.label = safeTokenName(token) - } - return token - }) - - tokenDetailsPromises.push(token) + token.label = safeTokenName(token) } + + return token + } + + addressToIdMap.forEach((id, tokenAddress) => { + tokenDetailsPromises.push(_fillToken(id, tokenAddress)) }) return (await Promise.all(tokenDetailsPromises)).filter(notEmpty) } - function _moveTokenToHeadOfArray(tokenAddress: string, tokenList: TokenDetails[]): TokenDetails[] { - const tokenIndex = tokenList.findIndex((t) => t.address === tokenAddress) - if (tokenIndex !== -1) { - const token = tokenList[tokenIndex] - tokenList.splice(tokenIndex, 1) - - return [token, ...tokenList] - } - - return tokenList - } + type TokenComparator = (a: TokenDetails, b: TokenDetails) => number - function _sortTokens(networkId: number, tokens: TokenDetails[]): TokenDetails[] { - // Sort tokens - let tokensSorted = tokens.sort(_tokenComparer) + function _createTokenComparator(networkId: number): TokenComparator { + let comparator: TokenComparator + // allows correct unicode comparison + const compareByLabel: TokenComparator = (a, b) => a.label.localeCompare(b.label) - // Make sure wxDAI and WETH are the first tokens switch (networkId) { case Network.Mainnet: - return _moveTokenToHeadOfArray(WETH_ADDRESS_MAINNET, tokensSorted) + comparator = (a, b): number => { + // WETH first + if (a.address === WETH_ADDRESS_MAINNET) return -1 + if (b.address === WETH_ADDRESS_MAINNET) return 1 + return compareByLabel(a, b) + } + break case Network.Rinkeby: - return _moveTokenToHeadOfArray(WETH_ADDRESS_RINKEBY, tokensSorted) + comparator = (a, b): number => { + // WETH first + if (a.address === WETH_ADDRESS_RINKEBY) return -1 + if (b.address === WETH_ADDRESS_RINKEBY) return 1 + return compareByLabel(a, b) + } + break case Network.xDAI: - tokensSorted = _moveTokenToHeadOfArray(WETH_ADDRESS_XDAI, tokensSorted) - return _moveTokenToHeadOfArray(WXDAI_ADDRESS_XDAI, tokensSorted) + comparator = (a, b): number => { + // WXDAI before WETH + if (a.address === WXDAI_ADDRESS_XDAI && b.address === WETH_ADDRESS_XDAI) return -1 + // WETH after WXDAI + if (a.address === WETH_ADDRESS_XDAI && b.address === WXDAI_ADDRESS_XDAI) return 1 + // WXDAI and WETH first + if (a.address === WXDAI_ADDRESS_XDAI) return -1 + if (b.address === WXDAI_ADDRESS_XDAI) return 1 + if (a.address === WETH_ADDRESS_XDAI) return -1 + if (b.address === WETH_ADDRESS_XDAI) return 1 + return compareByLabel(a, b) + } + break default: - return tokensSorted + comparator = compareByLabel } + + return comparator } async function _fetchToken( @@ -259,19 +271,12 @@ export function getTokensFactory(factoryParams: { const tokenDetails = await _fetchTokenDetails(networkId, filteredAddressesAndIds, tokensConfig) // Sort tokens - const tokenList = _sortTokens(networkId, tokenDetails) + // note that sort mutates tokenDetails + // but it's ok as tokenDetails is a newly created array + tokenDetails.sort(_createTokenComparator(networkId)) // Persist it - tokenListApi.persistTokens({ networkId, tokenList }) - } - - function _tokenComparer(a: TokenDetails, b: TokenDetails): number { - if (a.label < b.label) { - return -1 - } else if (a.label > b.label) { - return 1 - } - return 0 + tokenListApi.persistTokens({ networkId, tokenList: tokenDetails }) } async function updateTokens(networkId: number): Promise { From 2215623bed47e63a037052d8f37b26b81df0cc06 Mon Sep 17 00:00:00 2001 From: Velenir Date: Tue, 13 Oct 2020 09:59:42 +0300 Subject: [PATCH 4/9] More linter rules (#1523) * extend eslint:recommended * specify linter environment * linter fixes --- .eslintrc.js | 6 ++++++ src/api/index.ts | 3 ++- src/components/TokenOptionItem.tsx | 3 ++- src/components/TradesWidget/TradeRow.tsx | 1 + src/reducers-actions/orders.ts | 3 ++- src/reducers-actions/pendingOrders.ts | 1 + src/reducers-actions/trades.ts | 2 +- 7 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0f39addd6..14952dd17 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,7 @@ module.exports = { parser: '@typescript-eslint/parser', // Specifies the ESLint parser extends: [ + 'eslint:recommended', 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier @@ -44,4 +45,9 @@ module.exports = { globals: { VERSION: true, }, + env: { + browser: true, + node: true, + jest: true, + }, } diff --git a/src/api/index.ts b/src/api/index.ts index 2499e50b5..9802e3eee 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -167,10 +167,11 @@ function createTcrApi(web3: Web3): TcrApi | undefined { case 'none': tcrApi = undefined break - case 'multi-tcr': + case 'multi-tcr': { const multiTcrApiConfig = CONFIG.tcr tcrApi = new MultiTcrApiProxy({ web3, ...multiTcrApiConfig.config }) break + } default: throw new Error('Unknown implementation for DexPriceEstimatorApi: ' + type) diff --git a/src/components/TokenOptionItem.tsx b/src/components/TokenOptionItem.tsx index 9810b48f7..c26de0647 100644 --- a/src/components/TokenOptionItem.tsx +++ b/src/components/TokenOptionItem.tsx @@ -176,7 +176,7 @@ const generateMessage = ({ case TokenFromExchange.NOT_ERC20: return <>Not a valid ERC20 token // registered but not in list --> option to add - case TokenFromExchange.NOT_IN_TOKEN_LIST: + case TokenFromExchange.NOT_IN_TOKEN_LIST: { if (!token || !('id' in token)) return <>{defaultText} const handleAddToken: React.MouseEventHandler = async (e) => { @@ -201,6 +201,7 @@ const generateMessage = ({ ) + } default: return <>{defaultText} } diff --git a/src/components/TradesWidget/TradeRow.tsx b/src/components/TradesWidget/TradeRow.tsx index 1fc3e2d35..e9c253853 100644 --- a/src/components/TradesWidget/TradeRow.tsx +++ b/src/components/TradesWidget/TradeRow.tsx @@ -114,6 +114,7 @@ export const TradeRow: React.FC = (params) => { return `${fillPercentage} matched out of ${orderAmount} ${displayTokenSymbolOrLink(sellToken)}` } } + // falls through case 'liquidity': case 'unknown': default: diff --git a/src/reducers-actions/orders.ts b/src/reducers-actions/orders.ts index 7cba8d44a..400fdf293 100644 --- a/src/reducers-actions/orders.ts +++ b/src/reducers-actions/orders.ts @@ -114,12 +114,13 @@ export async function sideEffect(state: OrdersState, action: ReducerActionType): switch (action.type) { case 'OVERWRITE_ORDERS': case 'UPDATE_ORDERS': - case 'APPEND_ORDERS': + case 'APPEND_ORDERS': { const newTokenIdsFromOrders = new Set() // orders can contain many duplicated tokenIds state.orders.forEach(({ sellTokenId, buyTokenId }) => newTokenIdsFromOrders.add(sellTokenId).add(buyTokenId)) addUnlistedTokensToUserTokenListById(Array.from(newTokenIdsFromOrders)) + } } } diff --git a/src/reducers-actions/pendingOrders.ts b/src/reducers-actions/pendingOrders.ts index e5b679008..8756d0dc4 100644 --- a/src/reducers-actions/pendingOrders.ts +++ b/src/reducers-actions/pendingOrders.ts @@ -111,6 +111,7 @@ export const sideEffect = (state: PendingOrdersState, action: ReducerType): Pend case 'SAVE_PENDING_ORDERS': case 'REPLACE_PENDING_ORDERS': setStorageItem(STORAGE_PENDING_ORDER_KEY, state) + // falls through default: return state } diff --git a/src/reducers-actions/trades.ts b/src/reducers-actions/trades.ts index 3b08ef73d..acc78918f 100644 --- a/src/reducers-actions/trades.ts +++ b/src/reducers-actions/trades.ts @@ -134,7 +134,7 @@ function getPendingTrades(tradesByRevertKey: Map): Map() From 6bd857d8a3c80526d21e37d2cbb2f1ef57d74a3d Mon Sep 17 00:00:00 2001 From: Velenir Date: Tue, 13 Oct 2020 13:40:16 +0300 Subject: [PATCH 5/9] Fix orderbook tokens reset (#1526) * reset tokens only on OrderBook open * remove import --- src/components/OrderBookBtn.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/OrderBookBtn.tsx b/src/components/OrderBookBtn.tsx index 4a2abbd4c..fc54cc37f 100644 --- a/src/components/OrderBookBtn.tsx +++ b/src/components/OrderBookBtn.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import styled from 'styled-components' import Modal, { useModal } from 'components/common/Modal' @@ -120,14 +120,13 @@ export const OrderBookBtn: React.FC = (props: OrderBookBtnPro const [quoteToken, setQuoteToken] = useSafeState(quoteTokenDefault) const networkDescription = networkId !== Network.Mainnet ? ` (${getNetworkFromId(networkId)})` : '' - // Update if any of the base tokens change - useEffect(() => { - setBaseToken(baseTokenDefault) - setQuoteToken(quoteTokenDefault) - }, [baseTokenDefault, quoteTokenDefault, setBaseToken, setQuoteToken]) - const [modalHook, toggleModal] = useModal({ ...DEFAULT_MODAL_OPTIONS, + onShow: () => { + // Update if any of the base tokens change + setBaseToken(baseTokenDefault) + setQuoteToken(quoteTokenDefault) + }, onHide: () => { // Reset the selection on close setBaseToken(baseTokenDefault) From 556fc0b6c8d985d0cb7cb351469aec8c01930523 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 13 Oct 2020 14:45:41 +0200 Subject: [PATCH 6/9] 1486/token orders (#1527) * add useTokens to useOrders to subscribe to token list changes * tokenList changes 1. add contract token ids inside initial token detail loading * 1486.5/fix wrong tokens orders logic update (#1525) * fixed Mock TokenListAPI + useTokenList export fix * TokenListAPI: isListReady public var * useTokenList/useOrders: use isListReady * set listReady flags * tokenListApi ready logic fix * remove log --- src/api/tokenList/TokenListApi.ts | 10 ++++++++++ src/api/tokenList/TokenListApiMock.ts | 6 ++++++ src/components/OrderBookBtn.tsx | 2 +- src/components/PoolingWidget/index.tsx | 2 +- src/hooks/useManageTokens.tsx | 2 +- src/hooks/useOrders.ts | 14 +++++++++++--- src/hooks/useTokenBalances.ts | 2 +- src/hooks/useTokenList.ts | 11 ++++++++--- src/pages/OrderBook.tsx | 2 +- src/services/factories/getTokenFromExchange.ts | 7 +++++++ src/services/factories/tokenList.ts | 15 +++++++++++++++ 11 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/api/tokenList/TokenListApi.ts b/src/api/tokenList/TokenListApi.ts index 211efb5f4..b304a0af5 100644 --- a/src/api/tokenList/TokenListApi.ts +++ b/src/api/tokenList/TokenListApi.ts @@ -20,6 +20,8 @@ const addOverrideToDisabledTokens = (networkId: number) => (token: TokenDetails) } export interface TokenList extends SubscriptionsInterface { + setListReady: (state: boolean) => void + isListReady: boolean getTokens: (networkId: number) => TokenDetails[] addToken: (params: AddTokenParams) => void addTokens: (params: AddTokensParams) => void @@ -64,6 +66,10 @@ export class TokenListApiImpl extends GenericSubscriptions imple private _tokensByNetwork: { [networkId: number]: TokenDetails[] } private _tokenAddressNetworkSet: Set + // token list flag - prevents stale/incorrect data + // from being presented during token list calculation + public isListReady = true + public constructor({ networkIds, initialTokenList }: TokenListApiParams) { super() @@ -93,6 +99,10 @@ export class TokenListApiImpl extends GenericSubscriptions imple }) } + public setListReady(state: boolean): void { + this.isListReady = state + } + public hasToken(params: HasTokenParams): boolean { return this._tokenAddressNetworkSet.has(TokenListApiImpl.constructAddressNetworkKey(params)) } diff --git a/src/api/tokenList/TokenListApiMock.ts b/src/api/tokenList/TokenListApiMock.ts index f76f2249a..a996ec80f 100644 --- a/src/api/tokenList/TokenListApiMock.ts +++ b/src/api/tokenList/TokenListApiMock.ts @@ -5,6 +5,8 @@ import GenericSubscriptions from './Subscriptions' export class TokenListApiMock extends GenericSubscriptions implements TokenList { private _tokenList: TokenDetails[] + public isListReady = false + public constructor(tokenList: TokenDetails[]) { super() @@ -34,6 +36,10 @@ export class TokenListApiMock extends GenericSubscriptions imple this._tokenList = tokenList this.triggerSubscriptions(tokenList) } + + public setListReady(state: boolean): void { + this.isListReady = state + } } export default TokenListApiMock diff --git a/src/components/OrderBookBtn.tsx b/src/components/OrderBookBtn.tsx index fc54cc37f..49b379eca 100644 --- a/src/components/OrderBookBtn.tsx +++ b/src/components/OrderBookBtn.tsx @@ -115,7 +115,7 @@ export const OrderBookBtn: React.FC = (props: OrderBookBtnPro const { baseToken: baseTokenDefault, quoteToken: quoteTokenDefault, label, className } = props const { networkIdOrDefault: networkId } = useWalletConnection() // get all tokens - const tokenList = useTokenList({ networkId }) + const { tokens: tokenList } = useTokenList({ networkId }) const [baseToken, setBaseToken] = useSafeState(baseTokenDefault) const [quoteToken, setQuoteToken] = useSafeState(quoteTokenDefault) const networkDescription = networkId !== Network.Mainnet ? ` (${getNetworkFromId(networkId)})` : '' diff --git a/src/components/PoolingWidget/index.tsx b/src/components/PoolingWidget/index.tsx index 3bac93b90..489929ade 100644 --- a/src/components/PoolingWidget/index.tsx +++ b/src/components/PoolingWidget/index.tsx @@ -151,7 +151,7 @@ const PoolingInterface: React.FC = () => { const { networkId, networkIdOrDefault, userAddress } = useWalletConnection() // Get all the tokens for the current network - const tokenList = useTokenList({ networkId: networkIdOrDefault }) + const { tokens: tokenList } = useTokenList({ networkId: networkIdOrDefault }) const tokens = useMemo(() => { return ( diff --git a/src/hooks/useManageTokens.tsx b/src/hooks/useManageTokens.tsx index 4dfcf59d8..19818d1d2 100644 --- a/src/hooks/useManageTokens.tsx +++ b/src/hooks/useManageTokens.tsx @@ -148,7 +148,7 @@ const SearchInput: React.FC = (props) => { const ManageTokensContainer: React.FC = () => { const { networkId, networkIdOrDefault } = useWalletConnection() // get all tokens - const tokens = useTokenList({ networkId }) + const { tokens } = useTokenList({ networkId }) const [search, setSearch] = useState('') const { value: debouncedSearch, setImmediate: setDebouncedSearch } = useDebounce(search, 500) diff --git a/src/hooks/useOrders.ts b/src/hooks/useOrders.ts index 85d8c7fe5..c0b5a44e3 100644 --- a/src/hooks/useOrders.ts +++ b/src/hooks/useOrders.ts @@ -11,6 +11,7 @@ import useGlobalState from './useGlobalState' import { useWalletConnection } from './useWalletConnection' import usePendingOrders from './usePendingOrders' import { useCheckWhenTimeRemainingInBatch } from './useTimeRemainingInBatch' +import { useTokenList } from './useTokenList' // Constants/Types import { REFRESH_WHEN_SECONDS_LEFT } from 'const' @@ -32,6 +33,12 @@ export function useOrders(): Result { dispatch, ] = useGlobalState() + // TODO: check this - currently in use for subscription + // to change in token list to trigger update of orders + // and the incorrect state shown sometimes when loading app from + // fresh state and seeing incorrect token list Issue #1486 + const { tokens, isListReady } = useTokenList({ networkId }) + // Pending Orders const pendingOrders = usePendingOrders() @@ -51,7 +58,7 @@ export function useOrders(): Result { const fetchOrders = async (offset: number): Promise => { // isLoading is the important one // controls ongoing fetching chain - if (!userAddress || !networkId || !isLoading) { + if (!userAddress || !networkId || !isLoading || !isListReady) { // next isLoading = true will be when userAddress and networkId are valid setIsLoading(false) return @@ -115,7 +122,7 @@ export function useOrders(): Result { cancelled = true } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [offset, isLoading]) + }, [offset, isLoading, isListReady]) // allow to fresh start/refresh on demand const forceOrdersRefresh = useCallback((): void => { @@ -140,7 +147,8 @@ export function useOrders(): Result { } forceOrdersRefresh() dispatch(overwriteOrders([])) - }, [userAddress, networkId, forceOrdersRefresh, dispatch]) + // Subscribe to tokens change to force orders refresh + }, [tokens, userAddress, networkId, forceOrdersRefresh, dispatch]) return { orders, diff --git a/src/hooks/useTokenBalances.ts b/src/hooks/useTokenBalances.ts index 3a3276ce2..cd94a2f2b 100644 --- a/src/hooks/useTokenBalances.ts +++ b/src/hooks/useTokenBalances.ts @@ -114,7 +114,7 @@ export const useTokenBalances = (passOnParams: Partial = {}) const [error, setError] = useSafeState(false) // get all tokens, maybe without deprecated - const tokens = useTokenList({ networkId: walletInfo.networkId, ...passOnParams }) + const { tokens } = useTokenList({ networkId: walletInfo.networkId, ...passOnParams }) // Get token balances useEffect(() => { diff --git a/src/hooks/useTokenList.ts b/src/hooks/useTokenList.ts index 6e1f2f2c7..ad8bc3d81 100644 --- a/src/hooks/useTokenList.ts +++ b/src/hooks/useTokenList.ts @@ -3,20 +3,25 @@ import { TokenDetails } from 'types' import useSafeState from './useSafeState' import { getTokens, subscribeToTokenList } from 'services' import { EMPTY_ARRAY } from 'const' +import { tokenListApi } from 'api' export interface UseTokenListParams { networkId?: number excludeDeprecated?: boolean } -export const useTokenList = ({ networkId, excludeDeprecated }: UseTokenListParams = {}): TokenDetails[] => { +export const useTokenList = ({ networkId, excludeDeprecated }: UseTokenListParams = {}): { + tokens: TokenDetails[] + isListReady: boolean +} => { // sync get tokenList const unfilteredTokens = networkId === undefined ? EMPTY_ARRAY : getTokens(networkId) const tokens = useMemo(() => { if (!excludeDeprecated) return unfilteredTokens + const filteredList = unfilteredTokens.filter((token) => !token.disabled) - return unfilteredTokens.filter((token) => !token.disabled) + return filteredList }, [excludeDeprecated, unfilteredTokens]) // force update with a new value each time @@ -26,5 +31,5 @@ export const useTokenList = ({ networkId, excludeDeprecated }: UseTokenListParam return subscribeToTokenList(() => forceUpdate({})) }, [forceUpdate]) - return tokens + return { tokens, isListReady: tokenListApi.isListReady } } diff --git a/src/pages/OrderBook.tsx b/src/pages/OrderBook.tsx index 69fc91f0e..5f367f745 100644 --- a/src/pages/OrderBook.tsx +++ b/src/pages/OrderBook.tsx @@ -81,7 +81,7 @@ const OrderBookWrapper = styled.div` const OrderBook: React.FC = () => { const { networkIdOrDefault } = useWalletConnection() // get all tokens - const tokenList = useTokenList({ networkId: networkIdOrDefault }) + const { tokens: tokenList } = useTokenList({ networkId: networkIdOrDefault }) const [baseToken, setBaseToken] = useSafeState(null) const [quoteToken, setQuoteToken] = useSafeState(null) const [hops, setHops] = useSafeState(ORDER_BOOK_HOPS_DEFAULT) diff --git a/src/services/factories/getTokenFromExchange.ts b/src/services/factories/getTokenFromExchange.ts index fe56d1cac..3c06cb721 100644 --- a/src/services/factories/getTokenFromExchange.ts +++ b/src/services/factories/getTokenFromExchange.ts @@ -120,7 +120,14 @@ function getTokenFromExchangeByIdFactory( const { tokenListApi, exchangeApi, getTokenFromErc20 } = factoryParams return async ({ tokenId, networkId }: GetByIdParams): Promise => { + // Grab token list: either TCR or default token list + // if default token list is used, IDs need to be updated first + // or app will see strange data as wrong token IDs from config token list will be used const tokens = tokenListApi.getTokens(networkId) + // this will always return the initial config list first as it is injected + // early in the TokenListAPI constructor + // render 1: config list with incorrect IDs + // render 2: updated token list with correct IDs // Try from our current list of tokens, by id let token = getToken('id', tokenId, tokens) diff --git a/src/services/factories/tokenList.ts b/src/services/factories/tokenList.ts index 92393b834..f8a8ab3ef 100644 --- a/src/services/factories/tokenList.ts +++ b/src/services/factories/tokenList.ts @@ -181,6 +181,11 @@ export function getTokensFactory(factoryParams: { if (token) { token.label = safeTokenName(token) + // TODO: review this + // needs to be here to set correct IDs when using default initialTokenList + // from config as the IDs there are not correct + token.id = + addressToIdMap.get(token.address) !== token.id ? (addressToIdMap.get(token.address) as number) : token.id } return token @@ -283,10 +288,18 @@ export function getTokensFactory(factoryParams: { areTokensUpdated.add(networkId) try { + // Set token list readiness to false + // and prevent stale data being presented in app + tokenListApi.setListReady(false) + const numTokens = await retry(() => exchangeApi.getNumTokens(networkId)) const tokens = tokenListApi.getTokens(networkId) logDebug(`[tokenListFactory][${networkId}] Contract has ${numTokens}; local list has ${tokens.length}`) + + // TODO: review this logic + // why update tokenDetails when token list lengths don't match? + // why would token IDs not also need to be updated? if (numTokens > tokens.length) { // When there are more tokens in the contract than locally, fetch the new tokens await updateTokenDetails(networkId, numTokens) @@ -299,6 +312,8 @@ export function getTokensFactory(factoryParams: { logDebug(`[tokenListFactory][${networkId}] Failed to update tokens: ${e.message}`) // Clear flag so on next query we try again. areTokensUpdated.delete(networkId) + } finally { + tokenListApi.setListReady(true) } } From 9b22eef4bca46f5a7339e7239438a3146aca4a74 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 13 Oct 2020 15:13:47 +0200 Subject: [PATCH 7/9] fix networkOrDefault type and typo (#1528) * fix networkOrDefault type and typo * WalletInfo type --- src/api/wallet/WalletApi.ts | 2 +- src/components/TradeWidget/index.tsx | 2 +- src/hooks/useWalletConnection.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/wallet/WalletApi.ts b/src/api/wallet/WalletApi.ts index 269dfb2c5..0793ff765 100644 --- a/src/api/wallet/WalletApi.ts +++ b/src/api/wallet/WalletApi.ts @@ -67,7 +67,7 @@ export interface WalletApi { export interface WalletInfo { isConnected: boolean userAddress?: string - networkId?: number + networkId?: Network blockNumber?: number } diff --git a/src/components/TradeWidget/index.tsx b/src/components/TradeWidget/index.tsx index 63d3fd5b9..385b3bbcd 100644 --- a/src/components/TradeWidget/index.tsx +++ b/src/components/TradeWidget/index.tsx @@ -152,7 +152,7 @@ const TradeWidgetContainer: React.FC = () => { const sellTokenSymbol = decodeSymbol(encodedSellTokenSymbol || '') const receiveTokenSymbol = decodeSymbol(decodeReceiveTokenSymbol || '') - const { sellToken: initialSellTokenDefaultNetwork, buyToken: initialReceiveTokenDefaultNetwork } = + const { sellToken: initialSellTokenDefaultNetwork, receiveToken: initialReceiveTokenDefaultNetwork } = initialTokenSelection.networks[networkIdOrDefault] || {} const sellTokenWithFallback = useMemo( diff --git a/src/hooks/useWalletConnection.ts b/src/hooks/useWalletConnection.ts index fdef07e59..8d837ccb7 100644 --- a/src/hooks/useWalletConnection.ts +++ b/src/hooks/useWalletConnection.ts @@ -6,7 +6,7 @@ import { BlockchainUpdatePrompt, WalletInfo } from 'api/wallet/WalletApi' interface PendingStateObject extends WalletInfo { pending: true - networkIdOrDefault: number + networkIdOrDefault: Network } const PendingState: PendingStateObject = { @@ -33,7 +33,7 @@ const constructPendingState = ({ chainId, account, blockHeader }: BlockchainUpda } export const useWalletConnection = (): - | (WalletInfo & { pending: false; networkIdOrDefault: number }) + | (WalletInfo & { pending: false; networkIdOrDefault: Network }) | PendingStateObject => { const [walletInfo, setWalletInfo] = useSafeState(null) From b2a95d45cd67a14d5b5356b5a96717ac674b2e79 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 13 Oct 2020 17:39:08 +0200 Subject: [PATCH 8/9] Investigate orders load speed (#1529) * triggerSubs on list ready 1. add to tokenList service * isListReady and isLoading * use getter * fix mock * move isListReady logic inside awaited function --- src/api/tokenList/TokenListApi.ts | 14 +++++++++++--- src/api/tokenList/TokenListApiMock.ts | 4 ++++ src/hooks/useOrders.ts | 10 ++++++---- src/hooks/useTokenList.ts | 2 +- src/services/factories/tokenList.ts | 4 +++- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/api/tokenList/TokenListApi.ts b/src/api/tokenList/TokenListApi.ts index b304a0af5..dacdca5ce 100644 --- a/src/api/tokenList/TokenListApi.ts +++ b/src/api/tokenList/TokenListApi.ts @@ -1,4 +1,4 @@ -import { TokenDetails } from 'types' +import { Network, TokenDetails } from 'types' import { getTokensByNetwork } from './tokenList' import { logDebug } from 'utils' import GenericSubscriptions, { SubscriptionsInterface } from './Subscriptions' @@ -20,7 +20,8 @@ const addOverrideToDisabledTokens = (networkId: number) => (token: TokenDetails) } export interface TokenList extends SubscriptionsInterface { - setListReady: (state: boolean) => void + setListReady: (state: boolean, networkId?: Network) => void + getIsListReady: () => boolean isListReady: boolean getTokens: (networkId: number) => TokenDetails[] addToken: (params: AddTokenParams) => void @@ -99,8 +100,15 @@ export class TokenListApiImpl extends GenericSubscriptions imple }) } - public setListReady(state: boolean): void { + public setListReady(state: boolean, networkId?: Network): void { this.isListReady = state + if (this.isListReady && networkId) { + this.triggerSubscriptions(this._tokensByNetwork[networkId]) + } + } + + public getIsListReady(): boolean { + return this.isListReady } public hasToken(params: HasTokenParams): boolean { diff --git a/src/api/tokenList/TokenListApiMock.ts b/src/api/tokenList/TokenListApiMock.ts index a996ec80f..9de17d3fe 100644 --- a/src/api/tokenList/TokenListApiMock.ts +++ b/src/api/tokenList/TokenListApiMock.ts @@ -13,6 +13,10 @@ export class TokenListApiMock extends GenericSubscriptions imple this._tokenList = tokenList } + public getIsListReady(): boolean { + return this.isListReady + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public getTokens(_networkId: number): TokenDetails[] { return this._tokenList diff --git a/src/hooks/useOrders.ts b/src/hooks/useOrders.ts index c0b5a44e3..32e628221 100644 --- a/src/hooks/useOrders.ts +++ b/src/hooks/useOrders.ts @@ -48,9 +48,11 @@ export function useOrders(): Result { useEffect(() => { // continue loading new orders // from current offset - setIsLoading(true) + // make sure token list is ready before setting + isListReady && setIsLoading(true) + // whenever new block is mined - }, [blockNumber, setIsLoading]) + }, [blockNumber, isListReady, setIsLoading]) useEffect(() => { let cancelled = false @@ -58,7 +60,7 @@ export function useOrders(): Result { const fetchOrders = async (offset: number): Promise => { // isLoading is the important one // controls ongoing fetching chain - if (!userAddress || !networkId || !isLoading || !isListReady) { + if (!userAddress || !networkId || !isLoading) { // next isLoading = true will be when userAddress and networkId are valid setIsLoading(false) return @@ -122,7 +124,7 @@ export function useOrders(): Result { cancelled = true } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [offset, isLoading, isListReady]) + }, [offset, isLoading]) // allow to fresh start/refresh on demand const forceOrdersRefresh = useCallback((): void => { diff --git a/src/hooks/useTokenList.ts b/src/hooks/useTokenList.ts index ad8bc3d81..f2c0922af 100644 --- a/src/hooks/useTokenList.ts +++ b/src/hooks/useTokenList.ts @@ -31,5 +31,5 @@ export const useTokenList = ({ networkId, excludeDeprecated }: UseTokenListParam return subscribeToTokenList(() => forceUpdate({})) }, [forceUpdate]) - return { tokens, isListReady: tokenListApi.isListReady } + return { tokens, isListReady: tokenListApi.getIsListReady() } } diff --git a/src/services/factories/tokenList.ts b/src/services/factories/tokenList.ts index f8a8ab3ef..f622693aa 100644 --- a/src/services/factories/tokenList.ts +++ b/src/services/factories/tokenList.ts @@ -313,15 +313,17 @@ export function getTokensFactory(factoryParams: { // Clear flag so on next query we try again. areTokensUpdated.delete(networkId) } finally { - tokenListApi.setListReady(true) + tokenListApi.setListReady(true, networkId) } } return function (networkId: number): TokenDetails[] { if (!areTokensUpdated.has(networkId)) { logDebug(`[tokenListFactory][${networkId}] Will update tokens for network`) + updateTokens(networkId) } + return tokenListApi.getTokens(networkId) } } From 7bc5f661032f64709b72a44062c8cdb970eb63f4 Mon Sep 17 00:00:00 2001 From: David Sato Date: Tue, 13 Oct 2020 17:51:40 +0200 Subject: [PATCH 9/9] 1.5.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 044563564..9e8a86f0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@gnosis.pm/dex-react", - "version": "1.5.0-0", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e9b21d08c..279655d7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gnosis.pm/dex-react", - "version": "1.5.0-0", + "version": "1.5.0", "description": "", "main": "src/index.js", "sideEffects": false,