From d96d16979bb5880a0fef23afaa846fdbbcfb9d8d Mon Sep 17 00:00:00 2001 From: Petar Todorovic Date: Tue, 9 Jun 2026 12:34:15 +0200 Subject: [PATCH 1/4] perf(widget): defer yield opportunity hydration --- .../select-opportunity-list-item/index.tsx | 18 ++-- packages/widget/src/domain/types/stake.ts | 15 ++- packages/widget/src/domain/types/yields.ts | 38 ++++--- .../hooks/api/use-dashboard-yield-catalog.ts | 12 ++- .../widget/src/hooks/api/use-multi-yields.ts | 78 +++++++++++++-- .../src/hooks/api/use-token-list-yields.ts | 1 + .../get-yield-opportunity.ts | 99 ++++++++++--------- .../src/hooks/api/use-yield-providers.ts | 51 ++++++++++ .../src/hooks/api/use-yield-summaries.ts | 56 ++++++++++- .../earn-page/state/earn-page-context.tsx | 82 +++++++++++---- .../src/pages/details/earn-page/types.ts | 9 +- 11 files changed, 350 insertions(+), 109 deletions(-) create mode 100644 packages/widget/src/hooks/api/use-yield-providers.ts diff --git a/packages/widget/src/components/molecules/select-opportunity-list-item/index.tsx b/packages/widget/src/components/molecules/select-opportunity-list-item/index.tsx index 6f3ce768..2623435e 100644 --- a/packages/widget/src/components/molecules/select-opportunity-list-item/index.tsx +++ b/packages/widget/src/components/molecules/select-opportunity-list-item/index.tsx @@ -1,6 +1,6 @@ import type { ComponentProps } from "react"; import { useTranslation } from "react-i18next"; -import type { Yield } from "../../../domain/types/yields"; +import type { YieldBase } from "../../../domain/types/yields"; import { capitalizeFirstLetters, getRewardRateFormatted, @@ -17,17 +17,19 @@ import { selectItemText, } from "./styles.css"; -export const SelectOpportunityListItem = ({ +type SelectOpportunityListItemProps = { + item: T; + onYieldSelect: (item: T) => void; + testId?: string; + selected?: boolean; +}; + +export const SelectOpportunityListItem = ({ item, onYieldSelect, testId, selected, -}: { - item: Yield; - onYieldSelect: (item: Yield) => void; - testId?: string; - selected?: boolean; -}) => { +}: SelectOpportunityListItemProps) => { const { t } = useTranslation(); const onItemClick: ComponentProps["onItemClick"] = ({ diff --git a/packages/widget/src/domain/types/stake.ts b/packages/widget/src/domain/types/stake.ts index 178910a1..748cbd08 100644 --- a/packages/widget/src/domain/types/stake.ts +++ b/packages/widget/src/domain/types/stake.ts @@ -8,7 +8,12 @@ import type { InitParams } from "./init-params"; import type { PositionsData } from "./positions"; import type { TokenBalanceScanResponseDto } from "./token-balance"; import type { TokenString } from "./tokens"; -import { getYieldActionArg, isBittensorStaking, type Yield } from "./yields"; +import { + getYieldActionArg, + isBittensorStaking, + type Yield, + type YieldBase, +} from "./yields"; const amountGreaterThanZero = (val: TokenBalanceScanResponseDto) => new BigNumber(val.amount).isGreaterThan(0); @@ -87,7 +92,7 @@ export const getInitialToken = (args: { export const canBeInitialYield = (args: { initQueryParams: Maybe; - yieldDto: Yield; + yieldDto: YieldBase; tokenBalanceAmount: BigNumber; positionsData: PositionsData; }) => { @@ -113,7 +118,7 @@ const balanceValidForYield = ({ positionsData, }: { tokenBalanceAmount: BigNumber; - yieldDto: Yield; + yieldDto: YieldBase; positionsData: PositionsData; }) => tokenBalanceAmount.isGreaterThanOrEqualTo( @@ -149,7 +154,7 @@ const yieldsWithEnterMinBasedOnPosition = new Map>([ export const isNetworkWithEnterMinBasedOnPosition = (network: Networks) => yieldsWithEnterMinBasedOnPosition.has(network); -const isYieldWithEnterMinBasedOnPosition = (yieldDto: Yield) => +const isYieldWithEnterMinBasedOnPosition = (yieldDto: YieldBase) => Maybe.fromNullable( yieldsWithEnterMinBasedOnPosition.get( yieldDto.mechanics.gasFeeToken.network as Networks @@ -159,7 +164,7 @@ const isYieldWithEnterMinBasedOnPosition = (yieldDto: Yield) => .isJust(); export const getMinStakeAmount = ( - yieldDto: Yield, + yieldDto: YieldBase, positionsData: PositionsData ) => { const integrationMin = new BigNumber( diff --git a/packages/widget/src/domain/types/yields.ts b/packages/widget/src/domain/types/yields.ts index 96d55516..87d3f3ba 100644 --- a/packages/widget/src/domain/types/yields.ts +++ b/packages/widget/src/domain/types/yields.ts @@ -15,12 +15,16 @@ import type { SupportedSKChains } from "./chains"; import { EvmNetworks } from "./chains/networks"; import { equalTokens, tokenString } from "./tokens"; -export type Yield = YieldApiYieldDto & { - __fallback__: OldYieldDto; +export type YieldProviderDetails = ProviderDto; + +export type YieldBase = YieldApiYieldDto & { provider?: YieldProviderDetails; }; -export type YieldProviderDetails = ProviderDto; +export type Yield = YieldBase & { + __fallback__: OldYieldDto; +}; + type YieldRiskRatingTone = "positive" | "warning" | "danger" | "neutral"; type KnownYieldRiskRatingSource = YieldRiskEntryDto["source"]; type YieldRiskRatingSource = KnownYieldRiskRatingSource | (string & {}); @@ -110,7 +114,7 @@ export const getDashboardYieldCategoryForApiYieldType = ( ): DashboardYieldCategory => apiYieldTypeToDashboardCategory[yieldType]; export const getDashboardYieldCategory = ( - yieldDto: Yield + yieldDto: YieldBase ): DashboardYieldCategory | null => { const yieldType = getExtendedYieldType(yieldDto); @@ -192,7 +196,7 @@ const secondsToDays = (seconds: number | undefined) => { }; export const getYieldActionArg = ( - yieldDto: Yield, + yieldDto: YieldBase, type: YieldActionType, name: YieldArgumentName ): YieldArgumentConfig | null => { @@ -213,12 +217,12 @@ export const getYieldActionArg = ( }; export const isYieldActionArgRequired = ( - yieldDto: Yield, + yieldDto: YieldBase, type: YieldActionType, name: YieldArgumentName ) => !!getYieldActionArg(yieldDto, type, name)?.required; -export const getYieldRewardTokens = (yieldDto: Yield) => +export const getYieldRewardTokens = (yieldDto: YieldBase) => pipe( yieldDto.rewardRate?.components?.map((component) => component.token) ?? [], EArray.dedupeWith((a, b) => tokenString(a) === tokenString(b)), @@ -310,7 +314,9 @@ export const getYieldLockupPeriod = (yieldDto: Yield) => export const hasYieldExitSignatureVerification = (yieldDto: Yield) => !!yieldDto.__fallback__.args.exit?.args?.signatureVerification?.required; -export const getExtendedYieldType = (yieldDto: Yield): ExtendedYieldType => { +export const getExtendedYieldType = ( + yieldDto: YieldBase +): ExtendedYieldType => { if (isNativeStaking(yieldDto)) { return "native_staking"; } @@ -322,7 +328,7 @@ export const getExtendedYieldType = (yieldDto: Yield): ExtendedYieldType => { return yieldDto.mechanics.type; }; -export const getYieldOutputToken = (yieldDto: Yield) => +export const getYieldOutputToken = (yieldDto: YieldBase) => Maybe.fromNullable(yieldDto.outputToken).filter( (outputToken) => !equalTokens(outputToken, yieldDto.token) ); @@ -344,7 +350,7 @@ export const isDepositYieldType = (yieldType: ExtendedYieldType) => yieldType === "liquidity_pool"; export const getYieldTypeLabels = ( - yieldDto: Yield, + yieldDto: YieldBase, t: TFunction ): YieldTypeLabelsMap[keyof YieldTypeLabelsMap] => { const map = { @@ -426,15 +432,15 @@ const yieldTypesSortRank: { [Key in ExtendedYieldType]: number } = { concentrated_liquidity_pool: 10, }; -export const getYieldTypesSortRank = (yieldDto: Yield) => +export const getYieldTypesSortRank = (yieldDto: YieldBase) => yieldTypesSortRank[getExtendedYieldType(yieldDto)]; -const isEthereumStaking = (yieldDto: Yield) => +const isEthereumStaking = (yieldDto: YieldBase) => yieldDto.mechanics.type === "staking" && yieldDto.token.network === EvmNetworks.Ethereum && yieldDto.token.symbol === "ETH"; -const isNativeStaking = (yieldDto: Yield) => +const isNativeStaking = (yieldDto: YieldBase) => Maybe.fromFalsy(isEthereumStaking(yieldDto)) .chain(() => Maybe.fromFalsy( @@ -449,13 +455,13 @@ const isNativeStaking = (yieldDto: Yield) => .filter((v) => v.isEqualTo(32)) .isJust(); -const isPooledStaking = (yieldDto: Yield) => +const isPooledStaking = (yieldDto: YieldBase) => isEthereumStaking(yieldDto) && !isNativeStaking(yieldDto); -export const isYieldWithProviderOptions = (yieldDto: Yield) => +export const isYieldWithProviderOptions = (yieldDto: YieldBase) => !!getYieldActionArg(yieldDto, "enter", "providerId")?.required; -export const getYieldProviderYieldIds = (yieldDto: Yield) => +export const getYieldProviderYieldIds = (yieldDto: YieldBase) => getYieldActionArg(yieldDto, "enter", "providerId")?.options ?? []; export const isYieldIntegrationAggregator = (yieldDto: Yield) => diff --git a/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts b/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts index 451c140d..b96e2158 100644 --- a/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts +++ b/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts @@ -14,10 +14,12 @@ import { getYieldSummariesQueryKey, isVisibleYieldSummary, type YieldSummariesParams, + type YieldSummary, } from "./use-yield-summaries"; type DashboardCategoryInitialSelection = { token: TokenDto; + yieldDto: YieldSummary; yieldId: string; }; @@ -31,18 +33,21 @@ const staleTime = 1000 * 60 * 2; */ export const useDashboardYieldCatalog = ({ enabled = true, + network, }: { enabled?: boolean; + network?: TokenDto["network"] | null; } = {}) => { - const { network } = useSKWallet(); + const { network: walletNetwork } = useSKWallet(); const apiClient = useApiClient(); - const probeEnabled = enabled && !!network; + const catalogNetwork = network ?? walletNetwork; + const probeEnabled = enabled && !!catalogNetwork; const results = useQueries({ queries: dashboardYieldCategories.map((category) => { const params: YieldSummariesParams = { - network: network ?? undefined, + network: catalogNetwork ?? undefined, types: getApiYieldTypesForDashboardCategory(category), sort: "rewardRateDesc", limit: DEFAULT_YIELD_SUMMARIES_PAGE_LIMIT, @@ -75,6 +80,7 @@ export const useDashboardYieldCatalog = ({ availableCategories.push(category); initialSelectionByCategory.set(category, { token: firstVisible.token, + yieldDto: firstVisible, yieldId: firstVisible.id, }); }); diff --git a/packages/widget/src/hooks/api/use-multi-yields.ts b/packages/widget/src/hooks/api/use-multi-yields.ts index 954be0c5..cfb95121 100644 --- a/packages/widget/src/hooks/api/use-multi-yields.ts +++ b/packages/widget/src/hooks/api/use-multi-yields.ts @@ -14,6 +14,7 @@ import { map, mergeMap, Observable, + of, repeat, take, tap, @@ -36,20 +37,28 @@ import { isNonZeroRewardRateYield, type ValidatorsConfig, type Yield, + type YieldBase, } from "../../domain/types/yields"; import { useApiClient } from "../../providers/api/api-client-provider"; import { useSKQueryClient } from "../../providers/query-client"; import { useSKWallet } from "../../providers/sk-wallet"; import { useSavedRef } from "../use-saved-ref"; import { useValidatorsConfig } from "../use-validators-config"; -import { getYieldOpportunities } from "./use-yield-opportunity/get-yield-opportunity"; +import { + getYieldOpportunities, + getYieldOpportunityFromSummary, +} from "./use-yield-opportunity/get-yield-opportunity"; +import { + fetchYieldSummariesWithProvidersByIds, + type YieldSummaryWithProvider, +} from "./use-yield-summaries"; const multiYieldsStore = createStore({ - context: { data: new Map>() }, + context: { data: new Map>() }, on: { "yield-opportunity": ( context, - event: { data: { key: string; yieldDto: Yield } } + event: { data: { key: string; yieldDto: YieldSummaryWithProvider } } ) => { const newMap = new Map(context.data); const prev = newMap.get(event.data.key) ?? new Map(); @@ -62,7 +71,7 @@ const multiYieldsStore = createStore({ }, }); -export const useStreamMultiYields = (yieldIds: ReadonlyArray) => { +export const useStreamYieldSummaries = (yieldIds: ReadonlyArray) => { const { network, isConnected, isLedgerLive } = useSKWallet(); const apiClient = useApiClient(); @@ -79,7 +88,7 @@ export const useStreamMultiYields = (yieldIds: ReadonlyArray) => { const validatorsConfig = useValidatorsConfig(); useEffect(() => { - const sub = multipleYields$({ + const sub = multipleYieldSummaries$({ ...argsRef.current, yieldIds, validatorsConfig, @@ -186,6 +195,39 @@ const multipleYields$ = (args: { ) ); +const multipleYieldSummaries$ = (args: { + isLedgerLive: boolean; + queryClient: QueryClient; + apiClient: ReturnType; + isConnected: boolean; + network: SKWallet["network"]; + yieldIds: ReadonlyArray; + validatorsConfig: ValidatorsConfig; +}) => + args.yieldIds.length === 0 + ? EMPTY + : from( + fetchYieldSummariesWithProvidersByIds({ + apiClient: args.apiClient, + queryClient: args.queryClient, + yieldIds: args.yieldIds, + }) + ).pipe( + mergeMap((v) => from(v)), + filter( + (v): v is YieldSummaryWithProvider => + !!( + v && + defaultFiltered({ + data: [v], + isConnected: args.isConnected, + network: args.network, + isLedgerLive: args.isLedgerLive, + }).length > 0 + ) + ) + ); + const firstEligibleYield$ = (args: { isLedgerLive: boolean; queryClient: QueryClient; @@ -199,9 +241,9 @@ const firstEligibleYield$ = (args: { validatorsConfig: ValidatorsConfig; preferredTokenYieldsPerNetwork: PreferredTokenYieldsPerNetwork | null; }) => { - let defaultYield: Yield | null = null; + let defaultYield: YieldSummaryWithProvider | null = null; - const successStream = multipleYields$(args).pipe( + const successStream = multipleYieldSummaries$(args).pipe( tap((v) => { if (isNonZeroRewardRateYield(v) || !defaultYield) { defaultYield = v; @@ -232,20 +274,36 @@ const firstEligibleYield$ = (args: { }); }), take(1), - defaultIfEmpty(null) + defaultIfEmpty(null), + mergeMap((yieldSummary) => { + const selectedYield = yieldSummary ?? defaultYield; + + if (!selectedYield) { + return of(null); + } + + return from( + getYieldOpportunityFromSummary({ + isLedgerLive: args.isLedgerLive, + yieldDto: selectedYield, + queryClient: args.queryClient, + apiClient: args.apiClient, + }) + ).pipe(map((v) => (v.isRight() ? v.extract() : null))); + }) ); return new Observable((subscriber) => { successStream.subscribe({ complete: () => subscriber.complete(), - next: (v) => subscriber.next(v ?? defaultYield), + next: (v) => subscriber.next(v), error: (e) => subscriber.error(e), }); }); }; type SelectorInputData = { - data: Yield[]; + data: YieldBase[]; isConnected: boolean; network: SKWallet["network"]; isLedgerLive: boolean; diff --git a/packages/widget/src/hooks/api/use-token-list-yields.ts b/packages/widget/src/hooks/api/use-token-list-yields.ts index f88468da..edc6eb9b 100644 --- a/packages/widget/src/hooks/api/use-token-list-yields.ts +++ b/packages/widget/src/hooks/api/use-token-list-yields.ts @@ -174,6 +174,7 @@ export const useTokenListYields = ( }, [tokenBalances, yieldIdsByToken, yieldsById]); return { + yieldsById, yieldIdsByToken, yieldCountsByToken, maxYieldRatesByToken, diff --git a/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts b/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts index 0a7aba70..bf8ae227 100644 --- a/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts +++ b/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts @@ -3,9 +3,14 @@ import { EitherAsync } from "purify-ts"; import { isEthenaUsdeStaking, type Yield, + type YieldBase, type YieldProviderDetails, } from "../../../domain/types/yields"; import type { ApiClient } from "../../../providers/api/api-client"; +import { + fetchYieldProvider, + fetchYieldProviders, +} from "../use-yield-providers"; import { fetchYieldSummariesByIds } from "../use-yield-summaries"; type Params = { @@ -22,6 +27,9 @@ type MultiParams = Omit & { type ParamsWithQueryClient = Params & { queryClient: QueryClient; }; +type ParamsWithYieldDto = Omit & { + yieldDto: YieldBase; +}; type MultiParamsWithQueryClient = MultiParams & { queryClient: QueryClient; }; @@ -38,7 +46,6 @@ const getMultiKey = (params: MultiParams) => [ params.yieldIds, params.isLedgerLive, ]; -const getProviderKey = (providerId: string) => ["yield-provider", providerId]; const applyYieldOverrides = (yieldDto: Yield) => isEthenaUsdeStaking(yieldDto.id) @@ -66,51 +73,6 @@ const createYield = ({ __fallback__: legacyYield, }) satisfies Yield; -const fetchYieldProvider = async ({ - client, - providerId, - queryClient, -}: { - client: ReturnType; - providerId: string; - queryClient: QueryClient; -}): Promise => { - try { - return await queryClient.fetchQuery({ - queryKey: getProviderKey(providerId), - staleTime, - queryFn: () => - client.yield.ProvidersControllerGetProvider(providerId, undefined), - }); - } catch (e) { - console.log(e); - return undefined; - } -}; - -const fetchYieldProviders = async ({ - client, - providerIds, - queryClient, -}: { - client: ReturnType; - providerIds: ReadonlyArray; - queryClient: QueryClient; -}) => { - const providers = await Promise.all( - [...new Set(providerIds)].map(async (providerId) => ({ - providerId, - provider: await fetchYieldProvider({ client, providerId, queryClient }), - })) - ); - - return new Map( - providers.flatMap(({ provider, providerId }) => - provider ? [[providerId, provider]] : [] - ) - ); -}; - export const getYieldOpportunity = (params: ParamsWithQueryClient) => EitherAsync(() => params.queryClient.fetchQuery({ @@ -123,6 +85,18 @@ export const getYieldOpportunity = (params: ParamsWithQueryClient) => return new Error("Could not get yield opportunity"); }); +export const getYieldOpportunityFromSummary = (params: ParamsWithYieldDto) => + EitherAsync(() => + params.queryClient.fetchQuery({ + queryKey: getKey({ ...params, yieldId: params.yieldDto.id }), + staleTime, + queryFn: () => hydrateYieldSummaryQueryFn(params), + }) + ).mapLeft((e) => { + console.log(e); + return new Error("Could not get yield opportunity"); + }); + export const getYieldOpportunities = (params: MultiParamsWithQueryClient) => EitherAsync(() => params.queryClient.fetchQuery({ @@ -208,6 +182,39 @@ const fn = ({ }); }; +const hydrateYieldSummaryQueryFn = async ({ + isLedgerLive, + yieldDto, + queryClient, + signal, + suppressRichErrors, + apiClient, +}: ParamsWithYieldDto & { + signal?: AbortSignal; +}) => { + const client = apiClient.withOptions({ signal, suppressRichErrors }); + const [legacyYieldResult, provider] = await Promise.all([ + client.legacy.YieldControllerYieldOpportunity(yieldDto.id, { + params: { ledgerWalletAPICompatible: isLedgerLive }, + }), + yieldDto.provider + ? Promise.resolve(yieldDto.provider) + : fetchYieldProvider({ + client, + providerId: yieldDto.providerId, + queryClient, + }), + ]); + + return applyYieldOverrides( + createYield({ + legacyYield: legacyYieldResult, + provider, + yieldDto, + }) + ); +}; + const multiFn = ({ isLedgerLive, queryClient, diff --git a/packages/widget/src/hooks/api/use-yield-providers.ts b/packages/widget/src/hooks/api/use-yield-providers.ts new file mode 100644 index 00000000..4faab21c --- /dev/null +++ b/packages/widget/src/hooks/api/use-yield-providers.ts @@ -0,0 +1,51 @@ +import type { QueryClient } from "@tanstack/react-query"; +import type { YieldProviderDetails } from "../../domain/types/yields"; +import type { ApiClient } from "../../providers/api/api-client"; + +const staleTime = 1000 * 60 * 2; +const getProviderKey = (providerId: string) => ["yield-provider", providerId]; + +export const fetchYieldProvider = async ({ + client, + providerId, + queryClient, +}: { + client: ReturnType; + providerId: string; + queryClient: QueryClient; +}): Promise => { + try { + return await queryClient.fetchQuery({ + queryKey: getProviderKey(providerId), + staleTime, + queryFn: () => + client.yield.ProvidersControllerGetProvider(providerId, undefined), + }); + } catch (e) { + console.log(e); + return undefined; + } +}; + +export const fetchYieldProviders = async ({ + client, + providerIds, + queryClient, +}: { + client: ReturnType; + providerIds: ReadonlyArray; + queryClient: QueryClient; +}) => { + const providers = await Promise.all( + [...new Set(providerIds)].map(async (providerId) => ({ + providerId, + provider: await fetchYieldProvider({ client, providerId, queryClient }), + })) + ); + + return new Map( + providers.flatMap(({ provider, providerId }) => + provider ? [[providerId, provider]] : [] + ) + ); +}; diff --git a/packages/widget/src/hooks/api/use-yield-summaries.ts b/packages/widget/src/hooks/api/use-yield-summaries.ts index 9c6b8a2b..562cab0a 100644 --- a/packages/widget/src/hooks/api/use-yield-summaries.ts +++ b/packages/widget/src/hooks/api/use-yield-summaries.ts @@ -1,10 +1,16 @@ +import type { QueryClient } from "@tanstack/react-query"; import { isSupportedChain } from "../../domain/types/chains"; -import { isNonZeroRewardRateYield } from "../../domain/types/yields"; +import { + isEthenaUsdeStaking, + isNonZeroRewardRateYield, + type YieldProviderDetails, +} from "../../domain/types/yields"; import type { YieldDto, YieldsControllerGetYieldsParams, } from "../../generated/api/yield"; import type { useApiClient } from "../../providers/api/api-client-provider"; +import { fetchYieldProviders } from "./use-yield-providers"; /** * A yield "summary" is the new yields API DTO, returned by @@ -14,6 +20,9 @@ import type { useApiClient } from "../../providers/api/api-client-provider"; * `__fallback__` hydration, which is only required for a selected yield. */ export type YieldSummary = YieldDto; +export type YieldSummaryWithProvider = YieldSummary & { + provider?: YieldProviderDetails; +}; export const DEFAULT_YIELD_SUMMARIES_PAGE_LIMIT = 50; const DEFAULT_YIELD_IDS_CHUNK_SIZE = 100; @@ -50,6 +59,10 @@ type FetchByIdsArgs = { yieldIds: ReadonlyArray; }; +type FetchByIdsWithProvidersArgs = FetchByIdsArgs & { + queryClient: QueryClient; +}; + const unique = (items: ReadonlyArray) => [...new Set(items)]; const chunks = (items: ReadonlyArray, chunkSize: number): T[][] => { @@ -118,6 +131,47 @@ export const fetchYieldSummariesByIds = async ({ }); }; +const applyYieldSummaryOverrides = (yieldDto: T): T => + isEthenaUsdeStaking(yieldDto.id) + ? ({ + ...yieldDto, + metadata: { + ...yieldDto.metadata, + name: yieldDto.metadata.name.replace(/staking/i, ""), + }, + } as T) + : yieldDto; + +export const fetchYieldSummariesWithProvidersByIds = async ({ + apiClient, + queryClient, + signal, + suppressRichErrors, + yieldIds, +}: FetchByIdsWithProvidersArgs): Promise => { + const client = apiClient.withOptions({ signal, suppressRichErrors }); + const summaries = await fetchYieldSummariesByIds({ + apiClient, + signal, + suppressRichErrors, + yieldIds, + }); + const providersById = await fetchYieldProviders({ + client, + providerIds: summaries.map((yieldDto) => yieldDto.providerId), + queryClient, + }); + + return summaries.map((summary) => { + const provider = providersById.get(summary.providerId); + + return applyYieldSummaryOverrides({ + ...summary, + ...(provider ? { provider } : {}), + }); + }); +}; + /** * Fetch every page of yield summaries for the given params, looping `offset` * until all `total` items have been retrieved. diff --git a/packages/widget/src/pages/details/earn-page/state/earn-page-context.tsx b/packages/widget/src/pages/details/earn-page/state/earn-page-context.tsx index 34ae15ff..55675cbc 100644 --- a/packages/widget/src/pages/details/earn-page/state/earn-page-context.tsx +++ b/packages/widget/src/pages/details/earn-page/state/earn-page-context.tsx @@ -38,17 +38,21 @@ import { isYieldActionArgRequired, isYieldValidatorSelectionRequired, type Yield, + type YieldBase, } from "../../../../domain/types/yields"; import type { ValidatorDto } from "../../../../generated/api/yield"; import { useDashboardYieldCatalog } from "../../../../hooks/api/use-dashboard-yield-catalog"; import { useDefaultTokens } from "../../../../hooks/api/use-default-tokens"; -import { useStreamMultiYields } from "../../../../hooks/api/use-multi-yields"; +import { useStreamYieldSummaries } from "../../../../hooks/api/use-multi-yields"; import { useTokenBalancesScan } from "../../../../hooks/api/use-token-balances-scan"; import { useTokenListYields } from "../../../../hooks/api/use-token-list-yields"; import { useTokensPrices } from "../../../../hooks/api/use-tokens-prices"; import { useYieldKycGate } from "../../../../hooks/api/use-yield-kyc-gate"; import { useYieldOpportunity } from "../../../../hooks/api/use-yield-opportunity"; -import { getYieldOpportunity } from "../../../../hooks/api/use-yield-opportunity/get-yield-opportunity"; +import { + getYieldOpportunity, + getYieldOpportunityFromSummary, +} from "../../../../hooks/api/use-yield-opportunity/get-yield-opportunity"; import { useYieldValidators } from "../../../../hooks/api/use-yield-validators"; import { useNavigateWithScrollToTop } from "../../../../hooks/navigation/use-navigate-with-scroll-to-top"; import { @@ -210,7 +214,7 @@ export const EarnPageContextProvider = ({ const validatorSearchDebouncing = normalizedValidatorSearch !== debouncedValidatorSearch; - const multiYields = useStreamMultiYields( + const yieldSummaries = useStreamYieldSummaries( useMemo(() => availableYields.orDefault([]), [availableYields]) ); @@ -269,6 +273,7 @@ export const EarnPageContextProvider = ({ const dashboardYieldCatalog = useDashboardYieldCatalog({ enabled: dashboardVariant, + network: selectedToken.map((token) => token.network).extractNullable(), }); const availableDashboardYieldCategories = @@ -291,7 +296,11 @@ export const EarnPageContextProvider = ({ const dashboardSelectionByCategoryRef = useRef( new Map< DashboardYieldCategory, - { token: TokenBalanceScanResponseDto["token"]; yieldId: Yield["id"] } + { + token: TokenBalanceScanResponseDto["token"]; + yieldDto?: YieldBase; + yieldId: Yield["id"]; + } >() ); @@ -306,13 +315,14 @@ export const EarnPageContextProvider = ({ dashboardSelectionByCategoryRef.current.set(category, { token, + yieldDto, yieldId: yieldDto.id, }); }, [dashboardVariant, selectedStake, selectedToken]); const selectedStakeData = useMemo>( () => - Maybe.of(multiYields) + Maybe.of(yieldSummaries) .map((val) => [...val].sort((a, b) => b.rewardRate.total - a.rewardRate.total) ) @@ -374,7 +384,7 @@ export const EarnPageContextProvider = ({ { type: ExtendedYieldType; title: ReturnType["title"]; - items: Yield[]; + items: YieldBase[]; } >() ) @@ -402,7 +412,7 @@ export const EarnPageContextProvider = ({ [ dashboardVariant, deferredStakeSearch, - multiYields, + yieldSummaries, selectedDashboardYieldCategory, t, ] @@ -503,17 +513,28 @@ export const EarnPageContextProvider = ({ const selectDashboardTokenYield = useCallback( ({ token, + yieldDto, yieldId, }: { token: TokenBalanceScanResponseDto["token"]; + yieldDto?: YieldBase; yieldId: Yield["id"]; }) => { - getYieldOpportunity({ - yieldId, - isLedgerLive, - apiClient, - queryClient, - }) + const yieldOpportunity = yieldDto + ? getYieldOpportunityFromSummary({ + yieldDto, + isLedgerLive, + apiClient, + queryClient, + }) + : getYieldOpportunity({ + yieldId, + isLedgerLive, + apiClient, + queryClient, + }); + + yieldOpportunity .map((yieldDto) => { dispatch({ type: "dashboard/token-yield/select", @@ -541,7 +562,11 @@ export const EarnPageContextProvider = ({ if (!yieldId) return; - selectDashboardTokenYield({ token: tokenBalance.token, yieldId }); + selectDashboardTokenYield({ + token: tokenBalance.token, + yieldDto: tokenListYields.yieldsById.get(yieldId), + yieldId, + }); }, [ dashboardVariant, @@ -549,13 +574,36 @@ export const EarnPageContextProvider = ({ selectedDashboardYieldCategory, selectDashboardTokenYield, tokenListYields.yieldIdsByToken, + tokenListYields.yieldsById, ] ); const onYieldSelect = (yieldId: string) => { - Maybe.fromNullable(multiYields) - .chain((val) => List.find((v) => v.id === yieldId, val)) - .ifJust((val) => dispatch({ type: "yield/select", data: val })); + const yieldSummary = List.find( + (summary) => summary.id === yieldId, + yieldSummaries + ).extractNullable(); + + const yieldOpportunity = yieldSummary + ? getYieldOpportunityFromSummary({ + yieldDto: yieldSummary, + isLedgerLive, + apiClient, + queryClient, + }) + : getYieldOpportunity({ + yieldId, + isLedgerLive, + apiClient, + queryClient, + }); + + yieldOpportunity + .map((yieldDto) => { + dispatch({ type: "yield/select", data: yieldDto }); + return yieldDto; + }) + .run(); }; const onDashboardYieldCategorySelect = (category: DashboardYieldCategory) => { diff --git a/packages/widget/src/pages/details/earn-page/types.ts b/packages/widget/src/pages/details/earn-page/types.ts index cff6fd89..2d031d70 100644 --- a/packages/widget/src/pages/details/earn-page/types.ts +++ b/packages/widget/src/pages/details/earn-page/types.ts @@ -1,8 +1,11 @@ -import type { ExtendedYieldType, Yield } from "../../../domain/types/yields"; +import type { + ExtendedYieldType, + YieldBase, +} from "../../../domain/types/yields"; export type SelectedStakeData = { - all: Yield[]; - filtered: Yield[]; + all: YieldBase[]; + filtered: YieldBase[]; groupsWithCounts: Map< ExtendedYieldType, { From 51cc8f033ddffd2ce3412a0d8fbdec3039b93c59 Mon Sep 17 00:00:00 2001 From: Petar Todorovic Date: Tue, 9 Jun 2026 12:52:50 +0200 Subject: [PATCH 2/4] fix: remove legacy usage --- packages/widget/src/domain/types/yields.ts | 17 +- .../get-yield-opportunity.ts | 96 +++------ .../src/hooks/api/use-yield-summaries.ts | 3 +- .../state/use-stake-enter-request-dto.ts | 22 +- .../hooks/use-stake-exit-request-dto.ts | 16 +- .../hooks/use-unstake-machine.ts | 194 +----------------- .../review/hooks/use-stake-review.hook.ts | 16 +- .../review/hooks/use-unstake-review.hook.ts | 14 +- .../widget/src/providers/api/api-client.ts | 4 - .../select-validator-section.test.tsx | 7 +- packages/widget/tests/domain/kyc.test.ts | 7 +- packages/widget/tests/fixtures/index.ts | 8 +- .../position-details-model.test.tsx | 2 - .../tests/use-cases/staking-flow/setup.ts | 1 - .../use-cases/trust-incentive-apy/setup.ts | 2 +- 15 files changed, 48 insertions(+), 361 deletions(-) diff --git a/packages/widget/src/domain/types/yields.ts b/packages/widget/src/domain/types/yields.ts index 87d3f3ba..98590e46 100644 --- a/packages/widget/src/domain/types/yields.ts +++ b/packages/widget/src/domain/types/yields.ts @@ -2,7 +2,6 @@ import BigNumber from "bignumber.js"; import { Array as EArray, pipe } from "effect"; import type { TFunction } from "i18next"; import { Maybe } from "purify-ts"; -import type { YieldDto as OldYieldDto } from "../../generated/api/legacy"; import type { YieldType as ApiYieldType, ArgumentFieldDto, @@ -17,13 +16,11 @@ import { equalTokens, tokenString } from "./tokens"; export type YieldProviderDetails = ProviderDto; -export type YieldBase = YieldApiYieldDto & { +export type Yield = YieldApiYieldDto & { provider?: YieldProviderDetails; }; -export type Yield = YieldBase & { - __fallback__: OldYieldDto; -}; +export type YieldBase = Yield; type YieldRiskRatingTone = "positive" | "warning" | "danger" | "neutral"; type KnownYieldRiskRatingSource = YieldRiskEntryDto["source"]; @@ -281,9 +278,6 @@ export const getYieldCooldownPeriod = (yieldDto: Yield) => export const getYieldWarmupPeriod = (yieldDto: Yield) => secondsToDays(yieldDto.mechanics.warmupPeriod?.seconds); -export const getYieldCommission = (yieldDto: Yield) => - yieldDto.__fallback__.metadata.commission; - export const getYieldTvlUsd = (yieldDto: Yield) => { const tvlUsd = yieldDto.statistics?.tvlUsd; @@ -311,9 +305,6 @@ export const getYieldFeePercent = (yieldDto: Yield): number | null => { export const getYieldLockupPeriod = (yieldDto: Yield) => secondsToDays(yieldDto.mechanics.lockupPeriod?.seconds); -export const hasYieldExitSignatureVerification = (yieldDto: Yield) => - !!yieldDto.__fallback__.args.exit?.args?.signatureVerification?.required; - export const getExtendedYieldType = ( yieldDto: YieldBase ): ExtendedYieldType => { @@ -464,13 +455,9 @@ export const isYieldWithProviderOptions = (yieldDto: YieldBase) => export const getYieldProviderYieldIds = (yieldDto: YieldBase) => getYieldActionArg(yieldDto, "enter", "providerId")?.options ?? []; -export const isYieldIntegrationAggregator = (yieldDto: Yield) => - !!yieldDto.__fallback__.metadata.isIntegrationAggregator; - export const isYieldValidatorSelectionRequired = (yieldDto: Yield) => !!( yieldDto.mechanics.requiresValidatorSelection || - isYieldIntegrationAggregator(yieldDto) || isYieldActionArgRequired(yieldDto, "enter", "validatorAddress") || isYieldActionArgRequired(yieldDto, "enter", "validatorAddresses") ); diff --git a/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts b/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts index bf8ae227..058614f7 100644 --- a/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts +++ b/packages/widget/src/hooks/api/use-yield-opportunity/get-yield-opportunity.ts @@ -7,11 +7,8 @@ import { type YieldProviderDetails, } from "../../../domain/types/yields"; import type { ApiClient } from "../../../providers/api/api-client"; -import { - fetchYieldProvider, - fetchYieldProviders, -} from "../use-yield-providers"; -import { fetchYieldSummariesByIds } from "../use-yield-summaries"; +import { fetchYieldProvider } from "../use-yield-providers"; +import { fetchYieldSummariesWithProvidersByIds } from "../use-yield-summaries"; type Params = { yieldId: string; @@ -33,7 +30,6 @@ type ParamsWithYieldDto = Omit & { type MultiParamsWithQueryClient = MultiParams & { queryClient: QueryClient; }; -type YieldApiYield = Omit; const staleTime = 1000 * 60 * 2; const getKey = (params: Params) => [ @@ -59,18 +55,15 @@ const applyYieldOverrides = (yieldDto: Yield) => : yieldDto; const createYield = ({ - legacyYield, provider, yieldDto, }: { - legacyYield: Yield["__fallback__"]; provider: YieldProviderDetails | undefined; - yieldDto: YieldApiYield; + yieldDto: YieldBase; }) => ({ ...yieldDto, ...(provider ? { provider } : {}), - __fallback__: legacyYield, }) satisfies Yield; export const getYieldOpportunity = (params: ParamsWithQueryClient) => @@ -146,7 +139,6 @@ const multiQueryFn = async ( ) => (await multiFn(params)).unsafeCoerce(); const fn = ({ - isLedgerLive, yieldId, queryClient, signal, @@ -157,12 +149,10 @@ const fn = ({ }) => { return EitherAsync(async () => { const client = apiClient.withOptions({ signal, suppressRichErrors }); - const [newYieldResult, legacyYieldResult] = await Promise.all([ - client.yield.YieldsControllerGetYield(yieldId, undefined), - client.legacy.YieldControllerYieldOpportunity(yieldId, { - params: { ledgerWalletAPICompatible: isLedgerLive }, - }), - ]); + const newYieldResult = await client.yield.YieldsControllerGetYield( + yieldId, + undefined + ); const provider = await fetchYieldProvider({ client, providerId: newYieldResult.providerId, @@ -170,7 +160,6 @@ const fn = ({ }); return createYield({ - legacyYield: legacyYieldResult, provider, yieldDto: newYieldResult, }); @@ -183,7 +172,6 @@ const fn = ({ }; const hydrateYieldSummaryQueryFn = async ({ - isLedgerLive, yieldDto, queryClient, signal, @@ -193,22 +181,16 @@ const hydrateYieldSummaryQueryFn = async ({ signal?: AbortSignal; }) => { const client = apiClient.withOptions({ signal, suppressRichErrors }); - const [legacyYieldResult, provider] = await Promise.all([ - client.legacy.YieldControllerYieldOpportunity(yieldDto.id, { - params: { ledgerWalletAPICompatible: isLedgerLive }, - }), - yieldDto.provider - ? Promise.resolve(yieldDto.provider) - : fetchYieldProvider({ - client, - providerId: yieldDto.providerId, - queryClient, - }), - ]); + const provider = + yieldDto.provider ?? + (await fetchYieldProvider({ + client, + providerId: yieldDto.providerId, + queryClient, + })); return applyYieldOverrides( createYield({ - legacyYield: legacyYieldResult, provider, yieldDto, }) @@ -216,7 +198,6 @@ const hydrateYieldSummaryQueryFn = async ({ }; const multiFn = ({ - isLedgerLive, queryClient, yieldIds, signal, @@ -226,53 +207,22 @@ const multiFn = ({ signal?: AbortSignal; }) => { return EitherAsync(async () => { - const client = apiClient.withOptions({ signal, suppressRichErrors }); - const newYields = await fetchYieldSummariesByIds({ + const yields = await fetchYieldSummariesWithProvidersByIds({ apiClient, + queryClient, signal, suppressRichErrors, yieldIds, }); - const newYieldsById = new Map( - newYields.map((yieldDto) => [yieldDto.id, yieldDto]) - ); - const providersById = await fetchYieldProviders({ - client, - providerIds: [...newYieldsById.values()].map( - (yieldDto) => yieldDto.providerId - ), - queryClient, - }); - - const yields = await Promise.all( - yieldIds.map(async (yieldId) => { - const newYieldResult = newYieldsById.get(yieldId); - if (!newYieldResult) { - return null; - } - - try { - const legacyYieldResult = - await client.legacy.YieldControllerYieldOpportunity(yieldId, { - params: { ledgerWalletAPICompatible: isLedgerLive }, - }); - - return applyYieldOverrides( - createYield({ - legacyYield: legacyYieldResult, - provider: providersById.get(newYieldResult.providerId), - yieldDto: newYieldResult, - }) - ); - } catch (e) { - console.log(e); - return null; - } - }) + return yields.map((yieldDto) => + applyYieldOverrides( + createYield({ + provider: yieldDto.provider, + yieldDto, + }) + ) ); - - return yields.filter((yieldDto) => yieldDto !== null); }).mapLeft((e) => { console.log(e); return new Error("Could not get yield opportunities"); diff --git a/packages/widget/src/hooks/api/use-yield-summaries.ts b/packages/widget/src/hooks/api/use-yield-summaries.ts index 562cab0a..3fa4dc23 100644 --- a/packages/widget/src/hooks/api/use-yield-summaries.ts +++ b/packages/widget/src/hooks/api/use-yield-summaries.ts @@ -16,8 +16,7 @@ import { fetchYieldProviders } from "./use-yield-providers"; * A yield "summary" is the new yields API DTO, returned by * `YieldsControllerGetYields`. It contains everything category discovery and * list rendering need (`token`, `rewardRate`, `status`, `metadata`, - * `mechanics.type`, `providerId`) and intentionally does NOT include the legacy - * `__fallback__` hydration, which is only required for a selected yield. + * `mechanics.type`, `providerId`). */ export type YieldSummary = YieldDto; export type YieldSummaryWithProvider = YieldSummary & { diff --git a/packages/widget/src/pages/details/earn-page/state/use-stake-enter-request-dto.ts b/packages/widget/src/pages/details/earn-page/state/use-stake-enter-request-dto.ts index eb401842..f46a1791 100644 --- a/packages/widget/src/pages/details/earn-page/state/use-stake-enter-request-dto.ts +++ b/packages/widget/src/pages/details/earn-page/state/use-stake-enter-request-dto.ts @@ -2,11 +2,7 @@ import { List, Maybe } from "purify-ts"; import { useMemo } from "react"; import type { YieldCreateActionDto } from "../../../../domain/types/action"; import type { AddressesDto } from "../../../../domain/types/addresses"; -import { - getYieldActionArg, - isYieldIntegrationAggregator, - type Yield, -} from "../../../../domain/types/yields"; +import { getYieldActionArg, type Yield } from "../../../../domain/types/yields"; import type { ValidatorDto } from "../../../../generated/api/yield"; import { useSKWallet } from "../../../../providers/sk-wallet"; import { useEarnPageState } from "./earn-page-state-context"; @@ -44,25 +40,13 @@ export const useStakeEnterRequestDto = () => { )?.required; const providerId = selectedProviderYieldId.extract(); - if ( - providerIdRequired && - !providerId && - !isYieldIntegrationAggregator(selectedStake) - ) { + if (providerIdRequired && !providerId) { return Maybe.empty(); } - const selectedProviderArgs = isYieldIntegrationAggregator(selectedStake) - ? {} - : { providerId }; + const selectedProviderArgs = providerId ? { providerId } : {}; const validatorsOrProvider = (() => { - if (isYieldIntegrationAggregator(selectedStake)) { - return List.head(validators) - .chainNullable((v) => v.providerId) - .map((providerId) => ({ providerId })); - } - if ( getYieldActionArg(selectedStake, "enter", "validatorAddresses") ?.required diff --git a/packages/widget/src/pages/position-details/hooks/use-stake-exit-request-dto.ts b/packages/widget/src/pages/position-details/hooks/use-stake-exit-request-dto.ts index 3d820b3b..cba512c0 100644 --- a/packages/widget/src/pages/position-details/hooks/use-stake-exit-request-dto.ts +++ b/packages/widget/src/pages/position-details/hooks/use-stake-exit-request-dto.ts @@ -2,11 +2,7 @@ import { Just, List, Maybe } from "purify-ts"; import { useMemo } from "react"; import type { YieldCreateActionDto } from "../../../domain/types/action"; import type { AddressesDto } from "../../../domain/types/addresses"; -import { - getYieldActionArg, - isYieldIntegrationAggregator, - type Yield, -} from "../../../domain/types/yields"; +import { getYieldActionArg, type Yield } from "../../../domain/types/yields"; import { useSKWallet } from "../../../providers/sk-wallet"; import { useUnstakeOrPendingActionState } from "../state"; @@ -40,17 +36,7 @@ export const useStakeExitRequestDto = () => { NonNullable, "validatorAddress" | "subnetId" > - | Pick, "providerId"> >(() => { - if (isYieldIntegrationAggregator(val.integrationData)) { - return List.find( - (b) => !!b.validator?.providerId, - val.stakedOrLiquidBalances - ).map((b) => ({ - providerId: b.validator?.providerId, - validatorAddress: b.validator?.address, - })); - } if ( getYieldActionArg( val.integrationData, diff --git a/packages/widget/src/pages/position-details/hooks/use-unstake-machine.ts b/packages/widget/src/pages/position-details/hooks/use-unstake-machine.ts index 28f1346d..e7f06c45 100644 --- a/packages/widget/src/pages/position-details/hooks/use-unstake-machine.ts +++ b/packages/widget/src/pages/position-details/hooks/use-unstake-machine.ts @@ -5,9 +5,6 @@ import { EitherAsync, Maybe } from "purify-ts"; import { type RefObject, useState } from "react"; import { assign, setup } from "xstate"; import { getValidStakeSessionTx } from "../../../domain"; -import type { TransactionVerificationMessageDto } from "../../../domain/types/transaction"; -import type { SKWallet } from "../../../domain/types/wallet"; -import { hasYieldExitSignatureVerification } from "../../../domain/types/yields"; import { useTrackEvent } from "../../../hooks/tracking/use-track-event"; import { useSavedRef } from "../../../hooks/use-saved-ref"; import { useApiClient } from "../../../providers/api/api-client-provider"; @@ -25,17 +22,15 @@ export const useUnstakeMachine = ({ onDone }: { onDone: () => void }) => { ).unsafeCoerce(); const apiClient = useApiClient(); - const { network, address, additionalAddresses, signMessage } = useSKWallet(); + const { address, additionalAddresses } = useSKWallet(); const machineParams = useSavedRef({ onDone, trackEvent, exitStore, apiClient, - signMessage, getData: () => Maybe.fromRecord({ - network: Maybe.fromNullable(network), address: Maybe.fromNullable(address), }).map((val) => ({ ...val, @@ -58,7 +53,6 @@ const getMachine = ( RefObject<{ onDone: () => void; exitStore: ReturnType; - signMessage: ReturnType["signMessage"]; trackEvent: ReturnType; apiClient: ReturnType; getData: () => Maybe< @@ -67,8 +61,7 @@ const getMachine = ( ReturnType >["context"]["data"] > & { - network: NonNullable; - address: NonNullable; + address: string; } >; }> @@ -78,38 +71,21 @@ const getMachine = ( types: { context: {} as { error: Maybe; - transactionVerificationMessageDto: Maybe; - signedMessage: Maybe; data: ReturnType<(typeof ref)["current"]["getData"]>; }, events: {} as | { type: "UNSTAKE" } - | { - type: "__GET_VERIFICATION_MESSAGE__"; - val: GetMaybeJust>; - } | { type: "__SUBMIT__"; val: GetMaybeJust>; } | { type: "__RESET__" } - | { - type: "__GET_VERIFICATION_MESSAGE_SUCCESS__"; - val: TransactionVerificationMessageDto; - } - | { type: "__GET_VERIFICATION_MESSAGE_ERROR__"; val: unknown } - | { type: "CONTINUE_MESSAGE_SIGN" } - | { type: "CANCEL_MESSAGE_SIGN" } - | { type: "__SIGN_MESSAGE_SUCCESS__"; val: string } - | { type: "__SIGN_MESSAGE_ERROR__"; val: unknown } | { type: "__SUBMIT_SUCCESS__" } | { type: "__SUBMIT_ERROR__"; val: unknown }, }, }).createMachine({ context: { error: Maybe.empty(), - transactionVerificationMessageDto: Maybe.empty(), - signedMessage: Maybe.empty(), data: Maybe.empty(), }, on: { UNSTAKE: { target: ".check", reenter: true } }, @@ -119,10 +95,6 @@ const getMachine = ( check: { on: { - __GET_VERIFICATION_MESSAGE__: { - target: "getVerificationMessage", - actions: assign({ data: ({ event }) => Maybe.of(event.val) }), - }, __SUBMIT__: { target: "submit", actions: assign({ data: ({ event }) => Maybe.of(event.val) }), @@ -137,128 +109,12 @@ const getMachine = ( amount: val.requestDto.arguments?.amount, }); - if (hasYieldExitSignatureVerification(val.integrationData)) { - self.send({ type: "__GET_VERIFICATION_MESSAGE__", val }); - } else { - self.send({ type: "__SUBMIT__", val }); - } + self.send({ type: "__SUBMIT__", val }); }, Nothing: () => self.send({ type: "__RESET__" }), }), }, - getVerificationMessage: { - on: { - __GET_VERIFICATION_MESSAGE_SUCCESS__: { - target: "showPopup", - actions: assign(({ context, event }) => ({ - ...context, - transactionVerificationMessageDto: Maybe.of(event.val), - })), - }, - __GET_VERIFICATION_MESSAGE_ERROR__: { - target: ".error", - actions: assign(({ context, event }) => ({ - ...context, - error: Maybe.of(event.val), - })), - }, - }, - initial: "loading", - states: { - loading: { - entry: ({ self, context }) => - EitherAsync.liftEither( - context.data.toEither(new Error("Missing init values")) - ) - .chain((val) => - EitherAsync(() => - ref.current.apiClient.legacy.TransactionControllerGetTransactionVerificationMessageForNetwork( - val.network, - { - payload: { - addresses: { - address: val.addresses.address, - additionalAddresses: - val.addresses.additionalAddresses ?? undefined, - }, - }, - } - ) - ).mapLeft( - () => new Error("Failed to get verification message") - ) - ) - .caseOf({ - Right(v) { - self.send({ - type: "__GET_VERIFICATION_MESSAGE_SUCCESS__", - val: v, - }); - }, - Left(e) { - self.send({ - type: "__GET_VERIFICATION_MESSAGE_ERROR__", - val: e, - }); - }, - }), - }, - - error: {}, - }, - }, - - showPopup: { - on: { - CONTINUE_MESSAGE_SIGN: "signMessage", - CANCEL_MESSAGE_SIGN: "initial", - }, - }, - - signMessage: { - on: { - __SIGN_MESSAGE_SUCCESS__: { - target: "submit", - actions: assign(({ context, event }) => ({ - ...context, - signedMessage: Maybe.of(event.val), - })), - }, - __SIGN_MESSAGE_ERROR__: { - target: ".error", - actions: assign(({ context, event }) => ({ - ...context, - error: Maybe.of(event.val), - })), - }, - }, - initial: "loading", - states: { - loading: { - entry: ({ self, context }) => - EitherAsync.liftEither( - context.transactionVerificationMessageDto.toEither( - new Error("Missing transaction verification message") - ) - ) - .chain((val) => ref.current.signMessage(val.message)) - .caseOf({ - Right(v) { - self.send({ - type: "__SIGN_MESSAGE_SUCCESS__", - val: v, - }); - }, - Left(l) { - self.send({ type: "__SIGN_MESSAGE_ERROR__", val: l }); - }, - }), - }, - error: {}, - }, - }, - submit: { on: { __SUBMIT_SUCCESS__: "done", @@ -273,48 +129,8 @@ const getMachine = ( initial: "loading", states: { loading: { - entry: ({ - self, - context: { - data, - signedMessage, - transactionVerificationMessageDto, - }, - }) => - EitherAsync.liftEither( - data - .map((val) => - Maybe.fromRecord({ - data: Maybe.of(val), - requestDto: Maybe.of(val.requestDto), - transactionVerificationMessageDto, - signedMessage, - }) - .map( - (val) => - ({ - ...val.data, - requestDto: { - ...val.requestDto, - address: val.data.addresses.address, - arguments: { - ...(val.requestDto.arguments ?? {}), - // The backend still accepts this legacy verification bag - // even though the checked-in schema does not expose it yet. - signatureVerification: { - message: - val.transactionVerificationMessageDto - .message, - signed: val.signedMessage, - }, - } as typeof val.requestDto.arguments, - }, - }) as typeof val.data - ) - .orDefault(val) - ) - .toEither(new Error("Missing params")) - ) + entry: ({ self, context: { data } }) => + EitherAsync.liftEither(data.toEither(new Error("Missing params"))) .chain((val) => EitherAsync(() => ref.current.apiClient.yield.ActionsControllerExitYield({ diff --git a/packages/widget/src/pages/review/hooks/use-stake-review.hook.ts b/packages/widget/src/pages/review/hooks/use-stake-review.hook.ts index 8ba6ace8..e85dfead 100644 --- a/packages/widget/src/pages/review/hooks/use-stake-review.hook.ts +++ b/packages/widget/src/pages/review/hooks/use-stake-review.hook.ts @@ -7,7 +7,6 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; import { getTransactionGasEstimate } from "../../../domain/types/action"; import { getKycProviderName } from "../../../domain/types/kyc"; -import { getYieldCommission } from "../../../domain/types/yields"; import { useTokensPrices } from "../../../hooks/api/use-tokens-prices"; import { useYieldKycGate } from "../../../hooks/api/use-yield-kyc-gate"; import { usePositionDetailsStakeMatch } from "../../../hooks/navigation/use-position-details-stake-match"; @@ -19,7 +18,7 @@ import { useYieldType } from "../../../hooks/use-yield-type"; import { useApiClient } from "../../../providers/api/api-client-provider"; import { useEnterStakeStore } from "../../../providers/enter-stake-store"; import { useSettings } from "../../../providers/settings"; -import { APToPercentage, defaultFormattedNumber } from "../../../utils"; +import { defaultFormattedNumber } from "../../../utils"; import { getGasFeeInUSD } from "../../../utils/formatters"; import { useRegisterFooterButton } from "../../components/footer-outlet/context"; import type { MetaInfoProps } from "../pages/common-page/common.page"; @@ -231,17 +230,6 @@ export const useStakeReview = () => { [selectedStake, selectedToken, enterRequest.selectedValidators, variant] ); - const commissionFee = useMemo( - () => - selectedStake - .chainNullable(getYieldCommission) - .map((commission) => - commission.reduce((acc, curr) => acc + curr.value, 0) - ) - .map((val) => `${APToPercentage(val)}%`), - [selectedStake] - ); - return { token: selectedToken, amount, @@ -260,7 +248,7 @@ export const useStakeReview = () => { managementFee, performanceFee, feeConfigLoading: actionPreviewQuery.isLoading, - commissionFee, + commissionFee: Maybe.empty(), kycGate: yieldKycGate.gate, kycProviderName, kycStatusIsChecking: diff --git a/packages/widget/src/pages/review/hooks/use-unstake-review.hook.ts b/packages/widget/src/pages/review/hooks/use-unstake-review.hook.ts index c1db7a5c..d9ebb78e 100644 --- a/packages/widget/src/pages/review/hooks/use-unstake-review.hook.ts +++ b/packages/widget/src/pages/review/hooks/use-unstake-review.hook.ts @@ -146,16 +146,8 @@ export const useUnstakeActionReview = () => { const unstakeIsLoading = machineState.matches("check") || - machineState.matches({ getVerificationMessage: "loading" }) || - machineState.matches({ signMessage: "loading" }) || machineState.matches({ submit: "loading" }); - const showUnstakeSignMessagePopup = machineState.matches("showPopup"); - - const onContinueUnstakeSignMessage = () => - send({ type: "CONTINUE_MESSAGE_SIGN" }); - const onCloseUnstakeSignMessage = () => send({ type: "CANCEL_MESSAGE_SIGN" }); - const onClick = () => { if (unstakeIsLoading || kycGateIsBlocking) return; @@ -190,9 +182,9 @@ export const useUnstakeActionReview = () => { rewardTokenDetailsProps, token: interactedToken, metaInfo, - onContinueUnstakeSignMessage, - onCloseUnstakeSignMessage, - showUnstakeSignMessagePopup, + onContinueUnstakeSignMessage: () => {}, + onCloseUnstakeSignMessage: () => {}, + showUnstakeSignMessagePopup: false, gasCheckLoading: actionPreviewQuery.isLoading || actionPreviewQuery.isFetching || diff --git a/packages/widget/src/providers/api/api-client.ts b/packages/widget/src/providers/api/api-client.ts index a109d3ec..4974f129 100644 --- a/packages/widget/src/providers/api/api-client.ts +++ b/packages/widget/src/providers/api/api-client.ts @@ -128,10 +128,6 @@ const bindLegacyApi = ({ api.YieldControllerGetSingleYieldRewardsSummary, options ), - YieldControllerYieldOpportunity: bindOperation( - api.YieldControllerYieldOpportunity, - options - ), }); const bindYieldApi = ({ diff --git a/packages/widget/tests/components/select-validator-section.test.tsx b/packages/widget/tests/components/select-validator-section.test.tsx index 4d340d98..94a75a43 100644 --- a/packages/widget/tests/components/select-validator-section.test.tsx +++ b/packages/widget/tests/components/select-validator-section.test.tsx @@ -8,11 +8,7 @@ import type { useSelectValidator } from "../../src/pages/details/earn-page/compo import { UtilaSelectValidatorSection } from "../../src/pages-dashboard/overview/earn-page/utila-select-validator-section"; import { SettingsContextProvider } from "../../src/providers/settings"; import { i18nInstance } from "../../src/translation"; -import { - legacyYieldFixture, - yieldApiValidatorFixture, - yieldApiYieldFixture, -} from "../fixtures"; +import { yieldApiValidatorFixture, yieldApiYieldFixture } from "../fixtures"; import { describe, expect, it } from "../utils/test-extend"; import { render } from "../utils/test-utils"; @@ -47,7 +43,6 @@ const selectedStake = { exit: { fields: [] }, }, }, - __fallback__: legacyYieldFixture(), } as Yield; const createHookValue = ( diff --git a/packages/widget/tests/domain/kyc.test.ts b/packages/widget/tests/domain/kyc.test.ts index 62d00134..01cfe1c5 100644 --- a/packages/widget/tests/domain/kyc.test.ts +++ b/packages/widget/tests/domain/kyc.test.ts @@ -5,16 +5,11 @@ import { mapKycStatusToGate, } from "../../src/domain/types/kyc"; import type { Yield } from "../../src/domain/types/yields"; -import { - legacyYieldFixture, - yieldApiProviderFixture, - yieldApiYieldFixture, -} from "../fixtures"; +import { yieldApiProviderFixture, yieldApiYieldFixture } from "../fixtures"; const createYield = (overrides?: Partial): Yield => ({ ...yieldApiYieldFixture(), - __fallback__: legacyYieldFixture(), provider: yieldApiProviderFixture({ name: "Superstate", website: "https://superstate.com", diff --git a/packages/widget/tests/fixtures/index.ts b/packages/widget/tests/fixtures/index.ts index 6b1dea07..567c4966 100644 --- a/packages/widget/tests/fixtures/index.ts +++ b/packages/widget/tests/fixtures/index.ts @@ -7,15 +7,17 @@ import { EvmNetworks } from "../../src/domain/types/chains/networks"; import type { YieldBalanceDto } from "../../src/domain/types/positions"; import type { YieldRewardRateDto } from "../../src/domain/types/reward-rate"; import type { Yield } from "../../src/domain/types/yields"; -import type { TokenDto as LegacyTokenDto } from "../../src/generated/api/legacy"; +import type { + TokenDto as LegacyTokenDto, + YieldDto as LegacyYieldDto, +} from "../../src/generated/api/legacy"; import type { ValidatorDto, NetworkDto as YieldApiNetworkDto, ProviderDto as YieldApiProviderDto, } from "../../src/generated/api/yield"; -type YieldApiYieldDto = Omit; -type LegacyYieldDto = Yield["__fallback__"]; +type YieldApiYieldDto = Omit; const apyFaker = () => faker.number.float({ min: 0, max: 0.05 }); diff --git a/packages/widget/tests/pages-dashboard/position-details-model.test.tsx b/packages/widget/tests/pages-dashboard/position-details-model.test.tsx index 7250838b..04224407 100644 --- a/packages/widget/tests/pages-dashboard/position-details-model.test.tsx +++ b/packages/widget/tests/pages-dashboard/position-details-model.test.tsx @@ -8,7 +8,6 @@ import { getDashboardPositionDetailsModel, } from "../../src/pages-dashboard/position-details/position-details-model"; import { - legacyYieldFixture, yieldApiProviderFixture, yieldApiYieldFixture, yieldBalanceFixture, @@ -71,7 +70,6 @@ const makeYield = (overrides?: Partial): Yield => symbol: "rETH", }, }), - __fallback__: legacyYieldFixture(), provider: yieldApiProviderFixture({ name: "Rocket Pool" }), ...overrides, }) as Yield; diff --git a/packages/widget/tests/use-cases/staking-flow/setup.ts b/packages/widget/tests/use-cases/staking-flow/setup.ts index 221e73aa..75154ad3 100644 --- a/packages/widget/tests/use-cases/staking-flow/setup.ts +++ b/packages/widget/tests/use-cases/staking-flow/setup.ts @@ -433,7 +433,6 @@ export const setup = async (worker: TestWorker) => { name: "Benqi", website: "https://benqi.fi/", }), - __fallback__: yieldOp, }; return { diff --git a/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts b/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts index 80f6b1ab..84b84425 100644 --- a/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts +++ b/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts @@ -17,7 +17,7 @@ import { rkMockWallet } from "../../utils/mock-connector"; import type { TestWorker } from "../../utils/test-extend"; type LegacyTokenDto = ReturnType["token"]; -type YieldApiYieldDto = Omit; +type YieldApiYieldDto = Omit; const setUrl = ({ accountId, From 806d66c47834f73d00caa391c284d2cdd35a8053 Mon Sep 17 00:00:00 2001 From: Petar Todorovic Date: Tue, 9 Jun 2026 13:18:44 +0200 Subject: [PATCH 3/4] perf(widget): defer dashboard token yield lookup Fetch dashboard token yield summaries only after a token is selected. Avoid preloading yield metadata for the entire token picker. --- package.json | 3 +- .../hooks/api/use-dashboard-yield-catalog.ts | 6 +- .../src/hooks/api/use-token-list-yields.ts | 90 +------------ .../select-token-list-item.tsx | 55 +------- .../select-token-section/select-token.tsx | 58 +-------- .../select-token-section/styles.css.ts | 12 -- .../components/select-yield-section/index.tsx | 6 +- .../earn-page/state/earn-page-context.tsx | 119 ++++++++++++++---- .../state/earn-page-state-context.tsx | 37 +++++- .../pages/details/earn-page/state/types.ts | 7 +- 10 files changed, 153 insertions(+), 240 deletions(-) diff --git a/package.json b/package.json index 91dda17c..6b802fe2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "format": "turbo run format", "clean": "turbo run clean", "prepare": "husky", - "check-hygiene": "rev-dep config run --list-all-issues" + "check-hygiene": "rev-dep config run --list-all-issues", + "check": "turbo run lint && pnpm run check-hygiene && turbo run test && turbo run build" }, "devDependencies": { "@biomejs/biome": "catalog:", diff --git a/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts b/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts index b96e2158..6c5e0817 100644 --- a/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts +++ b/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts @@ -41,13 +41,13 @@ export const useDashboardYieldCatalog = ({ const { network: walletNetwork } = useSKWallet(); const apiClient = useApiClient(); - const catalogNetwork = network ?? walletNetwork; - const probeEnabled = enabled && !!catalogNetwork; + const catalogNetwork = network === null ? null : (network ?? walletNetwork); + const probeEnabled = enabled && (network === null || !!catalogNetwork); const results = useQueries({ queries: dashboardYieldCategories.map((category) => { const params: YieldSummariesParams = { - network: catalogNetwork ?? undefined, + ...(catalogNetwork ? { network: catalogNetwork } : {}), types: getApiYieldTypesForDashboardCategory(category), sort: "rewardRateDesc", limit: DEFAULT_YIELD_SUMMARIES_PAGE_LIMIT, diff --git a/packages/widget/src/hooks/api/use-token-list-yields.ts b/packages/widget/src/hooks/api/use-token-list-yields.ts index edc6eb9b..3f6c9439 100644 --- a/packages/widget/src/hooks/api/use-token-list-yields.ts +++ b/packages/widget/src/hooks/api/use-token-list-yields.ts @@ -1,13 +1,10 @@ -import { useQuery } from "@tanstack/react-query"; -import { useMemo } from "react"; import type { TokenBalanceScanResponseDto } from "../../domain/types/token-balance"; -import { tokenString } from "../../domain/types/tokens"; import { type DashboardYieldCategory, getDashboardYieldCategoryForApiYieldType, } from "../../domain/types/yields"; import type { YieldDto } from "../../generated/api/yield"; -import { useApiClient } from "../../providers/api/api-client-provider"; +import type { useApiClient } from "../../providers/api/api-client-provider"; import { getRewardRateFormatted, getRewardTypeFormatted, @@ -17,9 +14,7 @@ import { isVisibleYieldSummary, } from "./use-yield-summaries"; -const staleTime = 1000 * 60 * 2; - -export type TokenMaxYieldRate = { +type TokenMaxYieldRate = { rateFormatted: string; rateTypeLabel: string; }; @@ -100,84 +95,3 @@ export const getMaxYieldRateForToken = ( rateTypeLabel, }; }; - -export const useTokenListYields = ( - tokenBalances: ReadonlyArray, - dashboardYieldCategory?: DashboardYieldCategory | null -) => { - const apiClient = useApiClient(); - const yieldIds = getUniqueYieldIds(tokenBalances); - - const query = useQuery({ - queryKey: ["token-list-yields", yieldIds], - enabled: yieldIds.length > 0, - staleTime, - queryFn: async ({ signal }) => - fetchTokenListYieldSummaries({ - apiClient, - signal, - tokenBalances, - }), - }); - - const yieldsById = useMemo( - () => - new Map((query.data ?? []).map((yieldDto) => [yieldDto.id, yieldDto])), - [query.data] - ); - - const yieldIdsByToken = useMemo(() => { - const map = new Map(); - - for (const tokenBalance of tokenBalances) { - map.set( - tokenString(tokenBalance.token), - dashboardYieldCategory - ? getDashboardCategoryYieldIdsForToken( - tokenBalance.availableYields, - yieldsById, - dashboardYieldCategory - ) - : [...tokenBalance.availableYields] - ); - } - - return map; - }, [dashboardYieldCategory, tokenBalances, yieldsById]); - - const yieldCountsByToken = useMemo( - () => - new Map( - [...yieldIdsByToken.entries()].map(([token, tokenYieldIds]) => [ - token, - tokenYieldIds.length, - ]) - ), - [yieldIdsByToken] - ); - - const maxYieldRatesByToken = useMemo(() => { - const map = new Map(); - - for (const tokenBalance of tokenBalances) { - const maxYieldRate = getMaxYieldRateForToken( - yieldIdsByToken.get(tokenString(tokenBalance.token)) ?? [], - yieldsById - ); - - if (maxYieldRate) { - map.set(tokenString(tokenBalance.token), maxYieldRate); - } - } - - return map; - }, [tokenBalances, yieldIdsByToken, yieldsById]); - - return { - yieldsById, - yieldIdsByToken, - yieldCountsByToken, - maxYieldRatesByToken, - isLoading: query.isLoading, - }; -}; diff --git a/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token-list-item.tsx b/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token-list-item.tsx index 5fe1c854..c57e6e3e 100644 --- a/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token-list-item.tsx +++ b/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token-list-item.tsx @@ -1,7 +1,6 @@ import BigNumber from "bignumber.js"; import type { ComponentProps } from "react"; import { memo } from "react"; -import { useTranslation } from "react-i18next"; import { Box } from "../../../../../components/atoms/box"; import { SelectModalItem, @@ -10,40 +9,24 @@ import { import { TokenIcon } from "../../../../../components/atoms/token-icon"; import { Text } from "../../../../../components/atoms/typography/text"; import type { TokenBalanceScanResponseDto } from "../../../../../domain/types/token-balance"; -import type { TokenMaxYieldRate } from "../../../../../hooks/api/use-token-list-yields"; import { useTrackEvent } from "../../../../../hooks/tracking/use-track-event"; import { selectItemText } from "../../styles.css"; -import { maxYieldRateLabel, maxYieldRateText } from "./styles.css"; type Props = { item: TokenBalanceScanResponseDto; isConnected: boolean; isSelected: boolean; - availableYieldsCount?: number; - canSelectToken?: boolean; - maxYieldRate?: TokenMaxYieldRate; onTokenBalanceSelect: (tokenBalance: TokenBalanceScanResponseDto) => void; }; export const SelectTokenListItem = memo( - ({ - item, - isConnected, - isSelected, - availableYieldsCount = item.availableYields.length, - canSelectToken = true, - maxYieldRate, - onTokenBalanceSelect, - }: Props) => { + ({ item, isConnected, isSelected, onTokenBalanceSelect }: Props) => { const amountGreaterThanZero = new BigNumber(item.amount).isGreaterThan(0); const trackEvent = useTrackEvent(); - const { t } = useTranslation(); const _onItemClick: ComponentProps["onItemClick"] = ({ closeModal }) => { - if (!canSelectToken) return; - trackEvent("tokenSelected", { token: item.token.symbol }); onTokenBalanceSelect(item); closeModal(); @@ -54,11 +37,9 @@ export const SelectTokenListItem = memo( @@ -81,35 +62,9 @@ export const SelectTokenListItem = memo( - {t("select_token.yields_available", { - count: availableYieldsCount, - token_name: item.token.name, - })} + {item.token.name} - - {maxYieldRate ? ( - - - {maxYieldRate.rateFormatted} - - - - {t("select_token.up_to_rate_type", { - rateType: maxYieldRate.rateTypeLabel, - })} - - - ) : null} diff --git a/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token.tsx b/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token.tsx index ebfe38ea..f5c85d9e 100644 --- a/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token.tsx +++ b/packages/widget/src/pages/details/earn-page/components/select-token-section/select-token.tsx @@ -12,8 +12,7 @@ import { SelectModal } from "../../../../../components/atoms/select-modal"; import { TokenIcon } from "../../../../../components/atoms/token-icon"; import { Text } from "../../../../../components/atoms/typography/text"; import { VirtualList } from "../../../../../components/atoms/virtual-list"; -import type { TokenBalanceScanResponseDto } from "../../../../../domain/types/token-balance"; -import { equalTokens, tokenString } from "../../../../../domain/types/tokens"; +import { equalTokens } from "../../../../../domain/types/tokens"; import { useTrackEvent } from "../../../../../hooks/tracking/use-track-event"; import { useSettings } from "../../../../../providers/settings"; import { useSKWallet } from "../../../../../providers/sk-wallet"; @@ -22,24 +21,6 @@ import { useEarnPageContext } from "../../state/earn-page-context"; import { validatorVirtuosoContainer } from "../../styles.css"; import { SelectTokenListItem } from "./select-token-list-item"; -const getAvailableYieldsCount = ({ - item, - selectedDashboardYieldCategory, - tokenListYieldsIsLoading, - tokenYieldCountsByToken, -}: { - item: TokenBalanceScanResponseDto; - selectedDashboardYieldCategory: unknown; - tokenListYieldsIsLoading: boolean; - tokenYieldCountsByToken: ReadonlyMap; -}) => { - if (!selectedDashboardYieldCategory || tokenListYieldsIsLoading) { - return item.availableYields.length; - } - - return tokenYieldCountsByToken.get(tokenString(item.token)) ?? 0; -}; - export const SelectToken = ({ canSelect = true }: { canSelect?: boolean }) => { const { onSelectTokenClose, @@ -48,10 +29,6 @@ export const SelectToken = ({ canSelect = true }: { canSelect?: boolean }) => { selectedToken, onTokenSearch, tokenSearch, - selectedDashboardYieldCategory, - tokenMaxYieldRatesByToken, - tokenYieldCountsByToken, - tokenListYieldsIsLoading, } = useEarnPageContext(); const { variant } = useSettings(); @@ -71,25 +48,11 @@ export const SelectToken = ({ canSelect = true }: { canSelect?: boolean }) => { return { st, - tokenBalances: tokenBalances.filter( - (item) => - getAvailableYieldsCount({ - item, - selectedDashboardYieldCategory, - tokenListYieldsIsLoading, - tokenYieldCountsByToken, - }) > 0 - ), + tokenBalances, }; }) .extractNullable(), - [ - selectedDashboardYieldCategory, - selectedToken, - tokenBalancesData, - tokenListYieldsIsLoading, - tokenYieldCountsByToken, - ] + [selectedToken, tokenBalancesData] ); if (!data) return null; @@ -123,7 +86,6 @@ export const SelectToken = ({ canSelect = true }: { canSelect?: boolean }) => { searchValue={tokenSearch} onClose={onSelectTokenClose} onOpen={() => trackEvent("selectTokenModalOpened")} - isLoading={!!selectedDashboardYieldCategory && tokenListYieldsIsLoading} trigger={ { data={data.tokenBalances} estimateSize={() => 60} itemContent={(_index, item) => { - const tokenKey = tokenString(item.token); - const availableYieldsCount = getAvailableYieldsCount({ - item, - selectedDashboardYieldCategory, - tokenListYieldsIsLoading, - tokenYieldCountsByToken, - }); - const canSelectToken = - !selectedDashboardYieldCategory || - (!tokenListYieldsIsLoading && availableYieldsCount > 0); - return ( diff --git a/packages/widget/src/pages/details/earn-page/components/select-token-section/styles.css.ts b/packages/widget/src/pages/details/earn-page/components/select-token-section/styles.css.ts index c66815f2..a596eac9 100644 --- a/packages/widget/src/pages/details/earn-page/components/select-token-section/styles.css.ts +++ b/packages/widget/src/pages/details/earn-page/components/select-token-section/styles.css.ts @@ -126,18 +126,6 @@ export const selectTokenBalance = recipe({ }, }); -export const maxYieldRateText = style([ - atoms({ color: "positionsRewardRate", fontWeight: "medium" }), - { whiteSpace: "nowrap" }, -]); - -export const maxYieldRateLabel = style([ - { - textTransform: "uppercase", - whiteSpace: "nowrap", - }, -]); - export const minMaxContainer = recipe({ base: [ atoms({ diff --git a/packages/widget/src/pages/details/earn-page/components/select-yield-section/index.tsx b/packages/widget/src/pages/details/earn-page/components/select-yield-section/index.tsx index 186fde3f..9a2cbb3e 100644 --- a/packages/widget/src/pages/details/earn-page/components/select-yield-section/index.tsx +++ b/packages/widget/src/pages/details/earn-page/components/select-yield-section/index.tsx @@ -41,7 +41,11 @@ export const SelectYieldSection = () => { ) : ( selectedStakeData .map((val) => { - return val.all.length === 0 ? ( + const opportunityCount = dashboardVariant + ? val.filtered.length + : val.all.length; + + return opportunityCount === 0 ? ( token.network).extractNullable(), + network: null, }); const availableDashboardYieldCategories = dashboardYieldCatalog.availableCategories; - const selectedDashboardYieldCategory = selectedStake + const selectedStakeDashboardYieldCategory = selectedStake .chainNullable(getDashboardYieldCategory) .extractNullable(); + const [ + selectedDashboardYieldCategoryFallback, + setSelectedDashboardYieldCategoryFallback, + ] = useState(null); + const selectedDashboardYieldCategory = + selectedStakeDashboardYieldCategory ?? + selectedDashboardYieldCategoryFallback; - const tokenBalancesForYieldMetadata = useMemo( - () => tokenBalancesData.map((v) => v.filtered).orDefault([]), - [tokenBalancesData] - ); + useEffect(() => { + if (!selectedStakeDashboardYieldCategory) return; - const tokenListYields = useTokenListYields( - tokenBalancesForYieldMetadata, - dashboardVariant ? selectedDashboardYieldCategory : null - ); + setSelectedDashboardYieldCategoryFallback( + selectedStakeDashboardYieldCategory + ); + }, [selectedStakeDashboardYieldCategory]); const dashboardSelectionByCategoryRef = useRef( new Map< @@ -323,6 +330,14 @@ export const EarnPageContextProvider = ({ const selectedStakeData = useMemo>( () => Maybe.of(yieldSummaries) + .map((val) => + selectedStake + .filter( + (stake) => !val.some((yieldDto) => yieldDto.id === stake.id) + ) + .map((stake) => [stake, ...val]) + .orDefault(val) + ) .map((val) => [...val].sort((a, b) => b.rewardRate.total - a.rewardRate.total) ) @@ -412,6 +427,7 @@ export const EarnPageContextProvider = ({ [ dashboardVariant, deferredStakeSearch, + selectedStake, yieldSummaries, selectedDashboardYieldCategory, t, @@ -547,8 +563,52 @@ export const EarnPageContextProvider = ({ [apiClient, dispatch, isLedgerLive, queryClient] ); + const getDashboardTokenYield = useCallback( + async ({ + category, + tokenBalance, + }: { + category: DashboardYieldCategory; + tokenBalance: TokenBalanceScanResponseDto; + }) => { + const yieldSummaries = await queryClient.fetchQuery({ + queryKey: [ + "dashboard-token-yield-summaries", + tokenString(tokenBalance.token), + tokenBalance.availableYields, + ], + staleTime: 1000 * 60 * 2, + queryFn: ({ signal }) => + fetchYieldSummariesByIds({ + apiClient, + signal, + yieldIds: tokenBalance.availableYields, + }), + }); + + const yieldsById = new Map( + yieldSummaries.map((yieldDto) => [yieldDto.id, yieldDto]) + ); + const yieldId = getDashboardCategoryYieldIdsForToken( + tokenBalance.availableYields, + yieldsById, + category + )[0]; + + return yieldId + ? { + yieldId, + yieldDto: yieldsById.get(yieldId), + } + : null; + }, + [apiClient, queryClient] + ); + + const dashboardTokenSelectionRequestRef = useRef(0); + const onTokenBalanceSelect = useCallback( - (tokenBalance: TokenBalanceScanResponseDto) => { + async (tokenBalance: TokenBalanceScanResponseDto) => { const category = dashboardVariant ? selectedDashboardYieldCategory : null; if (!category) { @@ -556,25 +616,37 @@ export const EarnPageContextProvider = ({ return; } - const yieldId = tokenListYields.yieldIdsByToken.get( - tokenString(tokenBalance.token) - )?.[0]; + const selectionRequestId = dashboardTokenSelectionRequestRef.current + 1; + dashboardTokenSelectionRequestRef.current = selectionRequestId; + dispatch({ type: "token-only/select", data: tokenBalance.token }); + + const tokenYield = await getDashboardTokenYield({ + category, + tokenBalance, + }).catch((error) => { + console.log(error); + return null; + }); - if (!yieldId) return; + if ( + selectionRequestId !== dashboardTokenSelectionRequestRef.current || + !tokenYield + ) { + return; + } selectDashboardTokenYield({ token: tokenBalance.token, - yieldDto: tokenListYields.yieldsById.get(yieldId), - yieldId, + yieldDto: tokenYield.yieldDto, + yieldId: tokenYield.yieldId, }); }, [ dashboardVariant, dispatch, + getDashboardTokenYield, selectedDashboardYieldCategory, selectDashboardTokenYield, - tokenListYields.yieldIdsByToken, - tokenListYields.yieldsById, ] ); @@ -609,6 +681,8 @@ export const EarnPageContextProvider = ({ const onDashboardYieldCategorySelect = (category: DashboardYieldCategory) => { if (selectedDashboardYieldCategory === category) return; + setSelectedDashboardYieldCategoryFallback(category); + const target = dashboardSelectionByCategoryRef.current.get(category) ?? dashboardYieldCatalog.initialSelectionByCategory.get(category); @@ -845,7 +919,7 @@ export const EarnPageContextProvider = ({ tokenBalancesScanLoading || defaultTokensIsLoading; const selectYieldIsLoading = - (selectedStakeId.isNothing() && !hasNotYieldsForToken) || + (autoSelectYield && selectedStakeId.isNothing() && !hasNotYieldsForToken) || initYieldRes.isLoading || yieldOpportunityLoading || tokenBalancesScanLoading || @@ -948,9 +1022,6 @@ export const EarnPageContextProvider = ({ yieldOpportunityLoading, tokenBalancesScanLoading, tokenBalancesData, - tokenMaxYieldRatesByToken: tokenListYields.maxYieldRatesByToken, - tokenYieldCountsByToken: tokenListYields.yieldCountsByToken, - tokenListYieldsIsLoading: tokenListYields.isLoading, onTokenSearch, onValidatorSearch, buttonCTAText, diff --git a/packages/widget/src/pages/details/earn-page/state/earn-page-state-context.tsx b/packages/widget/src/pages/details/earn-page/state/earn-page-state-context.tsx index 2da1ed08..f8642b22 100644 --- a/packages/widget/src/pages/details/earn-page/state/earn-page-state-context.tsx +++ b/packages/widget/src/pages/details/earn-page/state/earn-page-state-context.tsx @@ -49,6 +49,7 @@ export const EarnPageStateUsageBoundaryProvider = ({ const getInitialState = (): State => ({ selectedToken: Maybe.empty(), selectedStakeId: Maybe.empty(), + autoSelectYield: true, selectedValidators: new Map(), stakeAmount: new BigNumber(0), useMaxAmount: false, @@ -89,6 +90,20 @@ export const EarnPageStateProvider = ({ children }: PropsWithChildren) => { .orDefault(state); } + case "token-only/select": { + return Maybe.fromFalsy( + state.selectedToken + .map((v) => !equalTokens(v, action.data)) + .orDefault(true) || state.selectedStakeId.isJust() + ) + .map(() => ({ + ...getInitialState(), + selectedToken: Maybe.of(action.data), + autoSelectYield: false, + })) + .orDefault(state); + } + case "dashboard/token-yield/select": { return Maybe.fromFalsy( state.selectedToken @@ -205,6 +220,7 @@ export const EarnPageStateProvider = ({ children }: PropsWithChildren) => { const { selectedToken, selectedStakeId, + autoSelectYield, selectedValidators, stakeAmount: _stakeAmount, useMaxAmount, @@ -321,14 +337,29 @@ export const EarnPageStateProvider = ({ children }: PropsWithChildren) => { useEffect(() => { if (shouldWaitForPositionsData) return; + if (!autoSelectYield) return; + selectedStakeId.ifNothing(() => initYield.ifJust(setYield)); - }, [initYield, selectedStakeId, shouldWaitForPositionsData, setYield]); + }, [ + autoSelectYield, + initYield, + selectedStakeId, + shouldWaitForPositionsData, + setYield, + ]); useEffect(() => { if (shouldWaitForPositionsData) return; + if (!autoSelectYield) return; selectedStakeId.ifNothing(() => initYieldRef.current.ifJust(setYield)); - }, [initYieldRef, shouldWaitForPositionsData, selectedStakeId, setYield]); + }, [ + autoSelectYield, + initYieldRef, + shouldWaitForPositionsData, + selectedStakeId, + setYield, + ]); /** * Reset selectedToken if we dont have initToken @@ -365,6 +396,7 @@ export const EarnPageStateProvider = ({ children }: PropsWithChildren) => { const value: State & ExtraData = useMemo( () => ({ selectedStakeId, + autoSelectYield, selectedStake, selectedValidators, stakeAmount, @@ -383,6 +415,7 @@ export const EarnPageStateProvider = ({ children }: PropsWithChildren) => { }), [ selectedStakeId, + autoSelectYield, selectedStake, selectedToken, selectedValidators, diff --git a/packages/widget/src/pages/details/earn-page/state/types.ts b/packages/widget/src/pages/details/earn-page/state/types.ts index 71598e37..3083b950 100644 --- a/packages/widget/src/pages/details/earn-page/state/types.ts +++ b/packages/widget/src/pages/details/earn-page/state/types.ts @@ -9,7 +9,6 @@ import type { Yield, } from "../../../../domain/types/yields"; import type { ValidatorDto } from "../../../../generated/api/yield"; -import type { TokenMaxYieldRate } from "../../../../hooks/api/use-token-list-yields"; import type { useEstimatedRewards } from "../../../../hooks/use-estimated-rewards"; import type { useProvidersDetails } from "../../../../hooks/use-provider-details"; import type { useRewardTokenDetails } from "../../../../hooks/use-reward-token-details"; @@ -21,6 +20,7 @@ export type State = { selectedStakeId: Maybe< TokenBalanceScanResponseDto["availableYields"][number] >; + autoSelectYield: boolean; selectedValidators: Map; stakeAmount: BigNumber; useMaxAmount: boolean; @@ -29,6 +29,7 @@ export type State = { }; type TokenBalanceSelectAction = Action<"token/select", TokenDto>; +type TokenOnlySelectAction = Action<"token-only/select", TokenDto>; type DashboardTokenYieldSelectAction = Action< "dashboard/token-yield/select", { token: TokenDto; yieldDto: Yield } @@ -52,6 +53,7 @@ type ProviderYieldIdSelectAction = Action< export type Actions = | TokenBalanceSelectAction + | TokenOnlySelectAction | DashboardTokenYieldSelectAction | YieldSelectAction | StakeAmountChangeAction @@ -123,9 +125,6 @@ export type EarnPageContextType = { all: TokenBalanceScanResponseDto[]; filtered: TokenBalanceScanResponseDto[]; }>; - tokenMaxYieldRatesByToken: ReadonlyMap; - tokenYieldCountsByToken: ReadonlyMap; - tokenListYieldsIsLoading: boolean; onTokenSearch: (value: string) => void; onValidatorSearch: (value: string) => void; validatorSearch: string; From ffbe10fb4a73af2ca09f017578f9d4081d5a7fb5 Mon Sep 17 00:00:00 2001 From: Petar Todorovic Date: Tue, 9 Jun 2026 15:36:19 +0200 Subject: [PATCH 4/4] fix(widget): use legacy enabled networks endpoint Fetch enabled networks from the legacy yields endpoint for chain configuration. Include wallet mode inputs in wagmi config cache keys. Omit empty wallet groups from the generated wallet list. --- .../widget/src/common/get-enabled-networks.ts | 4 +- .../widget/src/providers/api/api-client.ts | 13 +-- .../widget/src/providers/ethereum/config.ts | 18 ++- .../widget/src/providers/settings/types.ts | 1 + packages/widget/src/providers/wagmi/index.ts | 23 +++- packages/widget/tests/fixtures/index.ts | 86 --------------- .../widget/tests/mocks/legacy-api-handlers.ts | 6 + .../widget/tests/mocks/yield-api-handlers.ts | 9 -- .../tests/use-cases/deep-links-flow/setup.ts | 8 +- .../use-cases/external-provider/setup.ts | 13 +-- .../tests/use-cases/gas-warning-flow/setup.ts | 7 +- .../widget/tests/use-cases/geo-block.test.tsx | 4 +- .../use-cases/renders-initial-page.test.tsx | 14 +-- .../use-cases/select-opportunity.test.tsx | 103 +++++++++--------- .../widget/tests/use-cases/sk-wallet.test.tsx | 15 +-- .../tests/use-cases/staking-flow/setup.ts | 5 +- .../use-cases/trust-incentive-apy/setup.ts | 5 +- 17 files changed, 118 insertions(+), 216 deletions(-) diff --git a/packages/widget/src/common/get-enabled-networks.ts b/packages/widget/src/common/get-enabled-networks.ts index ece15507..088ed424 100644 --- a/packages/widget/src/common/get-enabled-networks.ts +++ b/packages/widget/src/common/get-enabled-networks.ts @@ -17,8 +17,8 @@ export const getEnabledNetworks = ({ queryKey: [config.appPrefix, "enabled-networks"], queryFn: async () => new Set( - (await apiClient.yield.NetworksControllerGetNetworks(undefined)).map( - (network) => network.id as Networks + (await apiClient.legacy.YieldControllerGetMyNetworks(undefined)).map( + (network) => network as Networks ) ), }) diff --git a/packages/widget/src/providers/api/api-client.ts b/packages/widget/src/providers/api/api-client.ts index 4974f129..5e1cd74f 100644 --- a/packages/widget/src/providers/api/api-client.ts +++ b/packages/widget/src/providers/api/api-client.ts @@ -119,11 +119,10 @@ const bindLegacyApi = ({ api.TokenControllerTokenBalancesScan, options ), - TransactionControllerGetTransactionVerificationMessageForNetwork: - bindOperation( - api.TransactionControllerGetTransactionVerificationMessageForNetwork, - options - ), + YieldControllerGetMyNetworks: bindOperation( + api.YieldControllerGetMyNetworks, + options + ), YieldControllerGetSingleYieldRewardsSummary: bindOperation( api.YieldControllerGetSingleYieldRewardsSummary, options @@ -155,10 +154,6 @@ const bindYieldApi = ({ ), HealthControllerHealth: bindOperation(api.HealthControllerHealth, options), KycControllerGetStatus: bindOperation(api.KycControllerGetStatus, options), - NetworksControllerGetNetworks: bindOperation( - api.NetworksControllerGetNetworks, - options - ), ProvidersControllerGetProvider: bindOperation( api.ProvidersControllerGetProvider, options diff --git a/packages/widget/src/providers/ethereum/config.ts b/packages/widget/src/providers/ethereum/config.ts index 77e040cb..ae0d5288 100644 --- a/packages/widget/src/providers/ethereum/config.ts +++ b/packages/widget/src/providers/ethereum/config.ts @@ -23,17 +23,19 @@ const queryFn = async ({ apiClient, queryClient, forceWalletConnectOnly, + institutionalWallets, variant, }: { apiClient: ApiClient; queryClient: QueryClient; forceWalletConnectOnly: boolean; + institutionalWallets: boolean; variant: VariantProps["variant"]; }): Promise<{ evmChainsMap: Partial; evmChains: Chain[]; connector: Maybe; - fineryWallets: ReturnType | null; + institutionalWallets: ReturnType | null; }> => getEnabledNetworks({ apiClient, queryClient }).caseOf({ Right: (networks) => { @@ -80,8 +82,10 @@ const queryFn = async ({ evmChainsMap: filteredEvmChainsMap, evmChains, connector: Maybe.fromPredicate(() => !!evmChains.length, connector), - fineryWallets: - variant === "finery" ? createFineryWallets(evmChains) : null, + institutionalWallets: + variant === "finery" || institutionalWallets + ? createFineryWallets(evmChains) + : null, }); }, Left: (l) => Promise.reject(l), @@ -91,7 +95,13 @@ export const getConfig = (opts: Parameters[0]) => EitherAsync(() => opts.queryClient.fetchQuery({ staleTime: Number.POSITIVE_INFINITY, - queryKey: [config.appPrefix, "evm-config"], + queryKey: [ + config.appPrefix, + "evm-config", + opts.variant, + opts.institutionalWallets, + opts.forceWalletConnectOnly, + ], queryFn: () => queryFn(opts), }) ).mapLeft((e) => { diff --git a/packages/widget/src/providers/settings/types.ts b/packages/widget/src/providers/settings/types.ts index c1aae9cc..10d1a0f9 100644 --- a/packages/widget/src/providers/settings/types.ts +++ b/packages/widget/src/providers/settings/types.ts @@ -84,6 +84,7 @@ export type SettingsProps = { | Record | ((chain: SupportedSKChains) => string); dashboardVariant?: boolean; + institutionalWallets?: boolean; hideChainSelector?: boolean; hideAccountAndChainSelector?: boolean; preferredTokenYieldsPerNetwork?: PreferredTokenYieldsPerNetwork; diff --git a/packages/widget/src/providers/wagmi/index.ts b/packages/widget/src/providers/wagmi/index.ts index 524920dd..cea4c7a9 100644 --- a/packages/widget/src/providers/wagmi/index.ts +++ b/packages/widget/src/providers/wagmi/index.ts @@ -60,6 +60,9 @@ const omitEnsUniversalResolver = (chain: T): T => { return { ...chain, contracts } as T; }; +const withoutEmptyWalletGroups = (walletList: WalletList): WalletList => + walletList.filter((walletGroup) => walletGroup.wallets.length > 0); + export type BuildWagmiConfig = typeof buildWagmiConfig; const buildWagmiConfig = async (opts: { @@ -82,6 +85,7 @@ const buildWagmiConfig = async (opts: { isLedgerLive: boolean; isSafe: boolean; chainIconMapping: SettingsProps["chainIconMapping"]; + institutionalWallets: boolean; variant: VariantProps["variant"]; solanaWallets: SolanaWallet[]; solanaConnection: Connection; @@ -104,6 +108,7 @@ const buildWagmiConfig = async (opts: { Promise.all([ getEvmConfig({ forceWalletConnectOnly: opts.forceWalletConnectOnly, + institutionalWallets: opts.institutionalWallets, queryClient: opts.queryClient, variant: opts.variant, apiClient: opts.apiClient, @@ -230,15 +235,15 @@ const buildWagmiConfig = async (opts: { const walletList = Just(null) .map(() => { - if (evmConfig.fineryWallets) { + if (evmConfig.institutionalWallets) { return [ { groupName: "Primary", - wallets: evmConfig.fineryWallets.primaryWallets, + wallets: evmConfig.institutionalWallets.primaryWallets, }, { groupName: "Other", - wallets: evmConfig.fineryWallets.otherWallets, + wallets: evmConfig.institutionalWallets.otherWallets, }, ...Maybe.catMaybes(miscConfig.connectors), ]; @@ -292,6 +297,7 @@ const buildWagmiConfig = async (opts: { })) ) .map((walletList) => opts.mapWalletListFn?.(walletList) ?? walletList) + .map(withoutEmptyWalletGroups) .map((walletList) => { return walletList.map((wg) => ({ ...wg, @@ -385,7 +391,6 @@ const buildWagmiConfig = async (opts: { }); }; -const queryKey = [config.appPrefix, "wagmi-config"]; const staleTime = Number.POSITIVE_INFINITY; export const useWagmiConfig = () => { @@ -396,6 +401,7 @@ export const useWagmiConfig = () => { disableInjectedProviderDiscovery, mapWalletFn, chainIconMapping, + institutionalWallets, variant, mapWalletListFn, tonConnectManifestUrl, @@ -412,7 +418,13 @@ export const useWagmiConfig = () => { const wagmiConfigQuery = useQuery({ staleTime, - queryKey, + queryKey: [ + config.appPrefix, + "wagmi-config", + variant, + !!institutionalWallets, + !!wagmi?.forceWalletConnectOnly, + ], queryFn: () => buildWagmiConfig({ mapWalletFn, @@ -427,6 +439,7 @@ export const useWagmiConfig = () => { externalProviders: externalProvidersRef, }), chainIconMapping, + institutionalWallets: !!institutionalWallets, variant, solanaWallets: solanaWallets.wallets, solanaConnection: solanaConnection.connection, diff --git a/packages/widget/tests/fixtures/index.ts b/packages/widget/tests/fixtures/index.ts index 567c4966..5eac6de2 100644 --- a/packages/widget/tests/fixtures/index.ts +++ b/packages/widget/tests/fixtures/index.ts @@ -13,7 +13,6 @@ import type { } from "../../src/generated/api/legacy"; import type { ValidatorDto, - NetworkDto as YieldApiNetworkDto, ProviderDto as YieldApiProviderDto, } from "../../src/generated/api/yield"; @@ -42,91 +41,6 @@ const yieldApiTokenFixture = ( ...overrides, }); -const miscNetworks = new Set([ - "aptos", - "cardano", - "near", - "solana", - "solana-devnet", - "stellar", - "stellar-testnet", - "sui", - "tezos", - "tron", - "ton", - "ton-testnet", - "hyperliquid", -]); - -const substrateNetworks = new Set([ - "polkadot", - "kusama", - "westend", - "bittensor", -]); - -const getYieldApiNetworkCategory = ( - id: YieldApiNetworkDto["id"] -): YieldApiNetworkDto["category"] => { - if (miscNetworks.has(id)) return "misc"; - if (substrateNetworks.has(id)) return "substrate"; - if ( - [ - "ethereum", - "ethereum-goerli", - "ethereum-holesky", - "ethereum-sepolia", - "ethereum-hoodi", - "arbitrum", - "base", - "base-sepolia", - "gnosis", - "optimism", - "polygon", - "polygon-amoy", - "starknet", - "zksync", - "linea", - "unichain", - "monad-testnet", - "monad", - "avalanche-c", - "avalanche-c-atomic", - "avalanche-p", - "binance", - "celo", - "fantom", - "harmony", - "moonriver", - "okc", - "viction", - "core", - "sonic", - "plasma", - "katana", - "hyperevm", - ].includes(id) - ) { - return "evm"; - } - - return "cosmos"; -}; - -export const yieldApiNetworkFixture = ( - overrides?: Partial -): YieldApiNetworkDto => { - const id = overrides?.id ?? "ethereum"; - - return { - id, - name: id, - category: getYieldApiNetworkCategory(id), - logoURI: `https://assets.stakek.it/networks/${id}.svg`, - ...overrides, - }; -}; - export const yieldApiProviderFixture = ( overrides?: Partial ): YieldApiProviderDto => ({ diff --git a/packages/widget/tests/mocks/legacy-api-handlers.ts b/packages/widget/tests/mocks/legacy-api-handlers.ts index 16d9a0c9..af754e88 100644 --- a/packages/widget/tests/mocks/legacy-api-handlers.ts +++ b/packages/widget/tests/mocks/legacy-api-handlers.ts @@ -23,6 +23,12 @@ const defaultYield = legacyYieldFixture({ }); export const getLegacyApiMock = () => [ + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await delay(); + + return HttpResponse.json([defaultToken.network]); + }), + http.get(legacyApiRoute("/v1/tokens"), async () => { await delay(); diff --git a/packages/widget/tests/mocks/yield-api-handlers.ts b/packages/widget/tests/mocks/yield-api-handlers.ts index 55284cc8..11acdd49 100644 --- a/packages/widget/tests/mocks/yield-api-handlers.ts +++ b/packages/widget/tests/mocks/yield-api-handlers.ts @@ -6,7 +6,6 @@ import type { import type { TokenDto } from "../../src/domain/types/tokens"; import { yieldApiActionFixture, - yieldApiNetworkFixture, yieldApiProviderFixture, yieldApiTransactionFixture, yieldApiValidatorsFixture, @@ -67,14 +66,6 @@ export const getYieldApiMock = () => [ }); }), - http.get(yieldApiRoute("/v1/networks"), async () => { - await delay(); - - return HttpResponse.json([ - yieldApiNetworkFixture({ id: defaultToken.network }), - ]); - }), - http.get(yieldApiRoute("/v1/yields"), async () => { await delay(); diff --git a/packages/widget/tests/use-cases/deep-links-flow/setup.ts b/packages/widget/tests/use-cases/deep-links-flow/setup.ts index 098c375a..ae468094 100644 --- a/packages/widget/tests/use-cases/deep-links-flow/setup.ts +++ b/packages/widget/tests/use-cases/deep-links-flow/setup.ts @@ -6,7 +6,6 @@ import { waitForMs } from "../../../src/utils"; import { legacyYieldFixture, yieldApiActionFixture, - yieldApiNetworkFixture, yieldApiTransactionFixture, yieldApiValidatorFixture, yieldApiValidatorsFixture, @@ -266,12 +265,9 @@ export const setup = async ( { token: ether, availableYields: ["ethereum-eth-etherfi-staking"] }, ]); }), - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); - return HttpResponse.json([ - yieldApiNetworkFixture({ id: token.network }), - yieldApiNetworkFixture({ id: ether.network }), - ]); + return HttpResponse.json([token.network, ether.network]); }), http.post(legacyApiRoute("/v1/tokens/balances/scan"), async () => { diff --git a/packages/widget/tests/use-cases/external-provider/setup.ts b/packages/widget/tests/use-cases/external-provider/setup.ts index 8df6c9ef..a808ca50 100644 --- a/packages/widget/tests/use-cases/external-provider/setup.ts +++ b/packages/widget/tests/use-cases/external-provider/setup.ts @@ -1,7 +1,6 @@ import { delay, HttpResponse, http } from "msw"; import { legacyYieldFixture, - yieldApiNetworkFixture, yieldApiValidatorsFixture, yieldApiYieldFixture, } from "../../fixtures"; @@ -123,15 +122,13 @@ export const setup = (worker: TestWorker) => { }); worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); return HttpResponse.json([ - yieldApiNetworkFixture({ id: etherNativeStaking.token.network }), - yieldApiNetworkFixture({ - id: avalancheAvaxNativeStaking.token.network, - }), - yieldApiNetworkFixture({ id: solanaNativeStaking.token.network }), - yieldApiNetworkFixture({ id: tonNativeStaking.token.network }), + etherNativeStaking.token.network, + avalancheAvaxNativeStaking.token.network, + solanaNativeStaking.token.network, + tonNativeStaking.token.network, ]); }), diff --git a/packages/widget/tests/use-cases/gas-warning-flow/setup.ts b/packages/widget/tests/use-cases/gas-warning-flow/setup.ts index fb427f68..d89be9f4 100644 --- a/packages/widget/tests/use-cases/gas-warning-flow/setup.ts +++ b/packages/widget/tests/use-cases/gas-warning-flow/setup.ts @@ -5,7 +5,6 @@ import { waitForMs } from "../../../src/utils"; import { legacyYieldFixture, yieldApiActionFixture, - yieldApiNetworkFixture, yieldApiTransactionFixture, yieldApiValidatorsFixture, yieldApiYieldFixture, @@ -151,11 +150,9 @@ export const setup = (worker: TestWorker) => { }) => yieldsTxGasAmountMap.set(yieldId, amount); worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); - return HttpResponse.json([ - yieldApiNetworkFixture({ id: avalancheCToken.network }), - ]); + return HttpResponse.json([avalancheCToken.network]); }), http.get(legacyApiRoute("/v1/tokens"), async () => { diff --git a/packages/widget/tests/use-cases/geo-block.test.tsx b/packages/widget/tests/use-cases/geo-block.test.tsx index 2f3baa37..3f9e308d 100644 --- a/packages/widget/tests/use-cases/geo-block.test.tsx +++ b/packages/widget/tests/use-cases/geo-block.test.tsx @@ -1,12 +1,12 @@ import { HttpResponse, http } from "msw"; -import { yieldApiRoute } from "../mocks/api-routes"; +import { legacyApiRoute } from "../mocks/api-routes"; import { describe, expect, it } from "../utils/test-extend"; import { renderApp } from "../utils/test-utils"; describe("Geo block", () => { it("Show geo block popup", async ({ worker }) => { worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { return HttpResponse.json( { code: 403, diff --git a/packages/widget/tests/use-cases/renders-initial-page.test.tsx b/packages/widget/tests/use-cases/renders-initial-page.test.tsx index 1b464802..bc09f210 100644 --- a/packages/widget/tests/use-cases/renders-initial-page.test.tsx +++ b/packages/widget/tests/use-cases/renders-initial-page.test.tsx @@ -1,9 +1,5 @@ import { delay, HttpResponse, http } from "msw"; -import { - legacyYieldFixture, - yieldApiNetworkFixture, - yieldApiYieldFixture, -} from "../fixtures"; +import { legacyYieldFixture, yieldApiYieldFixture } from "../fixtures"; import { legacyApiRoute, yieldApiRoute } from "../mocks/api-routes"; import { describe, expect, it } from "../utils/test-extend"; import { renderApp } from "../utils/test-utils"; @@ -81,13 +77,11 @@ describe("Renders initial page", () => { }); worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); return HttpResponse.json([ - yieldApiNetworkFixture({ id: etherNativeStaking.token.network }), - yieldApiNetworkFixture({ - id: avalancheAvaxNativeStaking.token.network, - }), + etherNativeStaking.token.network, + avalancheAvaxNativeStaking.token.network, ]); }), diff --git a/packages/widget/tests/use-cases/select-opportunity.test.tsx b/packages/widget/tests/use-cases/select-opportunity.test.tsx index 4dd855dd..f7983946 100644 --- a/packages/widget/tests/use-cases/select-opportunity.test.tsx +++ b/packages/widget/tests/use-cases/select-opportunity.test.tsx @@ -2,7 +2,6 @@ import { delay, HttpResponse, http } from "msw"; import { userEvent } from "vitest/browser"; import { legacyYieldFixture, - yieldApiNetworkFixture, yieldApiYieldFixture, yieldRiskSummaryFixture, } from "../fixtures"; @@ -139,61 +138,57 @@ describe("Select opportunity", () => { }; worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); - return HttpResponse.json( - ( - [ - "ethereum", - "ethereum-goerli", - "avalanche-c", - "celo", - "akash", - "cosmos", - "kava", - "osmosis", - "juno", - "stargaze", - "persistence", - "axelar", - "onomy", - "quicksilver", - "agoric", - "band-protocol", - "bitsong", - "chihuahua", - "comdex", - "crescent", - "cronos", - "cudos", - "fetch-ai", - "gravity-bridge", - "injective", - "irisnet", - "ki-network", - "mars-protocol", - "regen", - "secret", - "sentinel", - "sommelier", - "teritori", - "umee", - "coreum", - "desmos", - "dydx", - "optimism", - "fantom", - "arbitrum", - "polygon", - "binance", - "near", - "harmony", - "solana", - "tezos", - ] as const - ).map((id) => yieldApiNetworkFixture({ id })) - ); + return HttpResponse.json([ + "ethereum", + "ethereum-goerli", + "avalanche-c", + "celo", + "akash", + "cosmos", + "kava", + "osmosis", + "juno", + "stargaze", + "persistence", + "axelar", + "onomy", + "quicksilver", + "agoric", + "band-protocol", + "bitsong", + "chihuahua", + "comdex", + "crescent", + "cronos", + "cudos", + "fetch-ai", + "gravity-bridge", + "injective", + "irisnet", + "ki-network", + "mars-protocol", + "regen", + "secret", + "sentinel", + "sommelier", + "teritori", + "umee", + "coreum", + "desmos", + "dydx", + "optimism", + "fantom", + "arbitrum", + "polygon", + "binance", + "near", + "harmony", + "solana", + "tezos", + ]); }), http.get(legacyApiRoute("/v1/tokens"), async () => { await delay(); diff --git a/packages/widget/tests/use-cases/sk-wallet.test.tsx b/packages/widget/tests/use-cases/sk-wallet.test.tsx index f353a3ee..f44174b5 100644 --- a/packages/widget/tests/use-cases/sk-wallet.test.tsx +++ b/packages/widget/tests/use-cases/sk-wallet.test.tsx @@ -9,8 +9,7 @@ import { SKWalletProvider, useSKWallet } from "../../src/providers/sk-wallet"; import { SolanaProvider } from "../../src/providers/solana"; import { TrackingContextProviderWithProps } from "../../src/providers/tracking"; import { WagmiConfigProvider } from "../../src/providers/wagmi/provider"; -import { yieldApiNetworkFixture } from "../fixtures"; -import { yieldApiRoute } from "../mocks/api-routes"; +import { legacyApiRoute } from "../mocks/api-routes"; import { describe, expect, it, vi } from "../utils/test-extend"; import { renderHook } from "../utils/test-utils"; @@ -45,11 +44,9 @@ describe("SK Wallet", () => { const sendTransactionSpy = vi.fn(async () => "hash"); worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); - return HttpResponse.json([ - yieldApiNetworkFixture({ id: MiscNetworks.Solana }), - ]); + return HttpResponse.json([MiscNetworks.Solana]); }) ); @@ -124,11 +121,9 @@ describe("SK Wallet", () => { const sendTransactionSpy = vi.fn(async (_: unknown) => "hash"); worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); - return HttpResponse.json([ - yieldApiNetworkFixture({ id: MiscNetworks.Ton }), - ]); + return HttpResponse.json([MiscNetworks.Ton]); }) ); diff --git a/packages/widget/tests/use-cases/staking-flow/setup.ts b/packages/widget/tests/use-cases/staking-flow/setup.ts index 75154ad3..970eaddf 100644 --- a/packages/widget/tests/use-cases/staking-flow/setup.ts +++ b/packages/widget/tests/use-cases/staking-flow/setup.ts @@ -12,7 +12,6 @@ import type { import { waitForMs } from "../../../src/utils"; import { yieldApiActionFixture, - yieldApiNetworkFixture, yieldApiProviderFixture, yieldApiTransactionFixture, yieldApiValidatorsFixture, @@ -238,9 +237,9 @@ export const setup = async (worker: TestWorker) => { } as unknown as TransactionDto; worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); - return HttpResponse.json([yieldApiNetworkFixture({ id: "avalanche-c" })]); + return HttpResponse.json(["avalanche-c"]); }), http.get(legacyApiRoute("/v1/tokens"), async () => { diff --git a/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts b/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts index 84b84425..0cb77642 100644 --- a/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts +++ b/packages/widget/tests/use-cases/trust-incentive-apy/setup.ts @@ -7,7 +7,6 @@ import type { Yield } from "../../../src/domain/types/yields"; import { waitForMs } from "../../../src/utils"; import { legacyYieldFixture, - yieldApiNetworkFixture, yieldApiYieldFixture, yieldBalanceFixture, yieldRewardRateFixture, @@ -274,9 +273,9 @@ export const setup = async ( }); worker.use( - http.get(yieldApiRoute("/v1/networks"), async () => { + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { await delay(); - return HttpResponse.json([yieldApiNetworkFixture({ id: token.network })]); + return HttpResponse.json([token.network]); }), http.get(legacyApiRoute("/v1/tokens"), async () => { await delay();