diff --git a/package.json b/package.json
index 9487ca9e..43eb5ded 100644
--- a/package.json
+++ b/package.json
@@ -23,11 +23,5 @@
"husky": "catalog:",
"rev-dep": "catalog:",
"turbo": "catalog:"
- },
- "pnpm": {
- "patchedDependencies": {
- "purify-ts": "patches/purify-ts.patch",
- "@stakekit/rainbowkit@2.2.11": "patches/@stakekit__rainbowkit@2.2.11.patch"
- }
}
}
diff --git a/packages/widget/package.json b/packages/widget/package.json
index c26d1b74..4458d1d4 100644
--- a/packages/widget/package.json
+++ b/packages/widget/package.json
@@ -75,6 +75,7 @@
"@cosmos-kit/keplr": "catalog:",
"@cosmos-kit/leap": "catalog:",
"@cosmos-kit/walletconnect": "catalog:",
+ "@effect/atom-react": "catalog:",
"@effect/openapi-generator": "catalog:",
"@effect/platform-node": "catalog:",
"@faker-js/faker": "catalog:",
@@ -117,7 +118,6 @@
"@vitejs/plugin-react": "catalog:",
"@vitest/browser-playwright": "catalog:",
"@xstate/react": "catalog:",
- "@xstate/store": "catalog:",
"autoprefixer": "catalog:",
"babel-plugin-react-compiler": "catalog:",
"bignumber.js": "catalog:",
diff --git a/packages/widget/src/Dashboard.tsx b/packages/widget/src/Dashboard.tsx
index 91fb2ca0..0fb2240d 100644
--- a/packages/widget/src/Dashboard.tsx
+++ b/packages/widget/src/Dashboard.tsx
@@ -6,7 +6,6 @@ import { PendingCompletePage } from "./pages/complete/pages/pending-complete.pag
import { StakeCompletePage } from "./pages/complete/pages/stake-complete.page";
import { UnstakeCompletePage } from "./pages/complete/pages/unstake-complete.page";
import { EarnPageContextProvider } from "./pages/details/earn-page/state/earn-page-context";
-import { EarnPageStateUsageBoundaryProvider } from "./pages/details/earn-page/state/earn-page-state-context";
import { StakeReviewPage } from "./pages/review";
import { PendingReviewPage } from "./pages/review/pages/pending-review.page";
import { UnstakeReviewPage } from "./pages/review/pages/unstake-review.page";
@@ -40,77 +39,73 @@ export const Dashboard = () => {
return (
-
-
-
- }>
- {/* Earn Tab */}
- }>
- } />
+
+
+ }>
+ {/* Earn Tab */}
+ }>
+ } />
- }>
- } />
- } />
- } />
-
+ }>
+ } />
+ } />
+ } />
+
- {/* Manage Tab */}
- } />
+ {/* Manage Tab */}
+ } />
- {/* Position Details */}
- }
- >
- } />
+ {/* Position Details */}
+ }
+ >
+ } />
- {/* Staking */}
-
- } />
- } />
- } />
- } />
-
+ {/* Staking */}
+
+ } />
+ } />
+ } />
+ } />
+
- }
- />
+ }
+ />
- {/* Unstaking */}
-
- } />
- } />
- } />
- } />
-
+ {/* Unstaking */}
+
+ } />
+ } />
+ } />
+ } />
+
- {/* Pending Actions */}
-
- } />
- } />
- } />
-
+ {/* Pending Actions */}
+
+ } />
+ } />
+ } />
+
- {/* Rewards Tab */}
- {/* } /> */}
+ {/* Rewards Tab */}
+ {/* } /> */}
- {/* Activity Tab */}
- }>
- } />
- }
- />
-
+ {/* Activity Tab */}
+ }>
+ } />
+ }
+ />
-
-
-
+
+
+
diff --git a/packages/widget/src/common/get-token-balances.ts b/packages/widget/src/common/get-token-balances.ts
deleted file mode 100644
index f526b639..00000000
--- a/packages/widget/src/common/get-token-balances.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { QueryClient } from "@tanstack/react-query";
-import { EitherAsync, Right } from "purify-ts";
-import type { SKWallet } from "../domain/types/wallet";
-import type { DashboardYieldCategory } from "../domain/types/yields";
-import {
- getDefaultTokens,
- getYieldTypesForDashboardCategory,
-} from "../hooks/api/use-default-tokens";
-import { getTokenBalancesScan } from "../hooks/api/use-token-balances-scan";
-import type { ApiClient } from "../providers/api/api-client";
-import type { SettingsProps } from "../providers/settings/types";
-
-export const getTokenBalances = ({
- additionalAddresses,
- address,
- apiClient,
- network,
- selectedDashboardYieldCategory,
- queryClient,
- tokensForEnabledYieldsOnly,
-}: {
- additionalAddresses: SKWallet["additionalAddresses"];
- address: SKWallet["address"];
- apiClient: ApiClient;
- queryClient: QueryClient;
- network: SKWallet["network"];
- selectedDashboardYieldCategory?: DashboardYieldCategory | null;
- tokensForEnabledYieldsOnly: SettingsProps["tokensForEnabledYieldsOnly"];
-}) =>
- EitherAsync.fromPromise(() =>
- Promise.all([
- getDefaultTokens({
- apiClient,
- queryClient,
- network: network ?? undefined,
- enabledYieldsOnly: tokensForEnabledYieldsOnly,
- yieldTypes: getYieldTypesForDashboardCategory(
- selectedDashboardYieldCategory
- ),
- }),
- EitherAsync.liftEither(
- Right({ additionalAddresses, address, network })
- ).chain(async (params) => {
- if (!params.address || !params.network) {
- return Right([]);
- }
-
- return getTokenBalancesScan({
- apiClient,
- queryClient,
- tokenBalanceScanDto: {
- addresses: {
- address: params.address,
- additionalAddresses: params.additionalAddresses ?? undefined,
- },
- network: params.network,
- },
- }).mapLeft(() => new Error("could not get token balances scan"));
- }),
- ]).then(([defaultTokens, tokenBalances]) =>
- defaultTokens.chain((d) =>
- tokenBalances.map((b) => ({
- defaultTokens: d,
- tokenBalancesScan: b,
- }))
- )
- )
- ).mapLeft(() => new Error("could not get tokens"));
diff --git a/packages/widget/src/domain/types/stake.ts b/packages/widget/src/domain/types/stake.ts
index 748cbd08..77fc9509 100644
--- a/packages/widget/src/domain/types/stake.ts
+++ b/packages/widget/src/domain/types/stake.ts
@@ -151,9 +151,6 @@ const yieldsWithEnterMinBasedOnPosition = new Map>([
[Networks.Polkadot, new Set(["polkadot-dot-validator-staking"])],
]);
-export const isNetworkWithEnterMinBasedOnPosition = (network: Networks) =>
- yieldsWithEnterMinBasedOnPosition.has(network);
-
const isYieldWithEnterMinBasedOnPosition = (yieldDto: YieldBase) =>
Maybe.fromNullable(
yieldsWithEnterMinBasedOnPosition.get(
diff --git a/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts b/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts
deleted file mode 100644
index 6ee2dfa8..00000000
--- a/packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { useQueries } from "@tanstack/react-query";
-import { useMemo } from "react";
-import type { TokenDto } from "../../domain/types/tokens";
-import {
- type DashboardYieldCategory,
- getApiYieldTypesForDashboardCategory,
-} from "../../domain/types/yields";
-import { useApiClient } from "../../providers/api/api-client-provider";
-import { useSettings } from "../../providers/settings";
-import { useSKWallet } from "../../providers/sk-wallet";
-import {
- DEFAULT_YIELD_SUMMARIES_PAGE_LIMIT,
- fetchYieldSummariesPage,
- getYieldSummariesQueryKey,
- isVisibleYieldSummary,
- type YieldSummariesParams,
- type YieldSummary,
-} from "./use-yield-summaries";
-
-type DashboardCategoryInitialSelection = {
- token: TokenDto;
- yieldDto: YieldSummary;
- yieldId: string;
-};
-
-const staleTime = 1000 * 60 * 2;
-
-/**
- * Discovers available dashboard earn categories with one network-scoped,
- * reward-rate-sorted probe per category (no dependency on the wallet's token
- * holdings, no per-yield legacy hydration). The first visible summary of each
- * probe seeds that category's initial token + yield selection.
- */
-export const useDashboardYieldCatalog = ({
- enabled = true,
-}: {
- enabled?: boolean;
-} = {}) => {
- const { network, isConnecting } = useSKWallet();
- const apiClient = useApiClient();
- const { dashboardYieldCategoryOrder } = useSettings();
-
- const probeEnabled = enabled && !isConnecting;
-
- const results = useQueries({
- queries: dashboardYieldCategoryOrder.map((category) => {
- const params: YieldSummariesParams = {
- ...(network ? { network: network } : {}),
- types: getApiYieldTypesForDashboardCategory(category),
- sort: "rewardRateDesc",
- limit: DEFAULT_YIELD_SUMMARIES_PAGE_LIMIT,
- };
-
- return {
- enabled: probeEnabled,
- staleTime,
- queryKey: getYieldSummariesQueryKey(params),
- queryFn: ({ signal }: { signal: AbortSignal }) =>
- fetchYieldSummariesPage({ apiClient, params, signal }),
- };
- }),
- });
-
- return useMemo(() => {
- const initialSelectionByCategory = new Map<
- DashboardYieldCategory,
- DashboardCategoryInitialSelection
- >();
- const availableCategories: DashboardYieldCategory[] = [];
-
- dashboardYieldCategoryOrder.forEach((category, index) => {
- const firstVisible = (results[index]?.data ?? []).find(
- isVisibleYieldSummary
- );
-
- if (!firstVisible) return;
-
- availableCategories.push(category);
- initialSelectionByCategory.set(category, {
- token: firstVisible.token,
- yieldDto: firstVisible,
- yieldId: firstVisible.id,
- });
- });
-
- return {
- availableCategories,
- initialSelectionByCategory,
- isLoading: probeEnabled && results.some((result) => result.isLoading),
- };
- }, [dashboardYieldCategoryOrder, results, probeEnabled]);
-};
diff --git a/packages/widget/src/hooks/api/use-default-tokens.ts b/packages/widget/src/hooks/api/use-default-tokens.ts
deleted file mode 100644
index d6f28f80..00000000
--- a/packages/widget/src/hooks/api/use-default-tokens.ts
+++ /dev/null
@@ -1,312 +0,0 @@
-import {
- type InfiniteData,
- type QueryClient,
- useInfiniteQuery,
- useQuery,
-} from "@tanstack/react-query";
-import { EitherAsync } from "purify-ts";
-import type { TokenBalanceScanResponseDto } from "../../domain/types/token-balance";
-import type { TokenDto } from "../../domain/types/tokens";
-import {
- type DashboardYieldCategory,
- getApiYieldTypesForDashboardCategory,
-} from "../../domain/types/yields";
-import type {
- TokenControllerGetTokensParams as LegacyTokenGetTokensParams,
- TokenWithAvailableYieldsDto as LegacyTokenWithAvailableYieldsDto,
-} from "../../generated/api/legacy";
-import type {
- TokensControllerGetTokensParams as YieldTokenGetTokensParams,
- TokenWithAvailableYieldsDto as YieldTokenWithAvailableYieldsDto,
-} from "../../generated/api/yield";
-import type { ApiClient } from "../../providers/api/api-client";
-import { useApiClient } from "../../providers/api/api-client-provider";
-import { useSettings } from "../../providers/settings";
-import { useSKWallet } from "../../providers/sk-wallet";
-
-const DEFAULT_TOKENS_PAGE_LIMIT = 100;
-const DEFAULT_TOKENS_PAGE_CONCURRENCY = 5;
-
-type YieldTokenTypes = YieldTokenGetTokensParams["yieldTypes"];
-type DefaultTokensQueryParams = {
- enabledYieldsOnly?: boolean;
- network?: TokenDto["network"];
- yieldTypes?: YieldTokenTypes;
-};
-type DefaultTokensPage = {
- limit?: number;
- nextOffset?: number;
- offset?: number;
- tokens: TokenBalanceScanResponseDto[];
- total?: number;
-};
-type DefaultTokensPages = {
- pages: DefaultTokensPage[];
- pageParams: number[];
-};
-type FetchDefaultTokensPageParams = DefaultTokensQueryParams & {
- apiClient: ApiClient;
- limit?: number;
- offset?: number;
- signal?: AbortSignal;
-};
-
-const noopFetchNextPage = () => undefined;
-
-const getTokenGetTokensQueryKey = (params?: DefaultTokensQueryParams) =>
- ["/v1/tokens", ...(params ? [params] : [])] as const;
-
-const getAllDefaultTokensQueryKey = (params?: DefaultTokensQueryParams) =>
- ["/v1/tokens/all-pages", ...(params ? [params] : [])] as const;
-
-const getNextOffset = ({
- limit,
- offset,
- total,
-}: {
- limit: number;
- offset: number;
- total: number;
-}) => {
- const nextOffset = offset + limit;
-
- return nextOffset < total ? nextOffset : undefined;
-};
-
-const shouldUseYieldTokensApi = ({
- enabledYieldsOnly,
- yieldTypes,
-}: Pick) =>
- !!enabledYieldsOnly || !!yieldTypes?.length;
-
-const toTokenBalanceScanResponse = (
- tokenWithYields:
- | LegacyTokenWithAvailableYieldsDto
- | YieldTokenWithAvailableYieldsDto
-): TokenBalanceScanResponseDto => ({
- token: tokenWithYields.token,
- availableYields: tokenWithYields.availableYields,
- amount: "0",
-});
-
-export const getYieldTypesForDashboardCategory = (
- yieldCategory?: DashboardYieldCategory | null
-): YieldTokenTypes =>
- yieldCategory
- ? getApiYieldTypesForDashboardCategory(yieldCategory)
- : undefined;
-
-export const fetchDefaultTokens = async ({
- apiClient,
- enabledYieldsOnly,
- limit = DEFAULT_TOKENS_PAGE_LIMIT,
- network,
- offset: firstOffset = 0,
- signal,
- yieldTypes,
-}: FetchDefaultTokensPageParams): Promise => {
- if (shouldUseYieldTokensApi({ enabledYieldsOnly, yieldTypes })) {
- const yieldTokenParams = {
- networks: network
- ? [
- network as NonNullable<
- YieldTokenGetTokensParams["networks"]
- >[number],
- ]
- : undefined,
- yieldTypes,
- limit,
- };
-
- const fetchYieldTokensPage = async (
- offset: number
- ): Promise => {
- const page = await apiClient
- .withOptions({ signal })
- .yield.TokensControllerGetTokens({
- params: { ...yieldTokenParams, offset },
- });
-
- return {
- limit: page.limit,
- tokens: (page.items ?? []).map(toTokenBalanceScanResponse),
- nextOffset: getNextOffset(page),
- offset: page.offset,
- total: page.total,
- };
- };
-
- const firstPage = await fetchYieldTokensPage(firstOffset);
-
- if (firstPage.nextOffset === undefined) {
- return { pages: [firstPage], pageParams: [firstOffset] };
- }
-
- const remainingOffsets: number[] = [];
- for (
- let offset = firstPage.offset! + firstPage.limit!;
- offset < firstPage.total!;
- offset += firstPage.limit!
- ) {
- remainingOffsets.push(offset);
- }
-
- const pages = [firstPage];
- const pageParams = [firstOffset, ...remainingOffsets];
-
- for (
- let i = 0;
- i < remainingOffsets.length;
- i += DEFAULT_TOKENS_PAGE_CONCURRENCY
- ) {
- const chunk = remainingOffsets.slice(
- i,
- i + DEFAULT_TOKENS_PAGE_CONCURRENCY
- );
- const chunkPages = await Promise.all(
- chunk.map((offset) => fetchYieldTokensPage(offset))
- );
-
- pages.push(...chunkPages);
- }
-
- return { pages, pageParams };
- }
-
- const tokens = await apiClient
- .withOptions({ signal })
- .legacy.TokenControllerGetTokens({
- params: {
- enabledYieldsOnly: enabledYieldsOnly || undefined,
- network: network as LegacyTokenGetTokensParams["network"],
- },
- });
-
- return {
- pages: [{ tokens: tokens.map(toTokenBalanceScanResponse) }],
- pageParams: [firstOffset],
- };
-};
-
-export const useDefaultTokens = ({
- yieldCategory,
-}: {
- yieldCategory?: DashboardYieldCategory | null;
-} = {}) => {
- const { network } = useSKWallet();
- const { tokensForEnabledYieldsOnly } = useSettings();
- const apiClient = useApiClient();
- const queryParams: DefaultTokensQueryParams = {
- enabledYieldsOnly: !!tokensForEnabledYieldsOnly,
- network: network ?? undefined,
- yieldTypes: getYieldTypesForDashboardCategory(yieldCategory),
- };
- const shouldFetchAllPages = !!queryParams.yieldTypes?.length;
-
- const allPagesQuery = useQuery({
- queryKey: getAllDefaultTokensQueryKey(queryParams),
- enabled: shouldFetchAllPages,
- queryFn: async ({ signal }) => {
- const data = await fetchDefaultTokens({
- ...queryParams,
- apiClient,
- signal,
- });
-
- return data.pages.flatMap((page) => page.tokens);
- },
- staleTime: 1000 * 60 * 5,
- });
-
- const infiniteQuery = useInfiniteQuery({
- queryKey: getTokenGetTokensQueryKey(queryParams),
- enabled: !shouldFetchAllPages,
- initialPageParam: 0,
- queryFn: async ({ pageParam, signal }) => {
- if (shouldUseYieldTokensApi(queryParams)) {
- const page = await apiClient
- .withOptions({ signal })
- .yield.TokensControllerGetTokens({
- params: {
- networks: queryParams.network
- ? [
- queryParams.network as NonNullable<
- YieldTokenGetTokensParams["networks"]
- >[number],
- ]
- : undefined,
- yieldTypes: queryParams.yieldTypes,
- offset: pageParam,
- limit: DEFAULT_TOKENS_PAGE_LIMIT,
- },
- });
-
- return {
- limit: page.limit,
- tokens: (page.items ?? []).map(toTokenBalanceScanResponse),
- nextOffset: getNextOffset(page),
- offset: page.offset,
- total: page.total,
- };
- }
-
- const tokens = await apiClient
- .withOptions({ signal })
- .legacy.TokenControllerGetTokens({
- params: {
- enabledYieldsOnly: queryParams.enabledYieldsOnly || undefined,
- network:
- queryParams.network as LegacyTokenGetTokensParams["network"],
- },
- });
-
- return {
- tokens: tokens.map(toTokenBalanceScanResponse),
- };
- },
- getNextPageParam: (lastPage) => lastPage.nextOffset,
- select: (data) => data.pages.flatMap((page) => page.tokens),
- staleTime: 1000 * 60 * 5,
- });
-
- if (shouldFetchAllPages) {
- return {
- ...allPagesQuery,
- fetchNextPage: noopFetchNextPage,
- hasNextPage: false,
- isFetchingNextPage: false,
- };
- }
-
- return infiniteQuery;
-};
-
-export const getDefaultTokens = (
- params: Omit & {
- queryClient: QueryClient;
- }
-) => {
- const queryParams: DefaultTokensQueryParams = {
- enabledYieldsOnly: params.enabledYieldsOnly,
- network: params.network,
- yieldTypes: params.yieldTypes,
- };
-
- return EitherAsync(() =>
- params.queryClient.fetchQuery({
- queryKey: getAllDefaultTokensQueryKey(queryParams),
- queryFn: async () => {
- const data = await fetchDefaultTokens(params);
- params.queryClient.setQueryData<
- InfiniteData
- >(getTokenGetTokensQueryKey(queryParams), data);
-
- return data.pages.flatMap((page) => page.tokens);
- },
- staleTime: 1000 * 60 * 5,
- })
- ).mapLeft((e) => {
- console.log(e);
- return new Error("could not get default tokens");
- });
-};
diff --git a/packages/widget/src/hooks/api/use-multi-yields.ts b/packages/widget/src/hooks/api/use-multi-yields.ts
index a00b2fe5..9d8c9202 100644
--- a/packages/widget/src/hooks/api/use-multi-yields.ts
+++ b/packages/widget/src/hooks/api/use-multi-yields.ts
@@ -1,118 +1,27 @@
-import { hashKey, type QueryClient, useQuery } from "@tanstack/react-query";
-import { useSelector } from "@xstate/react";
-import { createStore } from "@xstate/store";
-import type BigNumber from "bignumber.js";
-import { EitherAsync, Maybe } from "purify-ts";
-import { useEffect, useMemo } from "react";
+import { type QueryClient, useQuery } from "@tanstack/react-query";
import { createSelector } from "reselect";
import {
- defaultIfEmpty,
EMPTY,
filter,
firstValueFrom,
from,
map,
mergeMap,
- Observable,
- of,
- repeat,
- take,
- tap,
- timer,
toArray,
} from "rxjs";
-import { tokenString } from "../../domain";
-import {
- isSupportedChain,
- type SupportedSKChains,
-} from "../../domain/types/chains";
-import type { InitParams } from "../../domain/types/init-params";
-import type { PositionsData } from "../../domain/types/positions";
-import {
- canBeInitialYield,
- type PreferredTokenYieldsPerNetwork,
-} from "../../domain/types/stake";
+import { isSupportedChain } from "../../domain/types/chains";
import type { SKWallet } from "../../domain/types/wallet";
-import {
- type DashboardYieldCategory,
- getDashboardYieldCategory,
- isNonZeroRewardRateYield,
- type ValidatorsConfig,
- type Yield,
- type YieldBase,
+import type {
+ ValidatorsConfig,
+ Yield,
+ 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,
- getYieldOpportunityFromSummary,
-} from "./use-yield-opportunity/get-yield-opportunity";
-import {
- fetchYieldSummariesWithProvidersByIds,
- type YieldSummaryWithProvider,
-} from "./use-yield-summaries";
-
-const multiYieldsStore = createStore({
- context: { data: new Map>() },
- on: {
- "yield-opportunity": (
- context,
- event: { data: { key: string; yieldDto: YieldSummaryWithProvider } }
- ) => {
- const newMap = new Map(context.data);
- const prev = newMap.get(event.data.key) ?? new Map();
-
- prev.set(event.data.yieldDto.id, event.data.yieldDto);
- newMap.set(event.data.key, prev);
-
- return { data: newMap };
- },
- },
-});
-
-export const useStreamYieldSummaries = (yieldIds: ReadonlyArray) => {
- const { network, isConnected, isLedgerLive } = useSKWallet();
- const apiClient = useApiClient();
-
- const argsRef = useSavedRef({
- isLedgerLive,
- queryClient: useSKQueryClient(),
- apiClient,
- network,
- isConnected,
- });
-
- const hashedKey = useMemo(() => hashKey(yieldIds), [yieldIds]);
-
- const validatorsConfig = useValidatorsConfig();
-
- useEffect(() => {
- const sub = multipleYieldSummaries$({
- ...argsRef.current,
- yieldIds,
- validatorsConfig,
- })
- .pipe(repeat({ delay: () => timer(1000 * 60 * 2) }))
- .subscribe({
- next: (v) =>
- multiYieldsStore.send({
- type: "yield-opportunity",
- data: { yieldDto: v, key: hashedKey },
- }),
- });
-
- return () => sub.unsubscribe();
- }, [argsRef, yieldIds, hashedKey, validatorsConfig]);
-
- return useSelector(multiYieldsStore, (state) => {
- const map = state.context.data.get(hashedKey);
-
- return map ? Array.from(map.values()) : [];
- });
-};
+import { getYieldOpportunities } from "./use-yield-opportunity/get-yield-opportunity";
export const useMultiYields = (
yieldIds: ReadonlyArray,
@@ -149,22 +58,6 @@ export const useMultiYields = (
});
};
-export const getFirstEligibleYield = (
- params: Parameters[0]
-) =>
- EitherAsync(() =>
- params.queryClient.fetchQuery({
- queryKey: getFirstEligibleYieldQueryKey({
- yieldIds: params.yieldIds,
- dashboardYieldCategory: params.dashboardYieldCategory,
- }),
- queryFn: () => firstValueFrom(firstEligibleYield$(params)),
- })
- ).mapLeft((e) => {
- console.log(e);
- return new Error("could not get first eligible yield");
- });
-
const multipleYields$ = (args: {
isLedgerLive: boolean;
queryClient: QueryClient;
@@ -200,119 +93,6 @@ 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;
- apiClient: ReturnType;
- isConnected: boolean;
- network: SKWallet["network"];
- yieldIds: ReadonlyArray;
- dashboardYieldCategory?: DashboardYieldCategory | null;
- initParams: InitParams;
- positionsData: PositionsData;
- tokenBalanceAmount: BigNumber;
- validatorsConfig: ValidatorsConfig;
- preferredTokenYieldsPerNetwork: PreferredTokenYieldsPerNetwork | null;
-}) => {
- let defaultYield: YieldSummaryWithProvider | null = null;
-
- const successStream = multipleYieldSummaries$(args).pipe(
- filter(
- (y) =>
- !args.dashboardYieldCategory ||
- getDashboardYieldCategory(y) === args.dashboardYieldCategory
- ),
- tap((v) => {
- if (isNonZeroRewardRateYield(v) || !defaultYield) {
- defaultYield = v;
- }
- }),
- filter((y) => {
- const preferredYieldId = Maybe.fromNullable(
- args.preferredTokenYieldsPerNetwork?.[
- y.token.network as SupportedSKChains
- ]?.[tokenString(y.token)]
- )
- .altLazy(() =>
- Maybe.fromNullable(args.preferredTokenYieldsPerNetwork).chainNullable(
- (v) => Object.values(v)[0][tokenString(y.token)]
- )
- )
- .extractNullable();
-
- if (preferredYieldId) {
- return y.id === preferredYieldId || preferredYieldId === "*";
- }
-
- return canBeInitialYield({
- initQueryParams: Maybe.fromNullable(args.initParams),
- yieldDto: y,
- tokenBalanceAmount: args.tokenBalanceAmount,
- positionsData: args.positionsData,
- });
- }),
- take(1),
- 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),
- error: (e) => subscriber.error(e),
- });
- });
-};
-
type SelectorInputData = {
data: YieldBase[];
isConnected: boolean;
@@ -342,26 +122,3 @@ const defaultFiltered = createSelector(
return network === o.token.network && defaultFilter;
})
);
-
-const getFirstEligibleYieldQueryKey = ({
- yieldIds,
- dashboardYieldCategory,
-}: {
- yieldIds: ReadonlyArray;
- dashboardYieldCategory?: DashboardYieldCategory | null;
-}) => ["first-eligible-yield", yieldIds, dashboardYieldCategory ?? null];
-
-export const getCachedFirstEligibleYield = ({
- queryClient,
- yieldIds,
- dashboardYieldCategory,
-}: {
- queryClient: QueryClient;
- yieldIds: ReadonlyArray;
- dashboardYieldCategory?: DashboardYieldCategory | null;
-}) =>
- Maybe.fromNullable(
- queryClient.getQueryData(
- getFirstEligibleYieldQueryKey({ yieldIds, dashboardYieldCategory })
- )
- );
diff --git a/packages/widget/src/hooks/api/use-token-balances-scan.ts b/packages/widget/src/hooks/api/use-token-balances-scan.ts
index e1183999..9c686832 100644
--- a/packages/widget/src/hooks/api/use-token-balances-scan.ts
+++ b/packages/widget/src/hooks/api/use-token-balances-scan.ts
@@ -1,4 +1,3 @@
-import type { QueryClient } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import { EitherAsync, Just, Maybe } from "purify-ts";
import { useCallback, useMemo } from "react";
@@ -59,19 +58,6 @@ export const useTokenBalancesScan = () => {
});
};
-export const getTokenBalancesScan = (
- params: Parameters[0] & { queryClient: QueryClient }
-) =>
- EitherAsync(() =>
- params.queryClient.fetchQuery({
- queryKey: getTokenTokenBalancesScanQueryKey(params.tokenBalanceScanDto),
- queryFn: async () => (await queryFn(params)).unsafeCoerce(),
- })
- ).mapLeft((e) => {
- console.log(e);
- return new Error("could not get multi yields");
- });
-
const queryFn = ({
apiClient,
tokenBalanceScanDto,
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 058614f7..4c335469 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
@@ -24,9 +24,6 @@ type MultiParams = Omit & {
type ParamsWithQueryClient = Params & {
queryClient: QueryClient;
};
-type ParamsWithYieldDto = Omit & {
- yieldDto: YieldBase;
-};
type MultiParamsWithQueryClient = MultiParams & {
queryClient: QueryClient;
};
@@ -78,18 +75,6 @@ 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({
@@ -171,32 +156,6 @@ const fn = ({
});
};
-const hydrateYieldSummaryQueryFn = async ({
- yieldDto,
- queryClient,
- signal,
- suppressRichErrors,
- apiClient,
-}: ParamsWithYieldDto & {
- signal?: AbortSignal;
-}) => {
- const client = apiClient.withOptions({ signal, suppressRichErrors });
- const provider =
- yieldDto.provider ??
- (await fetchYieldProvider({
- client,
- providerId: yieldDto.providerId,
- queryClient,
- }));
-
- return applyYieldOverrides(
- createYield({
- provider,
- yieldDto,
- })
- );
-};
-
const multiFn = ({
queryClient,
yieldIds,
diff --git a/packages/widget/src/hooks/api/use-yield-summaries.ts b/packages/widget/src/hooks/api/use-yield-summaries.ts
index eb697dd4..ae8bcb65 100644
--- a/packages/widget/src/hooks/api/use-yield-summaries.ts
+++ b/packages/widget/src/hooks/api/use-yield-summaries.ts
@@ -1,14 +1,12 @@
import type { QueryClient } from "@tanstack/react-query";
+import { chunksOf } from "effect/Array";
import { isSupportedChain } from "../../domain/types/chains";
import {
isEthenaUsdeStaking,
isNonZeroRewardRateYield,
type YieldProviderDetails,
} from "../../domain/types/yields";
-import type {
- YieldDto,
- YieldsControllerGetYieldsParams,
-} from "../../generated/api/yield";
+import type { YieldDto } from "../../generated/api/yield";
import type { useApiClient } from "../../providers/api/api-client-provider";
import { fetchYieldProviders } from "./use-yield-providers";
@@ -19,18 +17,12 @@ import { fetchYieldProviders } from "./use-yield-providers";
* `mechanics.type`, `providerId`).
*/
export type YieldSummary = YieldDto;
-export type YieldSummaryWithProvider = YieldSummary & {
+type YieldSummaryWithProvider = YieldSummary & {
provider?: YieldProviderDetails;
};
-export const DEFAULT_YIELD_SUMMARIES_PAGE_LIMIT = 50;
const DEFAULT_YIELD_IDS_CHUNK_SIZE = 100;
-export type YieldSummariesParams = Pick<
- YieldsControllerGetYieldsParams,
- "network" | "types" | "inputToken" | "sort" | "limit"
->;
-
/**
* A summary is "visible" when it is enterable, on a supported chain, and has a
* non-zero reward rate (matching the dashboard catalog's display semantics).
@@ -40,16 +32,6 @@ export const isVisibleYieldSummary = (summary: YieldSummary): boolean =>
isSupportedChain(summary.token.network) &&
isNonZeroRewardRateYield(summary);
-export const getYieldSummariesQueryKey = (
- params: YieldSummariesParams & { allPages?: boolean }
-) => ["yield-summaries", params];
-
-type FetchArgs = {
- apiClient: ReturnType;
- params: YieldSummariesParams;
- signal?: AbortSignal;
-};
-
type FetchByIdsArgs = {
apiClient: ReturnType;
chunkSize?: number;
@@ -64,30 +46,6 @@ type FetchByIdsWithProvidersArgs = FetchByIdsArgs & {
const unique = (items: ReadonlyArray) => [...new Set(items)];
-const chunks = (items: ReadonlyArray, chunkSize: number): T[][] => {
- const result: T[][] = [];
-
- for (let index = 0; index < items.length; index += chunkSize) {
- result.push(items.slice(index, index + chunkSize));
- }
-
- return result;
-};
-
-/**
- * Fetch a single page of yield summaries.
- */
-export const fetchYieldSummariesPage = async ({
- apiClient,
- params,
- signal,
-}: FetchArgs): Promise => {
- const client = apiClient.withOptions({ signal });
- const result = await client.yield.YieldsControllerGetYields({ params });
-
- return [...(result.items ?? [])];
-};
-
/**
* Fetch yield summaries by ID without ever sending an unbounded `yieldIds`
* query array. Results are ordered according to the first occurrence of each
@@ -110,7 +68,7 @@ export const fetchYieldSummariesByIds = async ({
const normalizedChunkSize = Math.max(1, chunkSize);
const summariesById = new Map();
- for (const chunk of chunks(ids, normalizedChunkSize)) {
+ for (const chunk of chunksOf(ids, normalizedChunkSize)) {
const result = await client.yield.YieldsControllerGetYields({
params: {
yieldIds: chunk,
diff --git a/packages/widget/src/hooks/use-handle-deep-links.ts b/packages/widget/src/hooks/use-handle-deep-links.ts
index 82b1ce27..caa9acc6 100644
--- a/packages/widget/src/hooks/use-handle-deep-links.ts
+++ b/packages/widget/src/hooks/use-handle-deep-links.ts
@@ -3,7 +3,7 @@ import { useEffect } from "react";
import { useNavigate } from "react-router";
import { usePendingActionDeepLink } from "../pages/details/earn-page/state/use-pending-action-deep-link";
import { useMountAnimation } from "../providers/mount-animation";
-import { usePendingActionStore } from "../providers/pending-action-store";
+import { useSetPendingActionRequest } from "../providers/pending-action-store";
import { useSKWallet } from "../providers/sk-wallet";
import { useInitQueryParams } from "./use-init-query-params";
import { useSavedRef } from "./use-saved-ref";
@@ -11,7 +11,7 @@ import { useSavedRef } from "./use-saved-ref";
export const useHandleDeepLinks = () => {
const pendingActionDeepLinkCheck = usePendingActionDeepLink();
const navigateRef = useSavedRef(useNavigate());
- const pendignActionStore = usePendingActionStore();
+ const setPendingActionRequest = useSetPendingActionRequest();
const initQueryParams = useInitQueryParams();
const { mountAnimationFinished } = useMountAnimation();
@@ -52,9 +52,9 @@ export const useHandleDeepLinks = () => {
appReady && val.type === "review"
)
.ifJust((val) => {
- pendignActionStore.send({
- type: "initFlow",
- data: {
+ setPendingActionRequest(
+ Maybe.of({
+ actionDto: Maybe.empty(),
requestDto: val.pendingActionDto.requestDto,
addresses: {
address: val.pendingActionDto.address,
@@ -64,14 +64,14 @@ export const useHandleDeepLinks = () => {
integrationData: val.pendingActionDto.integrationData,
interactedToken: val.balance.token,
pendingActionType: val.pendingActionDto.requestDto.action,
- },
- });
+ })
+ );
navigateRef.current(
`positions/${val.yieldOp.id}/${val.balanceId}/pending-action/review`
);
});
}, [
- pendignActionStore,
+ setPendingActionRequest,
pendingActionDeepLinkCheck.data,
appReady,
navigateRef,
diff --git a/packages/widget/src/pages-dashboard/activity/activity-details.page.tsx b/packages/widget/src/pages-dashboard/activity/activity-details.page.tsx
index f8671356..d8cb240a 100644
--- a/packages/widget/src/pages-dashboard/activity/activity-details.page.tsx
+++ b/packages/widget/src/pages-dashboard/activity/activity-details.page.tsx
@@ -1,4 +1,3 @@
-import { useSelector } from "@xstate/store/react";
import { Box } from "../../components/atoms/box";
import { ActionStatus, type TransactionType } from "../../domain/types/action";
import { useActivityComplete } from "../../pages/complete/hooks/use-activity-complete.hook";
@@ -6,20 +5,14 @@ import { useComplete } from "../../pages/complete/hooks/use-complete.hook";
import { CompletePageComponent } from "../../pages/complete/pages/common.page";
import { CompleteCommonContextProvider } from "../../pages/complete/state";
import { ActionReviewPage } from "../../pages/review/pages/action-review.page";
-import { useActivityContext } from "../../providers/activity-provider";
+import {
+ useActivitySelectedAction,
+ useActivitySelectedYield,
+} from "../../providers/activity-provider";
export const ActivityDetailsPage = () => {
- const activityContext = useActivityContext();
-
- const selectedAction = useSelector(
- activityContext,
- (state) => state.context.selectedAction
- ).extractNullable();
-
- const selectedYield = useSelector(
- activityContext,
- (state) => state.context.selectedYield
- ).extractNullable();
+ const selectedAction = useActivitySelectedAction().extractNullable();
+ const selectedYield = useActivitySelectedYield().extractNullable();
if (!selectedYield || !selectedAction) {
return null;
diff --git a/packages/widget/src/pages-dashboard/activity/activity.page.tsx b/packages/widget/src/pages-dashboard/activity/activity.page.tsx
index bf8e16ec..ed4818ff 100644
--- a/packages/widget/src/pages-dashboard/activity/activity.page.tsx
+++ b/packages/widget/src/pages-dashboard/activity/activity.page.tsx
@@ -1,5 +1,4 @@
import { useConnectModal } from "@stakekit/rainbowkit";
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
@@ -17,7 +16,10 @@ import {
} from "../../pages/details/activity-page/state/activity-page.context";
import type { ActionYieldDto } from "../../pages/details/activity-page/types";
import { FallbackContent } from "../../pages/details/positions-page/components/fallback-content";
-import { useActivityContext } from "../../providers/activity-provider";
+import {
+ useActivitySelectedAction,
+ useSetActivitySelection,
+} from "../../providers/activity-provider";
import { useSKWallet } from "../../providers/sk-wallet";
import { container } from "./styles.css";
@@ -100,7 +102,7 @@ const ActivityPageComponent = () => {
const _ActivityPage = () => {
const value = useActivityPageContext();
- const activityStore = useActivityContext();
+ const setActivitySelection = useSetActivitySelection();
const { openConnectModal } = useConnectModal();
const { isConnected, network } = useSKWallet();
@@ -111,14 +113,13 @@ const _ActivityPage = () => {
data.actionData.status === ActionStatus.SUCCESS ||
data.actionData.status === ActionStatus.PROCESSING
) {
- activityStore.send({
- type: "setSelectedAction",
- data: Maybe.of({
+ setActivitySelection(
+ Maybe.of({
selectedAction: data.actionData,
selectedYield: data.yieldData,
selectedValidators: data.validatorsData,
- }),
- });
+ })
+ );
}
if (
@@ -126,40 +127,30 @@ const _ActivityPage = () => {
data.actionData.status === ActionStatus.WAITING_FOR_NEXT ||
data.actionData.status === ActionStatus.FAILED
) {
- activityStore.send({
- type: "setSelectedAction",
- data: Maybe.of({
+ setActivitySelection(
+ Maybe.of({
selectedAction: data.actionData,
selectedYield: data.yieldData,
selectedValidators: data.validatorsData,
- }),
- });
+ })
+ );
}
return;
};
- const selectedAction = useSelector(
- activityStore,
- (state) => state.context.selectedAction
- );
+ const selectedAction = useActivitySelectedAction();
// biome-ignore lint: false
useEffect(() => {
- activityStore.send({
- type: "setSelectedAction",
- data: Maybe.empty(),
- });
- }, [network, activityStore]);
+ setActivitySelection(Maybe.empty());
+ }, [network, setActivitySelection]);
useEffect(() => {
if (!isConnected && selectedAction.isJust()) {
- activityStore.send({
- type: "setSelectedAction",
- data: Maybe.empty(),
- });
+ setActivitySelection(Maybe.empty());
}
- }, [isConnected, selectedAction, activityStore]);
+ }, [isConnected, selectedAction, setActivitySelection]);
return (
diff --git a/packages/widget/src/pages-dashboard/activity/index.tsx b/packages/widget/src/pages-dashboard/activity/index.tsx
index 385f71a7..b74d4abb 100644
--- a/packages/widget/src/pages-dashboard/activity/index.tsx
+++ b/packages/widget/src/pages-dashboard/activity/index.tsx
@@ -1,10 +1,12 @@
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { Outlet, useNavigate } from "react-router";
import { Box } from "../../components/atoms/box";
import { CaretLeftIcon } from "../../components/atoms/icons/caret-left";
import { AnimationPage } from "../../navigation/containers/animation-page";
-import { useActivityContext } from "../../providers/activity-provider";
+import {
+ useActivitySelectedAction,
+ useSetActivitySelection,
+} from "../../providers/activity-provider";
import { useSettings } from "../../providers/settings";
import { combineRecipeWithVariant } from "../../utils/styles";
import { ActivityPage } from "./activity.page";
@@ -13,17 +15,13 @@ import { activityDetailsContainer } from "./styles.css";
export const ActivityTabPage = () => {
const { variant } = useSettings();
const navigate = useNavigate();
- const activityStore = useActivityContext();
-
- const selectedAction = useSelector(
- activityStore,
- (state) => state.context.selectedAction
- );
+ const selectedAction = useActivitySelectedAction();
+ const setActivitySelection = useSetActivitySelection();
const showDetails = selectedAction.isJust();
const onBack = () => {
- activityStore.send({ type: "setSelectedAction", data: Maybe.empty() });
+ setActivitySelection(Maybe.empty());
navigate("/activity");
};
diff --git a/packages/widget/src/pages-dashboard/position-details/components/position-details-stake-actions.tsx b/packages/widget/src/pages-dashboard/position-details/components/position-details-stake-actions.tsx
index 61f3134c..43f5c074 100644
--- a/packages/widget/src/pages-dashboard/position-details/components/position-details-stake-actions.tsx
+++ b/packages/widget/src/pages-dashboard/position-details/components/position-details-stake-actions.tsx
@@ -1,18 +1,30 @@
-import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Navigate } from "react-router";
import { Box } from "../../../components/atoms/box";
+import { selectTokenButton } from "../../../components/atoms/button/styles.css";
+import { ContentLoaderSquare } from "../../../components/atoms/content-loader";
+import { Dropdown } from "../../../components/atoms/dropdown";
+import { MaxButton } from "../../../components/atoms/max-button";
+import { NumberInput } from "../../../components/atoms/number-input";
import { Spinner } from "../../../components/atoms/spinner";
+import { TokenIcon } from "../../../components/atoms/token-icon";
import { Text } from "../../../components/atoms/typography/text";
+import * as AmountToggle from "../../../components/molecules/amount-toggle";
import { KycGateCard } from "../../../components/molecules/kyc-gate-card";
+import type { TronResourceType } from "../../../domain/types/tron";
+import { getYieldActionArg } from "../../../domain/types/yields";
import { useUnstakeOrPendingActionParams } from "../../../hooks/navigation/use-unstake-or-pending-action-params";
+import { MetaInfo } from "../../../pages/components/meta-info";
import { PageCtaButton } from "../../../pages/components/page-cta";
-import { ExtraArgsSelection } from "../../../pages/details/earn-page/components/extra-args-selection";
-import { Footer } from "../../../pages/details/earn-page/components/footer";
-import { SelectTokenSection } from "../../../pages/details/earn-page/components/select-token-section";
-import { useEarnPageContext } from "../../../pages/details/earn-page/state/earn-page-context";
-import { useEarnPageDispatch } from "../../../pages/details/earn-page/state/earn-page-state-context";
-import { usePositionDetails } from "../../../pages/position-details/hooks/use-position-details";
+import {
+ minMaxContainer,
+ priceTxt,
+ selectTokenBalance,
+ selectTokenSection,
+} from "../../../pages/details/earn-page/components/select-token-section/styles.css";
+import { useSettings } from "../../../providers/settings";
+import { combineRecipeWithVariant } from "../../../utils/styles";
+import { usePositionDetailsStake } from "../hooks/use-position-details-stake";
import { PositionDetailsActionTabs } from "./position-details-action-tabs";
import {
positionDetailsActionsHasContent,
@@ -20,47 +32,270 @@ import {
} from "./position-details-actions";
import { container } from "./styles.css";
-const StakeKycGateSection = () => {
- const { kycGate, kycGateIsChecking, kycProviderName, onKycStatusRefresh } =
- useEarnPageContext();
+type PositionDetailsStakeState = ReturnType;
- if (kycGate.state === "pass" && !kycGateIsChecking) return null;
+const StakeKycGateSection = ({
+ stake,
+}: {
+ stake: PositionDetailsStakeState;
+}) => {
+ if (stake.kycGate.state === "pass" && !stake.kycGateIsChecking) return null;
return (
);
};
-const PositionDetailsStakeStateInitializer = ({
- positionDetails,
+const FixedToken = ({ stake }: { stake: PositionDetailsStakeState }) => {
+ const { variant } = useSettings();
+
+ return stake.selectedToken
+ .map((token) => {
+ return (
+
+
+ {token.symbol}
+
+ );
+ })
+ .extractNullable();
+};
+
+const PositionDetailsStakeTokenSection = ({
+ stake,
}: {
- positionDetails: ReturnType;
+ stake: PositionDetailsStakeState;
}) => {
- const dispatch = useEarnPageDispatch();
- const positionYield = positionDetails.integrationData.extractNullable();
+ const { t } = useTranslation();
+ const { variant } = useSettings();
- useEffect(() => {
- if (!positionYield) {
- return;
- }
+ const isLoading = stake.appLoading;
+ const {
+ submitted,
+ errors: {
+ stakeAmountGreaterThanAvailableAmount,
+ stakeAmountGreaterThanMax,
+ stakeAmountLessThanMin,
+ stakeAmountIsZero,
+ },
+ } = stake.validation;
+ const errorInput =
+ (submitted && stakeAmountIsZero) ||
+ stakeAmountGreaterThanAvailableAmount ||
+ stakeAmountGreaterThanMax ||
+ stakeAmountLessThanMin;
+ const errorBalance = stakeAmountGreaterThanAvailableAmount;
+ const min = stake.stakeMinAmount
+ .map((value) => `${t("shared.min")} ${value} ${stake.symbol}`)
+ .extractNullable();
+ const max = stake.stakeMaxAmount
+ .map((value) => `${t("shared.max")} ${value} ${stake.symbol}`)
+ .extractNullable();
+ const minMax = min || max;
- dispatch({ type: "positionDetails/stake/initialize", data: positionYield });
- }, [dispatch, positionYield]);
+ return isLoading ? (
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
- return null;
+ {minMax ? (
+
+
+ {min && max ? `${min} / ${max}` : (min ?? max)}
+
+
+ ) : null}
+
+
+
+
+ {stake.formattedPrice}
+
+
+
+
+
+
+ {stake.selectedTokenAvailableAmount
+ .map((value) => (
+
+
+ {({ state }) => (
+
+ {state === "full"
+ ? value.fullFormattedAmount
+ : value.shortFormattedAmount}
+ {value.symbol} {t("shared.available")}
+
+ )}
+
+
+ ))
+ .extractNullable()}
+
+
+
+ {!stake.isStakeTokenSameAsGasToken && (
+
+ )}
+
+
+
+ );
+};
+
+const PositionDetailsStakeFooter = ({
+ stake,
+}: {
+ stake: PositionDetailsStakeState;
+}) => (
+
+);
+
+const PositionDetailsStakeExtraArgs = ({
+ stake,
+}: {
+ stake: PositionDetailsStakeState;
+}) => {
+ const { t } = useTranslation();
+
+ return stake.selectedStake
+ .chainNullable((selectedStake) =>
+ getYieldActionArg(selectedStake, "enter", "tronResource")
+ )
+ .map((tronResources) => {
+ const options = (tronResources.options ?? []).map((value) => ({
+ label: value,
+ value: value as TronResourceType,
+ }));
+ const selectedOption = stake.tronResource
+ .map((value) => ({ value, label: value }))
+ .extract();
+ const isError =
+ stake.validation.submitted && stake.validation.errors.tronResource;
+
+ return (
+
+
+
+ {t("details.tron_resources.label")}
+
+
+
+ stake.onTronResourceSelect(value)}
+ selectedOption={selectedOption}
+ placeholder={t("details.tron_resources.placeholder")}
+ isError={isError}
+ />
+
+ );
+ })
+ .extractNullable();
};
export const PositionDetailsStakeActions = () => {
- const positionDetails = usePositionDetails();
+ const stake = usePositionDetailsStake();
+ const { positionDetails } = stake;
const { plain } = useUnstakeOrPendingActionParams();
- const { cta } = useEarnPageContext();
const { t } = useTranslation();
if (positionDetails.isLoading) {
@@ -117,20 +352,16 @@ export const PositionDetailsStakeActions = () => {
canUnstake={canUnstake}
/>
-
-
-
+
-
+
-
+
-
+
-
+
);
};
diff --git a/packages/widget/src/pages-dashboard/position-details/hooks/use-position-details-stake.ts b/packages/widget/src/pages-dashboard/position-details/hooks/use-position-details-stake.ts
new file mode 100644
index 00000000..48ab03e8
--- /dev/null
+++ b/packages/widget/src/pages-dashboard/position-details/hooks/use-position-details-stake.ts
@@ -0,0 +1,440 @@
+import { useConnectModal } from "@stakekit/rainbowkit";
+import { useMutation } from "@tanstack/react-query";
+import BigNumber from "bignumber.js";
+import { List, Maybe } from "purify-ts";
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import type { NumberInputProps } from "../../../components/atoms/number-input";
+import {
+ equalTokens,
+ getTokenPriceInUSD,
+ stakeTokenSameAsGasToken,
+} from "../../../domain";
+import { getKycProviderName } from "../../../domain/types/kyc";
+import type {
+ BalanceDataKey,
+ PositionsData,
+} from "../../../domain/types/positions";
+import { getInitSelectedValidators } from "../../../domain/types/stake";
+import type { TronResourceType } from "../../../domain/types/tron";
+import {
+ getYieldActionArg,
+ isYieldValidatorSelectionRequired,
+ type Yield,
+} from "../../../domain/types/yields";
+import type { ValidatorDto } from "../../../generated/api/yield";
+import { useTokenBalancesScan } from "../../../hooks/api/use-token-balances-scan";
+import { useTokensPrices } from "../../../hooks/api/use-tokens-prices";
+import { useYieldKycGate } from "../../../hooks/api/use-yield-kyc-gate";
+import { useYieldValidators } from "../../../hooks/api/use-yield-validators";
+import { useNavigateWithScrollToTop } from "../../../hooks/navigation/use-navigate-with-scroll-to-top";
+import {
+ getPositionDetailsStakeReviewPath,
+ usePositionDetailsStakeMatch,
+} from "../../../hooks/navigation/use-position-details-stake-match";
+import { useTrackEvent } from "../../../hooks/tracking/use-track-event";
+import { useAddLedgerAccount } from "../../../hooks/use-add-ledger-account";
+import { useEstimatedRewards } from "../../../hooks/use-estimated-rewards";
+import { useMaxMinYieldAmount } from "../../../hooks/use-max-min-yield-amount";
+import { useSavedRef } from "../../../hooks/use-saved-ref";
+import { useYieldType } from "../../../hooks/use-yield-type";
+import type { PageCta } from "../../../pages/components/page-cta";
+import { useAmountValidation } from "../../../pages/details/earn-page/state/use-amount-validation";
+import { useStakeEnterRequestDto } from "../../../pages/details/earn-page/state/use-stake-enter-request-dto";
+import { usePositionDetails } from "../../../pages/position-details/hooks/use-position-details";
+import { useSetEnterStakeRequest } from "../../../providers/enter-stake-store";
+import { useSettings } from "../../../providers/settings";
+import { useSKWallet } from "../../../providers/sk-wallet";
+import { defaultFormattedNumber, formatNumber } from "../../../utils";
+import { usePositionDetailsStakeMachine } from "../state/stake-machine";
+
+const resolveProviderYieldId = (selectedStake: Maybe) =>
+ selectedStake
+ .chainNullable((stake) => getYieldActionArg(stake, "enter", "providerId"))
+ .filter((arg) => !!arg.required && !!arg.options?.length)
+ .chain((arg) => List.head(arg.options ?? []));
+
+const resolveTronResource = (selectedStake: Maybe) =>
+ selectedStake
+ .chainNullable((stake) => getYieldActionArg(stake, "enter", "tronResource"))
+ .filter((arg) => !!arg.required)
+ .map(() => "ENERGY" as TronResourceType);
+
+export const usePositionDetailsStake = () => {
+ const { t } = useTranslation();
+ const positionDetails = usePositionDetails();
+ const positionDetailsStakeMatch = usePositionDetailsStakeMatch();
+ const integrationId = positionDetailsStakeMatch?.params.integrationId ?? "";
+ const balanceId = positionDetailsStakeMatch?.params.balanceId ?? "";
+ const { intent, dispatch } = usePositionDetailsStakeMachine({
+ integrationId,
+ balanceId,
+ });
+
+ const selectedStake = positionDetails.integrationData;
+ const selectedToken = selectedStake.map((stake) => stake.token);
+ const selectedProviderYieldId = resolveProviderYieldId(selectedStake);
+ const tronResource = Maybe.fromNullable(intent.tronResource).altLazy(() =>
+ resolveTronResource(selectedStake)
+ );
+
+ const tokenBalancesScan = useTokenBalancesScan();
+ const availableAmount = useMemo(
+ () =>
+ selectedToken
+ .chain((token) =>
+ Maybe.fromNullable(
+ tokenBalancesScan.data?.find((balance) =>
+ equalTokens(balance.token, token)
+ )
+ )
+ )
+ .map((balance) => new BigNumber(balance.amount)),
+ [selectedToken, tokenBalancesScan.data]
+ );
+
+ const positionsData = useMemo(
+ () =>
+ selectedStake
+ .map((stake) => {
+ const balances = [
+ ...positionDetails.positionBalancesByType
+ .map((byType) => [...byType.values()].flat())
+ .orDefault([]),
+ ];
+
+ return new Map([
+ [
+ stake.id,
+ {
+ yieldId: stake.id,
+ rewardRate: stake.rewardRate,
+ balanceData: new Map([
+ [
+ "default" as BalanceDataKey,
+ { type: "default" as const, balances },
+ ],
+ ]),
+ },
+ ],
+ ]) as PositionsData;
+ })
+ .orDefault(new Map() as PositionsData),
+ [positionDetails.positionBalancesByType, selectedStake]
+ );
+
+ const {
+ maxIntegrationAmount,
+ minIntegrationAmount,
+ minEnterOrExitAmount,
+ maxEnterOrExitAmount,
+ isForceMax,
+ } = useMaxMinYieldAmount({
+ type: "enter",
+ yieldOpportunity: selectedStake,
+ availableAmount,
+ positionsData,
+ });
+
+ const rawStakeAmount = new BigNumber(intent.stakeAmount);
+ const stakeAmount =
+ intent.useMaxAmount || !rawStakeAmount.isZero()
+ ? rawStakeAmount
+ : minEnterOrExitAmount;
+
+ const selectedTokenAvailableAmount = useMemo(
+ () =>
+ availableAmount.map((amount) => ({
+ symbol: selectedToken.mapOrDefault((token) => token.symbol, ""),
+ shortFormattedAmount: defaultFormattedNumber(amount),
+ fullFormattedAmount: formatNumber(amount),
+ amount,
+ })),
+ [availableAmount, selectedToken]
+ );
+
+ const validatorsRequired = selectedStake
+ .map(isYieldValidatorSelectionRequired)
+ .orDefault(false);
+ const yieldValidators = useYieldValidators({
+ enabled: validatorsRequired,
+ yieldId:
+ selectedStake.map((stake) => stake.id).extractNullable() ?? undefined,
+ network:
+ selectedStake.map((stake) => stake.token.network).extractNullable() ??
+ undefined,
+ });
+ const selectedValidators = useMemo(() => {
+ if (!validatorsRequired) {
+ return new Map();
+ }
+
+ const validators = yieldValidators.data ?? [];
+ return getInitSelectedValidators({
+ initQueryParams: Maybe.empty(),
+ validators,
+ });
+ }, [validatorsRequired, yieldValidators.data]);
+
+ const estimatedRewards = useEstimatedRewards({
+ selectedStake,
+ stakeAmount,
+ selectedValidators,
+ selectedProviderYieldId,
+ });
+
+ const pricesState = useTokensPrices({
+ token: selectedToken,
+ yieldDto: selectedStake,
+ });
+
+ const formattedPrice = useMemo(
+ () =>
+ Maybe.fromRecord({
+ prices: Maybe.fromNullable(pricesState.data),
+ selectedStake,
+ selectedToken,
+ }).mapOrDefault(
+ (value) =>
+ `$${defaultFormattedNumber(
+ getTokenPriceInUSD({
+ baseToken: value.selectedStake.token,
+ amount: stakeAmount,
+ token: value.selectedToken,
+ prices: value.prices,
+ pricePerShare: null,
+ })
+ )}`,
+ ""
+ ),
+ [pricesState.data, selectedStake, selectedToken, stakeAmount]
+ );
+
+ const stakeEnterRequestDto = useStakeEnterRequestDto({
+ selectedProviderYieldId,
+ selectedStake,
+ selectedToken,
+ selectedValidators,
+ stakeAmount,
+ tronResource,
+ useMaxAmount: intent.useMaxAmount,
+ });
+ const yieldKycGate = useYieldKycGate({ yieldDto: selectedStake });
+ const kycGateIsBlocking = yieldKycGate.isGateBlocking;
+ const kycProviderName = selectedStake
+ .map(getKycProviderName)
+ .extractNullable();
+ const onKycStatusRefresh = () => yieldKycGate.refetch();
+ const { openConnectModal } = useConnectModal();
+ const navigate = useNavigateWithScrollToTop();
+ const setEnterStakeRequest = useSetEnterStakeRequest();
+ const { isConnected, isLedgerLiveAccountPlaceholder, chain } = useSKWallet();
+
+ const {
+ stakeAmountGreaterThanAvailableAmount,
+ stakeAmountGreaterThanMax,
+ stakeAmountLessThanMin,
+ stakeAmountIsZero,
+ } = useAmountValidation({
+ availableAmount,
+ stakeAmount,
+ maxEnterOrExitAmount,
+ minEnterOrExitAmount,
+ });
+
+ const onClickHandler = useMutation({
+ mutationFn: async () => {
+ if (validation.hasErrors) return;
+ if (stakeEnterRequestDto.isNothing()) return;
+
+ if (!isConnected) return openConnectModal?.();
+ if (kycGateIsBlocking) return;
+
+ Maybe.fromRecord({
+ selectedToken,
+ stakeEnterRequestDto,
+ }).ifJust((value) => {
+ setEnterStakeRequest(
+ Maybe.of({
+ actionDto: Maybe.empty(),
+ addresses: value.stakeEnterRequestDto.addresses,
+ requestDto: value.stakeEnterRequestDto.dto,
+ selectedToken: value.selectedToken,
+ gasFeeToken: value.stakeEnterRequestDto.gasFeeToken,
+ selectedStake: value.stakeEnterRequestDto.selectedStake,
+ selectedValidators: value.stakeEnterRequestDto.selectedValidators,
+ })
+ );
+ navigate(
+ getPositionDetailsStakeReviewPath({ balanceId, integrationId }) ??
+ "/review"
+ );
+ });
+ },
+ });
+
+ const validation = useMemo(() => {
+ const errors = {
+ tronResource: false,
+ stakeAmountGreaterThanAvailableAmount,
+ stakeAmountGreaterThanMax,
+ stakeAmountLessThanMin,
+ stakeAmountIsZero,
+ };
+
+ if (
+ isConnected &&
+ selectedStake
+ .chainNullable((stake) =>
+ getYieldActionArg(stake, "enter", "tronResource")
+ )
+ .filter((arg) => !!arg.required)
+ .isJust() &&
+ tronResource.isNothing()
+ ) {
+ errors.tronResource = true;
+ }
+
+ return {
+ submitted: onClickHandler.status !== "idle",
+ hasErrors: Object.values(errors).some(Boolean),
+ errors,
+ };
+ }, [
+ isConnected,
+ onClickHandler.status,
+ selectedStake,
+ stakeAmountGreaterThanAvailableAmount,
+ stakeAmountGreaterThanMax,
+ stakeAmountIsZero,
+ stakeAmountLessThanMin,
+ tronResource,
+ ]);
+
+ const trackEvent = useTrackEvent();
+ const onMaxClick = () => {
+ trackEvent("positionDetailsPageMaxClicked", {
+ yieldId: selectedStake.map((stake) => stake.id).extractNullable(),
+ });
+ dispatch({
+ type: "stakeAmount/max",
+ amount: maxEnterOrExitAmount.toString(10),
+ });
+ };
+ const onStakeAmountChange: NumberInputProps["onChange"] = (amount) =>
+ dispatch({ type: "stakeAmount/change", amount: amount.toString(10) });
+ const onTronResourceSelect = (value: TronResourceType) =>
+ dispatch({ type: "tronResource/select", tronResource: value });
+ const onClickRef = useSavedRef(onClickHandler.mutate);
+
+ const addLedgerAccount = useAddLedgerAccount();
+ const connectClickRef = useSavedRef(() => {
+ if (isLedgerLiveAccountPlaceholder && chain) {
+ trackEvent("addLedgerAccountClicked");
+ return addLedgerAccount.mutate(chain);
+ }
+
+ trackEvent("connectWalletClicked");
+ openConnectModal?.();
+ });
+
+ const { externalProviders } = useSettings();
+ const isFetching =
+ positionDetails.isLoading ||
+ tokenBalancesScan.isLoading ||
+ yieldValidators.isLoading ||
+ pricesState.isLoading;
+ const buttonCTAText = useYieldType(selectedStake).mapOrDefault(
+ (yieldType) => yieldType.cta,
+ ""
+ );
+ const buttonDisabled =
+ isConnected &&
+ (isFetching || stakeEnterRequestDto.isNothing() || kycGateIsBlocking);
+ const appLoading = positionDetails.isLoading || selectedStake.isNothing();
+ const cta = useMemo(
+ () =>
+ isConnected && !isLedgerLiveAccountPlaceholder
+ ? {
+ disabled: buttonDisabled,
+ isLoading: !buttonCTAText || isFetching || yieldKycGate.isLoading,
+ onClick: () => onClickRef.current(),
+ label: buttonCTAText,
+ }
+ : externalProviders
+ ? null
+ : {
+ disabled: appLoading,
+ isLoading: appLoading,
+ label: t(
+ isLedgerLiveAccountPlaceholder
+ ? "init.ledger_add_account"
+ : "init.connect_wallet"
+ ),
+ onClick: () => connectClickRef.current(),
+ },
+ [
+ appLoading,
+ buttonCTAText,
+ buttonDisabled,
+ connectClickRef,
+ externalProviders,
+ isConnected,
+ isFetching,
+ isLedgerLiveAccountPlaceholder,
+ onClickRef,
+ t,
+ yieldKycGate.isLoading,
+ ]
+ );
+
+ const stakeMaxAmount = selectedStake
+ .filter(() => maxIntegrationAmount.isJust() && !isForceMax)
+ .map(() => maxEnterOrExitAmount.toNumber());
+ const stakeMinAmount = selectedStake
+ .filter(() => minIntegrationAmount.isJust() && !isForceMax)
+ .map(() => minEnterOrExitAmount.toNumber())
+ .filter((value) => new BigNumber(value).isGreaterThan(0));
+ const isStakeTokenSameAsGasToken = Maybe.fromRecord({
+ selectedStake,
+ selectedToken,
+ }).mapOrDefault(
+ (value) =>
+ stakeTokenSameAsGasToken({
+ stakeToken: value.selectedToken,
+ yieldDto: value.selectedStake,
+ }),
+ false
+ );
+
+ return {
+ appLoading,
+ cta,
+ estimatedRewards,
+ footerIsLoading: isFetching,
+ formattedPrice,
+ isFetching,
+ isStakeTokenSameAsGasToken,
+ kycGate: yieldKycGate.gate,
+ kycGateIsChecking:
+ yieldKycGate.isLoading ||
+ yieldKycGate.isFetching ||
+ yieldKycGate.isRefetching,
+ kycProviderName,
+ onKycStatusRefresh,
+ onMaxClick,
+ onStakeAmountChange,
+ onTronResourceSelect,
+ positionDetails,
+ selectedStake,
+ selectedToken,
+ selectedTokenAvailableAmount,
+ selectedValidators,
+ stakeAmount,
+ stakeMaxAmount,
+ stakeMinAmount,
+ symbol: selectedToken.mapOrDefault((token) => token.symbol, ""),
+ tronResource,
+ validation,
+ };
+};
diff --git a/packages/widget/src/pages-dashboard/position-details/state/stake-machine.ts b/packages/widget/src/pages-dashboard/position-details/state/stake-machine.ts
new file mode 100644
index 00000000..3d607567
--- /dev/null
+++ b/packages/widget/src/pages-dashboard/position-details/state/stake-machine.ts
@@ -0,0 +1,88 @@
+import { useAtom } from "@effect/atom-react";
+import { Data } from "effect";
+import * as Atom from "effect/unstable/reactivity/Atom";
+import type { TronResourceType } from "../../../domain/types/tron";
+
+type PositionDetailsStakeEntryParams = {
+ integrationId: string;
+ balanceId: string;
+};
+
+class PositionDetailsStakeEntryKey extends Data.Class {}
+
+type PositionDetailsStakeIntent = {
+ stakeAmount: string;
+ tronResource: TronResourceType | null;
+ useMaxAmount: boolean;
+};
+
+type PositionDetailsStakeAction =
+ | {
+ type: "stakeAmount/change";
+ amount: string;
+ }
+ | {
+ type: "stakeAmount/max";
+ amount: string;
+ }
+ | {
+ type: "tronResource/select";
+ tronResource: TronResourceType;
+ };
+
+const makeDefaultIntent = (): PositionDetailsStakeIntent => ({
+ stakeAmount: "0",
+ tronResource: null,
+ useMaxAmount: false,
+});
+
+const positionDetailsStakeAtom = Atom.family(
+ (_entry: PositionDetailsStakeEntryKey) => {
+ const intentAtom = Atom.make(
+ makeDefaultIntent()
+ );
+
+ return Atom.writable<
+ PositionDetailsStakeIntent,
+ PositionDetailsStakeAction
+ >(
+ (ctx) => ctx.get(intentAtom),
+ (ctx, action) => {
+ const intent = ctx.get(intentAtom);
+
+ switch (action.type) {
+ case "stakeAmount/change":
+ ctx.set(intentAtom, {
+ ...intent,
+ stakeAmount: action.amount,
+ useMaxAmount: false,
+ });
+ return;
+ case "stakeAmount/max":
+ ctx.set(intentAtom, {
+ ...intent,
+ stakeAmount: action.amount,
+ useMaxAmount: true,
+ });
+ return;
+ case "tronResource/select":
+ ctx.set(intentAtom, {
+ ...intent,
+ tronResource: action.tronResource,
+ });
+ return;
+ }
+ }
+ );
+ }
+);
+
+export const usePositionDetailsStakeMachine = (
+ entry: PositionDetailsStakeEntryParams
+) => {
+ const [intent, dispatch] = useAtom(
+ positionDetailsStakeAtom(new PositionDetailsStakeEntryKey(entry))
+ );
+
+ return { intent, dispatch };
+};
diff --git a/packages/widget/src/pages/complete/hooks/use-activity-complete.hook.ts b/packages/widget/src/pages/complete/hooks/use-activity-complete.hook.ts
index e1152e51..19837dd7 100644
--- a/packages/widget/src/pages/complete/hooks/use-activity-complete.hook.ts
+++ b/packages/widget/src/pages/complete/hooks/use-activity-complete.hook.ts
@@ -1,4 +1,3 @@
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
import { getActionInputToken } from "../../../domain/types/action";
@@ -6,18 +5,17 @@ import type { TokenDto } from "../../../domain/types/tokens";
import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
import { useYieldType } from "../../../hooks/use-yield-type";
-import { useActivityContext } from "../../../providers/activity-provider";
+import {
+ useActivitySelectedAction,
+ useActivitySelectedValidators,
+ useActivitySelectedYield,
+} from "../../../providers/activity-provider";
import { defaultFormattedNumber } from "../../../utils";
export const useActivityComplete = () => {
useTrackPage("activityComplete");
- const activityContext = useActivityContext();
-
- const selectedAction = useSelector(
- activityContext,
- (state) => state.context.selectedAction
- ).unsafeCoerce();
+ const selectedAction = useActivitySelectedAction().unsafeCoerce();
const amount = useMemo(
() =>
@@ -27,15 +25,8 @@ export const useActivityComplete = () => {
[selectedAction]
);
- const selectedYield = useSelector(
- activityContext,
- (state) => state.context.selectedYield
- );
-
- const selectedValidators = useSelector(
- activityContext,
- (state) => state.context.selectedValidators
- );
+ const selectedYield = useActivitySelectedYield();
+ const selectedValidators = useActivitySelectedValidators();
const yieldType = useYieldType(selectedYield).map((v) => v.type);
diff --git a/packages/widget/src/pages/complete/pages/pending-complete.page.tsx b/packages/widget/src/pages/complete/pages/pending-complete.page.tsx
index fcd9bea7..6a375b53 100644
--- a/packages/widget/src/pages/complete/pages/pending-complete.page.tsx
+++ b/packages/widget/src/pages/complete/pages/pending-complete.page.tsx
@@ -1,4 +1,3 @@
-import { useSelector } from "@xstate/store/react";
import BigNumber from "bignumber.js";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
@@ -7,7 +6,7 @@ import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { usePositionBalances } from "../../../hooks/use-position-balances";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
import { useYieldType } from "../../../hooks/use-yield-type";
-import { usePendingActionStore } from "../../../providers/pending-action-store";
+import { usePendingActionRequest } from "../../../providers/pending-action-store";
import { defaultFormattedNumber } from "../../../utils";
import { CompletePage } from "./common.page";
@@ -19,10 +18,7 @@ export const PendingCompletePage = () => {
integrationId: plain.integrationId,
});
- const pendingRequest = useSelector(
- usePendingActionStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const pendingRequest = usePendingActionRequest().unsafeCoerce();
const integrationData = useMemo(
() => Maybe.of(pendingRequest.integrationData),
diff --git a/packages/widget/src/pages/complete/pages/stake-complete.page.tsx b/packages/widget/src/pages/complete/pages/stake-complete.page.tsx
index 65aa69f5..8701eafa 100644
--- a/packages/widget/src/pages/complete/pages/stake-complete.page.tsx
+++ b/packages/widget/src/pages/complete/pages/stake-complete.page.tsx
@@ -1,21 +1,17 @@
-import { useSelector } from "@xstate/store/react";
import BigNumber from "bignumber.js";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
import { useYieldType } from "../../../hooks/use-yield-type";
-import { useEnterStakeStore } from "../../../providers/enter-stake-store";
+import { useEnterStakeRequest } from "../../../providers/enter-stake-store";
import { defaultFormattedNumber } from "../../../utils";
import { CompletePage } from "./common.page";
export const StakeCompletePage = () => {
useTrackPage("stakeComplete");
- const enterRequest = useSelector(
- useEnterStakeStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const enterRequest = useEnterStakeRequest().unsafeCoerce();
const selectedStake = useMemo(
() => Maybe.of(enterRequest.selectedStake),
diff --git a/packages/widget/src/pages/complete/pages/unstake-complete.page.tsx b/packages/widget/src/pages/complete/pages/unstake-complete.page.tsx
index b0d61797..5f5a16e0 100644
--- a/packages/widget/src/pages/complete/pages/unstake-complete.page.tsx
+++ b/packages/widget/src/pages/complete/pages/unstake-complete.page.tsx
@@ -1,4 +1,3 @@
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
import { useUnstakeOrPendingActionParams } from "../../../hooks/navigation/use-unstake-or-pending-action-params";
@@ -6,7 +5,7 @@ import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { usePositionBalances } from "../../../hooks/use-position-balances";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
import { useYieldType } from "../../../hooks/use-yield-type";
-import { useExitStakeStore } from "../../../providers/exit-stake-store";
+import { useExitStakeRequest } from "../../../providers/exit-stake-store";
import { defaultFormattedNumber } from "../../../utils";
import { CompletePage } from "./common.page";
@@ -17,10 +16,7 @@ export const UnstakeCompletePage = () => {
integrationId: plain.integrationId,
});
- const exitRequest = useSelector(
- useExitStakeStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const exitRequest = useExitStakeRequest().unsafeCoerce();
const integrationData = useMemo(
() => Maybe.of(exitRequest.integrationData),
diff --git a/packages/widget/src/pages/details/activity-page/state/activity-page.context.tsx b/packages/widget/src/pages/details/activity-page/state/activity-page.context.tsx
index 3e2c63ff..3d6c3f91 100644
--- a/packages/widget/src/pages/details/activity-page/state/activity-page.context.tsx
+++ b/packages/widget/src/pages/details/activity-page/state/activity-page.context.tsx
@@ -11,7 +11,7 @@ import {
useActivityFilterOptions,
usePrefetchActivityActionFilters,
} from "../../../../hooks/api/use-activity-actions";
-import { useActivityContext } from "../../../../providers/activity-provider";
+import { useSetActivitySelection } from "../../../../providers/activity-provider";
import { useSKWallet } from "../../../../providers/sk-wallet";
import { useActivityFilters } from "../hooks/use-activity-filters";
import type { ActionYieldDto } from "../types";
@@ -24,7 +24,7 @@ export const ActivityPageContext = createContext<
export const ActivityPageContextProvider = ({
children,
}: PropsWithChildren) => {
- const activityStore = useActivityContext();
+ const setActivitySelection = useSetActivitySelection();
const { openConnectModal } = useConnectModal();
const { isConnected } = useSKWallet();
const navigate = useNavigate();
@@ -36,14 +36,13 @@ export const ActivityPageContextProvider = ({
data.actionData.status === ActionStatus.SUCCESS ||
data.actionData.status === ActionStatus.PROCESSING
) {
- activityStore.send({
- type: "setSelectedAction",
- data: Maybe.of({
+ setActivitySelection(
+ Maybe.of({
selectedAction: data.actionData,
selectedYield: data.yieldData,
selectedValidators: data.validatorsData,
- }),
- });
+ })
+ );
const urls = data.actionData.transactions
.map((val) => ({ type: val.type, url: val.explorerUrl }))
@@ -70,14 +69,13 @@ export const ActivityPageContextProvider = ({
data.actionData.status === ActionStatus.WAITING_FOR_NEXT ||
data.actionData.status === ActionStatus.FAILED
) {
- activityStore.send({
- type: "setSelectedAction",
- data: Maybe.of({
+ setActivitySelection(
+ Maybe.of({
selectedAction: data.actionData,
selectedYield: data.yieldData,
selectedValidators: data.validatorsData,
- }),
- });
+ })
+ );
return navigate("/activity/review");
}
diff --git a/packages/widget/src/pages/details/earn-page/components/select-provider/index.tsx b/packages/widget/src/pages/details/earn-page/components/select-provider/index.tsx
index dfc5fe1b..4d4a31e3 100644
--- a/packages/widget/src/pages/details/earn-page/components/select-provider/index.tsx
+++ b/packages/widget/src/pages/details/earn-page/components/select-provider/index.tsx
@@ -14,10 +14,6 @@ import {
import { useMultiYields } from "../../../../../hooks/api/use-multi-yields";
import { formatCompactUsd } from "../../../../../utils/formatters";
import { useEarnPageContext } from "../../state/earn-page-context";
-import {
- useEarnPageDispatch,
- useEarnPageState,
-} from "../../state/earn-page-state-context";
import {
overflowEllipsis,
selectorSummaryCard,
@@ -45,9 +41,12 @@ const getProviderTvl = (tvlUsd: unknown) => {
};
export const SelectProvider = () => {
- const { selectedStake, selectedProviderYieldId } = useEarnPageState();
- const { appLoading } = useEarnPageContext();
- const dispatch = useEarnPageDispatch();
+ const {
+ appLoading,
+ onProviderYieldIdSelect,
+ selectedProviderYieldId,
+ selectedStake,
+ } = useEarnPageContext();
const { t } = useTranslation();
@@ -83,9 +82,7 @@ export const SelectProvider = () => {
providerSelection
.map((val) => (
- dispatch({ type: "providerYieldId/select", data: yieldDto.id })
- }
+ onItemClick={(yieldDto) => onProviderYieldIdSelect(yieldDto.id)}
providerYieldIds={val.providerYieldIdOptions}
selectedYieldId={val.selectedProviderYield.id}
trigger={
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 c57e6e3e..66b13d86 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
@@ -8,15 +8,15 @@ import {
} from "../../../../../components/atoms/select-modal";
import { TokenIcon } from "../../../../../components/atoms/token-icon";
import { Text } from "../../../../../components/atoms/typography/text";
-import type { TokenBalanceScanResponseDto } from "../../../../../domain/types/token-balance";
import { useTrackEvent } from "../../../../../hooks/tracking/use-track-event";
+import type { EarnTokenOption } from "../../state/effect-atom-poc/types";
import { selectItemText } from "../../styles.css";
type Props = {
- item: TokenBalanceScanResponseDto;
+ item: EarnTokenOption;
isConnected: boolean;
isSelected: boolean;
- onTokenBalanceSelect: (tokenBalance: TokenBalanceScanResponseDto) => void;
+ onTokenBalanceSelect: (tokenBalance: EarnTokenOption) => void;
};
export const SelectTokenListItem = memo(
diff --git a/packages/widget/src/pages/details/earn-page/earn.page.tsx b/packages/widget/src/pages/details/earn-page/earn.page.tsx
index 9876b9e8..43578174 100644
--- a/packages/widget/src/pages/details/earn-page/earn.page.tsx
+++ b/packages/widget/src/pages/details/earn-page/earn.page.tsx
@@ -22,7 +22,6 @@ import {
EarnPageContextProvider,
useEarnPageContext,
} from "./state/earn-page-context";
-import { EarnPageStateUsageBoundaryProvider } from "./state/earn-page-state-context";
const EarnKycGateSection = () => {
const { kycGate, kycGateIsChecking, kycProviderName, onKycStatusRefresh } =
@@ -89,11 +88,9 @@ const EarnPageComponent = () => {
};
const EarnPage = () => (
-
-
-
-
-
+
+
+
);
export const AnimatedEarnPage = () => {
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 91d17c2a..0a33869a 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
@@ -1,11 +1,13 @@
+import { useAtom, useAtomValue } from "@effect/atom-react";
import { useConnectModal } from "@stakekit/rainbowkit";
import { useMutation } from "@tanstack/react-query";
import BigNumber from "bignumber.js";
+import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
+import type * as Atom from "effect/unstable/reactivity/Atom";
import { List, Maybe } from "purify-ts";
import type { PropsWithChildren } from "react";
import {
createContext,
- useCallback,
useContext,
useDeferredValue,
useEffect,
@@ -21,12 +23,12 @@ import {
tokenString,
} from "../../../../domain";
import { getKycProviderName } from "../../../../domain/types/kyc";
-import { getInitSelectedValidators } from "../../../../domain/types/stake";
-import type { TokenBalanceScanResponseDto } from "../../../../domain/types/token-balance";
+import type { PositionsData } from "../../../../domain/types/positions";
import type { TronResourceType } from "../../../../domain/types/tron";
import {
type DashboardYieldCategory,
type ExtendedYieldType,
+ filterValidators,
getDashboardYieldCategory,
getExtendedYieldType,
getYieldRewardTokens,
@@ -35,17 +37,11 @@ import {
isBittensorStaking,
isNonZeroRewardRateYield,
isYieldActionArgRequired,
- isYieldValidatorSelectionRequired,
type YieldBase,
} from "../../../../domain/types/yields";
import type { ValidatorDto } from "../../../../generated/api/yield";
-import { useDefaultTokens } from "../../../../hooks/api/use-default-tokens";
-import { useStreamYieldSummaries } from "../../../../hooks/api/use-multi-yields";
-import { useTokenBalancesScan } from "../../../../hooks/api/use-token-balances-scan";
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 { useYieldValidators } from "../../../../hooks/api/use-yield-validators";
import { useNavigateWithScrollToTop } from "../../../../hooks/navigation/use-navigate-with-scroll-to-top";
import {
getPositionDetailsStakeReviewPath,
@@ -57,12 +53,12 @@ import { useDebouncedValue } from "../../../../hooks/use-debounced-value";
import { useEstimatedRewards } from "../../../../hooks/use-estimated-rewards";
import { useInitParams } from "../../../../hooks/use-init-params";
import { useMaxMinYieldAmount } from "../../../../hooks/use-max-min-yield-amount";
-import { usePositionsData } from "../../../../hooks/use-positions-data";
import { useProvidersDetails } from "../../../../hooks/use-provider-details";
import { useRewardTokenDetails } from "../../../../hooks/use-reward-token-details";
import { useSavedRef } from "../../../../hooks/use-saved-ref";
+import { useValidatorsConfig } from "../../../../hooks/use-validators-config";
import { useYieldType } from "../../../../hooks/use-yield-type";
-import { useEnterStakeStore } from "../../../../providers/enter-stake-store";
+import { useSetEnterStakeRequest } from "../../../../providers/enter-stake-store";
import { useMountAnimation } from "../../../../providers/mount-animation";
import { useSettings } from "../../../../providers/settings";
import { useSKWallet } from "../../../../providers/sk-wallet";
@@ -70,12 +66,11 @@ import { useWagmiConfig } from "../../../../providers/wagmi";
import { defaultFormattedNumber, formatNumber } from "../../../../utils";
import type { PageCta } from "../../../components/page-cta";
import type { SelectedStakeData } from "../types";
-import {
- useEarnPageDispatch,
- useEarnPageState,
-} from "./earn-page-state-context";
+import { YieldValidatorsPullKey } from "./effect-atom-poc/catalog/keys";
+import { useEarnMachine } from "./effect-atom-poc/hooks/use-earn-machine";
+import type { EarnTokenOption } from "./effect-atom-poc/types";
import type { EarnPageContextType } from "./types";
-import { useInitYield } from "./use-init-yield";
+import { useAmountValidation } from "./use-amount-validation";
import { usePendingActionDeepLink } from "./use-pending-action-deep-link";
import { useStakeEnterRequestDto } from "./use-stake-enter-request-dto";
@@ -83,53 +78,111 @@ const EarnPageContext = createContext(
undefined
);
+const getAsyncValue = (result: AsyncResult.AsyncResult) =>
+ AsyncResult.getOrElse(result, () => null as A | null);
+
+const getPullItems = (result: Atom.PullResult): A[] =>
+ getAsyncValue(result)?.items ?? [];
+
+const isAsyncErrorWithoutValue = (
+ result: AsyncResult.AsyncResult
+) => AsyncResult.isFailure(result) && getAsyncValue(result) === null;
+
+const maybeFromNullable = (value: A | null | undefined) =>
+ Maybe.fromNullable(value);
+
export const EarnPageContextProvider = ({
children,
registerFooterButton = true,
}: PropsWithChildren<{ registerFooterButton?: boolean }>) => {
- const {
- actions: { onMaxClick: _onMaxClick },
- selectedToken,
- selectedStakeId,
- autoSelectYield,
- selectedValidators,
- stakeAmount,
- selectedStake,
- tronResource,
- stakeAmountGreaterThanAvailableAmount,
- stakeAmountGreaterThanMax,
- stakeAmountLessThanMin,
- stakeAmountIsZero,
- availableAmount,
- availableYields,
- hasNotYieldsForToken,
- selectedProviderYieldId,
- selectedDashboardYieldCategory,
- availableDashboardYieldCategories,
- } = useEarnPageState();
- const dispatch = useEarnPageDispatch();
-
const { t } = useTranslation();
const initParams = useInitParams();
- const { dashboardVariant, externalProviders, variant, yieldGrouping } =
- useSettings();
+ const {
+ dashboardVariant,
+ dashboardYieldCategoryOrder,
+ externalProviders,
+ preferredTokenYieldsPerNetwork,
+ tokensForEnabledYieldsOnly,
+ variant,
+ yieldGrouping,
+ } = useSettings();
const dashboardYieldCategoryGroupingEnabled =
- dashboardVariant && yieldGrouping === "category";
+ !!dashboardVariant && yieldGrouping === "category";
+ const validatorsConfig = useValidatorsConfig();
+
+ const {
+ address,
+ additionalAddresses,
+ isConnected,
+ isConnecting,
+ isLedgerLiveAccountPlaceholder,
+ chain,
+ network,
+ } = useSKWallet();
+
+ const { view: machine, dispatch } = useEarnMachine({
+ address: isConnected ? address : null,
+ additionalAddresses,
+ categoryOrder: dashboardYieldCategoryOrder,
+ dashboardVariant: dashboardYieldCategoryGroupingEnabled,
+ initParams: initParams.data ?? null,
+ network: network ?? null,
+ preferredTokenYieldsPerNetwork: preferredTokenYieldsPerNetwork ?? null,
+ tokensForEnabledYieldsOnly: !!tokensForEnabledYieldsOnly,
+ });
- const { isConnected, isConnecting, isLedgerLiveAccountPlaceholder, chain } =
- useSKWallet();
+ const tokenOptions = useAtomValue(machine.resources.tokenOptionsAtom);
+ const [tokenOptionsPull, pullMoreTokens] = useAtom(
+ machine.resources.tokenOptionsPullAtom
+ );
+ const initYieldResult = useAtomValue(machine.resources.initYieldAtom);
+ const positionsDataResult = useAtomValue(machine.resources.positionsDataAtom);
+ const positionsData =
+ getAsyncValue(positionsDataResult) ?? (new Map() as PositionsData);
+
+ const selectedTokenOption = maybeFromNullable(machine.selection.token);
+ const selectedToken = selectedTokenOption.map((option) => option.token);
+ const selectedStake = maybeFromNullable(machine.selection.yield);
+ const selectedStakeId = machine.selection.yield?.id ?? null;
+ const filteredSelectedValidators = selectedStake
+ .map((yieldDto) =>
+ filterValidators({
+ validatorsConfig,
+ validators: [...machine.selection.validators],
+ network: yieldDto.token.network,
+ yieldId: yieldDto.id,
+ })
+ )
+ .orDefault([...machine.selection.validators]);
+ const selectedValidators = new Map(
+ filteredSelectedValidators.map((validator) => [
+ validator.address,
+ validator,
+ ])
+ );
+ const stakeAmount = new BigNumber(machine.form.stakeAmount);
+ const selectedProviderYieldId = maybeFromNullable(
+ machine.form.providerYieldId
+ );
+ const tronResource = maybeFromNullable(
+ machine.form.tronResource as TronResourceType | null
+ );
+ const selectedDashboardYieldCategory = machine.selection.category;
+ const availableDashboardYieldCategories =
+ dashboardYieldCategoryGroupingEnabled
+ ? [...machine.availableCategories]
+ : [];
+ const availableAmount = selectedTokenOption.map(
+ (tokenOption) => new BigNumber(tokenOption.amount)
+ );
+ const hasNotYieldsForToken = machine.status === "no-yields";
const yieldType = useYieldType(selectedStake).mapOrDefault(
(y) => y.title,
""
);
- const initYieldRes = useInitYield({
- selectedDashboardYieldCategory,
- selectedToken,
- });
-
const estimatedRewards = useEstimatedRewards({
selectedStake,
selectedValidators,
@@ -205,88 +258,45 @@ export const EarnPageContextProvider = ({
const validatorSearchDebouncing =
normalizedValidatorSearch !== debouncedValidatorSearch;
- const yieldSummaries = useStreamYieldSummaries(
- useMemo(() => availableYields.orDefault([]), [availableYields])
+ const validatorsResource = machine.resources.validators;
+ const validatorsPullAtom = validatorsResource.validatorsPullAtom(
+ new YieldValidatorsPullKey({
+ search: debouncedValidatorSearch || null,
+ })
+ );
+ const loadedValidatorsMap = useAtomValue(
+ validatorsResource.loadedValidatorsAtom
);
+ const [validatorsPullResult, pullMoreValidators] =
+ useAtom(validatorsPullAtom);
- const tokenBalancesScan = useTokenBalancesScan();
- const defaultTokens = useDefaultTokens({
- yieldCategory: dashboardYieldCategoryGroupingEnabled
- ? selectedDashboardYieldCategory
- : null,
- });
+ const yieldOptions = machine.resources.yieldsResult
+ ? (getAsyncValue(machine.resources.yieldsResult) ?? [])
+ : [];
const tokenBalancesData = useMemo(
() =>
- Maybe.fromRecord({
- defTb: Maybe.fromNullable(defaultTokens.data).alt(Maybe.of([])),
- tb: Maybe.fromNullable(tokenBalancesScan.data).alt(Maybe.of([])),
- })
- .map((val) => {
- const categoryTokenSet =
- dashboardYieldCategoryGroupingEnabled &&
- selectedDashboardYieldCategory
- ? new Set(val.defTb.map((item) => tokenString(item.token)))
- : null;
- const tokenBalancesScanData = categoryTokenSet
- ? val.tb.filter((item) =>
- categoryTokenSet.has(tokenString(item.token))
- )
- : val.tb;
-
- const { tbWithAmount, tbWithoutAmount, tbSet } =
- tokenBalancesScanData.reduce(
- (acc, b) => {
- acc.tbSet.add(tokenString(b.token));
-
- if (new BigNumber(b.amount).isGreaterThan(0)) {
- acc.tbWithAmount.push(b);
- } else {
- acc.tbWithoutAmount.push(b);
- }
-
- return acc;
- },
- {
- tbSet: new Set(),
- tbWithAmount: [] as TokenBalanceScanResponseDto[],
- tbWithoutAmount: [] as TokenBalanceScanResponseDto[],
- }
- );
-
- return [
- ...tbWithAmount,
- ...tbWithoutAmount,
- ...val.defTb.filter((t) => !tbSet.has(tokenString(t.token))),
- ];
- })
- .chain((tb) =>
- Maybe.of(deferredTokenSearch)
- .chain((val) =>
- val.length >= 1 ? Maybe.of(val.toLowerCase()) : Maybe.empty()
- )
- .map((lowerSearch) => ({
- all: tb,
- filtered: tb.filter(
- (t) =>
- t.token.name.toLowerCase().includes(lowerSearch) ||
- t.token.symbol.toLowerCase().includes(lowerSearch)
- ),
- }))
- .alt(Maybe.of({ all: tb, filtered: tb }))
- ),
- [
- dashboardYieldCategoryGroupingEnabled,
- defaultTokens.data,
- deferredTokenSearch,
- selectedDashboardYieldCategory,
- tokenBalancesScan.data,
- ]
+ Maybe.of([...tokenOptions.items]).chain((tokens) =>
+ Maybe.of(deferredTokenSearch)
+ .chain((val) =>
+ val.length >= 1 ? Maybe.of(val.toLowerCase()) : Maybe.empty()
+ )
+ .map((lowerSearch) => ({
+ all: tokens,
+ filtered: tokens.filter(
+ (t) =>
+ t.token.name.toLowerCase().includes(lowerSearch) ||
+ t.token.symbol.toLowerCase().includes(lowerSearch)
+ ),
+ }))
+ .alt(Maybe.of({ all: tokens, filtered: tokens }))
+ ),
+ [deferredTokenSearch, tokenOptions.items]
);
const selectedStakeData = useMemo>(
() =>
- Maybe.of(yieldSummaries)
+ Maybe.of([...yieldOptions])
.map((val) =>
selectedStake
.filter(
@@ -386,92 +396,45 @@ export const EarnPageContextProvider = ({
dashboardYieldCategoryGroupingEnabled,
deferredStakeSearch,
selectedStake,
- yieldSummaries,
+ yieldOptions,
selectedDashboardYieldCategory,
t,
]
);
- const shouldFetchValidators = selectedStake
- .map(isYieldValidatorSelectionRequired)
- .orDefault(false);
-
- const yieldValidators = useYieldValidators({
- enabled: shouldFetchValidators,
- yieldId: selectedStake.extract()?.id,
- network: selectedStake.extract()?.token.network,
- search: debouncedValidatorSearch,
- });
-
- useEffect(() => {
- const currentYieldId = selectedStake.map((val) => val.id).extractNullable();
-
- if (!currentYieldId) {
- return;
- }
-
- if (selectedValidators.size > 0) {
- return;
- }
-
- const shouldSelectDefaultValidator = selectedStake
- .map(isYieldValidatorSelectionRequired)
- .orDefault(false);
-
- if (!shouldSelectDefaultValidator) {
- return;
- }
-
- if (!yieldValidators.isFetched && !yieldValidators.isError) {
- return;
- }
-
- const nextValidator = List.head([
- ...getInitSelectedValidators({
- initQueryParams: Maybe.fromNullable(initParams.data),
- validators: yieldValidators.data ?? [],
- }).values(),
- ]).extractNullable();
-
- if (nextValidator) {
- dispatch({ type: "validator/select", data: nextValidator });
- }
- }, [
- dispatch,
- initParams.data,
- selectedStake,
- selectedValidators,
- yieldValidators.data,
- yieldValidators.isError,
- yieldValidators.isFetched,
- ]);
+ const shouldFetchValidators = validatorsResource.enabled;
const validatorsData = useMemo(
() =>
selectedStake
.filter(() => shouldFetchValidators)
- .chain(() =>
- Maybe.fromNullable(yieldValidators.data).map((validators) => {
- if (
- dashboardVariant ||
- variant === "utila" ||
- variant === "porto"
- ) {
- return [...validators].sort(
- (a, b) =>
- (b.rewardRate?.total ?? 0) - (a.rewardRate?.total ?? 0)
- );
- }
+ .map((yieldDto) => {
+ const validators = filterValidators({
+ validatorsConfig,
+ validators: debouncedValidatorSearch
+ ? getPullItems(validatorsPullResult)
+ : [...loadedValidatorsMap.values()],
+ network: yieldDto.token.network,
+ yieldId: yieldDto.id,
+ });
+
+ if (dashboardVariant || variant === "utila" || variant === "porto") {
+ return [...validators].sort(
+ (a, b) => (b.rewardRate?.total ?? 0) - (a.rewardRate?.total ?? 0)
+ );
+ }
- return validators;
- })
- ),
+ return validators;
+ }),
[
dashboardVariant,
+ debouncedValidatorSearch,
+ loadedValidatorsMap,
selectedStake,
shouldFetchValidators,
variant,
- yieldValidators.data,
+ validatorsConfig,
+ validatorsPullResult,
]
);
@@ -484,16 +447,14 @@ export const EarnPageContextProvider = ({
const onValidatorSearch: SelectModalProps["onSearch"] = (val) =>
setValidatorSearch(val);
- const onTokenBalanceSelect = useCallback(
- (tokenBalance: TokenBalanceScanResponseDto) =>
- dispatch({ type: "tokenBalance/select", data: tokenBalance }),
- [dispatch]
- );
+ const onTokenBalanceSelect = (tokenBalance: EarnTokenOption) =>
+ dispatch({
+ type: "token/select",
+ tokenKey: tokenString(tokenBalance.token),
+ });
const onYieldSelect = (yieldId: string) => {
- Maybe.fromNullable(yieldSummaries)
- .chain((val) => List.find((v) => v.id === yieldId, val))
- .ifJust((val) => dispatch({ type: "yield/select", data: val }));
+ dispatch({ type: "yield/select", yieldId });
};
const onDashboardYieldCategorySelect = (category: DashboardYieldCategory) => {
@@ -502,25 +463,39 @@ export const EarnPageContextProvider = ({
if (selectedDashboardYieldCategory === category) return;
dispatch({
- type: "dashboard/yield-category/select",
- data: category,
+ type: "category/select",
+ category,
});
};
const onValidatorSelect = (item: ValidatorDto) =>
selectedStake.ifJust((ss) =>
isYieldActionArgRequired(ss, "enter", "validatorAddresses")
- ? dispatch({ type: "validator/multiselect", data: item })
- : dispatch({ type: "validator/select", data: item })
+ ? dispatch({
+ type: "validator/multiselect",
+ validatorKey: item.address,
+ })
+ : dispatch({ type: "validator/select", validatorKey: item.address })
);
const onValidatorRemove = (item: ValidatorDto) =>
- dispatch({ type: "validator/remove", data: item });
+ dispatch({ type: "validator/remove", validatorKey: item.address });
const onStakeAmountChange: NumberInputProps["onChange"] = (val) =>
- dispatch({ type: "stakeAmount/change", data: val });
+ dispatch({ type: "stakeAmount/change", amount: val.toString(10) });
+
+ const onProviderYieldIdSelect = (yieldId: string) =>
+ dispatch({ type: "providerYieldId/select", providerYieldId: yieldId });
- const stakeEnterRequestDto = useStakeEnterRequestDto();
+ const stakeEnterRequestDto = useStakeEnterRequestDto({
+ selectedProviderYieldId,
+ selectedStake,
+ selectedToken,
+ selectedValidators,
+ stakeAmount,
+ tronResource,
+ useMaxAmount: machine.form.useMaxAmount,
+ });
const yieldKycGate = useYieldKycGate({ yieldDto: selectedStake });
const kycGateIsBlocking = yieldKycGate.isGateBlocking;
const kycProviderName = selectedStake
@@ -536,7 +511,7 @@ export const EarnPageContextProvider = ({
balanceId: positionDetailsStakeMatch?.params.balanceId,
integrationId: positionDetailsStakeMatch?.params.integrationId,
});
- const enterStakeStore = useEnterStakeStore();
+ const setEnterStakeRequest = useSetEnterStakeRequest();
const onClickHandler = useMutation({
mutationFn: async () => {
@@ -551,17 +526,17 @@ export const EarnPageContextProvider = ({
selectedToken,
}).unsafeCoerce();
- enterStakeStore.send({
- type: "initFlow",
- data: {
+ setEnterStakeRequest(
+ Maybe.of({
+ actionDto: Maybe.empty(),
addresses: val.stakeEnterRequestDto.addresses,
requestDto: val.stakeEnterRequestDto.dto,
selectedToken: val.selectedToken,
gasFeeToken: val.stakeEnterRequestDto.gasFeeToken,
selectedStake: val.stakeEnterRequestDto.selectedStake,
selectedValidators: val.stakeEnterRequestDto.selectedValidators,
- },
- });
+ })
+ );
navigate(positionDetailsStakeReviewPath ?? "/review");
},
});
@@ -571,7 +546,32 @@ export const EarnPageContextProvider = ({
// biome-ignore lint: false
useEffect(() => {
onClickHandlerResetRef.current();
- }, [isConnected, selectedStake, onClickHandlerResetRef]);
+ }, [isConnected, selectedStakeId, onClickHandlerResetRef]);
+
+ const {
+ maxIntegrationAmount,
+ minIntegrationAmount,
+ minEnterOrExitAmount,
+ maxEnterOrExitAmount,
+ isForceMax,
+ } = useMaxMinYieldAmount({
+ type: "enter",
+ yieldOpportunity: selectedStake,
+ availableAmount,
+ positionsData,
+ });
+
+ const {
+ stakeAmountGreaterThanAvailableAmount,
+ stakeAmountGreaterThanMax,
+ stakeAmountLessThanMin,
+ stakeAmountIsZero,
+ } = useAmountValidation({
+ availableAmount,
+ stakeAmount,
+ maxEnterOrExitAmount,
+ minEnterOrExitAmount,
+ });
const validation = useMemo(() => {
const val = {
@@ -620,19 +620,6 @@ export const EarnPageContextProvider = ({
tronResource,
]);
- const {
- maxIntegrationAmount,
- minIntegrationAmount,
- minEnterOrExitAmount,
- maxEnterOrExitAmount,
- isForceMax,
- } = useMaxMinYieldAmount({
- type: "enter",
- yieldOpportunity: selectedStake,
- availableAmount,
- positionsData: usePositionsData().data,
- });
-
const stakeMaxAmount = useMemo(
() =>
selectedStake
@@ -659,9 +646,9 @@ export const EarnPageContextProvider = ({
const { state } = useMountAnimation();
- const yieldOpportunityLoading = useYieldOpportunity(
- selectedStake.extract()?.id
- ).isLoading;
+ const yieldOpportunityLoading =
+ machine.status === "loading-yields" ||
+ !!machine.resources.yieldsResult?.waiting;
const appLoading =
selectedToken.isNothing() ||
@@ -671,12 +658,28 @@ export const EarnPageContextProvider = ({
isConnecting ||
!state.layout;
- const tokenBalancesScanLoading = tokenBalancesScan.isLoading;
- const defaultTokensIsLoading = defaultTokens.isLoading;
-
- const isFetching = tokenBalancesScan.isFetching;
-
- const isError = !tokenBalancesScan.data && tokenBalancesScan.isError;
+ const tokenBalancesScanLoading =
+ tokenOptions.balancesResult.waiting &&
+ tokenOptions.balanceItems.length === 0;
+ const defaultTokensIsLoading =
+ tokenOptions.defaultResult.waiting &&
+ tokenOptions.defaultItems.length === 0;
+
+ const isFetching =
+ tokenOptions.defaultResult.waiting ||
+ tokenOptions.balancesResult.waiting ||
+ initYieldResult.waiting ||
+ positionsDataResult.waiting ||
+ !!machine.resources.yieldsResult?.waiting;
+
+ const isError =
+ isAsyncErrorWithoutValue(tokenOptions.defaultResult) ||
+ isAsyncErrorWithoutValue(tokenOptions.balancesResult) ||
+ isAsyncErrorWithoutValue(initYieldResult) ||
+ isAsyncErrorWithoutValue(positionsDataResult) ||
+ (machine.resources.yieldsResult
+ ? isAsyncErrorWithoutValue(machine.resources.yieldsResult)
+ : false);
const buttonDisabled =
isConnected &&
@@ -697,11 +700,14 @@ export const EarnPageContextProvider = ({
const onMaxClick = () => {
trackEvent("earnPageMaxClicked");
- _onMaxClick();
+ dispatch({
+ type: "stakeAmount/max",
+ amount: maxEnterOrExitAmount.toString(10),
+ });
};
const onTronResourceSelect = (value: TronResourceType) =>
- dispatch({ type: "tronResource/select", data: value });
+ dispatch({ type: "tronResource/select", tronResource: value });
const onClickRef = useSavedRef(onClickHandler.mutate);
@@ -730,12 +736,26 @@ export const EarnPageContextProvider = ({
[selectedStake, selectedToken]
);
+ const tokenPullValue = getAsyncValue(tokenOptionsPull);
+ const validatorPullValue = getAsyncValue(validatorsPullResult);
+ const hasMoreTokens = tokenPullValue?.done === false;
+ const hasMoreValidators = validatorPullValue?.done === false;
+ const isLoadingMoreTokens =
+ tokenOptionsPull.waiting && getPullItems(tokenOptionsPull).length > 0;
+ const isLoadingMoreValidators =
+ validatorsPullResult.waiting &&
+ getPullItems(validatorsPullResult).length > 0;
+ const onLoadMoreTokens = () => pullMoreTokens();
+ const onLoadMoreValidators = () => pullMoreValidators();
+
const selectTokenIsLoading =
- tokenBalancesScanLoading || defaultTokensIsLoading;
+ machine.status === "loading-token-options" ||
+ machine.status === "loading-initial-selection" ||
+ tokenBalancesScanLoading ||
+ defaultTokensIsLoading;
const selectYieldIsLoading =
- (autoSelectYield && selectedStakeId.isNothing() && !hasNotYieldsForToken) ||
- initYieldRes.isLoading ||
+ machine.status === "loading-initial-selection" ||
yieldOpportunityLoading ||
tokenBalancesScanLoading ||
defaultTokensIsLoading;
@@ -743,15 +763,15 @@ export const EarnPageContextProvider = ({
const selectValidatorIsLoading =
defaultTokensIsLoading ||
tokenBalancesScanLoading ||
- initYieldRes.isLoading ||
yieldOpportunityLoading ||
validatorSearchDebouncing ||
- (shouldFetchValidators && yieldValidators.isLoading);
+ (shouldFetchValidators &&
+ validatorsPullResult.waiting &&
+ getPullItems(validatorsPullResult).length === 0);
const footerIsLoading =
defaultTokensIsLoading ||
tokenBalancesScanLoading ||
- initYieldRes.isLoading ||
yieldOpportunityLoading;
const cta = useMemo(
@@ -795,18 +815,22 @@ export const EarnPageContextProvider = ({
);
const value = {
+ machine,
+ machineStatus: machine.status,
cta,
selectedTokenAvailableAmount,
formattedPrice,
symbol,
selectedStakeData,
selectedStake,
+ selectedProviderYieldId,
selectedDashboardYieldCategory,
availableDashboardYieldCategories,
onDashboardYieldCategorySelect,
onYieldSelect,
onTokenBalanceSelect,
onStakeAmountChange,
+ onProviderYieldIdSelect,
estimatedRewards,
yieldType,
onMaxClick,
@@ -842,10 +866,10 @@ export const EarnPageContextProvider = ({
tokenSearch,
stakeSearch,
defaultTokensIsLoading,
- hasMoreTokens: !!defaultTokens.hasNextPage,
+ hasMoreTokens,
isLedgerLiveAccountPlaceholder,
- isLoadingMoreTokens: defaultTokens.isFetchingNextPage,
- onLoadMoreTokens: defaultTokens.fetchNextPage,
+ isLoadingMoreTokens,
+ onLoadMoreTokens,
tronResource,
onTronResourceSelect,
validation,
@@ -858,9 +882,9 @@ export const EarnPageContextProvider = ({
stakeMinAmount,
selectedToken,
validatorsData,
- hasMoreValidators: !!yieldValidators.hasNextPage,
- isLoadingMoreValidators: yieldValidators.isFetchingNextPage,
- onLoadMoreValidators: yieldValidators.fetchNextPage,
+ hasMoreValidators,
+ isLoadingMoreValidators,
+ onLoadMoreValidators,
validatorSearch,
hasNotYieldsForToken,
isStakeTokenSameAsGasToken,
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
deleted file mode 100644
index 30417ae9..00000000
--- a/packages/widget/src/pages/details/earn-page/state/earn-page-state-context.tsx
+++ /dev/null
@@ -1,581 +0,0 @@
-import BigNumber from "bignumber.js";
-import { Maybe } from "purify-ts";
-import type { Dispatch, PropsWithChildren } from "react";
-import {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useReducer,
-} from "react";
-import { equalTokens } from "../../../../domain";
-import type { Networks } from "../../../../domain/types/chains/networks";
-import { isNetworkWithEnterMinBasedOnPosition } from "../../../../domain/types/stake";
-import type { TokenBalanceScanResponseDto } from "../../../../domain/types/token-balance";
-import type { TokenDto } from "../../../../domain/types/tokens";
-import {
- type DashboardYieldCategory,
- getDashboardYieldCategory,
- type Yield,
-} from "../../../../domain/types/yields";
-import { useDashboardYieldCatalog } from "../../../../hooks/api/use-dashboard-yield-catalog";
-import { useYieldOpportunity } from "../../../../hooks/api/use-yield-opportunity";
-import { useMaxMinYieldAmount } from "../../../../hooks/use-max-min-yield-amount";
-import { usePositionsData } from "../../../../hooks/use-positions-data";
-import { useSavedRef } from "../../../../hooks/use-saved-ref";
-import { useSettings } from "../../../../providers/settings";
-import { useSKWallet } from "../../../../providers/sk-wallet";
-import type { Actions, ExtraData, State } from "./types";
-import { useAmountValidation } from "./use-amount-validation";
-import { useGetInitYield } from "./use-get-init-yield";
-import { useInitToken } from "./use-init-token";
-import { useInitYield } from "./use-init-yield";
-import { useTokenBalance } from "./use-token-balance";
-import { useTrackStateEvents } from "./use-track-state-events";
-import { onYieldSelectState } from "./utils";
-
-const EarnPageStateContext = createContext<(State & ExtraData) | undefined>(
- undefined
-);
-const EarnPageDispatchContext = createContext | undefined>(
- undefined
-);
-const EarnPageStateUsageBoundary = createContext(false);
-
-export const EarnPageStateUsageBoundaryProvider = ({
- children,
-}: PropsWithChildren) => {
- return (
-
- {children}
-
- );
-};
-
-const getInitialState = (): State => ({
- selectedToken: Maybe.empty(),
- selectedStakeId: Maybe.empty(),
- autoSelectYield: true,
- selectedValidators: new Map(),
- stakeAmount: new BigNumber(0),
- useMaxAmount: false,
- tronResource: Maybe.empty(),
- selectedProviderYieldId: Maybe.empty(),
- selectedDashboardYieldCategory: null,
-});
-
-export const EarnPageStateProvider = ({ children }: PropsWithChildren) => {
- const { network, isConnected } = useSKWallet();
- const { dashboardVariant, yieldGrouping } = useSettings();
- const dashboardYieldCategoryGroupingEnabled =
- dashboardVariant && yieldGrouping === "category";
- const dashboardYieldCategorySelectionEnabled =
- !dashboardVariant || dashboardYieldCategoryGroupingEnabled;
-
- const getInitYield = useGetInitYield();
-
- const positionsData = usePositionsData();
- const dashboardYieldCatalog = useDashboardYieldCatalog({
- enabled: dashboardYieldCategoryGroupingEnabled,
- });
-
- const reducer = (state: State, action: Actions): State => {
- const onTokenSelectState = ({
- availableYields,
- token,
- }: {
- availableYields?: TokenBalanceScanResponseDto["availableYields"];
- token: TokenDto;
- }) =>
- Maybe.fromFalsy(
- state.selectedToken.map((v) => !equalTokens(v, token)).orDefault(true)
- )
- .chain(() =>
- getInitYield({
- availableYields,
- selectedDashboardYieldCategory:
- dashboardYieldCategorySelectionEnabled
- ? state.selectedDashboardYieldCategory
- : null,
- selectedToken: token,
- })
- .map<{
- selectedDashboardYieldCategory: DashboardYieldCategory | null;
- yieldState: ReturnType | null;
- }>((yieldDto) => ({
- selectedDashboardYieldCategory:
- dashboardYieldCategorySelectionEnabled
- ? state.selectedDashboardYieldCategory
- : null,
- yieldState: onYieldSelectState({
- yieldDto,
- positionsData: positionsData.data,
- }),
- }))
- .alt(
- Maybe.of({
- selectedDashboardYieldCategory:
- dashboardYieldCategorySelectionEnabled
- ? state.selectedDashboardYieldCategory
- : null,
- yieldState: null,
- })
- )
- )
- .map(({ selectedDashboardYieldCategory, yieldState }) => ({
- ...getInitialState(),
- selectedToken: Maybe.of(token),
- selectedDashboardYieldCategory,
- ...yieldState,
- }))
- .orDefault(state);
-
- switch (action.type) {
- case "token/select": {
- return onTokenSelectState({ token: action.data });
- }
-
- case "tokenBalance/select": {
- return onTokenSelectState({
- availableYields: action.data.availableYields,
- token: action.data.token,
- });
- }
-
- case "dashboard/yield-category/select": {
- if (!dashboardYieldCategoryGroupingEnabled) return state;
-
- const target = dashboardYieldCatalog.initialSelectionByCategory.get(
- action.data
- );
-
- if (!target) {
- return {
- ...state,
- selectedDashboardYieldCategory: action.data,
- };
- }
-
- return Maybe.fromFalsy(
- state.selectedDashboardYieldCategory !== action.data ||
- state.selectedToken
- .map((v) => !equalTokens(v, target.token))
- .orDefault(true) ||
- state.selectedStakeId
- .map((v) => v !== target.yieldDto.id)
- .orDefault(true)
- )
- .map(() =>
- onYieldSelectState({
- yieldDto: target.yieldDto,
- positionsData: positionsData.data,
- })
- )
- .map((val) => ({
- ...getInitialState(),
- selectedToken: Maybe.of(target.token),
- selectedDashboardYieldCategory: action.data,
- ...val,
- }))
- .orDefault(state);
- }
-
- case "positionDetails/stake/initialize": {
- return Maybe.fromFalsy(
- state.selectedToken
- .map((v) => !equalTokens(v, action.data.token))
- .orDefault(true) ||
- state.selectedStakeId
- .map((v) => v !== action.data.id)
- .orDefault(true)
- )
- .map(() =>
- onYieldSelectState({
- yieldDto: action.data,
- positionsData: positionsData.data,
- })
- )
- .map((val) => ({
- ...getInitialState(),
- selectedToken: Maybe.of(action.data.token),
- selectedDashboardYieldCategory:
- dashboardYieldCategorySelectionEnabled
- ? getDashboardYieldCategory(action.data)
- : null,
- ...val,
- }))
- .orDefault(state);
- }
-
- case "yield/select": {
- return Maybe.fromFalsy(
- state.selectedStakeId.map((v) => v !== action.data.id).orDefault(true)
- )
- .map(() =>
- onYieldSelectState({
- yieldDto: action.data,
- positionsData: positionsData.data,
- })
- )
- .map((val) => ({
- ...getInitialState(),
- selectedToken: state.selectedToken,
- selectedDashboardYieldCategory:
- dashboardYieldCategorySelectionEnabled
- ? getDashboardYieldCategory(action.data)
- : null,
- ...val,
- }))
- .orDefault(state);
- }
-
- case "validator/select": {
- const selectedValidators = new Map();
- selectedValidators.set(action.data.address, action.data);
-
- return {
- ...state,
- selectedValidators,
- };
- }
-
- case "validator/multiselect": {
- const newMap = new Map(state.selectedValidators);
-
- if (newMap.has(action.data.address)) {
- newMap.delete(action.data.address);
- } else {
- newMap.set(action.data.address, action.data);
- }
-
- if (newMap.size === 0) return state;
-
- return {
- ...state,
- selectedValidators: newMap,
- };
- }
-
- case "providerYieldId/select": {
- return { ...state, selectedProviderYieldId: Maybe.of(action.data) };
- }
-
- case "validator/remove": {
- const selectedValidators = new Map(state.selectedValidators);
- selectedValidators.delete(action.data.address);
-
- return {
- ...state,
- selectedValidators,
- };
- }
-
- case "stakeAmount/change": {
- return {
- ...state,
- stakeAmount: action.data,
- useMaxAmount: false,
- };
- }
-
- case "stakeAmount/max": {
- return {
- ...state,
- stakeAmount: action.data,
- useMaxAmount: true,
- };
- }
-
- case "tronResource/select": {
- return { ...state, tronResource: Maybe.of(action.data) };
- }
-
- case "state/reset": {
- return getInitialState();
- }
-
- default:
- return state;
- }
- };
-
- const [state, dispatch] = useReducer(reducer, getInitialState());
-
- const {
- selectedToken,
- selectedStakeId,
- autoSelectYield,
- selectedValidators,
- stakeAmount: _stakeAmount,
- useMaxAmount,
- tronResource,
- selectedProviderYieldId,
- selectedDashboardYieldCategory: selectedDashboardYieldCategoryFallback,
- } = state;
-
- const initTokenRes = useInitToken();
- const initToken = useMemo(
- () => Maybe.fromNullable(initTokenRes.data),
- [initTokenRes.data]
- );
-
- const initYieldRes = useInitYield({
- selectedDashboardYieldCategory: dashboardYieldCategoryGroupingEnabled
- ? selectedDashboardYieldCategoryFallback
- : null,
- selectedToken,
- });
- const initYield = useMemo(
- () => Maybe.fromNullable(initYieldRes.data),
- [initYieldRes.data]
- );
-
- const yieldOpportunity = useYieldOpportunity(selectedStakeId.extract());
-
- const selectedStake = useMemo(
- () => Maybe.fromNullable(yieldOpportunity.data),
- [yieldOpportunity.data]
- );
- const selectedStakeDashboardYieldCategory = selectedStake
- .chainNullable(getDashboardYieldCategory)
- .extractNullable();
- const selectedDashboardYieldCategory = dashboardYieldCategorySelectionEnabled
- ? (selectedStakeDashboardYieldCategory ??
- selectedDashboardYieldCategoryFallback)
- : null;
-
- const { availableAmount, availableYields } = useTokenBalance({
- selectedDashboardYieldCategory: dashboardYieldCategoryGroupingEnabled
- ? selectedDashboardYieldCategory
- : null,
- selectedToken,
- });
-
- const { minEnterOrExitAmount, maxEnterOrExitAmount, isForceMax } =
- useMaxMinYieldAmount({
- type: "enter",
- yieldOpportunity: Maybe.fromNullable(yieldOpportunity.data),
- availableAmount,
- positionsData: positionsData.data,
- });
-
- /**
- * If stake amount is less then min, use min
- */
- const stakeAmount = useMemo(
- () => (isForceMax ? maxEnterOrExitAmount : _stakeAmount),
- [isForceMax, maxEnterOrExitAmount, _stakeAmount]
- );
-
- const setToken = useCallback(
- (token: TokenDto) => dispatch({ type: "token/select", data: token }),
- []
- );
-
- const setYield = useCallback(
- (yieldDto: Yield) => dispatch({ type: "yield/select", data: yieldDto }),
- []
- );
-
- /**
- * Reset selectedToken if we changed network
- */
- selectedToken.ifJust((ss) => {
- if (network && ss.network !== network) {
- dispatch({ type: "state/reset" });
- }
- });
-
- const selectedTokenRef = useSavedRef(selectedToken);
- const initTokenRef = useSavedRef(initToken);
- const initYieldRef = useSavedRef(initYield);
-
- const hasNotYieldsForToken =
- !initYieldRes.isLoading &&
- !initYieldRes.data &&
- initToken.isJust() &&
- selectedToken.isJust() &&
- initYield.isNothing() &&
- selectedStakeId.isNothing();
-
- useEffect(() => {
- if (!isConnected && selectedTokenRef.current.isJust()) {
- dispatch({ type: "state/reset" });
- }
- }, [selectedTokenRef, isConnected]);
-
- const shouldWaitForPositionsData = useMemo(
- () =>
- initToken
- .filter(
- (it) =>
- isNetworkWithEnterMinBasedOnPosition(it.network as Networks) &&
- positionsData.isPending
- )
- .isJust(),
- [initToken, positionsData]
- );
-
- /**
- * Set initial token
- */
- useEffect(() => {
- if (shouldWaitForPositionsData) return;
-
- initToken.ifJust(setToken);
- }, [shouldWaitForPositionsData, initToken, setToken]);
-
- useEffect(() => {
- if (shouldWaitForPositionsData) return;
-
- selectedToken.ifNothing(() => initTokenRef.current.ifJust(setToken));
- }, [shouldWaitForPositionsData, initTokenRef, selectedToken, setToken]);
-
- /**
- * Set initial yield
- */
- useEffect(() => {
- if (shouldWaitForPositionsData) return;
-
- if (!autoSelectYield) return;
-
- selectedStakeId.ifNothing(() => initYield.ifJust(setYield));
- }, [
- autoSelectYield,
- initYield,
- selectedStakeId,
- shouldWaitForPositionsData,
- setYield,
- ]);
-
- useEffect(() => {
- if (shouldWaitForPositionsData) return;
- if (!autoSelectYield) return;
-
- selectedStakeId.ifNothing(() => initYieldRef.current.ifJust(setYield));
- }, [
- autoSelectYield,
- initYieldRef,
- shouldWaitForPositionsData,
- selectedStakeId,
- setYield,
- ]);
-
- /**
- * Reset selectedToken if we dont have initToken
- * Case when we changed account, but we dont have available yields
- */
- useEffect(() => {
- initToken.ifNothing(() =>
- selectedToken.ifJust(() => dispatch({ type: "state/reset" }))
- );
- }, [initToken, selectedToken]);
-
- useTrackStateEvents({ initToken, initYield });
-
- const actions = useMemo(
- () => ({
- onMaxClick: () =>
- dispatch({ type: "stakeAmount/max", data: maxEnterOrExitAmount }),
- }),
- [maxEnterOrExitAmount]
- );
-
- const {
- stakeAmountGreaterThanAvailableAmount,
- stakeAmountGreaterThanMax,
- stakeAmountLessThanMin,
- stakeAmountIsZero,
- } = useAmountValidation({
- availableAmount,
- stakeAmount,
- maxEnterOrExitAmount,
- minEnterOrExitAmount,
- });
-
- const value: State & ExtraData = useMemo(
- () => ({
- selectedStakeId,
- autoSelectYield,
- selectedStake,
- selectedValidators,
- stakeAmount,
- useMaxAmount,
- actions,
- tronResource,
- stakeAmountGreaterThanAvailableAmount,
- stakeAmountGreaterThanMax,
- stakeAmountLessThanMin,
- stakeAmountIsZero,
- availableAmount,
- availableYields,
- selectedToken,
- hasNotYieldsForToken,
- selectedProviderYieldId,
- selectedDashboardYieldCategory,
- availableDashboardYieldCategories: dashboardYieldCategoryGroupingEnabled
- ? dashboardYieldCatalog.availableCategories
- : [],
- }),
- [
- selectedStakeId,
- autoSelectYield,
- selectedStake,
- selectedToken,
- selectedValidators,
- stakeAmount,
- useMaxAmount,
- actions,
- tronResource,
- stakeAmountGreaterThanAvailableAmount,
- stakeAmountGreaterThanMax,
- stakeAmountLessThanMin,
- stakeAmountIsZero,
- availableAmount,
- availableYields,
- hasNotYieldsForToken,
- selectedProviderYieldId,
- selectedDashboardYieldCategory,
- dashboardYieldCategoryGroupingEnabled,
- dashboardYieldCatalog.availableCategories,
- ]
- );
-
- return (
-
-
- {children}
-
-
- );
-};
-
-const useUsageCheck = () => {
- const check = useContext(EarnPageStateUsageBoundary);
- if (!check) {
- throw new Error("hook must be used withing EarnPageStateUsageBoundary");
- }
-};
-
-export const useEarnPageState = () => {
- useUsageCheck();
-
- const state = useContext(EarnPageStateContext);
- if (state === undefined) {
- throw new Error("useState must be used within a StateProvider");
- }
-
- return state;
-};
-
-export const useEarnPageDispatch = () => {
- useUsageCheck();
-
- const dispatch = useContext(EarnPageDispatchContext);
- if (dispatch === undefined) {
- throw new Error("useDispatch must be used within a StateProvider");
- }
-
- return dispatch;
-};
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/atoms.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/atoms.ts
new file mode 100644
index 00000000..47b942c1
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/atoms.ts
@@ -0,0 +1,691 @@
+import BigNumber from "bignumber.js";
+import { Effect, Option, Stream } from "effect";
+import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
+import * as Atom from "effect/unstable/reactivity/Atom";
+import { config } from "../../../../../../config";
+import { tokenString } from "../../../../../../domain";
+import type { Networks } from "../../../../../../domain/types/chains/networks";
+import {
+ type BalanceDataKey,
+ getPositionBalanceDataKey,
+ type PositionsData,
+ type YieldBalanceDto,
+ type YieldBalancesByYieldDto,
+} from "../../../../../../domain/types/positions";
+import type { TokenBalanceScanDto } from "../../../../../../domain/types/token-balance";
+import type { TokenDto } from "../../../../../../domain/types/tokens";
+import {
+ type DashboardYieldCategory,
+ getApiYieldTypesForDashboardCategory,
+ isNonZeroRewardRateYield,
+} from "../../../../../../domain/types/yields";
+import type {
+ BalancesRequestDto,
+ ValidatorDto,
+ YieldDto,
+} from "../../../../../../generated/api/yield";
+import { StakeKitApiService, widgetAtomRuntime } from "../runtime";
+import {
+ EarnCatalogError,
+ type EarnCatalogOperation,
+ type EarnCatalogUnderlyingError,
+ type EarnTokenOption,
+ type EarnTokenOptionsState,
+ type EarnValidatorsPullParams,
+} from "../types";
+import {
+ type AvailableYieldCategoriesKey,
+ DefaultTokenOptionsKey,
+ InitTokenOptionKey,
+ InitYieldKey,
+ type PositionsDataKey,
+ TokenBalancesScanKey,
+ type TokenOptionsKey,
+ type YieldCatalogKey,
+ type YieldValidatorsKey,
+} from "./keys";
+import { loadAllPages } from "./utilities";
+
+const catalogSWR = Atom.swr({
+ staleTime: config.queryClient.staleTime,
+ revalidateOnMount: true,
+});
+
+const DEFAULT_PAGE_SIZE = 100;
+const PREFERRED_PAGE_CONCURRENCY = 5;
+
+const toCatalogError =
+ (operation: EarnCatalogOperation) => (cause: EarnCatalogUnderlyingError) =>
+ new EarnCatalogError({ operation, cause });
+
+const withCatalogError =
+ (operation: EarnCatalogOperation) =>
+ (
+ effect: Effect.Effect
+ ) =>
+ effect.pipe(Effect.mapError(toCatalogError(operation)));
+
+const mapCatalogStreamError =
+ (operation: EarnCatalogOperation) =>
+ (
+ stream: Stream.Stream
+ ) =>
+ stream.pipe(Stream.mapError(toCatalogError(operation)));
+
+const toNetworksParam = (network: Networks | null) =>
+ network ? ([network] as const) : undefined;
+
+const toYieldTypesParam = (category: DashboardYieldCategory | null) =>
+ category ? getApiYieldTypesForDashboardCategory(category) : undefined;
+
+const shouldUseYieldTokensApi = ({
+ category,
+ tokensForEnabledYieldsOnly,
+}: {
+ category: DashboardYieldCategory | null;
+ tokensForEnabledYieldsOnly: boolean;
+}) => tokensForEnabledYieldsOnly || !!toYieldTypesParam(category)?.length;
+
+const getNextOffset = ({
+ itemsLength,
+ limit,
+ offset,
+ total,
+}: {
+ itemsLength: number;
+ limit: number;
+ offset: number;
+ total: number;
+}) => {
+ const nextOffset = offset + itemsLength;
+ return nextOffset < total && itemsLength >= limit
+ ? Option.some(nextOffset)
+ : Option.none();
+};
+
+const getBalanceValidators = (balance: YieldBalanceDto) =>
+ balance.validators ?? (balance.validator ? [balance.validator] : []);
+
+const toPositionsData = (balancesData: YieldBalancesByYieldDto[]) =>
+ balancesData.reduce((acc, val) => {
+ acc.set(val.yieldId, {
+ yieldId: val.yieldId,
+ rewardRate: val.rewardRate,
+ balanceData: [...val.balances]
+ .sort((a, b) =>
+ getPositionBalanceDataKey(a).localeCompare(
+ getPositionBalanceDataKey(b)
+ )
+ )
+ .reduce(
+ (acc, balance) => {
+ const key = getPositionBalanceDataKey(balance);
+ const prev = acc.get(key);
+ const validators = getBalanceValidators(balance);
+
+ if (prev) {
+ prev.balances.push(balance);
+ } else if (key === "default") {
+ acc.set(key, {
+ balances: [balance],
+ type: "default",
+ });
+ } else {
+ acc.set(key, {
+ balances: [balance],
+ type: "validators",
+ validators,
+ });
+ }
+
+ return acc;
+ },
+ new Map<
+ BalanceDataKey,
+ { balances: YieldBalanceDto[] } & (
+ | {
+ type: "validators";
+ validators: NonNullable;
+ }
+ | { type: "default" }
+ )
+ >()
+ ),
+ });
+
+ return acc;
+ }, new Map() as PositionsData);
+
+export const availableYieldCategoriesAtom = Atom.family(
+ (key: AvailableYieldCategoriesKey) =>
+ widgetAtomRuntime
+ .atom(() =>
+ Effect.gen(function* () {
+ const api = yield* StakeKitApiService;
+
+ const availability = yield* Effect.all(
+ key.categoryOrder.map((category) =>
+ Effect.gen(function* () {
+ const response = yield* api.yield.YieldsControllerGetYields({
+ params: {
+ ...(key.network && { network: key.network }),
+ limit: DEFAULT_PAGE_SIZE,
+ types: getApiYieldTypesForDashboardCategory(category),
+ },
+ });
+
+ const hasVisibleYield = (response.items ?? []).some(
+ (yieldDto) =>
+ yieldDto.status.enter && isNonZeroRewardRateYield(yieldDto)
+ );
+
+ return hasVisibleYield ? category : null;
+ })
+ ),
+ { concurrency: PREFERRED_PAGE_CONCURRENCY }
+ );
+
+ return availability.filter(
+ (category): category is DashboardYieldCategory => category !== null
+ );
+ }).pipe(withCatalogError("available-yield-categories"))
+ )
+ .pipe(catalogSWR)
+);
+
+export const earnYieldCatalogAtom = Atom.family((key: YieldCatalogKey) =>
+ widgetAtomRuntime
+ .atom(() =>
+ Effect.gen(function* () {
+ const api = yield* StakeKitApiService;
+
+ return yield* loadAllPages({
+ concurrency: PREFERRED_PAGE_CONCURRENCY,
+ fetchPage: (offset: number) =>
+ api.yield.YieldsControllerGetYields({
+ params: {
+ limit: DEFAULT_PAGE_SIZE,
+ offset,
+ types: toYieldTypesParam(key.category),
+ network: key.selectedToken.token.network,
+ yieldIds: key.selectedToken.availableYields,
+ },
+ }),
+ pageSize: DEFAULT_PAGE_SIZE,
+ });
+ }).pipe(withCatalogError("earn-yield-catalog"))
+ )
+ .pipe(catalogSWR)
+);
+
+export const initYieldAtom = Atom.family((key: InitYieldKey) =>
+ widgetAtomRuntime
+ .atom(() =>
+ Effect.gen(function* () {
+ if (!key.yieldId) {
+ return null;
+ }
+
+ const api = yield* StakeKitApiService;
+
+ return yield* api.yield.YieldsControllerGetYield(
+ key.yieldId,
+ undefined
+ );
+ }).pipe(withCatalogError("init-yield"))
+ )
+ .pipe(catalogSWR)
+);
+
+export const positionsDataAtom = Atom.family((key: PositionsDataKey) =>
+ widgetAtomRuntime
+ .atom(() =>
+ Effect.gen(function* () {
+ if (!key.address || !key.network) {
+ return new Map() as PositionsData;
+ }
+
+ const api = yield* StakeKitApiService;
+ const response = yield* api.yield.YieldsControllerGetAggregateBalances({
+ payload: {
+ queries: [
+ {
+ address: key.address,
+ network:
+ key.network as BalancesRequestDto["queries"][number]["network"],
+ },
+ ],
+ },
+ });
+
+ return toPositionsData(response.items as YieldBalancesByYieldDto[]);
+ }).pipe(withCatalogError("positions-data"))
+ )
+ .pipe(catalogSWR)
+);
+
+const toDefaultTokenOption = (tokenWithYields: {
+ readonly token: TokenDto;
+ readonly availableYields: ReadonlyArray;
+}): EarnTokenOption => ({
+ token: tokenWithYields.token,
+ availableYields: tokenWithYields.availableYields,
+ amount: "0",
+ source: "default",
+});
+
+const toBalanceTokenOption = (tokenBalance: {
+ readonly token: TokenDto;
+ readonly availableYields: ReadonlyArray;
+ readonly amount: string;
+}): EarnTokenOption => ({
+ token: tokenBalance.token,
+ availableYields: tokenBalance.availableYields,
+ amount: tokenBalance.amount,
+ source: "balance",
+});
+
+const toInitTokenOption = (tokenWithYields: {
+ readonly token: TokenDto;
+ readonly availableYields: ReadonlyArray;
+}): EarnTokenOption => ({
+ token: tokenWithYields.token,
+ availableYields: tokenWithYields.availableYields,
+ amount: "0",
+ source: "init",
+});
+
+const toInitYieldTokenOption = (yieldDto: YieldDto): EarnTokenOption => ({
+ token: yieldDto.token,
+ availableYields: [yieldDto.id],
+ amount: "0",
+ source: "init",
+});
+
+const findInitTokenOption = ({
+ network,
+ token,
+ tokenOptions,
+}: {
+ network: Networks | null;
+ token: string;
+ tokenOptions: ReadonlyArray;
+}) =>
+ tokenOptions.find((option) => {
+ const tokenSymbolCompare =
+ token.toLowerCase() === option.token.symbol.toLowerCase();
+ const tokenNetworkCompare =
+ !!network && network.toLowerCase() === option.token.network.toLowerCase();
+ const tokenStringCompare = tokenString(option.token) === token;
+
+ return (tokenSymbolCompare && tokenNetworkCompare) || tokenStringCompare;
+ }) ?? null;
+
+const defaultTokenOptionsPullAtom = Atom.family((key: DefaultTokenOptionsKey) =>
+ widgetAtomRuntime.pull(() =>
+ Stream.paginate(0, (offset) =>
+ Effect.gen(function* () {
+ const api = yield* StakeKitApiService;
+
+ if (
+ shouldUseYieldTokensApi({
+ category: key.category,
+ tokensForEnabledYieldsOnly: key.tokensForEnabledYieldsOnly,
+ })
+ ) {
+ const response = yield* api.yield.TokensControllerGetTokens({
+ params: {
+ limit: DEFAULT_PAGE_SIZE,
+ offset,
+ networks: toNetworksParam(key.network),
+ yieldTypes: toYieldTypesParam(key.category),
+ },
+ });
+ const items = (response.items ?? []).map(toDefaultTokenOption);
+
+ return [
+ items,
+ getNextOffset({
+ itemsLength: response.items?.length ?? 0,
+ limit: response.limit,
+ offset: response.offset,
+ total: response.total,
+ }),
+ ] as const;
+ }
+
+ if (offset > 0) {
+ return [[], Option.none()] as const;
+ }
+
+ const response = yield* api.legacy.TokenControllerGetTokens({
+ params: {
+ network: key.network ?? undefined,
+ },
+ });
+
+ return [
+ response.map(toDefaultTokenOption),
+ Option.none(),
+ ] as const;
+ })
+ ).pipe(mapCatalogStreamError("default-token-options"))
+ )
+);
+
+const initTokenOptionAtom = Atom.family((key: InitTokenOptionKey) =>
+ widgetAtomRuntime
+ .atom(() =>
+ Effect.gen(function* () {
+ if (!key.token) {
+ return null;
+ }
+
+ const api = yield* StakeKitApiService;
+ const response = yield* api.legacy.TokenControllerGetTokens({
+ params: {
+ network: key.network ?? undefined,
+ },
+ });
+
+ return findInitTokenOption({
+ network: key.network,
+ token: key.token,
+ tokenOptions: response.map(toInitTokenOption),
+ });
+ }).pipe(withCatalogError("init-token-option"))
+ )
+ .pipe(catalogSWR)
+);
+
+const tokenBalancesScanAtom = Atom.family((key: TokenBalancesScanKey) =>
+ widgetAtomRuntime
+ .atom(() =>
+ Effect.gen(function* () {
+ if (!key.address || !key.network) {
+ return [];
+ }
+
+ const api = yield* StakeKitApiService;
+ const response = yield* api.legacy.TokenControllerTokenBalancesScan({
+ payload: {
+ addresses: {
+ address: key.address,
+ additionalAddresses: key.additionalAddresses ?? undefined,
+ },
+ network: key.network as TokenBalanceScanDto["network"],
+ },
+ });
+
+ return response.map(toBalanceTokenOption);
+ }).pipe(withCatalogError("token-balances-scan"))
+ )
+ .pipe(catalogSWR)
+);
+
+const getTokenOptionRank = (token: EarnTokenOption) => {
+ if (token.source === "balance") {
+ return new BigNumber(token.amount).isGreaterThan(0) ? 0 : 2;
+ }
+
+ if (token.source === "init") {
+ return 1;
+ }
+
+ return 3;
+};
+
+const mergeTokenOptions = ({
+ balanceItems,
+ defaultItems,
+ initItems,
+}: {
+ balanceItems: ReadonlyArray;
+ defaultItems: ReadonlyArray;
+ initItems: ReadonlyArray;
+}) => {
+ const mergeAvailableYields = (
+ prev: EarnTokenOption | undefined,
+ next: EarnTokenOption
+ ) => [
+ ...new Set([...(prev?.availableYields ?? []), ...next.availableYields]),
+ ];
+
+ const mergeOption = (
+ prev: { option: EarnTokenOption } | undefined,
+ next: EarnTokenOption
+ ): EarnTokenOption => ({
+ ...next,
+ availableYields: mergeAvailableYields(prev?.option, next),
+ });
+
+ const byKey = new Map<
+ string,
+ {
+ balanceIndex: number | null;
+ defaultIndex: number | null;
+ initIndex: number | null;
+ option: EarnTokenOption;
+ }
+ >();
+
+ defaultItems.forEach((option, defaultIndex) => {
+ byKey.set(tokenString(option.token), {
+ balanceIndex: null,
+ defaultIndex,
+ initIndex: null,
+ option: mergeOption(undefined, option),
+ });
+ });
+
+ initItems.forEach((option, initIndex) => {
+ const key = tokenString(option.token);
+ const prev = byKey.get(key);
+
+ byKey.set(key, {
+ balanceIndex: null,
+ defaultIndex: prev?.defaultIndex ?? null,
+ initIndex,
+ option: mergeOption(prev, option),
+ });
+ });
+
+ balanceItems.forEach((option, balanceIndex) => {
+ const key = tokenString(option.token);
+ const prev = byKey.get(key);
+
+ byKey.set(key, {
+ balanceIndex,
+ defaultIndex: prev?.defaultIndex ?? null,
+ initIndex: prev?.initIndex ?? null,
+ option: mergeOption(prev, option),
+ });
+ });
+
+ return [...byKey.values()]
+ .sort((a, b) => {
+ const rankDiff =
+ getTokenOptionRank(a.option) - getTokenOptionRank(b.option);
+
+ if (rankDiff !== 0) {
+ return rankDiff;
+ }
+
+ return (
+ (a.balanceIndex ??
+ a.initIndex ??
+ a.defaultIndex ??
+ Number.MAX_SAFE_INTEGER) -
+ (b.balanceIndex ??
+ b.initIndex ??
+ b.defaultIndex ??
+ Number.MAX_SAFE_INTEGER)
+ );
+ })
+ .map(({ option }) => option);
+};
+
+export const mergedTokenOptionsAtom = Atom.family((key: TokenOptionsKey) => {
+ const defaultTokenOptionsAtom = defaultTokenOptionsPullAtom(
+ new DefaultTokenOptionsKey({
+ category: key.category,
+ network: key.network,
+ tokensForEnabledYieldsOnly: key.tokensForEnabledYieldsOnly,
+ })
+ );
+ const tokenBalancesAtom = tokenBalancesScanAtom(
+ new TokenBalancesScanKey({
+ address: key.address,
+ additionalAddresses: key.additionalAddresses,
+ network: key.network,
+ })
+ );
+ const initTokenAtom = initTokenOptionAtom(
+ new InitTokenOptionKey({
+ token: key.initToken,
+ network: key.initTokenNetwork,
+ })
+ );
+ const initYieldAtomValue = initYieldAtom(
+ new InitYieldKey({ yieldId: key.initYieldId })
+ );
+
+ return Atom.make((context) => {
+ const defaultResult = context.get(defaultTokenOptionsAtom);
+ const balancesResult = context.get(tokenBalancesAtom);
+ const initTokenResult = context.get(initTokenAtom);
+ const initYieldResult = context.get(initYieldAtomValue);
+ const defaultItems = defaultResult.pipe(
+ AsyncResult.value,
+ Option.map((value) => value.items),
+ Option.getOrElse(() => [])
+ );
+ const balanceItems = balancesResult.pipe(
+ AsyncResult.value,
+ Option.getOrElse(() => [])
+ );
+ const initYield = initYieldResult.pipe(AsyncResult.value, Option.getOrNull);
+ const initToken = initTokenResult.pipe(AsyncResult.value, Option.getOrNull);
+ const initItems = [
+ initYield ? toInitYieldTokenOption(initYield) : null,
+ initToken,
+ ].filter((option): option is EarnTokenOption => option !== null);
+
+ return {
+ defaultResult,
+ balancesResult,
+ initTokenResult,
+ initYieldResult,
+ defaultItems,
+ balanceItems,
+ initItems,
+ items: mergeTokenOptions({ balanceItems, defaultItems, initItems }),
+ };
+ });
+});
+
+export const tokenOptionsPullAtom = defaultTokenOptionsPullAtom;
+
+export const yieldValidatorsAtom = Atom.family(
+ ({ selectedYieldId }: YieldValidatorsKey) => {
+ const preferredValidatorsAtom = widgetAtomRuntime
+ .atom(() =>
+ Effect.gen(function* () {
+ const api = yield* StakeKitApiService;
+
+ const preferredValidators = yield* loadAllPages({
+ concurrency: PREFERRED_PAGE_CONCURRENCY,
+ fetchPage: (offset: number) =>
+ api.yield.YieldsControllerGetYieldValidators(selectedYieldId, {
+ params: {
+ limit: DEFAULT_PAGE_SIZE,
+ offset,
+ preferred: true,
+ },
+ }),
+ pageSize: DEFAULT_PAGE_SIZE,
+ });
+
+ return preferredValidators;
+ }).pipe(withCatalogError("preferred-validators"))
+ )
+ .pipe(catalogSWR);
+
+ const loadedValidatorsAtom = Atom.writable<
+ Map,
+ ReadonlyArray
+ >(
+ (context) => {
+ const preferredValidators = context.get(preferredValidatorsAtom).pipe(
+ AsyncResult.value,
+ Option.getOrElse(() => [])
+ );
+
+ return new Map([
+ ...preferredValidators.map(
+ (validator) => [validator.address, validator] as const
+ ),
+ ]);
+ },
+ (context, value) => {
+ const newValue = new Map(context.get(loadedValidatorsAtom));
+
+ value.forEach((validator) => {
+ newValue.set(validator.address, validator);
+ });
+
+ context.setSelf(newValue);
+ }
+ );
+
+ /**
+ * If search is provided, we search all preferred and non-preferred validators
+ * If search is not provided, we search only non-preferred validators
+ */
+ const validatorsPullAtom = Atom.family(
+ ({ search }: EarnValidatorsPullParams) =>
+ widgetAtomRuntime.pull(
+ (context) => {
+ return Stream.paginate(0, (offset) =>
+ Effect.gen(function* () {
+ const api = yield* StakeKitApiService;
+ const response =
+ yield* api.yield.YieldsControllerGetYieldValidators(
+ selectedYieldId,
+ {
+ params: {
+ limit: DEFAULT_PAGE_SIZE,
+ name: search || undefined,
+ address: search || undefined,
+ offset,
+ ...(search ? { preferred: false } : {}),
+ },
+ }
+ );
+ const items = response.items ?? [];
+
+ context.set(loadedValidatorsAtom, items);
+
+ return [
+ items,
+ getNextOffset({
+ itemsLength: items.length,
+ limit: response.limit,
+ offset: response.offset,
+ total: response.total,
+ }),
+ ] as const;
+ })
+ ).pipe(mapCatalogStreamError("validators"));
+ },
+ { initialValue: [] }
+ )
+ );
+
+ return {
+ loadedValidatorsAtom,
+ preferredValidatorsAtom,
+ validatorsPullAtom,
+ };
+ }
+);
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/keys.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/keys.ts
new file mode 100644
index 00000000..c597cc89
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/keys.ts
@@ -0,0 +1,73 @@
+import { Data } from "effect";
+import type { Networks } from "../../../../../../domain/types/chains/networks";
+import type { TokenBalanceScanDto } from "../../../../../../domain/types/token-balance";
+import type { DashboardYieldCategory } from "../../../../../../domain/types/yields";
+import type { YieldDto } from "../../../../../../generated/api/yield";
+import type { EarnTokenOption } from "../types";
+
+export class AvailableYieldCategoriesKey extends Data.TaggedClass(
+ "AvailableYieldCategoriesKey"
+)<{
+ network: Networks | null;
+ categoryOrder: ReadonlyArray;
+}> {}
+
+export class YieldCatalogKey extends Data.TaggedClass("YieldCatalogKey")<{
+ selectedToken: EarnTokenOption;
+ category: DashboardYieldCategory | null;
+}> {}
+
+export class InitYieldKey extends Data.TaggedClass("InitYieldKey")<{
+ yieldId: string | null;
+}> {}
+
+export class PositionsDataKey extends Data.TaggedClass("PositionsDataKey")<{
+ address: string | null;
+ network: Networks | null;
+}> {}
+
+export class TokenOptionsKey extends Data.TaggedClass("TokenOptionsKey")<{
+ address: string | null;
+ additionalAddresses?:
+ | TokenBalanceScanDto["addresses"]["additionalAddresses"]
+ | null;
+ network: Networks | null;
+ category: DashboardYieldCategory | null;
+ initToken: string | null;
+ initTokenNetwork: Networks | null;
+ initYieldId: string | null;
+ tokensForEnabledYieldsOnly: boolean;
+}> {}
+
+export class DefaultTokenOptionsKey extends Data.TaggedClass(
+ "DefaultTokenOptionsKey"
+)<{
+ network: Networks | null;
+ category: DashboardYieldCategory | null;
+ tokensForEnabledYieldsOnly: boolean;
+}> {}
+
+export class TokenBalancesScanKey extends Data.TaggedClass(
+ "TokenBalancesScanKey"
+)<{
+ address: string | null;
+ additionalAddresses?:
+ | TokenBalanceScanDto["addresses"]["additionalAddresses"]
+ | null;
+ network: Networks | null;
+}> {}
+
+export class InitTokenOptionKey extends Data.TaggedClass("InitTokenOptionKey")<{
+ token: string | null;
+ network: Networks | null;
+}> {}
+
+export class YieldValidatorsKey extends Data.TaggedClass("YieldValidatorsKey")<{
+ selectedYieldId: YieldDto["id"];
+}> {}
+
+export class YieldValidatorsPullKey extends Data.TaggedClass(
+ "YieldValidatorsPullKey"
+)<{
+ search: string | null;
+}> {}
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/utilities.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/utilities.ts
new file mode 100644
index 00000000..5f175b58
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/catalog/utilities.ts
@@ -0,0 +1,35 @@
+import { Effect } from "effect";
+
+type PaginatedPage- = {
+ items?: ReadonlyArray
- | null;
+ total?: number | null;
+};
+
+type LoadAllPagesParams
- = {
+ concurrency: number;
+ fetchPage: (offset: number) => Effect.Effect, E, R>;
+ pageSize: number;
+};
+
+export const loadAllPages =
- ({
+ concurrency,
+ fetchPage,
+ pageSize,
+}: LoadAllPagesParams
- ): Effect.Effect, E, R> =>
+ Effect.gen(function* () {
+ const firstPage = yield* fetchPage(0);
+ const total = firstPage.total ?? 0;
+
+ const remainingOffsets = Array.from(
+ {
+ length: Math.max(0, Math.ceil(total / pageSize) - 1),
+ },
+ (_, index) => (index + 1) * pageSize
+ );
+
+ const remainingItems = yield* Effect.forEach(remainingOffsets, fetchPage, {
+ concurrency,
+ }).pipe(Effect.map((pages) => pages.flatMap((page) => page.items ?? [])));
+
+ return [...(firstPage.items ?? []), ...remainingItems];
+ });
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/hooks/use-earn-machine.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/hooks/use-earn-machine.ts
new file mode 100644
index 00000000..ec21d52b
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/hooks/use-earn-machine.ts
@@ -0,0 +1,18 @@
+import { useAtom } from "@effect/atom-react";
+import { earnMachineAtom } from "../machine/atoms";
+import { EarnEntryKey, type EarnEntryParams } from "../types";
+
+export const useEarnMachine = (entryParams: EarnEntryParams) => {
+ const [view, dispatch] = useAtom(
+ earnMachineAtom(
+ new EarnEntryKey({
+ ...entryParams,
+ })
+ )
+ );
+
+ return {
+ view,
+ dispatch,
+ };
+};
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/actions.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/actions.ts
new file mode 100644
index 00000000..c062e5d9
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/actions.ts
@@ -0,0 +1,44 @@
+import type { DashboardYieldCategory } from "../../../../../../domain/types/yields";
+import type { EarnTokenKey, EarnValidatorKey, EarnYieldId } from "../types";
+
+export type EarnAction =
+ | {
+ readonly type: "token/select";
+ readonly tokenKey: EarnTokenKey | null;
+ }
+ | {
+ readonly type: "yield/select";
+ readonly yieldId: EarnYieldId | null;
+ }
+ | {
+ readonly type: "category/select";
+ readonly category: DashboardYieldCategory | null;
+ }
+ | {
+ readonly type: "validator/select";
+ readonly validatorKey: EarnValidatorKey;
+ }
+ | {
+ readonly type: "validator/multiselect";
+ readonly validatorKey: EarnValidatorKey;
+ }
+ | {
+ readonly type: "validator/remove";
+ readonly validatorKey: EarnValidatorKey;
+ }
+ | {
+ readonly type: "providerYieldId/select";
+ readonly providerYieldId: string | null;
+ }
+ | {
+ readonly type: "stakeAmount/change";
+ readonly amount: string;
+ }
+ | {
+ readonly type: "stakeAmount/max";
+ readonly amount: string;
+ }
+ | {
+ readonly type: "tronResource/select";
+ readonly tronResource: string | null;
+ };
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/atoms.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/atoms.ts
new file mode 100644
index 00000000..7500b283
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/atoms.ts
@@ -0,0 +1,30 @@
+import * as Atom from "effect/unstable/reactivity/Atom";
+import { resolveEarnView } from "../resolver/resolver";
+import {
+ type EarnEntryKey,
+ type EarnMachineIntent,
+ type EarnMachineView,
+ makeDefaultEarnIntent,
+} from "../types";
+import type { EarnAction } from "./actions";
+import { applyEarnAction } from "./reducer";
+
+export const earnMachineAtom = Atom.family((entry: EarnEntryKey) => {
+ const earnIntentAtom = Atom.make(makeDefaultEarnIntent());
+
+ return Atom.writable(
+ (context) =>
+ resolveEarnView({
+ context,
+ entry,
+ intent: context.get(earnIntentAtom),
+ }),
+ (ctx, action) => {
+ const intent = ctx.get(earnIntentAtom);
+
+ const newIntent = applyEarnAction({ action, intent });
+
+ ctx.set(earnIntentAtom, newIntent);
+ }
+ );
+});
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/reducer.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/reducer.ts
new file mode 100644
index 00000000..1523f5c1
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/machine/reducer.ts
@@ -0,0 +1,90 @@
+import { Match } from "effect";
+import type { EarnMachineIntent, EarnYieldId } from "../types";
+import type { EarnAction } from "./actions";
+
+const resetYieldScopedIntent = (
+ intent: EarnMachineIntent,
+ selectedYieldId: EarnYieldId | null
+): EarnMachineIntent => ({
+ ...intent,
+ selectedProviderYieldId: null,
+ selectedValidatorKeys: new Set(),
+ selectedYieldId,
+ stakeAmount: "0",
+ tronResource: null,
+ useMaxAmount: false,
+});
+
+export const applyEarnAction = ({
+ action,
+ intent,
+}: {
+ action: EarnAction;
+ intent: EarnMachineIntent;
+}): EarnMachineIntent =>
+ Match.value(action).pipe(
+ Match.when({ type: "token/select" }, (action) =>
+ resetYieldScopedIntent(
+ {
+ ...intent,
+ selectedTokenKey: action.tokenKey,
+ },
+ null
+ )
+ ),
+ Match.when({ type: "yield/select" }, (action) =>
+ intent.selectedYieldId === action.yieldId
+ ? intent
+ : resetYieldScopedIntent(intent, action.yieldId)
+ ),
+ Match.when({ type: "category/select" }, (action) =>
+ resetYieldScopedIntent(
+ {
+ ...intent,
+ selectedCategory: action.category,
+ },
+ null
+ )
+ ),
+ Match.when({ type: "validator/select" }, (action) => ({
+ ...intent,
+ selectedValidatorKeys: new Set([action.validatorKey]),
+ })),
+ Match.when({ type: "validator/multiselect" }, (action) => {
+ const next = new Set(intent.selectedValidatorKeys);
+ if (next.has(action.validatorKey)) {
+ next.delete(action.validatorKey);
+ } else {
+ next.add(action.validatorKey);
+ }
+
+ return next.size === 0
+ ? intent
+ : { ...intent, selectedValidatorKeys: next };
+ }),
+ Match.when({ type: "validator/remove" }, (action) => {
+ const next = new Set(intent.selectedValidatorKeys);
+ next.delete(action.validatorKey);
+
+ return { ...intent, selectedValidatorKeys: next };
+ }),
+ Match.when({ type: "providerYieldId/select" }, (action) => ({
+ ...intent,
+ selectedProviderYieldId: action.providerYieldId,
+ })),
+ Match.when({ type: "stakeAmount/change" }, (action) => ({
+ ...intent,
+ stakeAmount: action.amount,
+ useMaxAmount: false,
+ })),
+ Match.when({ type: "stakeAmount/max" }, (action) => ({
+ ...intent,
+ stakeAmount: action.amount,
+ useMaxAmount: true,
+ })),
+ Match.when({ type: "tronResource/select" }, (action) => ({
+ ...intent,
+ tronResource: action.tronResource,
+ })),
+ Match.exhaustive
+ );
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/category.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/category.ts
new file mode 100644
index 00000000..add56a39
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/category.ts
@@ -0,0 +1,29 @@
+import { DashboardYieldCategory } from "../../../../../../domain/types/yields";
+
+export const resolveCategory = ({
+ availableCategories,
+ categoryOrder,
+ selectedCategory,
+ dashboardVariant,
+}: {
+ availableCategories: ReadonlyArray;
+ categoryOrder: ReadonlyArray;
+ selectedCategory: DashboardYieldCategory | null;
+ dashboardVariant: boolean;
+}) => {
+ if (!dashboardVariant) {
+ return null;
+ }
+
+ if (
+ selectedCategory &&
+ (availableCategories.length === 0 ||
+ availableCategories.includes(selectedCategory))
+ ) {
+ return selectedCategory;
+ }
+
+ return (
+ availableCategories[0] ?? categoryOrder[0] ?? DashboardYieldCategory.Stake
+ );
+};
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/form.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/form.ts
new file mode 100644
index 00000000..0878aa07
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/form.ts
@@ -0,0 +1,59 @@
+import type { PositionsData } from "../../../../../../domain/types/positions";
+import { getMinStakeAmount } from "../../../../../../domain/types/stake";
+import { getYieldActionArg } from "../../../../../../domain/types/yields";
+import type {
+ EarnMachineForm,
+ EarnMachineIntent,
+ EarnYieldOption,
+} from "../types";
+
+export const resolveForm = ({
+ intent,
+ positionsData,
+ selectedYield,
+}: {
+ intent: EarnMachineIntent;
+ positionsData: PositionsData;
+ selectedYield: EarnYieldOption;
+}): EarnMachineForm => ({
+ providerYieldId:
+ intent.selectedProviderYieldId ?? resolveProviderYieldId(selectedYield),
+ stakeAmount: resolveStakeAmount({
+ intent,
+ positionsData,
+ selectedYield,
+ }),
+ tronResource: intent.tronResource ?? resolveTronResource(selectedYield),
+ useMaxAmount: intent.useMaxAmount,
+});
+
+const resolveProviderYieldId = (selectedYield: EarnYieldOption) => {
+ const providerArg = getYieldActionArg(selectedYield, "enter", "providerId");
+
+ if (!providerArg?.required || !providerArg.options?.length) {
+ return null;
+ }
+
+ return providerArg.options[0] ?? null;
+};
+
+const resolveTronResource = (selectedYield: EarnYieldOption) =>
+ getYieldActionArg(selectedYield, "enter", "tronResource")?.required
+ ? "ENERGY"
+ : null;
+
+const resolveStakeAmount = ({
+ intent,
+ positionsData,
+ selectedYield,
+}: {
+ intent: EarnMachineIntent;
+ positionsData: PositionsData;
+ selectedYield: EarnYieldOption;
+}) => {
+ if (intent.useMaxAmount || intent.stakeAmount !== "0") {
+ return intent.stakeAmount;
+ }
+
+ return getMinStakeAmount(selectedYield, positionsData).toString(10);
+};
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/resolver.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/resolver.ts
new file mode 100644
index 00000000..6e3f55cf
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/resolver.ts
@@ -0,0 +1 @@
+export { resolveEarnView } from "./view";
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/status.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/status.ts
new file mode 100644
index 00000000..eb0eae91
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/status.ts
@@ -0,0 +1,41 @@
+import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
+import type {
+ EarnCatalogError,
+ EarnEntryKey,
+ EarnTokenOption,
+ EarnTokenOptionsState,
+ EarnYieldOption,
+} from "../types";
+
+export const isResolvingTokenOptions = (tokenOptions: EarnTokenOptionsState) =>
+ tokenOptions.items.length === 0 &&
+ (AsyncResult.isInitial(tokenOptions.defaultResult) ||
+ AsyncResult.isWaiting(tokenOptions.defaultResult) ||
+ AsyncResult.isInitial(tokenOptions.balancesResult) ||
+ AsyncResult.isWaiting(tokenOptions.balancesResult));
+
+export const isResolvingInitialSelection = ({
+ entry,
+ tokenOptions,
+}: {
+ entry: EarnEntryKey;
+ tokenOptions: EarnTokenOptionsState;
+}) =>
+ (!!entry.initParams?.token &&
+ isResolvingResult(tokenOptions.initTokenResult)) ||
+ (!!entry.initParams?.yieldId &&
+ isResolvingResult(tokenOptions.initYieldResult));
+
+export const isResolvingYields = (
+ yieldsResult: AsyncResult.AsyncResult<
+ ReadonlyArray,
+ EarnCatalogError
+ >
+) => AsyncResult.isInitial(yieldsResult) || AsyncResult.isWaiting(yieldsResult);
+
+const isResolvingResult = (
+ result: AsyncResult.AsyncResult<
+ EarnTokenOption | EarnYieldOption | null,
+ EarnCatalogError
+ >
+) => AsyncResult.isInitial(result) || AsyncResult.isWaiting(result);
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/token.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/token.ts
new file mode 100644
index 00000000..8fabee97
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/token.ts
@@ -0,0 +1,62 @@
+import { Maybe } from "purify-ts";
+import { tokenString } from "../../../../../../domain";
+import type { SupportedSKChains } from "../../../../../../domain/types/chains";
+import { getInitialToken } from "../../../../../../domain/types/stake";
+import type {
+ EarnEntryKey,
+ EarnTokenKey,
+ EarnTokenOption,
+ EarnTokenOptionsState,
+} from "../types";
+
+export const resolveToken = ({
+ entry,
+ selectedTokenKey,
+ tokenOptions,
+}: {
+ entry: EarnEntryKey;
+ selectedTokenKey: EarnTokenKey | null;
+ tokenOptions: EarnTokenOptionsState;
+}) => {
+ if (selectedTokenKey) {
+ const selected = findTokenByKey(tokenOptions.items, selectedTokenKey);
+ if (selected) {
+ return selected;
+ }
+ }
+
+ if (entry.initParams?.yieldId) {
+ const selected = tokenOptions.initItems.find((option) =>
+ option.availableYields.includes(entry.initParams?.yieldId ?? "")
+ );
+ if (selected) {
+ return selected;
+ }
+ }
+
+ const initialToken = getInitialToken({
+ initQueryParams: Maybe.fromNullable(entry.initParams),
+ tokenBalances: tokenOptions.balanceItems,
+ defaultTokens: [...tokenOptions.initItems, ...tokenOptions.defaultItems],
+ network: entry.network as SupportedSKChains | null,
+ preferredTokenYieldsPerNetwork:
+ entry.preferredTokenYieldsPerNetwork ?? null,
+ }).extractNullable();
+
+ if (initialToken) {
+ const selected = findTokenByKey(
+ tokenOptions.items,
+ tokenString(initialToken)
+ );
+ if (selected) {
+ return selected;
+ }
+ }
+
+ return tokenOptions.items[0] ?? null;
+};
+
+const findTokenByKey = (
+ tokens: ReadonlyArray,
+ tokenKey: EarnTokenKey
+) => tokens.find((token) => tokenString(token.token) === tokenKey) ?? null;
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/validators.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/validators.ts
new file mode 100644
index 00000000..1f767291
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/validators.ts
@@ -0,0 +1,32 @@
+import { Maybe } from "purify-ts";
+import { getInitSelectedValidators } from "../../../../../../domain/types/stake";
+import type { EarnEntryKey, EarnValidatorOption } from "../types";
+
+export const resolveValidators = ({
+ entry,
+ selectedValidatorKeys,
+ validatorOptions,
+}: {
+ entry: EarnEntryKey;
+ selectedValidatorKeys: ReadonlySet;
+ validatorOptions: ReadonlyArray;
+}) => {
+ if (validatorOptions.length === 0) {
+ return [];
+ }
+
+ const selectedValidators = validatorOptions.filter((validator) =>
+ selectedValidatorKeys.has(validator.address)
+ );
+
+ if (selectedValidators.length > 0) {
+ return selectedValidators;
+ }
+
+ return [
+ ...getInitSelectedValidators({
+ initQueryParams: Maybe.fromNullable(entry.initParams),
+ validators: [...validatorOptions],
+ }).values(),
+ ];
+};
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/view.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/view.ts
new file mode 100644
index 00000000..995d2ff3
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/view.ts
@@ -0,0 +1,334 @@
+import { Option, pipe } from "effect";
+import type { AsyncResult as AsyncResultValue } from "effect/unstable/reactivity/AsyncResult";
+import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
+import * as Atom from "effect/unstable/reactivity/Atom";
+import {
+ getDashboardYieldCategory,
+ isYieldValidatorSelectionRequired,
+} from "../../../../../../domain/types/yields";
+import type { ValidatorDto } from "../../../../../../generated/api/yield";
+import {
+ availableYieldCategoriesAtom,
+ earnYieldCatalogAtom,
+ initYieldAtom,
+ mergedTokenOptionsAtom,
+ positionsDataAtom,
+ tokenOptionsPullAtom,
+ yieldValidatorsAtom,
+} from "../catalog/atoms";
+import {
+ AvailableYieldCategoriesKey,
+ DefaultTokenOptionsKey,
+ InitYieldKey,
+ PositionsDataKey,
+ TokenOptionsKey,
+ YieldCatalogKey,
+ YieldValidatorsKey,
+} from "../catalog/keys";
+import type {
+ EarnCatalogError,
+ EarnEntryKey,
+ EarnMachineForm,
+ EarnMachineIntent,
+ EarnMachineView,
+ EarnValidatorsResource,
+ EarnYieldOption,
+} from "../types";
+import { resolveCategory } from "./category";
+import { resolveForm } from "./form";
+import {
+ isResolvingInitialSelection,
+ isResolvingTokenOptions,
+ isResolvingYields,
+} from "./status";
+import { resolveToken } from "./token";
+import { resolveValidators } from "./validators";
+import { resolveYield, resolveYieldOptions } from "./yield";
+
+const getAsyncValue = (result: AsyncResultValue) =>
+ pipe(result, AsyncResult.value, Option.getOrNull);
+
+const getIntentForm = (intent: EarnMachineIntent): EarnMachineForm => ({
+ providerYieldId: intent.selectedProviderYieldId,
+ stakeAmount: intent.stakeAmount,
+ tronResource: intent.tronResource,
+ useMaxAmount: intent.useMaxAmount,
+});
+
+const mergeYieldOptions = (yields: ReadonlyArray) => {
+ const byId = new Map();
+
+ yields.forEach((yieldDto) => {
+ if (yieldDto) {
+ byId.set(yieldDto.id, yieldDto);
+ }
+ });
+
+ return [...byId.values()];
+};
+
+const emptyValidatorsMapAtom = Atom.writable<
+ Map,
+ ReadonlyArray
+>(
+ () => new Map(),
+ () => {}
+);
+
+const emptyValidatorsPullAtom = Atom.writable<
+ Atom.PullResult,
+ void
+>(
+ () =>
+ AsyncResult.success({
+ done: true,
+ items: [] as unknown as [ValidatorDto, ...ValidatorDto[]],
+ }),
+ () => {}
+);
+
+const disabledValidatorsResource = {
+ enabled: false,
+ loadedValidatorsAtom: emptyValidatorsMapAtom,
+ validatorsPullAtom: () => emptyValidatorsPullAtom,
+} satisfies EarnValidatorsResource;
+
+export const resolveEarnView = ({
+ context,
+ entry,
+ intent,
+}: {
+ context: Atom.AtomContext;
+ entry: EarnEntryKey;
+ intent: EarnMachineIntent;
+}): EarnMachineView => {
+ const initYieldAtomValue = initYieldAtom(
+ new InitYieldKey({ yieldId: entry.initParams?.yieldId ?? null })
+ );
+ const initYieldResult = context.get(initYieldAtomValue);
+ const initYield = getAsyncValue(initYieldResult);
+ const initYieldCategory = entry.dashboardVariant
+ ? initYield
+ ? getDashboardYieldCategory(initYield)
+ : null
+ : null;
+ const availableCategoriesAtom = entry.dashboardVariant
+ ? availableYieldCategoriesAtom(
+ new AvailableYieldCategoriesKey({
+ network: entry.network,
+ categoryOrder: entry.categoryOrder,
+ })
+ )
+ : null;
+
+ const availableCategories = availableCategoriesAtom
+ ? pipe(
+ context.get(availableCategoriesAtom),
+ AsyncResult.value,
+ Option.getOrElse(() => [])
+ )
+ : [];
+
+ const category = resolveCategory({
+ availableCategories,
+ categoryOrder: entry.categoryOrder,
+ selectedCategory: intent.selectedCategory ?? initYieldCategory,
+ dashboardVariant: entry.dashboardVariant,
+ });
+
+ const tokenOptionsPullAtomValue = tokenOptionsPullAtom(
+ new DefaultTokenOptionsKey({
+ network: entry.network,
+ category,
+ tokensForEnabledYieldsOnly: !!entry.tokensForEnabledYieldsOnly,
+ })
+ );
+ const tokenOptionsAtom = mergedTokenOptionsAtom(
+ new TokenOptionsKey({
+ address: entry.address,
+ additionalAddresses: entry.additionalAddresses,
+ network: entry.network,
+ category,
+ initToken: entry.initParams?.token ?? null,
+ initTokenNetwork: entry.initParams?.network ?? null,
+ initYieldId: entry.initParams?.yieldId ?? null,
+ tokensForEnabledYieldsOnly: !!entry.tokensForEnabledYieldsOnly,
+ })
+ );
+ const tokenOptions = context.get(tokenOptionsAtom);
+ const positionsDataAtomValue = positionsDataAtom(
+ new PositionsDataKey({
+ address: entry.address,
+ network: entry.network,
+ })
+ );
+ const positionsData =
+ getAsyncValue(context.get(positionsDataAtomValue)) ?? new Map();
+
+ if (
+ !intent.selectedTokenKey &&
+ !intent.selectedYieldId &&
+ isResolvingInitialSelection({ entry, tokenOptions })
+ ) {
+ return {
+ status: "loading-initial-selection",
+ availableCategories,
+ selection: {
+ category,
+ token: null,
+ validators: [],
+ yield: null,
+ },
+ form: getIntentForm(intent),
+ resources: {
+ yieldsResult: null,
+ initYieldAtom: initYieldAtomValue,
+ positionsDataAtom: positionsDataAtomValue,
+ tokenOptionsAtom,
+ tokenOptionsPullAtom: tokenOptionsPullAtomValue,
+ validators: disabledValidatorsResource,
+ },
+ can: {
+ selectToken: tokenOptions.items.length > 0,
+ selectYield: false,
+ selectValidator: false,
+ submit: false,
+ },
+ };
+ }
+
+ const selectedToken = resolveToken({
+ entry,
+ selectedTokenKey: intent.selectedTokenKey,
+ tokenOptions,
+ });
+
+ if (!selectedToken) {
+ return {
+ status: isResolvingTokenOptions(tokenOptions)
+ ? "loading-token-options"
+ : "no-tokens",
+ availableCategories,
+ selection: {
+ category,
+ token: null,
+ validators: [],
+ yield: null,
+ },
+ form: getIntentForm(intent),
+ resources: {
+ yieldsResult: null,
+ initYieldAtom: initYieldAtomValue,
+ positionsDataAtom: positionsDataAtomValue,
+ tokenOptionsAtom,
+ tokenOptionsPullAtom: tokenOptionsPullAtomValue,
+ validators: disabledValidatorsResource,
+ },
+ can: {
+ selectToken: tokenOptions.items.length > 0,
+ selectYield: false,
+ selectValidator: false,
+ submit: false,
+ },
+ };
+ }
+
+ const yieldCatalogAtom = earnYieldCatalogAtom(
+ new YieldCatalogKey({
+ selectedToken,
+ category,
+ })
+ );
+ const yieldCatalogResult = context.get(yieldCatalogAtom);
+ const yieldsResult = yieldCatalogResult.pipe(
+ AsyncResult.map((catalogYieldOptions) =>
+ resolveYieldOptions({
+ selectedToken,
+ yieldsById: mergeYieldOptions([...catalogYieldOptions, initYield]),
+ })
+ )
+ );
+ const yieldOptions = getAsyncValue(yieldsResult) ?? [];
+ const selectedYield = resolveYield({
+ entry,
+ positionsData,
+ selectedToken,
+ selectedYieldId: intent.selectedYieldId,
+ yieldOptions,
+ });
+
+ if (!selectedYield) {
+ return {
+ status: isResolvingYields(yieldsResult) ? "loading-yields" : "no-yields",
+ availableCategories,
+ selection: {
+ category,
+ token: selectedToken,
+ validators: [],
+ yield: null,
+ },
+ form: getIntentForm(intent),
+ resources: {
+ yieldsResult,
+ initYieldAtom: initYieldAtomValue,
+ positionsDataAtom: positionsDataAtomValue,
+ tokenOptionsAtom,
+ tokenOptionsPullAtom: tokenOptionsPullAtomValue,
+ validators: disabledValidatorsResource,
+ },
+ can: {
+ selectToken: tokenOptions.items.length > 0,
+ selectYield: yieldOptions.length > 0,
+ selectValidator: false,
+ submit: false,
+ },
+ };
+ }
+
+ const validators = isYieldValidatorSelectionRequired(selectedYield)
+ ? ({
+ ...yieldValidatorsAtom(
+ new YieldValidatorsKey({ selectedYieldId: selectedYield.id })
+ ),
+ enabled: true,
+ } satisfies EarnValidatorsResource)
+ : disabledValidatorsResource;
+ const validatorOptions = validators.enabled
+ ? [...context.get(validators.loadedValidatorsAtom).values()]
+ : [];
+ const selectedValidators = resolveValidators({
+ entry,
+ selectedValidatorKeys: intent.selectedValidatorKeys,
+ validatorOptions,
+ });
+
+ return {
+ status: "ready",
+ availableCategories,
+ selection: {
+ category,
+ token: selectedToken,
+ validators: selectedValidators,
+ yield: selectedYield,
+ },
+ form: resolveForm({
+ intent,
+ positionsData,
+ selectedYield,
+ }),
+ resources: {
+ yieldsResult,
+ initYieldAtom: initYieldAtomValue,
+ positionsDataAtom: positionsDataAtomValue,
+ tokenOptionsAtom,
+ tokenOptionsPullAtom: tokenOptionsPullAtomValue,
+ validators,
+ },
+ can: {
+ selectToken: tokenOptions.items.length > 0,
+ selectYield: yieldOptions.length > 0,
+ selectValidator: validators.enabled,
+ submit: !validators.enabled || selectedValidators.length > 0,
+ },
+ };
+};
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/yield.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/yield.ts
new file mode 100644
index 00000000..ae7ab206
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/resolver/yield.ts
@@ -0,0 +1,134 @@
+import BigNumber from "bignumber.js";
+import { Maybe } from "purify-ts";
+import { tokenString } from "../../../../../../domain";
+import {
+ isSupportedChain,
+ type SupportedSKChains,
+} from "../../../../../../domain/types/chains";
+import type { PositionsData } from "../../../../../../domain/types/positions";
+import { canBeInitialYield } from "../../../../../../domain/types/stake";
+import { isNonZeroRewardRateYield } from "../../../../../../domain/types/yields";
+import type {
+ EarnEntryKey,
+ EarnTokenOption,
+ EarnYieldId,
+ EarnYieldOption,
+} from "../types";
+
+const blockedInitialYieldIds = new Set([
+ "binance-bnb-native-staking",
+ "binance-testnet-bnb-native-staking",
+ "avax-native-staking",
+]);
+
+export const resolveYieldOptions = ({
+ selectedToken,
+ yieldsById,
+}: {
+ selectedToken: EarnTokenOption | null;
+ yieldsById: ReadonlyArray;
+}) => {
+ if (!selectedToken) {
+ return [];
+ }
+
+ const availableYields = new Set(selectedToken.availableYields);
+
+ return yieldsById.filter(
+ (yieldOption) =>
+ availableYields.has(yieldOption.id) && canShowYieldOption(yieldOption)
+ );
+};
+
+export const resolveYield = ({
+ entry,
+ positionsData,
+ selectedYieldId,
+ selectedToken,
+ yieldOptions,
+}: {
+ entry: EarnEntryKey;
+ positionsData: PositionsData;
+ selectedYieldId: EarnYieldId | null;
+ selectedToken: EarnTokenOption;
+ yieldOptions: ReadonlyArray;
+}) => {
+ if (selectedYieldId) {
+ const selected = findYieldById(yieldOptions, selectedYieldId);
+ if (selected) {
+ return selected;
+ }
+ }
+
+ const initYieldId = entry.initParams?.yieldId;
+ if (initYieldId) {
+ const selected = findYieldById(yieldOptions, initYieldId);
+ if (selected) {
+ return selected;
+ }
+ }
+
+ const preferredYieldId = getPreferredYieldId({
+ preferredTokenYieldsPerNetwork:
+ entry.preferredTokenYieldsPerNetwork ?? null,
+ selectedToken,
+ });
+ if (preferredYieldId) {
+ if (preferredYieldId === "*") {
+ return getDefaultYield(yieldOptions);
+ }
+
+ const selected = findYieldById(yieldOptions, preferredYieldId);
+ if (selected) {
+ return selected;
+ }
+ }
+
+ const tokenBalanceAmount = new BigNumber(selectedToken.amount);
+ const eligibleYield =
+ yieldOptions.find((yieldDto) =>
+ canBeInitialYield({
+ initQueryParams: Maybe.fromNullable(entry.initParams),
+ yieldDto,
+ tokenBalanceAmount,
+ positionsData,
+ })
+ ) ?? null;
+
+ return eligibleYield ?? getDefaultYield(yieldOptions);
+};
+
+const canShowYieldOption = (yieldOption: EarnYieldOption) =>
+ yieldOption.status.enter &&
+ isSupportedChain(yieldOption.token.network) &&
+ !blockedInitialYieldIds.has(yieldOption.id);
+
+const findYieldById = (
+ yieldOptions: ReadonlyArray,
+ yieldId: EarnYieldId
+) =>
+ yieldOptions.find(
+ (yieldOption) => yieldOption.id.toLowerCase() === yieldId.toLowerCase()
+ ) ?? null;
+
+const getDefaultYield = (yieldOptions: ReadonlyArray) =>
+ yieldOptions.find(isNonZeroRewardRateYield) ?? yieldOptions[0] ?? null;
+
+const getPreferredYieldId = ({
+ preferredTokenYieldsPerNetwork,
+ selectedToken,
+}: {
+ preferredTokenYieldsPerNetwork: EarnEntryKey["preferredTokenYieldsPerNetwork"];
+ selectedToken: EarnTokenOption;
+}) => {
+ const tokenKey = tokenString(selectedToken.token);
+ const networkPreferred =
+ preferredTokenYieldsPerNetwork?.[
+ selectedToken.token.network as SupportedSKChains
+ ];
+ const fallbackPreferred = preferredTokenYieldsPerNetwork
+ ? Object.values(preferredTokenYieldsPerNetwork)[0]
+ : undefined;
+
+ return networkPreferred?.[tokenKey] ?? fallbackPreferred?.[tokenKey] ?? null;
+};
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/runtime/index.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/runtime/index.ts
new file mode 100644
index 00000000..9c929d9e
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/runtime/index.ts
@@ -0,0 +1,29 @@
+import { Effect, Layer } from "effect";
+import * as Atom from "effect/unstable/reactivity/Atom";
+import {
+ MissingStakeKitApiClient,
+ StakeKitApiService,
+ stakeKitEffectApiClientAtom,
+} from "../../../../../../providers/effect-atom-runtime/stakekit-api-service";
+import { EarnCatalogError } from "../types";
+
+export { StakeKitApiService };
+
+export const widgetAtomRuntime = Atom.runtime((get) => {
+ const apiClient = get(stakeKitEffectApiClientAtom);
+
+ return apiClient
+ ? Layer.succeed(StakeKitApiService, apiClient)
+ : Layer.effect(
+ StakeKitApiService,
+ Effect.fail(
+ new EarnCatalogError({
+ operation: "runtime",
+ cause: new MissingStakeKitApiClient({
+ message:
+ "StakeKit Effect API client was not initialized in the atom runtime.",
+ }),
+ })
+ )
+ );
+});
diff --git a/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/types.ts b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/types.ts
new file mode 100644
index 00000000..bf73d814
--- /dev/null
+++ b/packages/widget/src/pages/details/earn-page/state/effect-atom-poc/types.ts
@@ -0,0 +1,176 @@
+import { Data } from "effect";
+import type * as HttpClientError from "effect/unstable/http/HttpClientError";
+import type { AsyncResult } from "effect/unstable/reactivity/AsyncResult";
+import type {
+ Atom,
+ PullResult,
+ Writable,
+} from "effect/unstable/reactivity/Atom";
+import type { Networks } from "../../../../../domain/types/chains/networks";
+import type { InitParams } from "../../../../../domain/types/init-params";
+import type { PositionsData } from "../../../../../domain/types/positions";
+import type { PreferredTokenYieldsPerNetwork } from "../../../../../domain/types/stake";
+import type { TokenBalanceScanDto } from "../../../../../domain/types/token-balance";
+import type { TokenDto } from "../../../../../domain/types/tokens";
+import type { DashboardYieldCategory } from "../../../../../domain/types/yields";
+import type { LegacyApiError } from "../../../../../generated/api/legacy";
+import type {
+ ValidatorDto,
+ YieldApiError,
+ YieldDto,
+} from "../../../../../generated/api/yield";
+import type { MissingStakeKitApiClient } from "../../../../../providers/effect-atom-runtime/stakekit-api-service";
+
+export type EarnTokenOption = {
+ readonly token: TokenDto;
+ readonly availableYields: ReadonlyArray;
+ readonly amount: string;
+ readonly source: "balance" | "default" | "init";
+};
+export type EarnYieldOption = YieldDto;
+export type EarnValidatorOption = ValidatorDto;
+
+export type EarnTokenKey = string;
+export type EarnYieldId = string;
+export type EarnValidatorKey = string;
+
+export type EarnCatalogOperation =
+ | "available-yield-categories"
+ | "default-token-options"
+ | "earn-yield-catalog"
+ | "init-token-option"
+ | "init-yield"
+ | "positions-data"
+ | "preferred-validators"
+ | "runtime"
+ | "token-balances-scan"
+ | "validators";
+
+export type EarnCatalogUnderlyingError =
+ | HttpClientError.HttpClientError
+ | LegacyApiError
+ | MissingStakeKitApiClient
+ | YieldApiError;
+
+export class EarnCatalogError extends Data.TaggedError("EarnCatalogError")<{
+ readonly operation: EarnCatalogOperation;
+ readonly cause: EarnCatalogUnderlyingError;
+}> {}
+
+export type EarnEntryParams = {
+ address: string | null;
+ additionalAddresses?:
+ | TokenBalanceScanDto["addresses"]["additionalAddresses"]
+ | null;
+ network: Networks | null;
+ dashboardVariant: boolean;
+ categoryOrder: ReadonlyArray;
+ initParams?: InitParams | null;
+ preferredTokenYieldsPerNetwork?: PreferredTokenYieldsPerNetwork | null;
+ tokensForEnabledYieldsOnly?: boolean;
+};
+
+export class EarnEntryKey extends Data.Class {}
+
+export type EarnMachineIntent = {
+ selectedTokenKey: EarnTokenKey | null;
+ selectedYieldId: EarnYieldId | null;
+ selectedValidatorKeys: ReadonlySet;
+ selectedProviderYieldId: string | null;
+ selectedCategory: DashboardYieldCategory | null;
+ stakeAmount: string;
+ useMaxAmount: boolean;
+ tronResource: string | null;
+};
+
+type EarnMachineSelection = {
+ category: DashboardYieldCategory | null;
+ token: EarnTokenOption | null;
+ yield: EarnYieldOption | null;
+ validators: ReadonlyArray;
+};
+
+export type EarnMachineForm = {
+ providerYieldId: string | null;
+ stakeAmount: string;
+ useMaxAmount: boolean;
+ tronResource: string | null;
+};
+
+export type EarnMachineStatus =
+ | "loading-initial-selection"
+ | "loading-token-options"
+ | "no-tokens"
+ | "loading-yields"
+ | "no-yields"
+ | "ready";
+
+export type EarnTokenOptionsState = {
+ readonly items: ReadonlyArray;
+ readonly defaultItems: ReadonlyArray;
+ readonly balanceItems: ReadonlyArray;
+ readonly initItems: ReadonlyArray;
+ readonly defaultResult: PullResult;
+ readonly balancesResult: AsyncResult<
+ ReadonlyArray,
+ EarnCatalogError
+ >;
+ readonly initTokenResult: AsyncResult<
+ EarnTokenOption | null,
+ EarnCatalogError
+ >;
+ readonly initYieldResult: AsyncResult;
+};
+
+export type EarnValidatorsPullParams = {
+ readonly search: string | null;
+};
+
+export type EarnValidatorsResource = {
+ readonly enabled: boolean;
+ readonly loadedValidatorsAtom: Writable<
+ Map,
+ ReadonlyArray
+ >;
+ readonly validatorsPullAtom: (
+ key: EarnValidatorsPullParams
+ ) => Writable, void>;
+};
+
+export type EarnMachineView = {
+ status: EarnMachineStatus;
+ selection: EarnMachineSelection;
+ form: EarnMachineForm;
+ availableCategories: ReadonlyArray;
+ resources: {
+ initYieldAtom: Atom>;
+ positionsDataAtom: Atom>;
+ tokenOptionsAtom: Atom;
+ tokenOptionsPullAtom: Writable<
+ PullResult,
+ void
+ >;
+ yieldsResult: AsyncResult<
+ ReadonlyArray,
+ EarnCatalogError
+ > | null;
+ validators: EarnValidatorsResource;
+ };
+ can: {
+ selectToken: boolean;
+ selectYield: boolean;
+ selectValidator: boolean;
+ submit: boolean;
+ };
+};
+
+export const makeDefaultEarnIntent = (): EarnMachineIntent => ({
+ selectedProviderYieldId: null,
+ selectedTokenKey: null,
+ selectedValidatorKeys: new Set(),
+ selectedYieldId: null,
+ stakeAmount: "0",
+ tronResource: null,
+ useMaxAmount: false,
+ selectedCategory: null,
+});
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 ce84ee57..cedef044 100644
--- a/packages/widget/src/pages/details/earn-page/state/types.ts
+++ b/packages/widget/src/pages/details/earn-page/state/types.ts
@@ -1,7 +1,6 @@
import type BigNumber from "bignumber.js";
import type { Maybe } from "purify-ts";
import type { KycGate } from "../../../../domain/types/kyc";
-import type { TokenBalanceScanResponseDto } from "../../../../domain/types/token-balance";
import type { TokenDto } from "../../../../domain/types/tokens";
import type { TronResourceType } from "../../../../domain/types/tron";
import type {
@@ -12,83 +11,17 @@ import type { ValidatorDto } from "../../../../generated/api/yield";
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";
-import type { Action } from "../../../../types/utils";
import type { PageCta } from "../../../components/page-cta";
import type { SelectedStakeData } from "../types";
-
-export type State = {
- selectedToken: Maybe;
- selectedStakeId: Maybe<
- TokenBalanceScanResponseDto["availableYields"][number]
- >;
- autoSelectYield: boolean;
- selectedValidators: Map;
- stakeAmount: BigNumber;
- useMaxAmount: boolean;
- tronResource: Maybe;
- selectedProviderYieldId: Maybe;
- selectedDashboardYieldCategory: DashboardYieldCategory | null;
-};
-
-type TokenBalanceSelectAction = Action<"token/select", TokenDto>;
-type TokenBalanceWithYieldsSelectAction = Action<
- "tokenBalance/select",
- TokenBalanceScanResponseDto
->;
-type DashboardYieldCategorySelectAction = Action<
- "dashboard/yield-category/select",
- DashboardYieldCategory
->;
-type PositionDetailsStakeInitializeAction = Action<
- "positionDetails/stake/initialize",
- Yield
->;
-type YieldSelectAction = Action<"yield/select", Yield>;
-
-type StakeAmountChangeAction = Action<"stakeAmount/change", BigNumber>;
-type StakeAmountMaxAction = Action<"stakeAmount/max", BigNumber>;
-type StateResetAction = Action<"state/reset">;
-
-type ValidatorSelectAction = Action<"validator/select", ValidatorDto>;
-type ValidatorMultiSelectAction = Action<"validator/multiselect", ValidatorDto>;
-type ValidatorRemoveAction = Action<"validator/remove", ValidatorDto>;
-
-type SelectTronResourceAction = Action<"tronResource/select", TronResourceType>;
-
-type ProviderYieldIdSelectAction = Action<
- "providerYieldId/select",
- Yield["id"]
->;
-
-export type Actions =
- | TokenBalanceSelectAction
- | TokenBalanceWithYieldsSelectAction
- | DashboardYieldCategorySelectAction
- | PositionDetailsStakeInitializeAction
- | YieldSelectAction
- | StakeAmountChangeAction
- | StakeAmountMaxAction
- | StateResetAction
- | ValidatorSelectAction
- | ValidatorMultiSelectAction
- | ValidatorRemoveAction
- | SelectTronResourceAction
- | ProviderYieldIdSelectAction;
-
-export type ExtraData = {
- actions: { onMaxClick: () => void };
- selectedStake: Maybe;
- stakeAmountLessThanMin: boolean;
- stakeAmountGreaterThanMax: boolean;
- stakeAmountGreaterThanAvailableAmount: boolean;
- stakeAmountIsZero: boolean;
- availableAmount: Maybe;
- availableYields: Maybe;
- hasNotYieldsForToken: boolean;
- availableDashboardYieldCategories: DashboardYieldCategory[];
-};
+import type {
+ EarnMachineStatus,
+ EarnMachineView,
+ EarnTokenOption,
+} from "./effect-atom-poc/types";
export type EarnPageContextType = {
+ machine: EarnMachineView;
+ machineStatus: EarnMachineStatus;
cta: PageCta;
selectedTokenAvailableAmount: Maybe<{
symbol: string;
@@ -100,17 +33,19 @@ export type EarnPageContextType = {
symbol: string;
rewardsTokenSymbol: string;
selectedStakeData: Maybe;
- selectedStake: ExtraData["selectedStake"];
+ selectedStake: Maybe;
+ selectedProviderYieldId: Maybe;
selectedDashboardYieldCategory: DashboardYieldCategory | null;
availableDashboardYieldCategories: DashboardYieldCategory[];
onDashboardYieldCategorySelect: (category: DashboardYieldCategory) => void;
onYieldSelect: (yieldId: string) => void;
- onTokenBalanceSelect: (tokenBalance: TokenBalanceScanResponseDto) => void;
+ onTokenBalanceSelect: (tokenBalance: EarnTokenOption) => void;
onStakeAmountChange: (value: BigNumber) => void;
+ onProviderYieldIdSelect: (yieldId: Yield["id"]) => void;
estimatedRewards: ReturnType;
yieldType: string;
onMaxClick: () => void;
- stakeAmount: State["stakeAmount"];
+ stakeAmount: BigNumber;
isFetching: boolean;
buttonDisabled: boolean;
onClick: () => void;
@@ -122,7 +57,7 @@ export type EarnPageContextType = {
onYieldSearch: (value: string) => void;
onValidatorSelect: (item: ValidatorDto) => void;
onValidatorRemove: (item: ValidatorDto) => void;
- selectedValidators: State["selectedValidators"];
+ selectedValidators: Map;
isError: boolean;
rewardToken: ReturnType;
onSelectOpportunityClose: () => void;
@@ -132,10 +67,10 @@ export type EarnPageContextType = {
appLoading: boolean;
yieldOpportunityLoading: boolean;
tokenBalancesScanLoading: boolean;
- selectedToken: State["selectedToken"];
+ selectedToken: Maybe;
tokenBalancesData: Maybe<{
- all: TokenBalanceScanResponseDto[];
- filtered: TokenBalanceScanResponseDto[];
+ all: EarnTokenOption[];
+ filtered: EarnTokenOption[];
}>;
onTokenSearch: (value: string) => void;
onValidatorSearch: (value: string) => void;
@@ -145,7 +80,7 @@ export type EarnPageContextType = {
tokenSearch: string;
stakeSearch: string;
defaultTokensIsLoading: boolean;
- tronResource: State["tronResource"];
+ tronResource: Maybe;
onTronResourceSelect: (value: TronResourceType) => void;
validation: {
submitted: boolean;
diff --git a/packages/widget/src/pages/details/earn-page/state/use-get-init-yield.ts b/packages/widget/src/pages/details/earn-page/state/use-get-init-yield.ts
deleted file mode 100644
index 8a9e0c7f..00000000
--- a/packages/widget/src/pages/details/earn-page/state/use-get-init-yield.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Maybe } from "purify-ts";
-import { useCallback } from "react";
-import { tokenString } from "../../../../domain";
-import type { TokenBalanceScanResponseDto } from "../../../../domain/types/token-balance";
-import type { TokenDto } from "../../../../domain/types/tokens";
-import type { DashboardYieldCategory } from "../../../../domain/types/yields";
-import { getCachedFirstEligibleYield } from "../../../../hooks/api/use-multi-yields";
-import { useSKQueryClient } from "../../../../providers/query-client";
-import { useTokenBalancesMap } from "./use-token-balances-map";
-
-export const useGetInitYield = () => {
- const queryClient = useSKQueryClient();
- const tokenBalancesMap = useTokenBalancesMap();
-
- return useCallback(
- ({
- availableYields,
- selectedDashboardYieldCategory,
- selectedToken,
- }: {
- availableYields?: TokenBalanceScanResponseDto["availableYields"];
- selectedDashboardYieldCategory?: DashboardYieldCategory | null;
- selectedToken: TokenDto;
- }) =>
- (availableYields
- ? Maybe.of({ availableYields })
- : Maybe.fromNullable(tokenBalancesMap.get(tokenString(selectedToken)))
- ).chain((val) =>
- getCachedFirstEligibleYield({
- dashboardYieldCategory: selectedDashboardYieldCategory,
- queryClient,
- yieldIds: val.availableYields,
- })
- ),
- [queryClient, tokenBalancesMap]
- );
-};
diff --git a/packages/widget/src/pages/details/earn-page/state/use-get-token-balances-map.ts b/packages/widget/src/pages/details/earn-page/state/use-get-token-balances-map.ts
deleted file mode 100644
index 539ed54f..00000000
--- a/packages/widget/src/pages/details/earn-page/state/use-get-token-balances-map.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useCallback } from "react";
-import { tokenString } from "../../../../domain";
-import type { TokenBalanceScanResponseDto } from "../../../../domain/types/token-balance";
-import type { TokenString } from "../../../../domain/types/tokens";
-
-export const useGetTokenBalancesMap = () =>
- useCallback(
- ({
- defaultTokens,
- tokenBalancesScan,
- }: {
- tokenBalancesScan: ReadonlyArray;
- defaultTokens: ReadonlyArray;
- }) =>
- new Map([
- ...(defaultTokens ?? []).map((v) => [tokenString(v.token), v] as const),
- ...(tokenBalancesScan ?? []).map(
- (v) => [tokenString(v.token), v] as const
- ),
- ]),
- []
- );
diff --git a/packages/widget/src/pages/details/earn-page/state/use-init-token.ts b/packages/widget/src/pages/details/earn-page/state/use-init-token.ts
deleted file mode 100644
index 8bb31cfb..00000000
--- a/packages/widget/src/pages/details/earn-page/state/use-init-token.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { useQuery } from "@tanstack/react-query";
-import { BigNumber } from "bignumber.js";
-import { EitherAsync, Maybe } from "purify-ts";
-import { getTokenBalances } from "../../../../common/get-token-balances";
-import { tokenString } from "../../../../domain";
-import { getInitialToken } from "../../../../domain/types/stake";
-import { getFirstEligibleYield } from "../../../../hooks/api/use-multi-yields";
-import { getInitParams } from "../../../../hooks/use-init-params";
-import { usePositionsData } from "../../../../hooks/use-positions-data";
-import { useValidatorsConfig } from "../../../../hooks/use-validators-config";
-import { useApiClient } from "../../../../providers/api/api-client-provider";
-import { useSKQueryClient } from "../../../../providers/query-client";
-import { useSettings } from "../../../../providers/settings";
-import { useSKWallet } from "../../../../providers/sk-wallet";
-import { useGetTokenBalancesMap } from "./use-get-token-balances-map";
-
-/**
- *
- * @summary Get init token + loads its available yields
- */
-export const useInitToken = () => {
- const getTokenBalancesMap = useGetTokenBalancesMap();
- const {
- isLedgerLive,
- isConnected,
- network,
- additionalAddresses,
- address,
- isConnecting,
- } = useSKWallet();
- const queryClient = useSKQueryClient();
- const apiClient = useApiClient();
- const { data: positionsData } = usePositionsData();
-
- const {
- externalProviders,
- tokensForEnabledYieldsOnly,
- preferredTokenYieldsPerNetwork,
- } = useSettings();
-
- const validatorsConfig = useValidatorsConfig();
-
- return useQuery({
- staleTime: Number.POSITIVE_INFINITY,
- queryKey: [
- "init-token",
- isConnected,
- network,
- address,
- additionalAddresses,
- ],
- enabled: !isConnecting,
- queryFn: async () =>
- (
- await getTokenBalances({
- additionalAddresses,
- address,
- apiClient,
- network,
- queryClient,
- tokensForEnabledYieldsOnly,
- }).chain((val) =>
- getInitParams({
- isLedgerLive,
- queryClient,
- apiClient,
- externalProviders,
- }).chain((initParams) =>
- EitherAsync.liftEither(
- getInitialToken({
- network,
- preferredTokenYieldsPerNetwork:
- preferredTokenYieldsPerNetwork ?? null,
- defaultTokens: val.defaultTokens,
- tokenBalances: val.tokenBalancesScan,
- initQueryParams: Maybe.fromNullable(initParams),
- }).toEither(new Error("could not get initial token"))
- ).chain((token) =>
- EitherAsync.liftEither(
- Maybe.fromNullable(
- getTokenBalancesMap(val).get(tokenString(token))
- ).toEither(new Error("could not get token balance"))
- )
- .chain((tokenBalance) =>
- getFirstEligibleYield({
- isConnected,
- isLedgerLive,
- queryClient,
- apiClient,
- network,
- yieldIds: tokenBalance.availableYields,
- initParams: initParams,
- positionsData: positionsData,
- tokenBalanceAmount: new BigNumber(tokenBalance.amount),
- validatorsConfig,
- preferredTokenYieldsPerNetwork:
- preferredTokenYieldsPerNetwork ?? null,
- })
- )
- .map(() => token)
- )
- )
- )
- ).unsafeCoerce(),
- });
-};
diff --git a/packages/widget/src/pages/details/earn-page/state/use-init-yield.ts b/packages/widget/src/pages/details/earn-page/state/use-init-yield.ts
deleted file mode 100644
index 9f44f0b3..00000000
--- a/packages/widget/src/pages/details/earn-page/state/use-init-yield.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { useQuery } from "@tanstack/react-query";
-import BigNumber from "bignumber.js";
-import { EitherAsync, Maybe } from "purify-ts";
-import { getTokenBalances } from "../../../../common/get-token-balances";
-import { tokenString } from "../../../../domain";
-import type { TokenDto } from "../../../../domain/types/tokens";
-import type { DashboardYieldCategory } from "../../../../domain/types/yields";
-import { getFirstEligibleYield } from "../../../../hooks/api/use-multi-yields";
-import { getInitParams } from "../../../../hooks/use-init-params";
-import { usePositionsData } from "../../../../hooks/use-positions-data";
-import { useValidatorsConfig } from "../../../../hooks/use-validators-config";
-import { useApiClient } from "../../../../providers/api/api-client-provider";
-import { useSKQueryClient } from "../../../../providers/query-client";
-import { useSettings } from "../../../../providers/settings";
-import { useSKWallet } from "../../../../providers/sk-wallet";
-import { useGetTokenBalancesMap } from "./use-get-token-balances-map";
-
-export const useInitYield = ({
- selectedDashboardYieldCategory,
- selectedToken,
-}: {
- selectedDashboardYieldCategory?: DashboardYieldCategory | null;
- selectedToken: Maybe;
-}) => {
- const getTokenBalancesMap = useGetTokenBalancesMap();
- const {
- isLedgerLive,
- isConnected,
- network,
- additionalAddresses,
- address,
- isConnecting,
- } = useSKWallet();
- const queryClient = useSKQueryClient();
- const apiClient = useApiClient();
- const {
- externalProviders,
- tokensForEnabledYieldsOnly,
- preferredTokenYieldsPerNetwork,
- } = useSettings();
- const { data: positionsData } = usePositionsData();
-
- const validatorsConfig = useValidatorsConfig();
-
- return useQuery({
- staleTime: Number.POSITIVE_INFINITY,
- queryKey: [
- "init-yield",
- isConnected,
- network,
- additionalAddresses,
- address,
- selectedDashboardYieldCategory ?? null,
- selectedToken.extract(),
- ],
- enabled: !isConnecting,
- queryFn: async () =>
- (
- await EitherAsync.liftEither(
- selectedToken.toEither(new Error("no token selected"))
- ).chain((token) =>
- getTokenBalances({
- additionalAddresses,
- address,
- apiClient,
- network,
- queryClient,
- selectedDashboardYieldCategory,
- tokensForEnabledYieldsOnly,
- })
- .chain((val) =>
- EitherAsync.liftEither(
- Maybe.fromNullable(
- getTokenBalancesMap(val).get(tokenString(token))
- ).toEither(new Error("could not get token balance"))
- )
- )
- .chain((val) =>
- getInitParams({
- isLedgerLive,
- queryClient,
- apiClient,
- externalProviders,
- }).chain((initParams) =>
- getFirstEligibleYield({
- isConnected,
- isLedgerLive,
- queryClient,
- apiClient,
- network,
- yieldIds: val.availableYields,
- dashboardYieldCategory: selectedDashboardYieldCategory,
- initParams: initParams,
- positionsData: positionsData,
- tokenBalanceAmount: new BigNumber(val.amount),
- validatorsConfig,
- preferredTokenYieldsPerNetwork:
- preferredTokenYieldsPerNetwork ?? null,
- })
- )
- )
- )
- ).unsafeCoerce(),
- });
-};
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 f46a1791..a5904c40 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
@@ -1,22 +1,31 @@
+import type BigNumber from "bignumber.js";
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 type { TokenDto } from "../../../../domain/types/tokens";
+import type { TronResourceType } from "../../../../domain/types/tron";
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";
-export const useStakeEnterRequestDto = () => {
- const {
- selectedStake,
- stakeAmount,
- useMaxAmount,
- selectedValidators,
- tronResource,
- selectedToken,
- selectedProviderYieldId,
- } = useEarnPageState();
+export const useStakeEnterRequestDto = ({
+ selectedProviderYieldId,
+ selectedStake,
+ selectedToken,
+ selectedValidators,
+ stakeAmount,
+ tronResource,
+ useMaxAmount,
+}: {
+ selectedProviderYieldId: Maybe;
+ selectedStake: Maybe;
+ selectedToken: Maybe;
+ selectedValidators: Map;
+ stakeAmount: BigNumber;
+ tronResource: Maybe;
+ useMaxAmount: boolean;
+}) => {
const { address, additionalAddresses, isLedgerLive } = useSKWallet();
return useMemo(
diff --git a/packages/widget/src/pages/details/earn-page/state/use-token-balance.ts b/packages/widget/src/pages/details/earn-page/state/use-token-balance.ts
deleted file mode 100644
index 107ee6e4..00000000
--- a/packages/widget/src/pages/details/earn-page/state/use-token-balance.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import BigNumber from "bignumber.js";
-import type { Maybe } from "purify-ts";
-import { useMemo } from "react";
-import { tokenString } from "../../../../domain";
-import type { TokenDto } from "../../../../domain/types/tokens";
-import type { DashboardYieldCategory } from "../../../../domain/types/yields";
-import { useTokenBalancesMap } from "./use-token-balances-map";
-
-export const useTokenBalance = ({
- selectedDashboardYieldCategory,
- selectedToken,
-}: {
- selectedDashboardYieldCategory?: DashboardYieldCategory | null;
- selectedToken: Maybe;
-}) => {
- const tokenBalancesMap = useTokenBalancesMap({
- selectedDashboardYieldCategory,
- });
-
- const tokenBalance = useMemo(
- () =>
- selectedToken.chainNullable((val) =>
- tokenBalancesMap.get(tokenString(val))
- ),
- [selectedToken, tokenBalancesMap]
- );
-
- const availableAmount = useMemo(
- () => tokenBalance.map((v) => new BigNumber(v.amount)),
- [tokenBalance]
- );
-
- const availableYields = useMemo(
- () => tokenBalance.map((v) => v.availableYields),
- [tokenBalance]
- );
-
- return {
- availableAmount,
- availableYields,
- tokenBalance,
- };
-};
diff --git a/packages/widget/src/pages/details/earn-page/state/use-token-balances-map.ts b/packages/widget/src/pages/details/earn-page/state/use-token-balances-map.ts
deleted file mode 100644
index 4e7afeaa..00000000
--- a/packages/widget/src/pages/details/earn-page/state/use-token-balances-map.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { DashboardYieldCategory } from "../../../../domain/types/yields";
-import { useDefaultTokens } from "../../../../hooks/api/use-default-tokens";
-import { useTokenBalancesScan } from "../../../../hooks/api/use-token-balances-scan";
-import { useGetTokenBalancesMap } from "./use-get-token-balances-map";
-
-export const useTokenBalancesMap = ({
- selectedDashboardYieldCategory,
-}: {
- selectedDashboardYieldCategory?: DashboardYieldCategory | null;
-} = {}) => {
- const tokenBalancesScan = useTokenBalancesScan();
- const defaultTokens = useDefaultTokens({
- yieldCategory: selectedDashboardYieldCategory,
- });
-
- return useGetTokenBalancesMap()({
- defaultTokens: defaultTokens.data ?? [],
- tokenBalancesScan: tokenBalancesScan.data ?? [],
- });
-};
diff --git a/packages/widget/src/pages/details/earn-page/state/use-track-state-events.ts b/packages/widget/src/pages/details/earn-page/state/use-track-state-events.ts
deleted file mode 100644
index c72f161f..00000000
--- a/packages/widget/src/pages/details/earn-page/state/use-track-state-events.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { Maybe } from "purify-ts";
-import { useEffect } from "react";
-import type { TokenDto } from "../../../../domain/types/tokens";
-import type { Yield } from "../../../../domain/types/yields";
-import { useTrackEvent } from "../../../../hooks/tracking/use-track-event";
-
-export const useTrackStateEvents = ({
- initToken,
- initYield,
-}: {
- initToken: Maybe;
- initYield: Maybe;
-}) => {
- const trackEvent = useTrackEvent();
-
- /**
- * Track init yield selected event
- */
- useEffect(() => {
- initYield.ifJust((v) =>
- trackEvent("initYield", {
- yieldId: v.id,
- network: v.token.network,
- tokenName: v.token.name,
- tokenAddress: v.token.address,
- tokenSymbol: v.token.symbol,
- })
- );
- }, [initYield, trackEvent]);
-
- /**
- * Track init token selected event
- */
- useEffect(() => {
- initToken.ifJust((v) =>
- trackEvent("initToken", {
- name: v.name,
- network: v.network,
- address: v.address,
- symbol: v.symbol,
- decimals: v.decimals,
- })
- );
- }, [initToken, trackEvent]);
-};
diff --git a/packages/widget/src/pages/details/earn-page/state/utils.ts b/packages/widget/src/pages/details/earn-page/state/utils.ts
deleted file mode 100644
index c874a776..00000000
--- a/packages/widget/src/pages/details/earn-page/state/utils.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { List, Maybe } from "purify-ts";
-import type { PositionsData } from "../../../../domain/types/positions";
-import { getMinStakeAmount } from "../../../../domain/types/stake";
-import { getYieldActionArg, type Yield } from "../../../../domain/types/yields";
-import type { State } from "./types";
-
-export const onYieldSelectState = ({
- yieldDto,
- positionsData,
-}: {
- yieldDto: Yield;
- positionsData: PositionsData;
-}): Pick<
- State,
- | "selectedStakeId"
- | "stakeAmount"
- | "selectedValidators"
- | "tronResource"
- | "selectedProviderYieldId"
-> => ({
- selectedStakeId: Maybe.of(yieldDto.id),
- stakeAmount: getMinStakeAmount(yieldDto, positionsData),
- selectedValidators: new Map(),
- tronResource: Maybe.fromFalsy(
- getYieldActionArg(yieldDto, "enter", "tronResource")?.required
- ).map(() => "ENERGY"),
- selectedProviderYieldId: Maybe.fromNullable(
- getYieldActionArg(yieldDto, "enter", "providerId")
- )
- .filter((val) => !!val.required && !!val.options?.length)
- .chain((val) => List.head(val.options ?? [])),
-});
diff --git a/packages/widget/src/pages/position-details/hooks/use-pending-actions.ts b/packages/widget/src/pages/position-details/hooks/use-pending-actions.ts
index fa1fa81e..18aa7799 100644
--- a/packages/widget/src/pages/position-details/hooks/use-pending-actions.ts
+++ b/packages/widget/src/pages/position-details/hooks/use-pending-actions.ts
@@ -21,7 +21,7 @@ import {
} from "../../../hooks/navigation/use-unstake-or-pending-action-params";
import { useTrackEvent } from "../../../hooks/tracking/use-track-event";
import { useSavedRef } from "../../../hooks/use-saved-ref";
-import { usePendingActionStore } from "../../../providers/pending-action-store";
+import { useSetPendingActionRequest } from "../../../providers/pending-action-store";
import { useSKWallet } from "../../../providers/sk-wallet";
import { defaultFormattedNumber } from "../../../utils";
import {
@@ -231,7 +231,7 @@ export const usePendingActions = () => {
const pendingActionSelectValidatorMatch =
usePendingActionSelectValidatorMatch();
- const pendingActionStore = usePendingActionStore();
+ const setPendingActionRequest = useSetPendingActionRequest();
const continuePendingActionFlow = ({
integrationData,
@@ -253,9 +253,9 @@ export const usePendingActions = () => {
integration: integrationData,
selectedValidators,
}).ifRight((val) => {
- pendingActionStore.send({
- type: "initFlow",
- data: {
+ setPendingActionRequest(
+ Maybe.of({
+ actionDto: Maybe.empty(),
gasFeeToken: val.gasFeeToken,
integrationData: val.integrationData,
interactedToken: yieldBalance.token,
@@ -265,8 +265,8 @@ export const usePendingActions = () => {
address: val.address,
additionalAddresses: val.additionalAddresses,
},
- },
- });
+ })
+ );
const pendingActionReviewPath =
getPositionDetailsPendingActionReviewPath(plain);
diff --git a/packages/widget/src/pages/position-details/hooks/use-position-details.ts b/packages/widget/src/pages/position-details/hooks/use-position-details.ts
index b67b2e65..05ad8237 100644
--- a/packages/widget/src/pages/position-details/hooks/use-position-details.ts
+++ b/packages/widget/src/pages/position-details/hooks/use-position-details.ts
@@ -22,7 +22,7 @@ import {
} from "../../../hooks/navigation/use-unstake-or-pending-action-params";
import { useTrackEvent } from "../../../hooks/tracking/use-track-event";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
-import { useExitStakeStore } from "../../../providers/exit-stake-store";
+import { useSetExitStakeRequest } from "../../../providers/exit-stake-store";
import { defaultFormattedNumber } from "../../../utils";
import {
useUnstakeOrPendingActionDispatch,
@@ -57,7 +57,7 @@ export const usePositionDetails = () => {
const { plain } = useUnstakeOrPendingActionParams();
const stakeExitRequestDto = useStakeExitRequestDto();
- const exitStore = useExitStakeStore();
+ const setExitStakeRequest = useSetExitStakeRequest();
const yieldKycGate = useYieldKycGate({ yieldDto: integrationData });
const kycGateIsBlocking = yieldKycGate.isGateBlocking;
const kycProviderName = integrationData
@@ -97,17 +97,17 @@ export const usePositionDetails = () => {
integrationData,
unstakeToken,
}).ifJust((val) => {
- exitStore.send({
- type: "initFlow",
- data: {
+ setExitStakeRequest(
+ Maybe.of({
addresses: val.stakeExitRequestDto.addresses,
+ actionDto: Maybe.empty(),
gasFeeToken: val.stakeExitRequestDto.gasFeeToken,
integrationData: val.integrationData,
requestDto: val.stakeExitRequestDto.dto,
unstakeAmount,
unstakeToken: val.unstakeToken,
- },
- });
+ })
+ );
navigate(
getPositionDetailsUnstakeReviewPath(plain) ?? "unstake/review"
);
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 e7f06c45..2bbbb2d3 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
@@ -1,6 +1,4 @@
import { useMachine } from "@xstate/react";
-import type { SnapshotFromStore } from "@xstate/store";
-import { useSelector } from "@xstate/store/react";
import { EitherAsync, Maybe } from "purify-ts";
import { type RefObject, useState } from "react";
import { assign, setup } from "xstate";
@@ -8,18 +6,19 @@ import { getValidStakeSessionTx } from "../../../domain";
import { useTrackEvent } from "../../../hooks/tracking/use-track-event";
import { useSavedRef } from "../../../hooks/use-saved-ref";
import { useApiClient } from "../../../providers/api/api-client-provider";
-import { useExitStakeStore } from "../../../providers/exit-stake-store";
+import {
+ type ExitStakeRequest,
+ useExitStakeRequest,
+ useSetExitStakeRequest,
+} from "../../../providers/exit-stake-store";
import { useSKWallet } from "../../../providers/sk-wallet";
import type { GetMaybeJust } from "../../../types/utils";
export const useUnstakeMachine = ({ onDone }: { onDone: () => void }) => {
const trackEvent = useTrackEvent();
- const exitStore = useExitStakeStore();
- const exitRequest = useSelector(
- useExitStakeStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const exitRequest = useExitStakeRequest().unsafeCoerce();
+ const setExitStakeRequest = useSetExitStakeRequest();
const apiClient = useApiClient();
const { address, additionalAddresses } = useSKWallet();
@@ -27,7 +26,7 @@ export const useUnstakeMachine = ({ onDone }: { onDone: () => void }) => {
const machineParams = useSavedRef({
onDone,
trackEvent,
- exitStore,
+ setExitStakeRequest,
apiClient,
getData: () =>
Maybe.fromRecord({
@@ -52,18 +51,10 @@ const getMachine = (
ref: Readonly<
RefObject<{
onDone: () => void;
- exitStore: ReturnType;
+ setExitStakeRequest: ReturnType;
trackEvent: ReturnType;
apiClient: ReturnType;
- getData: () => Maybe<
- GetMaybeJust<
- SnapshotFromStore<
- ReturnType
- >["context"]["data"]
- > & {
- address: string;
- }
- >;
+ getData: () => Maybe;
}>
>
) =>
@@ -141,10 +132,12 @@ const getMachine = (
EitherAsync.liftEither(getValidStakeSessionTx(actionDto))
)
.ifRight((result) =>
- ref.current.exitStore.send({
- type: "setActionDto",
- data: result,
- })
+ ref.current.setExitStakeRequest((request) =>
+ request.map((value) => ({
+ ...value,
+ actionDto: Maybe.of(result),
+ }))
+ )
)
)
.caseOf({
diff --git a/packages/widget/src/pages/review/hooks/use-action-review.hook.ts b/packages/widget/src/pages/review/hooks/use-action-review.hook.ts
index 4a2b35a6..6c69decf 100644
--- a/packages/widget/src/pages/review/hooks/use-action-review.hook.ts
+++ b/packages/widget/src/pages/review/hooks/use-action-review.hook.ts
@@ -1,4 +1,3 @@
-import { useSelector } from "@xstate/store/react";
import { List, Maybe } from "purify-ts";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
@@ -16,7 +15,10 @@ import {
} from "../../../domain/types/yields";
import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { useYieldType } from "../../../hooks/use-yield-type";
-import { useActivityContext } from "../../../providers/activity-provider";
+import {
+ useActivitySelectedAction,
+ useActivitySelectedYield,
+} from "../../../providers/activity-provider";
import { defaultFormattedNumber } from "../../../utils";
import { dateOlderThen7Days } from "../../../utils/date";
import { MaybeWindow } from "../../../utils/maybe-window";
@@ -28,17 +30,8 @@ export const useActionReview = () => {
const navigate = useNavigate();
const { t } = useTranslation();
- const activityContext = useActivityContext();
-
- const selectedAction = useSelector(
- activityContext,
- (state) => state.context.selectedAction
- ).unsafeCoerce();
-
- const selectedYield = useSelector(
- activityContext,
- (state) => state.context.selectedYield
- ).unsafeCoerce();
+ const selectedAction = useActivitySelectedAction().unsafeCoerce();
+ const selectedYield = useActivitySelectedYield().unsafeCoerce();
const inputToken = useMemo(
() =>
diff --git a/packages/widget/src/pages/review/hooks/use-pending-review.hook.ts b/packages/widget/src/pages/review/hooks/use-pending-review.hook.ts
index 014695d3..306023ae 100644
--- a/packages/widget/src/pages/review/hooks/use-pending-review.hook.ts
+++ b/packages/widget/src/pages/review/hooks/use-pending-review.hook.ts
@@ -1,5 +1,4 @@
import { useMutation, useQuery } from "@tanstack/react-query";
-import { useSelector } from "@xstate/store/react";
import BigNumber from "bignumber.js";
import { Maybe } from "purify-ts";
import type { ComponentProps } from "react";
@@ -14,20 +13,20 @@ import { useGasWarningCheck } from "../../../hooks/use-gas-warning-check";
import { getRewardTokenSymbols } from "../../../hooks/use-reward-token-details/get-reward-token-symbols";
import { useSavedRef } from "../../../hooks/use-saved-ref";
import { useApiClient } from "../../../providers/api/api-client-provider";
-import { usePendingActionStore } from "../../../providers/pending-action-store";
+import {
+ usePendingActionRequest,
+ useSetPendingActionRequest,
+} from "../../../providers/pending-action-store";
import { defaultFormattedNumber } from "../../../utils";
import { getGasFeeInUSD } from "../../../utils/formatters";
import type { PageCta } from "../../components/page-cta";
import type { MetaInfoProps } from "../pages/common-page/common.page";
export const usePendingActionReview = () => {
- const pendingActionStore = usePendingActionStore();
+ const setPendingActionRequest = useSetPendingActionRequest();
const apiClient = useApiClient();
- const pendingRequest = useSelector(
- pendingActionStore,
- (state) => state.context.data
- ).unsafeCoerce();
+ const pendingRequest = usePendingActionRequest().unsafeCoerce();
const actionPreviewQuery = useQuery({
enabled: !!pendingRequest,
@@ -114,7 +113,9 @@ export const usePendingActionReview = () => {
(await actionPreviewQuery.refetch()).data ??
Promise.reject(new Error("Pending actions error")),
onSuccess: (data) => {
- pendingActionStore.send({ type: "setActionDto", data });
+ setPendingActionRequest((request) =>
+ request.map((value) => ({ ...value, actionDto: Maybe.of(data) }))
+ );
navigate("../steps", { relative: "path" });
},
});
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 80c57036..13aed76a 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
@@ -1,5 +1,4 @@
import { useMutation, useQuery } from "@tanstack/react-query";
-import { useSelector } from "@xstate/store/react";
import BigNumber from "bignumber.js";
import { List, Maybe } from "purify-ts";
import { useMemo } from "react";
@@ -17,7 +16,10 @@ import { useRewardTokenDetails } from "../../../hooks/use-reward-token-details";
import { useSavedRef } from "../../../hooks/use-saved-ref";
import { useYieldType } from "../../../hooks/use-yield-type";
import { useApiClient } from "../../../providers/api/api-client-provider";
-import { useEnterStakeStore } from "../../../providers/enter-stake-store";
+import {
+ useEnterStakeRequest,
+ useSetEnterStakeRequest,
+} from "../../../providers/enter-stake-store";
import { useSettings } from "../../../providers/settings";
import { defaultFormattedNumber } from "../../../utils";
import { getGasFeeInUSD } from "../../../utils/formatters";
@@ -26,12 +28,8 @@ import type { MetaInfoProps } from "../pages/common-page/common.page";
import { useFees } from "./use-fees";
export const useStakeReview = () => {
- const enterStore = useEnterStakeStore();
-
- const enterRequest = useSelector(
- enterStore,
- (state) => state.context.data
- ).unsafeCoerce();
+ const enterRequest = useEnterStakeRequest().unsafeCoerce();
+ const setEnterStakeRequest = useSetEnterStakeRequest();
const apiClient = useApiClient();
@@ -195,7 +193,9 @@ export const useStakeReview = () => {
);
},
onSuccess: (data) => {
- enterStore.send({ type: "setActionDto", data });
+ setEnterStakeRequest((request) =>
+ request.map((value) => ({ ...value, actionDto: Maybe.of(data) }))
+ );
if (positionDetailsStakeReviewMatch) {
navigate("../steps", { relative: "path" });
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 89025ef4..5a2d4797 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
@@ -1,5 +1,4 @@
import { useQuery } from "@tanstack/react-query";
-import { useSelector } from "@xstate/store/react";
import BigNumber from "bignumber.js";
import { Maybe } from "purify-ts";
import type { ComponentProps } from "react";
@@ -19,7 +18,7 @@ import { useGasWarningCheck } from "../../../hooks/use-gas-warning-check";
import { getRewardTokenSymbols } from "../../../hooks/use-reward-token-details/get-reward-token-symbols";
import { useSavedRef } from "../../../hooks/use-saved-ref";
import { useApiClient } from "../../../providers/api/api-client-provider";
-import { useExitStakeStore } from "../../../providers/exit-stake-store";
+import { useExitStakeRequest } from "../../../providers/exit-stake-store";
import { defaultFormattedNumber } from "../../../utils";
import { getGasFeeInUSD } from "../../../utils/formatters";
import type { PageCta } from "../../components/page-cta";
@@ -27,10 +26,7 @@ import { useUnstakeMachine } from "../../position-details/hooks/use-unstake-mach
import type { MetaInfoProps } from "../pages/common-page/common.page";
export const useUnstakeActionReview = () => {
- const exitRequest = useSelector(
- useExitStakeStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const exitRequest = useExitStakeRequest().unsafeCoerce();
const apiClient = useApiClient();
diff --git a/packages/widget/src/pages/steps/pages/activity-steps.page.tsx b/packages/widget/src/pages/steps/pages/activity-steps.page.tsx
index 878bb46b..2bb1f91e 100644
--- a/packages/widget/src/pages/steps/pages/activity-steps.page.tsx
+++ b/packages/widget/src/pages/steps/pages/activity-steps.page.tsx
@@ -1,31 +1,21 @@
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
import { getActionInputToken } from "../../../domain/types/action";
import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
-import { useActivityContext } from "../../../providers/activity-provider";
+import {
+ useActivitySelectedAction,
+ useActivitySelectedValidators,
+ useActivitySelectedYield,
+} from "../../../providers/activity-provider";
import { StepsPage } from "./common.page";
export const ActivityStepsPage = () => {
useTrackPage("activitySteps");
- const activityContext = useActivityContext();
-
- const selectedAction = useSelector(
- activityContext,
- (state) => state.context.selectedAction
- ).unsafeCoerce();
-
- const selectedYield = useSelector(
- activityContext,
- (state) => state.context.selectedYield
- ).unsafeCoerce();
-
- const selectedValidators = useSelector(
- activityContext,
- (state) => state.context.selectedValidators
- ).unsafeCoerce();
+ const selectedAction = useActivitySelectedAction().unsafeCoerce();
+ const selectedYield = useActivitySelectedYield().unsafeCoerce();
+ const selectedValidators = useActivitySelectedValidators().unsafeCoerce();
const providersDetails = useProvidersDetails({
integrationData: useMemo(() => Maybe.of(selectedYield), [selectedYield]),
diff --git a/packages/widget/src/pages/steps/pages/pending-steps.page.tsx b/packages/widget/src/pages/steps/pages/pending-steps.page.tsx
index 6be4a85d..c40b6ca4 100644
--- a/packages/widget/src/pages/steps/pages/pending-steps.page.tsx
+++ b/packages/widget/src/pages/steps/pages/pending-steps.page.tsx
@@ -1,20 +1,16 @@
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
import { useUnstakeOrPendingActionParams } from "../../../hooks/navigation/use-unstake-or-pending-action-params";
import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { usePositionBalances } from "../../../hooks/use-position-balances";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
-import { usePendingActionStore } from "../../../providers/pending-action-store";
+import { usePendingActionRequest } from "../../../providers/pending-action-store";
import { StepsPage } from "./common.page";
export const PendingStepsPage = () => {
useTrackPage("pendingActionSteps");
- const pendingRequest = useSelector(
- usePendingActionStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const pendingRequest = usePendingActionRequest().unsafeCoerce();
const { plain } = useUnstakeOrPendingActionParams();
diff --git a/packages/widget/src/pages/steps/pages/stake-steps.page.tsx b/packages/widget/src/pages/steps/pages/stake-steps.page.tsx
index 363dafe1..562d9b1b 100644
--- a/packages/widget/src/pages/steps/pages/stake-steps.page.tsx
+++ b/packages/widget/src/pages/steps/pages/stake-steps.page.tsx
@@ -1,9 +1,8 @@
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
-import { useEnterStakeStore } from "../../../providers/enter-stake-store";
+import { useEnterStakeRequest } from "../../../providers/enter-stake-store";
import { useSKWallet } from "../../../providers/sk-wallet";
import { StepsPage } from "./common.page";
@@ -12,10 +11,7 @@ export const StakeStepsPage = () => {
const { address, network } = useSKWallet();
- const enterRequest = useSelector(
- useEnterStakeStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const enterRequest = useEnterStakeRequest().unsafeCoerce();
const onSignSuccess = () =>
Maybe.fromRecord({
diff --git a/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx b/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx
index 42f7aa3d..08af6b0c 100644
--- a/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx
+++ b/packages/widget/src/pages/steps/pages/unstake-steps.page.tsx
@@ -1,20 +1,16 @@
-import { useSelector } from "@xstate/store/react";
import { Maybe } from "purify-ts";
import { useMemo } from "react";
import { useUnstakeOrPendingActionParams } from "../../../hooks/navigation/use-unstake-or-pending-action-params";
import { useTrackPage } from "../../../hooks/tracking/use-track-page";
import { usePositionBalances } from "../../../hooks/use-position-balances";
import { useProvidersDetails } from "../../../hooks/use-provider-details";
-import { useExitStakeStore } from "../../../providers/exit-stake-store";
+import { useExitStakeRequest } from "../../../providers/exit-stake-store";
import { StepsPage } from "./common.page";
export const UnstakeStepsPage = () => {
useTrackPage("unstakeSteps");
- const exitRequest = useSelector(
- useExitStakeStore(),
- (state) => state.context.data
- ).unsafeCoerce();
+ const exitRequest = useExitStakeRequest().unsafeCoerce();
const { plain } = useUnstakeOrPendingActionParams();
const positionBalances = usePositionBalances({
diff --git a/packages/widget/src/providers/activity-provider/index.tsx b/packages/widget/src/providers/activity-provider/index.tsx
index 62ad7f26..f2ab921e 100644
--- a/packages/widget/src/providers/activity-provider/index.tsx
+++ b/packages/widget/src/providers/activity-provider/index.tsx
@@ -1,54 +1,31 @@
-import { createStore } from "@xstate/store";
+import { useAtomSet, useAtomValue } from "@effect/atom-react";
+import * as Atom from "effect/unstable/reactivity/Atom";
import { Maybe } from "purify-ts";
-import { createContext, type PropsWithChildren, useContext } from "react";
import type { ActionDto } from "../../domain/types/action";
import type { Yield } from "../../domain/types/yields";
import type { ValidatorDto } from "../../generated/api/yield";
-const store = createStore({
- context: {
- selectedAction: Maybe.empty() as Maybe,
- selectedYield: Maybe.empty() as Maybe,
- selectedValidators: Maybe.empty() as Maybe,
- },
- on: {
- setSelectedAction: (
- _,
- event: {
- data: Maybe<{
- selectedAction: ActionDto;
- selectedYield: Yield;
- selectedValidators: ValidatorDto[];
- }>;
- }
- ) => ({
- selectedAction: event.data.map(({ selectedAction }) => selectedAction),
- selectedYield: event.data.map(({ selectedYield }) => selectedYield),
- selectedValidators: event.data.map(
- ({ selectedValidators }) => selectedValidators
- ),
- }),
- },
-});
-
-const ActivityContext = createContext(undefined);
-
-export const ActivityProvider = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
+type ActivitySelection = {
+ selectedAction: ActionDto;
+ selectedYield: Yield;
+ selectedValidators: ValidatorDto[];
};
-export const useActivityContext = () => {
- const value = useContext(ActivityContext);
+type ActivitySelectionState = Maybe;
- if (!value) {
- throw new Error(
- "useActivityContext must be used within a ActivityProvider"
- );
- }
+const activitySelectionAtom = Atom.make(
+ Maybe.empty()
+).pipe(Atom.keepAlive, Atom.withLabel("activitySelectionAtom"));
- return value;
-};
+const useActivitySelection = () => useAtomValue(activitySelectionAtom);
+
+export const useActivitySelectedAction = () =>
+ useActivitySelection().map(({ selectedAction }) => selectedAction);
+
+export const useActivitySelectedYield = () =>
+ useActivitySelection().map(({ selectedYield }) => selectedYield);
+
+export const useActivitySelectedValidators = () =>
+ useActivitySelection().map(({ selectedValidators }) => selectedValidators);
+
+export const useSetActivitySelection = () => useAtomSet(activitySelectionAtom);
diff --git a/packages/widget/src/providers/api/api-client.ts b/packages/widget/src/providers/api/api-client.ts
index ee44b0cf..5edcef20 100644
--- a/packages/widget/src/providers/api/api-client.ts
+++ b/packages/widget/src/providers/api/api-client.ts
@@ -22,6 +22,11 @@ type ApiClientOptions = {
readonly suppressRichErrors?: boolean;
};
+export type EffectApiClient = {
+ readonly legacy: LegacyApi.LegacyApi;
+ readonly yield: YieldApi.YieldApi;
+};
+
const runtime = ManagedRuntime.make(FetchHttpClient.layer);
const inspectResponse = ({
@@ -211,7 +216,9 @@ export const createApiClient = ({
}: WidgetApiClientOptions) => {
const baseClient = runtime.runSync(HttpClient.HttpClient);
- const bindApiClients = (options?: ApiClientOptions) => {
+ const createEffectApiClients = (
+ options?: Pick
+ ): EffectApiClient => {
const legacyHttpClient = configureClient({
apiKey,
baseUrl,
@@ -224,19 +231,28 @@ export const createApiClient = ({
client: baseClient,
suppressRichErrors: options?.suppressRichErrors,
});
- const legacyApi = LegacyApi.make(legacyHttpClient);
- const yieldApi = YieldApi.make(yieldHttpClient);
return {
- legacy: bindLegacyApi({ api: legacyApi, options }),
- yield: bindYieldApi({ api: yieldApi, options }),
+ legacy: LegacyApi.make(legacyHttpClient),
+ yield: YieldApi.make(yieldHttpClient),
+ };
+ };
+
+ const bindApiClients = (options?: ApiClientOptions) => {
+ const effectApi = createEffectApiClients(options);
+
+ return {
+ legacy: bindLegacyApi({ api: effectApi.legacy, options }),
+ yield: bindYieldApi({ api: effectApi.yield, options }),
};
};
+ const effectClients = createEffectApiClients();
const boundClients = bindApiClients();
return {
...boundClients,
+ effect: effectClients,
withOptions: (options: ApiClientOptions) => bindApiClients(options),
};
};
diff --git a/packages/widget/src/providers/effect-atom-runtime/index.tsx b/packages/widget/src/providers/effect-atom-runtime/index.tsx
new file mode 100644
index 00000000..2669d044
--- /dev/null
+++ b/packages/widget/src/providers/effect-atom-runtime/index.tsx
@@ -0,0 +1,18 @@
+import { RegistryProvider } from "@effect/atom-react";
+import type { PropsWithChildren } from "react";
+import { config } from "../../config";
+import { useApiClient } from "../api/api-client-provider";
+import { stakeKitEffectApiClientAtom } from "./stakekit-api-service";
+
+export const SKAtomRuntimeProvider = ({ children }: PropsWithChildren) => {
+ const apiClient = useApiClient();
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/widget/src/providers/effect-atom-runtime/stakekit-api-service.ts b/packages/widget/src/providers/effect-atom-runtime/stakekit-api-service.ts
new file mode 100644
index 00000000..194c89de
--- /dev/null
+++ b/packages/widget/src/providers/effect-atom-runtime/stakekit-api-service.ts
@@ -0,0 +1,18 @@
+import { Context, Data } from "effect";
+import * as Atom from "effect/unstable/reactivity/Atom";
+import type { EffectApiClient } from "../api/api-client";
+
+export class MissingStakeKitApiClient extends Data.TaggedError(
+ "MissingStakeKitApiClient"
+)<{
+ readonly message: string;
+}> {}
+
+export class StakeKitApiService extends Context.Service<
+ StakeKitApiService,
+ EffectApiClient
+>()("stakekit/widget/StakeKitApiService") {}
+
+export const stakeKitEffectApiClientAtom = Atom.make(
+ null
+).pipe(Atom.withLabel("stakeKitEffectApiClientAtom"));
diff --git a/packages/widget/src/providers/enter-stake-store/index.tsx b/packages/widget/src/providers/enter-stake-store/index.tsx
index 04a0258f..736aa145 100644
--- a/packages/widget/src/providers/enter-stake-store/index.tsx
+++ b/packages/widget/src/providers/enter-stake-store/index.tsx
@@ -1,6 +1,6 @@
-import { createStore } from "@xstate/store";
+import { useAtomSet, useAtomValue } from "@effect/atom-react";
+import * as Atom from "effect/unstable/reactivity/Atom";
import { Maybe } from "purify-ts";
-import { createContext, type PropsWithChildren, useContext } from "react";
import type {
ActionDto,
YieldCreateActionDto,
@@ -10,7 +10,7 @@ import type { TokenDto } from "../../domain/types/tokens";
import type { Yield } from "../../domain/types/yields";
import type { ValidatorDto } from "../../generated/api/yield";
-type InitData = {
+type EnterStakeInitData = {
requestDto: YieldCreateActionDto;
addresses: AddressesDto;
gasFeeToken: Yield["token"];
@@ -19,43 +19,17 @@ type InitData = {
selectedToken: TokenDto;
};
-type Store = Maybe }>;
+type EnterStakeRequest = EnterStakeInitData & {
+ actionDto: Maybe;
+};
-const store = createStore({
- context: { data: Maybe.empty() as Store },
- on: {
- initFlow: (_, event: { data: InitData }) => ({
- data: Maybe.of({ ...event.data, actionDto: Maybe.empty() }),
- }),
- setActionDto: (context, event: { data: ActionDto }) => ({
- data: context.data.map((data) => ({
- ...data,
- actionDto: Maybe.of(event.data),
- })),
- }),
- },
-});
+type EnterStakeState = Maybe;
-const EnterStakeStoreContext = createContext(
- undefined
+const enterStakeRequestAtom = Atom.make(Maybe.empty()).pipe(
+ Atom.keepAlive,
+ Atom.withLabel("enterStakeRequestAtom")
);
-export const EnterStakeStoreProvider = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
-};
-
-export const useEnterStakeStore = () => {
- const value = useContext(EnterStakeStoreContext);
+export const useEnterStakeRequest = () => useAtomValue(enterStakeRequestAtom);
- if (!value) {
- throw new Error(
- "useEnterStakeStore must be used within a EnterStakeStoreProvider"
- );
- }
-
- return value;
-};
+export const useSetEnterStakeRequest = () => useAtomSet(enterStakeRequestAtom);
diff --git a/packages/widget/src/providers/exit-stake-store/index.tsx b/packages/widget/src/providers/exit-stake-store/index.tsx
index 3083df69..268a0834 100644
--- a/packages/widget/src/providers/exit-stake-store/index.tsx
+++ b/packages/widget/src/providers/exit-stake-store/index.tsx
@@ -1,7 +1,7 @@
-import { createStore } from "@xstate/store";
+import { useAtomSet, useAtomValue } from "@effect/atom-react";
import type BigNumber from "bignumber.js";
+import * as Atom from "effect/unstable/reactivity/Atom";
import { Maybe } from "purify-ts";
-import { createContext, type PropsWithChildren, useContext } from "react";
import type {
ActionDto,
YieldCreateActionDto,
@@ -10,7 +10,7 @@ import type { AddressesDto } from "../../domain/types/addresses";
import type { TokenDto, YieldTokenDto } from "../../domain/types/tokens";
import type { Yield } from "../../domain/types/yields";
-type InitData = {
+type ExitStakeInitData = {
requestDto: YieldCreateActionDto;
addresses: AddressesDto;
gasFeeToken: Yield["token"];
@@ -19,43 +19,17 @@ type InitData = {
unstakeToken: TokenDto | YieldTokenDto;
};
-type Store = Maybe }>;
+export type ExitStakeRequest = ExitStakeInitData & {
+ actionDto: Maybe;
+};
-const store = createStore({
- context: { data: Maybe.empty() as Store },
- on: {
- initFlow: (_, event: { data: InitData }) => ({
- data: Maybe.of({ ...event.data, actionDto: Maybe.empty() }),
- }),
- setActionDto: (context, event: { data: ActionDto }) => ({
- data: context.data.map((data) => ({
- ...data,
- actionDto: Maybe.of(event.data),
- })),
- }),
- },
-});
+type ExitStakeState = Maybe;
-const ExitStakeStoreContext = createContext(
- undefined
+const exitStakeRequestAtom = Atom.make(Maybe.empty()).pipe(
+ Atom.keepAlive,
+ Atom.withLabel("exitStakeRequestAtom")
);
-export const ExitStakeStoreProvider = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
-};
-
-export const useExitStakeStore = () => {
- const value = useContext(ExitStakeStoreContext);
+export const useExitStakeRequest = () => useAtomValue(exitStakeRequestAtom);
- if (!value) {
- throw new Error(
- "useExitStakeStore must be used within a ExitStakeStoreProvider"
- );
- }
-
- return value;
-};
+export const useSetExitStakeRequest = () => useAtomSet(exitStakeRequestAtom);
diff --git a/packages/widget/src/providers/index.tsx b/packages/widget/src/providers/index.tsx
index fbe44d8d..65eaaf34 100644
--- a/packages/widget/src/providers/index.tsx
+++ b/packages/widget/src/providers/index.tsx
@@ -6,16 +6,12 @@ import { SummaryProvider } from "../hooks/use-summary";
import { DisableTransitionDurationProvider } from "../navigation/containers/animation-layout";
import { CurrentLayoutProvider } from "../pages/components/layout/layout-context";
import { PoweredByHeightProvider } from "../pages/components/powered-by";
-import { EarnPageStateProvider } from "../pages/details/earn-page/state/earn-page-state-context";
import { i18nInstance } from "../translation";
-import { ActivityProvider } from "./activity-provider";
import { SKApiClientProvider } from "./api/api-client-provider";
-import { EnterStakeStoreProvider } from "./enter-stake-store";
-import { ExitStakeStoreProvider } from "./exit-stake-store";
+import { SKAtomRuntimeProvider } from "./effect-atom-runtime";
import { ListStateContextProvider } from "./list-state";
import { SKLocationProvider } from "./location";
import { MountAnimationProvider } from "./mount-animation";
-import { PendingActionStoreProvider } from "./pending-action-store";
import { SKQueryClientProvider } from "./query-client";
import { RainbowProvider } from "./rainbow";
import { RootElementProvider } from "./root-element";
@@ -34,49 +30,41 @@ export const Providers = ({
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- {children}
-
-
-
-
-
+
+ {children}
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/widget/src/providers/misc/tron-connector.ts b/packages/widget/src/providers/misc/tron-connector.ts
index 6d929f56..48d3f76c 100644
--- a/packages/widget/src/providers/misc/tron-connector.ts
+++ b/packages/widget/src/providers/misc/tron-connector.ts
@@ -171,7 +171,7 @@ export const getTronConnectors = ({
createTronConnector({
walletDetailsParams,
metaConfig: "tronLedger",
- adapter: new LedgerAdapter(),
+ adapter: new LedgerAdapter() as unknown as Adapter,
}),
}),
],
diff --git a/packages/widget/src/providers/pending-action-store/index.tsx b/packages/widget/src/providers/pending-action-store/index.tsx
index 6a612f39..2e306280 100644
--- a/packages/widget/src/providers/pending-action-store/index.tsx
+++ b/packages/widget/src/providers/pending-action-store/index.tsx
@@ -1,6 +1,6 @@
-import { createStore } from "@xstate/store";
+import { useAtomSet, useAtomValue } from "@effect/atom-react";
+import * as Atom from "effect/unstable/reactivity/Atom";
import { Maybe } from "purify-ts";
-import { createContext, type PropsWithChildren, useContext } from "react";
import type {
ActionDto,
YieldCreateManageActionDto,
@@ -10,7 +10,7 @@ import type { YieldPendingActionType } from "../../domain/types/pending-action";
import type { TokenDto, YieldTokenDto } from "../../domain/types/tokens";
import type { Yield } from "../../domain/types/yields";
-type InitData = {
+type PendingActionInitData = {
requestDto: YieldCreateManageActionDto;
addresses: AddressesDto;
pendingActionType: YieldPendingActionType;
@@ -19,43 +19,18 @@ type InitData = {
gasFeeToken: TokenDto;
};
-type Store = Maybe }>;
-
-const store = createStore({
- context: { data: Maybe.empty() as Store },
- on: {
- initFlow: (_, event: { data: InitData }) => ({
- data: Maybe.of({ ...event.data, actionDto: Maybe.empty() }),
- }),
- setActionDto: (context, event: { data: ActionDto }) => ({
- data: context.data.map((data) => ({
- ...data,
- actionDto: Maybe.of(event.data),
- })),
- }),
- },
-});
-
-const PendingActionStoreContext = createContext(
- undefined
-);
-
-export const PendingActionStoreProvider = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
+type PendingActionRequest = PendingActionInitData & {
+ actionDto: Maybe;
};
-export const usePendingActionStore = () => {
- const value = useContext(PendingActionStoreContext);
+type PendingActionState = Maybe;
- if (!value) {
- throw new Error(
- "usePendingActionStore must be used within a PendingActionProvider"
- );
- }
+const pendingActionRequestAtom = Atom.make(
+ Maybe.empty()
+).pipe(Atom.keepAlive, Atom.withLabel("pendingActionRequestAtom"));
- return value;
-};
+export const usePendingActionRequest = () =>
+ useAtomValue(pendingActionRequestAtom);
+
+export const useSetPendingActionRequest = () =>
+ useAtomSet(pendingActionRequestAtom);
diff --git a/packages/widget/tests/hooks/default-tokens.test.ts b/packages/widget/tests/hooks/default-tokens.test.ts
deleted file mode 100644
index 91c7fbdd..00000000
--- a/packages/widget/tests/hooks/default-tokens.test.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { QueryClient } from "@tanstack/react-query";
-import { describe, expect, it, vi } from "vitest";
-import type { TokenBalanceScanResponseDto } from "../../src/domain/types/token-balance";
-import type { TokenDto } from "../../src/domain/types/tokens";
-import type { TokenWithAvailableYieldsDto as LegacyTokenWithAvailableYieldsDto } from "../../src/generated/api/legacy";
-import type { TokenWithAvailableYieldsDto as YieldTokenWithAvailableYieldsDto } from "../../src/generated/api/yield";
-import {
- fetchDefaultTokens,
- getDefaultTokens,
-} from "../../src/hooks/api/use-default-tokens";
-import type { ApiClient } from "../../src/providers/api/api-client";
-
-const createToken = (symbol: string): TokenDto => ({
- name: symbol,
- symbol,
- decimals: 18,
- network: "ethereum",
- logoURI: `https://assets.stakek.it/tokens/${symbol.toLowerCase()}.svg`,
-});
-
-const createTokenWithYields = (
- symbol: string,
- yieldId = `${symbol.toLowerCase()}-yield`
-): YieldTokenWithAvailableYieldsDto => ({
- token: createToken(symbol) as YieldTokenWithAvailableYieldsDto["token"],
- availableYields: [yieldId],
-});
-
-const createApiClient = ({
- getLegacyTokens = vi.fn(),
- getYieldTokens = vi.fn(),
-}: {
- getLegacyTokens?: ReturnType;
- getYieldTokens?: ReturnType;
-}) =>
- ({
- withOptions: () => ({
- legacy: {
- TokenControllerGetTokens: getLegacyTokens,
- },
- yield: {
- TokensControllerGetTokens: getYieldTokens,
- },
- }),
- }) as unknown as ApiClient;
-
-describe("fetchDefaultTokens", () => {
- it("uses the yield API with network and yield type filters", async () => {
- const tokens = [
- createTokenWithYields("ETH", "eth-staking"),
- createTokenWithYields("SOL", "sol-staking"),
- createTokenWithYields("USDC", "usdc-staking"),
- ];
- const getLegacyTokens = vi.fn();
- const getYieldTokens = vi.fn(async ({ params }) => ({
- items: tokens.slice(params.offset, params.offset + params.limit),
- total: tokens.length,
- offset: params.offset,
- limit: params.limit,
- }));
- const apiClient = createApiClient({ getLegacyTokens, getYieldTokens });
-
- const { pages } = await fetchDefaultTokens({
- apiClient,
- limit: 2,
- network: "ethereum",
- offset: 0,
- yieldTypes: ["staking"],
- });
-
- expect(pages).toHaveLength(2);
- expect(pages[0]).toMatchObject({
- limit: 2,
- tokens: tokens.slice(0, 2).map(
- (tokenWithYields): TokenBalanceScanResponseDto => ({
- ...tokenWithYields,
- amount: "0",
- })
- ),
- nextOffset: 2,
- offset: 0,
- total: 3,
- });
- expect(pages[1].tokens).toEqual(
- [tokens[2]].map((tokenWithYields) => ({
- ...tokenWithYields,
- amount: "0",
- }))
- );
- expect(getLegacyTokens).not.toHaveBeenCalled();
- expect(getYieldTokens.mock.calls.map(([arg]) => arg.params.offset)).toEqual(
- [0, 2]
- );
- });
-
- it("uses the legacy API when no enabled-yield filter is requested", async () => {
- const token = {
- token: createToken("ETH") as LegacyTokenWithAvailableYieldsDto["token"],
- availableYields: ["eth-staking"],
- };
- const getLegacyTokens = vi.fn(async () => [token]);
- const getYieldTokens = vi.fn();
- const apiClient = createApiClient({ getLegacyTokens, getYieldTokens });
-
- const { pages } = await fetchDefaultTokens({
- apiClient,
- network: "ethereum",
- });
-
- expect(pages).toHaveLength(1);
- expect(pages[0].tokens).toEqual([{ ...token, amount: "0" }]);
- expect(pages[0].nextOffset).toBeUndefined();
- expect(getYieldTokens).not.toHaveBeenCalled();
- expect(getLegacyTokens).toHaveBeenCalledWith({
- params: {
- enabledYieldsOnly: undefined,
- network: "ethereum",
- },
- });
- });
-});
-
-describe("getDefaultTokens", () => {
- it("fetches every page from the paginated yield API", async () => {
- const tokens = [
- createTokenWithYields("ETH"),
- createTokenWithYields("SOL"),
- createTokenWithYields("USDC"),
- createTokenWithYields("ATOM"),
- createTokenWithYields("OSMO"),
- ];
- let activePageRequests = 0;
- let maxActivePageRequests = 0;
- const getYieldTokens = vi.fn(
- async ({ params }: { params: { offset?: number; limit?: number } }) => {
- const offset = params.offset ?? 0;
- const limit = params.limit ?? 2;
-
- if (offset > 0) {
- activePageRequests += 1;
- maxActivePageRequests = Math.max(
- maxActivePageRequests,
- activePageRequests
- );
- await new Promise((resolve) => setTimeout(resolve, 0));
- activePageRequests -= 1;
- }
-
- return {
- items: tokens.slice(offset, offset + limit),
- total: tokens.length,
- offset,
- limit,
- };
- }
- );
- const apiClient = createApiClient({ getYieldTokens });
- const queryClient = new QueryClient({
- defaultOptions: { queries: { retry: false } },
- });
-
- const result = await getDefaultTokens({
- apiClient,
- enabledYieldsOnly: true,
- limit: 2,
- network: "ethereum",
- queryClient,
- }).run();
-
- expect(result.unsafeCoerce().map((item) => item.token.symbol)).toEqual([
- "ETH",
- "SOL",
- "USDC",
- "ATOM",
- "OSMO",
- ]);
- expect(getYieldTokens.mock.calls.map(([arg]) => arg.params.offset)).toEqual(
- [0, 2, 4]
- );
- expect(maxActivePageRequests).toBe(2);
- });
-});
diff --git a/packages/widget/tests/use-cases/rwa-kyc-flow.test.tsx b/packages/widget/tests/use-cases/rwa-kyc-flow.test.tsx
index 17ea290d..02e057d9 100644
--- a/packages/widget/tests/use-cases/rwa-kyc-flow.test.tsx
+++ b/packages/widget/tests/use-cases/rwa-kyc-flow.test.tsx
@@ -29,14 +29,9 @@ const skProps = {
},
} satisfies SKAppProps;
-const mockKycStatus = ({
- status,
- statusAfterRefresh,
-}: {
- status: string;
- statusAfterRefresh?: string;
-}) => {
+const mockKycStatus = ({ status }: { status: string }) => {
let calls = 0;
+ let currentStatus = status;
let requestedAddress: string | null = null;
const handler = http.get(
@@ -47,8 +42,7 @@ const mockKycStatus = ({
requestedAddress = new URL(request.url).searchParams.get("address");
return HttpResponse.json({
- kycStatus:
- calls > 1 && statusAfterRefresh ? statusAfterRefresh : status,
+ kycStatus: currentStatus,
kycUrl: "https://issuer.example/verify",
});
}
@@ -58,6 +52,9 @@ const mockKycStatus = ({
handler,
getCalls: () => calls,
getRequestedAddress: () => requestedAddress,
+ setStatus: (newStatus: string) => {
+ currentStatus = newStatus;
+ },
};
};
@@ -216,10 +213,7 @@ describe("RWA KYC flow", () => {
});
it("refreshes pending status with Check status", async ({ worker }) => {
- const kycStatus = mockKycStatus({
- status: "pending",
- statusAfterRefresh: "approved",
- });
+ const kycStatus = mockKycStatus({ status: "pending" });
worker.use(...mockKycRequiredDefaultYield(), kycStatus.handler);
@@ -229,6 +223,7 @@ describe("RWA KYC flow", () => {
.element(app.getByTestId("kyc-gate-card-pending"))
.toBeInTheDocument();
+ kycStatus.setStatus("approved");
await userEvent.click(app.getByTestId("kyc-gate-check-status"));
await expect
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2ee83a2a..427c4253 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -33,12 +33,15 @@ catalogs:
'@cosmos-kit/walletconnect':
specifier: 2.15.1
version: 2.15.1
+ '@effect/atom-react':
+ specifier: 4.0.0-beta.87
+ version: 4.0.0-beta.87
'@effect/openapi-generator':
- specifier: 4.0.0-beta.68
- version: 4.0.0-beta.68
+ specifier: 4.0.0-beta.87
+ version: 4.0.0-beta.87
'@effect/platform-node':
- specifier: 4.0.0-beta.68
- version: 4.0.0-beta.68
+ specifier: 4.0.0-beta.87
+ version: 4.0.0-beta.87
'@faker-js/faker':
specifier: ^10.4.0
version: 10.4.0
@@ -162,9 +165,6 @@ catalogs:
'@xstate/react':
specifier: ^6.1.0
version: 6.1.0
- '@xstate/store':
- specifier: ^3.17.5
- version: 3.17.5
autoprefixer:
specifier: ^10.5.0
version: 10.5.0
@@ -187,8 +187,8 @@ catalogs:
specifier: ^4.1.0
version: 4.1.0
effect:
- specifier: 4.0.0-beta.68
- version: 4.0.0-beta.68
+ specifier: 4.0.0-beta.87
+ version: 4.0.0-beta.87
eventemitter3:
specifier: ^5.0.4
version: 5.0.4
@@ -318,7 +318,7 @@ importers:
version: 2.5.0
'@commitlint/cli':
specifier: 'catalog:'
- version: 21.0.2(@types/node@25.7.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)
+ version: 21.0.2(@types/node@26.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)
'@commitlint/config-conventional':
specifier: 'catalog:'
version: 21.0.2
@@ -427,12 +427,15 @@ importers:
'@cosmos-kit/walletconnect':
specifier: 'catalog:'
version: 2.15.1(@cosmjs/amino@0.36.2)(@cosmjs/proto-signing@0.36.2)(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(@walletconnect/types@2.11.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1))(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)
+ '@effect/atom-react':
+ specifier: 'catalog:'
+ version: 4.0.0-beta.87(effect@4.0.0-beta.87)(react@19.2.7)(scheduler@0.27.0)
'@effect/openapi-generator':
specifier: 'catalog:'
- version: 4.0.0-beta.68(@effect/platform-node@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.68)
+ version: 4.0.0-beta.87(@effect/platform-node@4.0.0-beta.87(bufferutil@4.0.9)(effect@4.0.0-beta.87)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.87)(encoding@0.1.13)
'@effect/platform-node':
specifier: 'catalog:'
- version: 4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10)
+ version: 4.0.0-beta.87(bufferutil@4.0.9)(effect@4.0.0-beta.87)(ioredis@5.10.1)(utf-8-validate@5.0.10)
'@faker-js/faker':
specifier: 'catalog:'
version: 10.4.0
@@ -553,9 +556,6 @@ importers:
'@xstate/react':
specifier: 'catalog:'
version: 6.1.0(@types/react@19.2.17)(react@19.2.7)(xstate@5.31.1)
- '@xstate/store':
- specifier: 'catalog:'
- version: 3.17.5(react@19.2.7)
autoprefixer:
specifier: 'catalog:'
version: 10.5.0(postcss@8.5.14)
@@ -579,7 +579,7 @@ importers:
version: 4.1.0
effect:
specifier: 'catalog:'
- version: 4.0.0-beta.68
+ version: 4.0.0-beta.87
eventemitter3:
specifier: 'catalog:'
version: 5.0.4
@@ -708,40 +708,32 @@ packages:
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
engines: {node: '>=6.9.0'}
- '@babel/compat-data@7.29.3':
- resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==}
+ '@babel/compat-data@7.29.7':
+ resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==}
engines: {node: '>=6.9.0'}
'@babel/core@7.29.0':
resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
engines: {node: '>=6.9.0'}
- '@babel/generator@7.29.1':
- resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
- engines: {node: '>=6.9.0'}
-
'@babel/generator@7.29.7':
resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
engines: {node: '>=6.9.0'}
- '@babel/helper-compilation-targets@7.28.6':
- resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-globals@7.28.0':
- resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ '@babel/helper-compilation-targets@7.29.7':
+ resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==}
engines: {node: '>=6.9.0'}
'@babel/helper-globals@7.29.7':
resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
engines: {node: '>=6.9.0'}
- '@babel/helper-module-imports@7.28.6':
- resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ '@babel/helper-module-imports@7.29.7':
+ resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
engines: {node: '>=6.9.0'}
- '@babel/helper-module-transforms@7.28.6':
- resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ '@babel/helper-module-transforms@7.29.7':
+ resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -778,19 +770,14 @@ packages:
resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==}
engines: {node: ^22.18.0 || >=24.11.0}
- '@babel/helper-validator-option@7.27.1':
- resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
engines: {node: '>=6.9.0'}
- '@babel/helpers@7.29.2':
- resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==}
+ '@babel/helpers@7.29.7':
+ resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
engines: {node: '>=6.9.0'}
- '@babel/parser@7.29.3':
- resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==}
- engines: {node: '>=6.0.0'}
- hasBin: true
-
'@babel/parser@7.29.7':
resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
engines: {node: '>=6.0.0'}
@@ -894,18 +881,10 @@ packages:
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
- '@babel/template@7.28.6':
- resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
- engines: {node: '>=6.9.0'}
-
'@babel/template@7.29.7':
resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
engines: {node: '>=6.9.0'}
- '@babel/traverse@7.29.0':
- resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
- engines: {node: '>=6.9.0'}
-
'@babel/traverse@7.29.7':
resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
engines: {node: '>=6.9.0'}
@@ -1397,24 +1376,31 @@ packages:
resolution: {integrity: sha512-NStEiNulgpo8s1NQvvr/vVUaA31eYSkquOolxritMBiLCdJBM1rgNtOdQ1S2JfrDnn3vrU/UVkCE9ZHIpj5AzQ==}
engines: {node: '>=18'}
- '@effect/openapi-generator@4.0.0-beta.68':
- resolution: {integrity: sha512-+rWHNxb5OGei5A03ThiZKxQt8Q3NCPLIB+Gl5AinKW53gZWJ+eE77m1wrOYOyAQ7Zm8EGkxL1tTYDDdDimeJ6w==}
+ '@effect/atom-react@4.0.0-beta.87':
+ resolution: {integrity: sha512-obmHFW1PQDsb5vet/qaQuByGZL9htoZ7TgShh1E9xpKPZ2JoQNMibkytDyAg48E/s02FVGJxGlvCdEU8PBnTkA==}
+ peerDependencies:
+ effect: ^4.0.0-beta.87
+ react: ^19.2.4
+ scheduler: '*'
+
+ '@effect/openapi-generator@4.0.0-beta.87':
+ resolution: {integrity: sha512-yuhfX6hsZTnAmG5w6vj9E2M2IrGLa3n/cDTkaTBMGMJ0859qjpGVzuF+9/4Gl8r27aZ7AJxSQ9iMTVBF08kWoQ==}
hasBin: true
peerDependencies:
- '@effect/platform-node': ^4.0.0-beta.68
- effect: ^4.0.0-beta.68
+ '@effect/platform-node': ^4.0.0-beta.87
+ effect: ^4.0.0-beta.87
- '@effect/platform-node-shared@4.0.0-beta.68':
- resolution: {integrity: sha512-a6q/Rid2CQQBSoTIhBnT2oCozeaEvx5DXreSdT4jbvxPSC0j2l0EqcYdHgvEGDqvhocIAezgmSQbcHNcnyDg8w==}
+ '@effect/platform-node-shared@4.0.0-beta.87':
+ resolution: {integrity: sha512-I0X2aU+OSqLm76cMDWll9/kT+jmnGEt17uGJw/HlS8YdyfhkASjjKId+AqTqiJhQ3kp5+ciCHdJWjS6IBPtdBQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
- effect: ^4.0.0-beta.68
+ effect: ^4.0.0-beta.87
- '@effect/platform-node@4.0.0-beta.68':
- resolution: {integrity: sha512-q2WakGBfZ5wIg9WdL4flbIwxC5TNFq1/qroT4HRT9yOl19dLg0khd2LE9ewOybOPrTQHV3PUyV3Sx6Y3eMYTWQ==}
+ '@effect/platform-node@4.0.0-beta.87':
+ resolution: {integrity: sha512-bmW72GSCWcvsQStO39BmRgwcQ+311qXm9/MjllS6MgtYP0Id0Ivlysj5L+YTjCRwfJAimmlZEWBphWTwbA+bXw==}
engines: {node: '>=18.0.0'}
peerDependencies:
- effect: ^4.0.0-beta.68
+ effect: ^4.0.0-beta.87
ioredis: ^5.7.0
'@emnapi/core@1.10.0':
@@ -2284,6 +2270,9 @@ packages:
'@lit-labs/ssr-dom-shim@1.5.1':
resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==}
+ '@lit-labs/ssr-dom-shim@1.6.0':
+ resolution: {integrity: sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==}
+
'@lit/react@1.0.8':
resolution: {integrity: sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==}
peerDependencies:
@@ -4382,6 +4371,7 @@ packages:
'@stellar/stellar-base@14.1.0':
resolution: {integrity: sha512-A8kFli6QGy22SRF45IjgPAJfUNGjnI+R7g4DF5NZYVsD1kGf7B4ITyc4OPclLV9tqNI4/lXxafGEw0JEUbHixw==}
engines: {node: '>=20.0.0'}
+ deprecated: This package is now rolled into @stellar/stellar-sdk. Please use @stellar/stellar-sdk to continue receiving updates and support.
'@stellar/stellar-sdk@14.2.0':
resolution: {integrity: sha512-7nh2ogzLRMhfkIC0fGjn1LHUzk3jqVw8tjAuTt5ADWfL9CSGBL18ILucE9igz2L/RU2AZgeAvhujAnW91Ut/oQ==}
@@ -4663,8 +4653,8 @@ packages:
cpu: [arm64]
os: [win32]
- '@tybys/wasm-util@0.10.2':
- resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
+ '@tybys/wasm-util@0.10.3':
+ resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -5229,17 +5219,6 @@ packages:
xstate:
optional: true
- '@xstate/store@3.17.5':
- resolution: {integrity: sha512-ITajQVRAMa1pgmgM0OdRFYQ6OQ8GK4ynh391p13GKtZnELXn9Hgs9pr4mqO1Z95HH9INcmczmWi5xk3O18GBWg==}
- peerDependencies:
- react: ^18.2.0 || ^19.0.0
- solid-js: ^1.7.6
- peerDependenciesMeta:
- react:
- optional: true
- solid-js:
- optional: true
-
'@zeit/schemas@2.36.0':
resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==}
@@ -5499,11 +5478,6 @@ packages:
resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==}
engines: {node: '>=6.0.0'}
- baseline-browser-mapping@2.10.29:
- resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==}
- engines: {node: '>=6.0.0'}
- hasBin: true
-
baseline-browser-mapping@2.10.38:
resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==}
engines: {node: '>=6.0.0'}
@@ -5589,8 +5563,8 @@ packages:
resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==}
engines: {node: '>=14.16'}
- brace-expansion@1.1.14:
- resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==}
+ brace-expansion@1.1.15:
+ resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
@@ -6248,11 +6222,11 @@ packages:
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
- effect@4.0.0-beta.68:
- resolution: {integrity: sha512-JVmKXJvpyKBzr4xEr9nRlA6wQoInsI2WCfu2F69On49hvyDAmAOMb5ACa51HJksvRdqbSg7Zf0d++NQebY9cFQ==}
+ effect@4.0.0-beta.87:
+ resolution: {integrity: sha512-GANlkUQo9pPN4tIOeUhF/gdgBq4OA1w5lUstT/GjS3Csd48Y6KV1FbxsEtq4/SNRBkl6JE0nU9pP9fi6/+GU7A==}
- electron-to-chromium@1.5.353:
- resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==}
+ electron-to-chromium@1.5.377:
+ resolution: {integrity: sha512-cH1jZgJHoezfTnKfKwnScpHywTFVnJUNITDPREFdhNjiuD502+QFpG0Qk7G8jhsV/f+CEAFlIrzP1fT+IMb92g==}
elliptic@6.6.1:
resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==}
@@ -6646,6 +6620,7 @@ packages:
git-raw-commits@5.0.1:
resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==}
engines: {node: '>=18'}
+ deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead.
hasBin: true
glob@7.2.3:
@@ -6742,8 +6717,8 @@ packages:
hmac-drbg@1.0.1:
resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
- hono@4.12.26:
- resolution: {integrity: sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==}
+ hono@4.12.27:
+ resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==}
engines: {node: '>=16.9.0'}
html-encoding-sniffer@4.0.0:
@@ -6805,9 +6780,6 @@ packages:
idb-keyval@6.2.1:
resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==}
- idb-keyval@6.2.2:
- resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==}
-
idb-keyval@6.2.5:
resolution: {integrity: sha512-eKQkTnS0relYsSOYomx8ozIbmdsQCKUdhyuIaQ2DZgKuaxtyQQMkyD/wlnQN32pO3yutN1b1L8uqwcDKaJd7/Q==}
@@ -6830,8 +6802,8 @@ packages:
immer@11.1.8:
resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==}
- immutable@5.1.6:
- resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==}
+ immutable@5.1.7:
+ resolution: {integrity: sha512-47Xb+LFbZ/ZIjQMj6Q5J3IfK7PJFuqRdFOC9FpGgRTK6U2dAEVmkR9hp58qU4FpYux5YXpneDwkj2EP6lppzFA==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
@@ -7330,6 +7302,9 @@ packages:
lit-html@3.3.2:
resolution: {integrity: sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==}
+ lit-html@3.3.3:
+ resolution: {integrity: sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==}
+
lit@2.8.0:
resolution: {integrity: sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==}
@@ -7405,10 +7380,6 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
- lru-cache@11.3.6:
- resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==}
- engines: {node: 20 || >=22}
-
lru-cache@11.5.1:
resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==}
engines: {node: 20 || >=22}
@@ -7779,8 +7750,9 @@ packages:
node-readfiles@0.2.0:
resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==}
- node-releases@2.0.44:
- resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==}
+ node-releases@2.0.48:
+ resolution: {integrity: sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==}
+ engines: {node: '>=18'}
node-stdlib-browser@1.3.1:
resolution: {integrity: sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==}
@@ -8062,6 +8034,9 @@ packages:
pako@2.1.0:
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
+ pako@2.2.0:
+ resolution: {integrity: sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -9133,8 +9108,8 @@ packages:
thread-stream@0.15.2:
resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==}
- thread-stream@3.1.0:
- resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
+ thread-stream@3.2.0:
+ resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==}
throat@5.0.0:
resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==}
@@ -9165,11 +9140,11 @@ packages:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
- tldts-core@7.0.30:
- resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==}
+ tldts-core@7.4.4:
+ resolution: {integrity: sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==}
- tldts@7.0.30:
- resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==}
+ tldts@7.4.4:
+ resolution: {integrity: sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==}
hasBin: true
tmpl@1.0.5:
@@ -9335,8 +9310,8 @@ packages:
undici-types@7.21.0:
resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==}
- undici-types@7.25.0:
- resolution: {integrity: sha512-AXNgS1Byr27fTI+2bsPEkV9CxkT8H6xNyRI68b3TatlZo3RkzlqQBLL+w7SmGPVpokjHbcuNVQUWE7FRTg+LRA==}
+ undici-types@7.28.0:
+ resolution: {integrity: sha512-LJAfY+2w6HGeT8d8J1wNQsUGUEGio6NWWpwdwurQe4f6oojzCFuGLizl1KSve4irsTxyLly1QhEeE6iapdaIvQ==}
undici-types@8.3.0:
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
@@ -10165,19 +10140,19 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
- '@babel/compat-data@7.29.3': {}
+ '@babel/compat-data@7.29.7': {}
'@babel/core@7.29.0':
dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/generator': 7.29.1
- '@babel/helper-compilation-targets': 7.28.6
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
- '@babel/helpers': 7.29.2
- '@babel/parser': 7.29.3
- '@babel/template': 7.28.6
- '@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.0)
+ '@babel/helpers': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
debug: 4.4.3
@@ -10187,14 +10162,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/generator@7.29.1':
- dependencies:
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
- '@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.31
- jsesc: 3.1.0
-
'@babel/generator@7.29.7':
dependencies:
'@babel/parser': 7.29.7
@@ -10203,31 +10170,29 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
- '@babel/helper-compilation-targets@7.28.6':
+ '@babel/helper-compilation-targets@7.29.7':
dependencies:
- '@babel/compat-data': 7.29.3
- '@babel/helper-validator-option': 7.27.1
+ '@babel/compat-data': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
browserslist: 4.28.2
lru-cache: 5.1.1
semver: 6.3.1
- '@babel/helper-globals@7.28.0': {}
-
'@babel/helper-globals@7.29.7': {}
- '@babel/helper-module-imports@7.28.6':
+ '@babel/helper-module-imports@7.29.7':
dependencies:
- '@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
- '@babel/helper-module-imports': 7.28.6
- '@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.29.0
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
@@ -10247,16 +10212,12 @@ snapshots:
'@babel/helper-validator-identifier@8.0.2': {}
- '@babel/helper-validator-option@7.27.1': {}
-
- '@babel/helpers@7.29.2':
- dependencies:
- '@babel/template': 7.28.6
- '@babel/types': 7.29.0
+ '@babel/helper-validator-option@7.29.7': {}
- '@babel/parser@7.29.3':
+ '@babel/helpers@7.29.7':
dependencies:
- '@babel/types': 7.29.0
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
'@babel/parser@7.29.7':
dependencies:
@@ -10352,30 +10313,12 @@ snapshots:
'@babel/runtime@7.29.2': {}
- '@babel/template@7.28.6':
- dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
-
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
'@babel/parser': 7.29.7
'@babel/types': 7.29.7
- '@babel/traverse@7.29.0':
- dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/generator': 7.29.1
- '@babel/helper-globals': 7.28.0
- '@babel/parser': 7.29.3
- '@babel/template': 7.28.6
- '@babel/types': 7.29.0
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
-
'@babel/traverse@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
@@ -10456,10 +10399,10 @@ snapshots:
bech32: 2.0.0
cross-fetch: 3.2.0(encoding@0.1.13)
fastq: 1.20.1
- isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))
+ isomorphic-ws: 4.0.1(ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10))
nanoid: 3.3.14
ts-custom-error: 3.3.1
- ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)
+ ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- encoding
@@ -10729,11 +10672,11 @@ snapshots:
- zod
optional: true
- '@commitlint/cli@21.0.2(@types/node@25.7.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)':
+ '@commitlint/cli@21.0.2(@types/node@26.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)':
dependencies:
'@commitlint/format': 21.0.1
'@commitlint/lint': 21.0.2
- '@commitlint/load': 21.0.2(@types/node@25.7.0)(typescript@6.0.3)
+ '@commitlint/load': 21.0.2(@types/node@26.0.0)(typescript@6.0.3)
'@commitlint/read': 21.0.2(conventional-commits-parser@6.4.0)
'@commitlint/types': 21.0.1
tinyexec: 1.2.4
@@ -10778,14 +10721,14 @@ snapshots:
'@commitlint/rules': 21.0.2
'@commitlint/types': 21.0.1
- '@commitlint/load@21.0.2(@types/node@25.7.0)(typescript@6.0.3)':
+ '@commitlint/load@21.0.2(@types/node@26.0.0)(typescript@6.0.3)':
dependencies:
'@commitlint/config-validator': 21.0.1
'@commitlint/execute-rule': 21.0.1
'@commitlint/resolve-extends': 21.0.1
'@commitlint/types': 21.0.1
cosmiconfig: 9.0.2(typescript@6.0.3)
- cosmiconfig-typescript-loader: 6.3.0(@types/node@25.7.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3)
+ cosmiconfig-typescript-loader: 6.3.0(@types/node@26.0.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3)
es-toolkit: 1.48.1
is-plain-obj: 4.1.0
picocolors: 1.1.1
@@ -10939,8 +10882,8 @@ snapshots:
'@cosmjs/socket@0.36.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
dependencies:
'@cosmjs/stream': 0.36.2
- isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10))
- ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)
+ isomorphic-ws: 4.0.1(ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10))
+ ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)
xstream: 11.14.0
transitivePeerDependencies:
- bufferutil
@@ -11450,24 +11393,33 @@ snapshots:
'@scure/base': 1.2.6
eventemitter3: 5.0.4
- '@effect/openapi-generator@4.0.0-beta.68(@effect/platform-node@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.68)':
+ '@effect/atom-react@4.0.0-beta.87(effect@4.0.0-beta.87)(react@19.2.7)(scheduler@0.27.0)':
dependencies:
- '@effect/platform-node': 4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10)
- effect: 4.0.0-beta.68
+ effect: 4.0.0-beta.87
+ react: 19.2.7
+ scheduler: 0.27.0
+
+ '@effect/openapi-generator@4.0.0-beta.87(@effect/platform-node@4.0.0-beta.87(bufferutil@4.0.9)(effect@4.0.0-beta.87)(ioredis@5.10.1)(utf-8-validate@5.0.10))(effect@4.0.0-beta.87)(encoding@0.1.13)':
+ dependencies:
+ '@effect/platform-node': 4.0.0-beta.87(bufferutil@4.0.9)(effect@4.0.0-beta.87)(ioredis@5.10.1)(utf-8-validate@5.0.10)
+ effect: 4.0.0-beta.87
+ swagger2openapi: 7.0.8(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
- '@effect/platform-node-shared@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(utf-8-validate@5.0.10)':
+ '@effect/platform-node-shared@4.0.0-beta.87(bufferutil@4.0.9)(effect@4.0.0-beta.87)(utf-8-validate@5.0.10)':
dependencies:
'@types/ws': 8.18.1
- effect: 4.0.0-beta.68
- ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
+ effect: 4.0.0-beta.87
+ ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
- '@effect/platform-node@4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(ioredis@5.10.1)(utf-8-validate@5.0.10)':
+ '@effect/platform-node@4.0.0-beta.87(bufferutil@4.0.9)(effect@4.0.0-beta.87)(ioredis@5.10.1)(utf-8-validate@5.0.10)':
dependencies:
- '@effect/platform-node-shared': 4.0.0-beta.68(bufferutil@4.0.9)(effect@4.0.0-beta.68)(utf-8-validate@5.0.10)
- effect: 4.0.0-beta.68
+ '@effect/platform-node-shared': 4.0.0-beta.87(bufferutil@4.0.9)(effect@4.0.0-beta.87)(utf-8-validate@5.0.10)
+ effect: 4.0.0-beta.87
ioredis: 5.10.1
mime: 4.1.0
undici: 8.3.0
@@ -11981,7 +11933,7 @@ snapshots:
'@jest/pattern@30.4.0':
dependencies:
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
jest-regex-util: 30.4.0
'@jest/schemas@29.6.3':
@@ -12027,7 +11979,7 @@ snapshots:
'@jest/schemas': 30.4.1
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
'@types/yargs': 17.0.35
chalk: 4.1.2
@@ -12317,6 +12269,8 @@ snapshots:
'@lit-labs/ssr-dom-shim@1.5.1': {}
+ '@lit-labs/ssr-dom-shim@1.6.0': {}
+
'@lit/react@1.0.8(@types/react@19.2.17)':
dependencies:
'@types/react': 19.2.17
@@ -12328,7 +12282,7 @@ snapshots:
'@lit/reactive-element@2.1.2':
dependencies:
- '@lit-labs/ssr-dom-shim': 1.5.1
+ '@lit-labs/ssr-dom-shim': 1.6.0
'@luno-kit/core@0.0.13(@dedot/chaintypes@0.123.0(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(dedot@0.13.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ioredis@5.10.1)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)':
dependencies:
@@ -12608,7 +12562,7 @@ snapshots:
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
- '@tybys/wasm-util': 0.10.2
+ '@tybys/wasm-util': 0.10.3
optional: true
'@next/env@16.2.9': {}
@@ -14844,7 +14798,7 @@ snapshots:
'@solana/rpc-spec': 2.3.0(typescript@6.0.3)
'@solana/rpc-spec-types': 2.3.0(typescript@6.0.3)
typescript: 6.0.3
- undici-types: 7.25.0
+ undici-types: 7.28.0
'@solana/rpc-types@2.3.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)':
dependencies:
@@ -15933,7 +15887,7 @@ snapshots:
dependencies:
'@trezor/utils': 9.5.0(tslib@2.8.1)
tslib: 2.8.1
- ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
+ ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -16067,7 +16021,7 @@ snapshots:
'@turbo/windows-arm64@2.9.18':
optional: true
- '@tybys/wasm-util@0.10.2':
+ '@tybys/wasm-util@0.10.3':
dependencies:
tslib: 2.8.1
optional: true
@@ -16236,7 +16190,7 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
'@types/yargs-parser@21.0.3': {}
@@ -16907,7 +16861,7 @@ snapshots:
'@walletconnect/jsonrpc-utils': 1.0.8
'@walletconnect/safe-json': 1.0.2
events: 3.3.0
- ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)
+ ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -16915,8 +16869,8 @@ snapshots:
'@walletconnect/keyvaluestorage@1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10)))(ioredis@5.10.1)':
dependencies:
'@walletconnect/safe-json': 1.0.2
- idb-keyval: 6.2.2
- unstorage: 1.17.5(idb-keyval@6.2.2)(ioredis@5.10.1)
+ idb-keyval: 6.2.5
+ unstorage: 1.17.5(idb-keyval@6.2.5)(ioredis@5.10.1)
optionalDependencies:
'@react-native-async-storage/async-storage': 1.24.0(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))
transitivePeerDependencies:
@@ -18120,10 +18074,6 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
- '@xstate/store@3.17.5(react@19.2.7)':
- optionalDependencies:
- react: 19.2.7
-
'@zeit/schemas@2.36.0': {}
'@zondax/ledger-js@1.3.1':
@@ -18407,8 +18357,6 @@ snapshots:
base64url@3.0.1: {}
- baseline-browser-mapping@2.10.29: {}
-
baseline-browser-mapping@2.10.38: {}
bech32@1.1.4: {}
@@ -18503,7 +18451,7 @@ snapshots:
widest-line: 4.0.1
wrap-ansi: 8.1.0
- brace-expansion@1.1.14:
+ brace-expansion@1.1.15:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
@@ -18564,10 +18512,10 @@ snapshots:
browserslist@4.28.2:
dependencies:
- baseline-browser-mapping: 2.10.29
- caniuse-lite: 1.0.30001792
- electron-to-chromium: 1.5.353
- node-releases: 2.0.44
+ baseline-browser-mapping: 2.10.38
+ caniuse-lite: 1.0.30001799
+ electron-to-chromium: 1.5.377
+ node-releases: 2.0.48
update-browserslist-db: 1.2.3(browserslist@4.28.2)
bs58@4.0.1:
@@ -18895,9 +18843,9 @@ snapshots:
core-util-is@1.0.3: {}
- cosmiconfig-typescript-loader@6.3.0(@types/node@25.7.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3):
+ cosmiconfig-typescript-loader@6.3.0(@types/node@26.0.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3):
dependencies:
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
cosmiconfig: 9.0.2(typescript@6.0.3)
jiti: 2.6.1
typescript: 6.0.3
@@ -19212,7 +19160,7 @@ snapshots:
ee-first@1.1.1: {}
- effect@4.0.0-beta.68:
+ effect@4.0.0-beta.87:
dependencies:
'@standard-schema/spec': 1.1.0
fast-check: 4.8.0
@@ -19225,7 +19173,7 @@ snapshots:
uuid: 14.0.0
yaml: 2.9.0
- electron-to-chromium@1.5.353: {}
+ electron-to-chromium@1.5.377: {}
elliptic@6.6.1:
dependencies:
@@ -19768,7 +19716,7 @@ snapshots:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
- hono@4.12.26:
+ hono@4.12.27:
optional: true
html-encoding-sniffer@4.0.0:
@@ -19833,10 +19781,7 @@ snapshots:
idb-keyval@6.2.1:
optional: true
- idb-keyval@6.2.2: {}
-
- idb-keyval@6.2.5:
- optional: true
+ idb-keyval@6.2.5: {}
ieee754@1.2.1: {}
@@ -19851,7 +19796,7 @@ snapshots:
immer@11.1.8: {}
- immutable@5.1.6:
+ immutable@5.1.7:
optional: true
import-fresh@3.3.1:
@@ -20015,6 +19960,10 @@ snapshots:
dependencies:
ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)
+ isomorphic-ws@4.0.1(ws@7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)):
+ dependencies:
+ ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10)
+
isows@1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)):
dependencies:
ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
@@ -20139,7 +20088,7 @@ snapshots:
jest-mock@30.4.1:
dependencies:
'@jest/types': 30.4.1
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
jest-util: 30.4.1
jest-regex-util@29.6.3: {}
@@ -20158,7 +20107,7 @@ snapshots:
jest-util@30.4.1:
dependencies:
'@jest/types': 30.4.1
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
chalk: 4.1.2
ci-info: 4.4.0
graceful-fs: 4.2.11
@@ -20393,9 +20342,9 @@ snapshots:
lit-element@4.2.2:
dependencies:
- '@lit-labs/ssr-dom-shim': 1.5.1
+ '@lit-labs/ssr-dom-shim': 1.6.0
'@lit/reactive-element': 2.1.2
- lit-html: 3.3.2
+ lit-html: 3.3.3
lit-html@2.8.0:
dependencies:
@@ -20405,6 +20354,10 @@ snapshots:
dependencies:
'@types/trusted-types': 2.0.7
+ lit-html@3.3.3:
+ dependencies:
+ '@types/trusted-types': 2.0.7
+
lit@2.8.0:
dependencies:
'@lit/reactive-element': 1.6.3
@@ -20421,7 +20374,7 @@ snapshots:
dependencies:
'@lit/reactive-element': 2.1.2
lit-element: 4.2.2
- lit-html: 3.3.2
+ lit-html: 3.3.3
locate-path@5.0.0:
dependencies:
@@ -20472,10 +20425,7 @@ snapshots:
lru-cache@10.4.3: {}
- lru-cache@11.3.6: {}
-
- lru-cache@11.5.1:
- optional: true
+ lru-cache@11.5.1: {}
lru-cache@5.1.1:
dependencies:
@@ -20749,7 +20699,7 @@ snapshots:
minimatch@3.1.5:
dependencies:
- brace-expansion: 1.1.14
+ brace-expansion: 1.1.15
minimist@1.2.8: {}
@@ -20946,7 +20896,7 @@ snapshots:
dependencies:
es6-promise: 3.3.1
- node-releases@2.0.44: {}
+ node-releases@2.0.48: {}
node-stdlib-browser@1.3.1:
dependencies:
@@ -21140,7 +21090,7 @@ snapshots:
'@noble/hashes': 1.8.0
'@scure/bip32': 1.7.0
'@scure/bip39': 1.6.0
- abitype: 1.2.3(typescript@6.0.3)(zod@3.22.4)
+ abitype: 1.2.4(typescript@6.0.3)(zod@3.22.4)
eventemitter3: 5.0.1
optionalDependencies:
typescript: 6.0.3
@@ -21156,7 +21106,7 @@ snapshots:
'@noble/hashes': 1.8.0
'@scure/bip32': 1.7.0
'@scure/bip39': 1.6.0
- abitype: 1.2.3(typescript@6.0.3)(zod@3.25.76)
+ abitype: 1.2.4(typescript@6.0.3)(zod@3.25.76)
eventemitter3: 5.0.1
optionalDependencies:
typescript: 6.0.3
@@ -21167,11 +21117,11 @@ snapshots:
ox@0.6.7(typescript@6.0.3)(zod@3.25.76):
dependencies:
'@adraffy/ens-normalize': 1.11.1
- '@noble/curves': 1.9.7
- '@noble/hashes': 1.8.0
- '@scure/bip32': 1.7.0
+ '@noble/curves': 1.8.1
+ '@noble/hashes': 1.7.1
+ '@scure/bip32': 1.6.2
'@scure/bip39': 1.5.4
- abitype: 1.2.4(typescript@6.0.3)(zod@3.25.76)
+ abitype: 1.0.8(typescript@6.0.3)(zod@3.25.76)
eventemitter3: 5.0.1
optionalDependencies:
typescript: 6.0.3
@@ -21268,6 +21218,8 @@ snapshots:
pako@2.1.0: {}
+ pako@2.2.0: {}
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -21358,7 +21310,7 @@ snapshots:
safe-stable-stringify: 2.5.0
slow-redact: 0.3.2
sonic-boom: 4.2.1
- thread-stream: 3.1.0
+ thread-stream: 3.2.0
pino@7.11.0:
dependencies:
@@ -21401,7 +21353,7 @@ snapshots:
porto@0.2.35(@tanstack/react-query@5.100.10(react@19.2.7))(@types/react@19.2.17)(@wagmi/core@3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76)))(immer@11.1.8)(react-native@0.81.1(@babel/core@7.29.0)(@types/react@19.2.17)(bufferutil@4.0.9)(react@19.2.7)(utf-8-validate@5.0.10))(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))(wagmi@3.6.14):
dependencies:
'@wagmi/core': 3.4.11(@tanstack/query-core@5.100.10)(@types/react@19.2.17)(immer@11.1.8)(react@19.2.7)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.7))(viem@2.48.11(bufferutil@4.0.9)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@3.25.76))
- hono: 4.12.26
+ hono: 4.12.27
idb-keyval: 6.2.5
mipd: 0.0.7(typescript@6.0.3)
ox: 0.9.17(typescript@6.0.3)(zod@4.4.3)
@@ -21512,7 +21464,7 @@ snapshots:
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.1
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
long: 5.2.5
protobufjs@7.5.5:
@@ -21527,7 +21479,7 @@ snapshots:
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.1
- '@types/node': 25.7.0
+ '@types/node': 26.0.0
long: 5.2.5
proxy-compare@2.5.1: {}
@@ -22078,7 +22030,7 @@ snapshots:
sass@1.85.0:
dependencies:
chokidar: 4.0.3
- immutable: 5.1.6
+ immutable: 5.1.7
source-map-js: 1.2.1
optionalDependencies:
'@parcel/watcher': 2.5.6
@@ -22404,7 +22356,7 @@ snapshots:
fetch-cookie: 3.0.1
isomorphic-fetch: 3.0.0(encoding@0.1.13)
lossless-json: 4.3.0
- pako: 2.1.0
+ pako: 2.2.0
starknet-types-07: '@starknet-io/types-js@0.7.10'
ts-mixer: 6.0.4
transitivePeerDependencies:
@@ -22545,7 +22497,7 @@ snapshots:
dependencies:
real-require: 0.1.0
- thread-stream@3.1.0:
+ thread-stream@3.2.0:
dependencies:
real-require: 0.2.0
@@ -22576,11 +22528,11 @@ snapshots:
tinyrainbow@3.1.0: {}
- tldts-core@7.0.30: {}
+ tldts-core@7.4.4: {}
- tldts@7.0.30:
+ tldts@7.4.4:
dependencies:
- tldts-core: 7.0.30
+ tldts-core: 7.4.4
tmpl@1.0.5: {}
@@ -22611,7 +22563,7 @@ snapshots:
tough-cookie@6.0.1:
dependencies:
- tldts: 7.0.30
+ tldts: 7.4.4
tr46@0.0.3: {}
@@ -22737,7 +22689,7 @@ snapshots:
undici-types@7.21.0: {}
- undici-types@7.25.0: {}
+ undici-types@7.28.0: {}
undici-types@8.3.0: {}
@@ -22785,18 +22737,18 @@ snapshots:
picomatch: 4.0.4
webpack-virtual-modules: 0.6.2
- unstorage@1.17.5(idb-keyval@6.2.2)(ioredis@5.10.1):
+ unstorage@1.17.5(idb-keyval@6.2.5)(ioredis@5.10.1):
dependencies:
anymatch: 3.1.3
chokidar: 5.0.0
destr: 2.0.5
h3: 1.15.11
- lru-cache: 11.3.6
+ lru-cache: 11.5.1
node-fetch-native: 1.6.7
ofetch: 1.5.1
ufo: 1.6.4
optionalDependencies:
- idb-keyval: 6.2.2
+ idb-keyval: 6.2.5
ioredis: 5.10.1
until-async@3.0.2: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index dd60fa2c..2df629b2 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -12,8 +12,9 @@ catalog:
"@cosmos-kit/keplr": ^2.17.1
"@cosmos-kit/leap": ^2.17.1
"@cosmos-kit/walletconnect": 2.15.1
- "@effect/openapi-generator": 4.0.0-beta.68
- "@effect/platform-node": 4.0.0-beta.68
+ "@effect/atom-react": 4.0.0-beta.87
+ "@effect/openapi-generator": 4.0.0-beta.87
+ "@effect/platform-node": 4.0.0-beta.87
"@faker-js/faker": ^10.4.0
"@ledgerhq/wallet-api-client": ^1.14.5
"@ledgerhq/wallet-api-core": ^1.32.1
@@ -56,7 +57,6 @@ catalog:
"@vitejs/plugin-react": ^6.0.2
"@vitest/browser-playwright": ^4.1.9
"@xstate/react": ^6.1.0
- "@xstate/store": ^3.17.5
autoprefixer: ^10.5.0
babel-plugin-react-compiler: 1.0.0
bignumber.js: ^11.1.1
@@ -64,7 +64,7 @@ catalog:
clsx: ^2.1.1
cosmjs-types: ^0.10.1
date-fns: ^4.1.0
- effect: 4.0.0-beta.68
+ effect: 4.0.0-beta.87
eventemitter3: ^5.0.4
husky: ^9.1.7
i18next: ^26.1.0
@@ -108,3 +108,7 @@ minimumReleaseAge: 4320
minimumReleaseAgeExclude:
- "@stakekit/rainbowkit"
+
+patchedDependencies:
+ "@stakekit/rainbowkit@2.2.11": patches/@stakekit__rainbowkit@2.2.11.patch
+ purify-ts: patches/purify-ts.patch