Skip to content

Commit

Permalink
refactor: move safe overviews into gateway api
Browse files Browse the repository at this point in the history
  • Loading branch information
schmanu committed Sep 25, 2024
1 parent 3a3294a commit a84a4e0
Show file tree
Hide file tree
Showing 23 changed files with 149 additions and 76 deletions.
2 changes: 1 addition & 1 deletion src/components/common/NetworkSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import PlusIcon from '@/public/images/common/plus.svg'
import useAddressBook from '@/hooks/useAddressBook'
import { CreateSafeOnSpecificChain } from '@/features/multichain/components/CreateSafeOnNewChain'
import { useGetSafeOverviewQuery } from '@/store/safeOverviews'
import { useGetSafeOverviewQuery } from '@/store/api/gateway'

const ChainIndicatorWithFiatBalance = ({
isSelected,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import SafeTokenWidget from '..'
import { toBeHex } from 'ethers'
import { AppRoutes } from '@/config/routes'
import useSafeTokenAllocation, { useSafeVotingPower } from '@/hooks/useSafeTokenAllocation'
import * as safePass from '@/store/safePass'
import type { CampaignLeaderboardEntry } from '@/store/safePass'
import * as safePass from '@/store/api/safePass'
import type { CampaignLeaderboardEntry } from '@/store/api/safePass'

jest.mock('@/hooks/useChainId')

Expand Down
2 changes: 1 addition & 1 deletion src/components/common/SafeTokenWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import css from './styles.module.css'
import useSafeAddress from '@/hooks/useSafeAddress'
import { skipToken } from '@reduxjs/toolkit/query/react'
import { useDarkMode } from '@/hooks/useDarkMode'
import { useGetOwnGlobalCampaignRankQuery } from '@/store/safePass'
import { useGetOwnGlobalCampaignRankQuery } from '@/store/api/safePass'
import { formatAmount } from '@/utils/formatNumber'

const TOKEN_DECIMALS = 18
Expand Down
2 changes: 1 addition & 1 deletion src/components/transactions/SingleTx/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ExpandableTransactionItem, {
} from '@/components/transactions/TxListItem/ExpandableTransactionItem'
import GroupLabel from '../GroupLabel'
import { isMultisigDetailedExecutionInfo } from '@/utils/transaction-guards'
import { useGetTransactionDetailsQuery } from '@/store/gateway'
import { useGetTransactionDetailsQuery } from '@/store/api/gateway'
import { skipToken } from '@reduxjs/toolkit/query/react'
import { asError } from '@/services/exceptions/utils'

Expand Down
2 changes: 1 addition & 1 deletion src/components/transactions/TxDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import useIsPending from '@/hooks/useIsPending'
import { isImitation, isTrustedTx } from '@/utils/transactions'
import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
import { useGetTransactionDetailsQuery } from '@/store/gateway'
import { useGetTransactionDetailsQuery } from '@/store/api/gateway'
import { asError } from '@/services/exceptions/utils'
import { POLLING_INTERVAL } from '@/config/constants'

Expand Down
2 changes: 1 addition & 1 deletion src/components/tx-flow/flows/ExecuteBatch/ReviewBatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import WalletRejectionError from '@/components/tx/SignOrExecuteForm/WalletReject
import useUserNonce from '@/components/tx/AdvancedParams/useUserNonce'
import { getLatestSafeVersion } from '@/utils/chains'
import { HexEncodedData } from '@/components/transactions/HexEncodedData'
import { useGetMultipleTransactionDetailsQuery } from '@/store/gateway'
import { useGetMultipleTransactionDetailsQuery } from '@/store/api/gateway'
import { skipToken } from '@reduxjs/toolkit/query/react'
import NetworkWarning from '@/components/new-safe/create/NetworkWarning'

Expand Down
2 changes: 1 addition & 1 deletion src/components/tx/DecodedTx/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import DecodedData from '@/components/transactions/TxDetails/TxData/DecodedData'
import accordionCss from '@/styles/accordion.module.css'
import HelpToolTip from './HelpTooltip'
import { useGetTransactionDetailsQuery } from '@/store/gateway'
import { useGetTransactionDetailsQuery } from '@/store/api/gateway'
import { skipToken } from '@reduxjs/toolkit/query/react'
import { asError } from '@/services/exceptions/utils'

Expand Down
2 changes: 1 addition & 1 deletion src/components/tx/SignOrExecuteForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { MigrateToL2Information } from './MigrateToL2Information'
import { extractMigrationL2MasterCopyAddress } from '@/utils/transactions'

import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { useGetTransactionDetailsQuery, useLazyGetTransactionDetailsQuery } from '@/store/gateway'
import { useGetTransactionDetailsQuery, useLazyGetTransactionDetailsQuery } from '@/store/api/gateway'
import { skipToken } from '@reduxjs/toolkit/query/react'
import NetworkWarning from '@/components/new-safe/create/NetworkWarning'

Expand Down
2 changes: 1 addition & 1 deletion src/components/welcome/MyAccounts/AccountItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import FiatValue from '@/components/common/FiatValue'
import QueueActions from './QueueActions'
import { useGetHref } from './useGetHref'
import { extractCounterfactualSafeSetup, isPredictedSafeProps } from '@/features/counterfactual/utils'
import { useGetSafeOverviewQuery } from '@/store/safeOverviews'
import { useGetSafeOverviewQuery } from '@/store/api/gateway'
import useWallet from '@/hooks/wallets/useWallet'
import { skipToken } from '@reduxjs/toolkit/query'

Expand Down
2 changes: 1 addition & 1 deletion src/components/welcome/MyAccounts/MultiAccountItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { AddNetworkButton } from './AddNetworkButton'
import { isPredictedSafeProps } from '@/features/counterfactual/utils'
import ChainIndicator from '@/components/common/ChainIndicator'
import MultiAccountContextMenu from '@/components/sidebar/SafeListContextMenu/MultiAccountContextMenu'
import { useGetMultipleSafeOverviewsQuery } from '@/store/safeOverviews'
import { useGetMultipleSafeOverviewsQuery } from '@/store/api/gateway'
import useWallet from '@/hooks/wallets/useWallet'
import { selectCurrency } from '@/store/settingsSlice'

Expand Down
2 changes: 1 addition & 1 deletion src/features/speedup/components/SpeedUpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { getTransactionTrackingType } from '@/services/analytics/tx-tracking'
import { trackError } from '@/services/exceptions'
import ErrorCodes from '@/services/exceptions/ErrorCodes'
import CheckWallet from '@/components/common/CheckWallet'
import { useLazyGetTransactionDetailsQuery } from '@/store/gateway'
import { useLazyGetTransactionDetailsQuery } from '@/store/api/gateway'
import NetworkWarning from '@/components/new-safe/create/NetworkWarning'

type Props = {
Expand Down
2 changes: 1 addition & 1 deletion src/features/stake/components/StakePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import WidgetDisclaimer from '@/components/common/WidgetDisclaimer'
import useStakeConsent from '@/features/stake/useStakeConsent'
import StakingWidget from '../StakingWidget'
import { useRouter } from 'next/router'
import { useGetIsSanctionedQuery } from '@/store/ofac'
import { useGetIsSanctionedQuery } from '@/store/api/ofac'
import { skipToken } from '@reduxjs/toolkit/query/react'
import useWallet from '@/hooks/wallets/useWallet'
import useSafeInfo from '@/hooks/useSafeInfo'
Expand Down
2 changes: 1 addition & 1 deletion src/features/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import { calculateFeePercentageInBps } from '@/features/swap/helpers/fee'
import { UiOrderTypeToOrderType } from '@/features/swap/helpers/utils'
import { FEATURES } from '@/utils/chains'
import { useGetIsSanctionedQuery } from '@/store/ofac'
import { useGetIsSanctionedQuery } from '@/store/api/ofac'
import { skipToken } from '@reduxjs/toolkit/query/react'
import { getKeyWithTrueValue } from '@/utils/helpers'

Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useDelegates.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import useSafeInfo from '@/hooks/useSafeInfo'
import useWallet from '@/hooks/wallets/useWallet'
import { useGetDelegatesQuery } from '@/store/gateway'
import { useGetDelegatesQuery } from '@/store/api/gateway'
import { skipToken } from '@reduxjs/toolkit/query/react'

const useDelegates = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useTxNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import useSafeAddress from './useSafeAddress'
import { getExplorerLink } from '@/utils/gateway'
import { isWalletRejection } from '@/utils/wallets'
import { getTxLink } from '@/utils/tx-link'
import { useLazyGetTransactionDetailsQuery } from '@/store/gateway'
import { useLazyGetTransactionDetailsQuery } from '@/store/api/gateway'

const TxNotifications = {
[TxEvent.SIGN_FAILED]: 'Failed to sign. Please try again.',
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useTxTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { trackEvent, WALLET_EVENTS } from '@/services/analytics'
import { TxEvent, txSubscribe } from '@/services/tx/txEvents'
import { useEffect } from 'react'
import useChainId from './useChainId'
import { useLazyGetTransactionDetailsQuery } from '@/store/gateway'
import { useLazyGetTransactionDetailsQuery } from '@/store/api/gateway'

const events = {
[TxEvent.SIGNED]: WALLET_EVENTS.OFFCHAIN_SIGNATURE,
Expand Down
72 changes: 71 additions & 1 deletion src/store/__tests__/safeOverviews.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderHook, waitFor } from '@/tests/test-utils'
import { useGetMultipleSafeOverviewsQuery, useGetSafeOverviewQuery } from '../safeOverviews'
import { useGetMultipleSafeOverviewsQuery, useGetSafeOverviewQuery } from '../api/gateway'
import { faker } from '@faker-js/faker'
import { getSafeOverviews } from '@safe-global/safe-gateway-typescript-sdk'

Expand Down Expand Up @@ -247,5 +247,75 @@ describe('safeOverviews', () => {
expect(result.current.isLoading).toBeFalsy()
})
})

it('Should split big batches into multiple requests', async () => {
// Requests overviews for 15 Safes at once
const request = {
currency: 'usd',
safes: [
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
{ address: faker.finance.ethereumAddress(), chainId: '1', isWatchlist: false },
],
}

const firstBatchOverviews = request.safes.slice(0, 10).map((safe) => ({
address: { value: safe.address },
chainId: '1',
awaitingConfirmation: null,
fiatTotal: faker.string.numeric({ length: { min: 1, max: 6 } }),
owners: [{ value: faker.finance.ethereumAddress() }],
threshold: 1,
queued: 0,
}))

const secondBatchOverviews = request.safes.slice(10).map((safe) => ({
address: { value: safe.address },
chainId: '1',
awaitingConfirmation: null,
fiatTotal: faker.string.numeric({ length: { min: 1, max: 6 } }),
owners: [{ value: faker.finance.ethereumAddress() }],
threshold: 1,
queued: 0,
}))

// Mock two fetch requests for the 2 batches
mockedGetSafeOverviews.mockResolvedValueOnce(firstBatchOverviews).mockResolvedValueOnce(secondBatchOverviews)

const { result } = renderHook(() => useGetMultipleSafeOverviewsQuery(request))

// Request should get queued and remain loading for the queue seconds
expect(result.current.isLoading).toBeTruthy()

await waitFor(() => {
expect(result.current.isLoading).toBeFalsy()
expect(result.current.error).toBeUndefined()
expect(result.current.data).toEqual([...firstBatchOverviews, ...secondBatchOverviews])
})

// Expect that the correct requests were sent
expect(mockedGetSafeOverviews).toHaveBeenCalledTimes(2)
expect(mockedGetSafeOverviews).toHaveBeenCalledWith(
request.safes.slice(0, 10).map((safe) => `1:${safe.address}`),
{ currency: 'usd', exclude_spam: true, trusted: true },
)

expect(mockedGetSafeOverviews).toHaveBeenCalledWith(
request.safes.slice(10).map((safe) => `1:${safe.address}`),
{ currency: 'usd', exclude_spam: true, trusted: true },
)
})
})
})
6 changes: 5 additions & 1 deletion src/store/gateway.ts → src/store/api/gateway/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import type { BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes'
import type { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query/react'
import { getDelegates } from '@safe-global/safe-gateway-typescript-sdk'
import type { DelegateResponse } from '@safe-global/safe-gateway-typescript-sdk/dist/types/delegates'
import { safeOverviewEndpoints } from './safeOverviews'

const noopBaseQuery: BaseQueryFn<
export const noopBaseQuery: BaseQueryFn<
unknown, // QueryArg type
unknown, // ResultType
FetchBaseQueryError, // ErrorType
Expand Down Expand Up @@ -48,6 +49,7 @@ export const gatewayApi = createApi({
}
},
}),
...safeOverviewEndpoints(builder),
}),
})

Expand All @@ -56,4 +58,6 @@ export const {
useGetMultipleTransactionDetailsQuery,
useLazyGetTransactionDetailsQuery,
useGetDelegatesQuery,
useGetSafeOverviewQuery,
useGetMultipleSafeOverviewsQuery,
} = gatewayApi
101 changes: 51 additions & 50 deletions src/store/safeOverviews.ts → src/store/api/gateway/safeOverviews.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { createApi } from '@reduxjs/toolkit/query/react'
import { type BaseQueryFn, type FetchBaseQueryError, type EndpointBuilder } from '@reduxjs/toolkit/query/react'

import { type SafeOverview, getSafeOverviews } from '@safe-global/safe-gateway-typescript-sdk'
import { sameAddress } from '@/utils/addresses'
import type { RootState } from '.'
import { selectCurrency } from './settingsSlice'
import type { RootState } from '../..'
import { selectCurrency } from '../../settingsSlice'
import { type SafeItem } from '@/components/welcome/MyAccounts/useAllSafes'

const noopBaseQuery = async () => ({ data: null })

type SafeOverviewQueueItem = {
safeAddress: string
walletAddress?: string
Expand Down Expand Up @@ -114,50 +112,53 @@ type MultiOverviewQueryParams = {
safes: SafeItem[]
}

export const safeOverviewApi = createApi({
reducerPath: 'safeOverviewApi',
baseQuery: noopBaseQuery,
endpoints: (builder) => ({
getSafeOverview: builder.query<
SafeOverview | undefined,
{ safeAddress: string; walletAddress?: string; chainId: string }
>({
async queryFn({ safeAddress, walletAddress, chainId }, { getState }) {
const state = getState()
const currency = selectCurrency(state as RootState)

try {
const safeOverview = await batchedFetcher.getOverview({ chainId, currency, walletAddress, safeAddress })
return { data: safeOverview }
} catch (error) {
return { error: { status: 'CUSTOM_ERROR', data: (error as Error).message } }
}
},
}),
getMultipleSafeOverviews: builder.query<SafeOverview[], MultiOverviewQueryParams>({
async queryFn(params) {
const { safes, walletAddress, currency } = params

try {
const promisedSafeOverviews = safes.map((safe) =>
batchedFetcher.getOverview({
chainId: safe.chainId,
safeAddress: safe.address,
currency,
walletAddress,
}),
)
const safeOverviews = await Promise.all(promisedSafeOverviews)
return { data: safeOverviews.filter(Boolean) as SafeOverview[] }
} catch (error) {
return { error: { status: 'CUSTOM_ERROR', data: (error as Error).message } }
}
},
}),
export const safeOverviewEndpoints = (
builder: EndpointBuilder<
BaseQueryFn<
unknown, // QueryArg type
unknown, // ResultType
FetchBaseQueryError, // ErrorType
{}, // DefinitionExtraOptions
{} // Meta
>,
never,
'gatewayApi'
>,
) => ({
getSafeOverview: builder.query<
SafeOverview | undefined,
{ safeAddress: string; walletAddress?: string; chainId: string }
>({
async queryFn({ safeAddress, walletAddress, chainId }, { getState }) {
const state = getState()
const currency = selectCurrency(state as RootState)

try {
const safeOverview = await batchedFetcher.getOverview({ chainId, currency, walletAddress, safeAddress })
return { data: safeOverview }
} catch (error) {
return { error: { status: 'CUSTOM_ERROR', error: (error as Error).message } }
}
},
}),
getMultipleSafeOverviews: builder.query<SafeOverview[], MultiOverviewQueryParams>({
async queryFn(params) {
const { safes, walletAddress, currency } = params

try {
const promisedSafeOverviews = safes.map((safe) =>
batchedFetcher.getOverview({
chainId: safe.chainId,
safeAddress: safe.address,
currency,
walletAddress,
}),
)
const safeOverviews = await Promise.all(promisedSafeOverviews)
return { data: safeOverviews.filter(Boolean) as SafeOverview[] }
} catch (error) {
return { error: { status: 'CUSTOM_ERROR', error: (error as Error).message } }
}
},
}),
})

// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetSafeOverviewQuery, useLazyGetSafeOverviewQuery, useGetMultipleSafeOverviewsQuery } =
safeOverviewApi
2 changes: 1 addition & 1 deletion src/store/ofac.ts → src/store/api/ofac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createApi } from '@reduxjs/toolkit/query/react'
import { selectChainById } from '@/store/chainsSlice'
import { Contract } from 'ethers'
import { createWeb3ReadOnly } from '@/hooks/wallets/web3'
import type { RootState } from '.'
import type { RootState } from '..'
import { CHAINALYSIS_OFAC_CONTRACT } from '@/config/constants'
import chains from '@/config/chains'

Expand Down
File renamed without changes.
Loading

0 comments on commit a84a4e0

Please sign in to comment.