From 9bd33d57ed98d33ca8125afaf1061e04d7d471bb Mon Sep 17 00:00:00 2001 From: Petar Todorovic Date: Fri, 16 May 2025 11:25:02 +0200 Subject: [PATCH 1/3] feat: whitelisted validators, hide chain modal prop --- .changeset/pink-garlics-see.md | 5 +++ package.json | 2 +- packages/widget/package.json | 2 +- packages/widget/src/App.tsx | 6 +-- .../molecules/account-modal/index.tsx | 7 ++- .../components/molecules/header/header.tsx | 4 +- .../src/hooks/api/use-activity-actions.ts | 4 ++ .../widget/src/hooks/api/use-multi-yields.ts | 9 +++- .../src/hooks/api/use-yield-opportunity.ts | 45 ++++++++++--------- packages/widget/src/hooks/use-init-params.ts | 6 +++ .../src/hooks/use-whitelisted-validators.ts | 14 ++++++ .../components/select-token-section/index.tsx | 4 +- .../components/select-yield-section/index.tsx | 4 +- .../select-yield-reward-details.tsx | 2 +- .../src/pages/details/earn-page/earn.page.tsx | 2 +- .../details/earn-page/state/use-init-token.ts | 5 +++ .../details/earn-page/state/use-init-yield.ts | 5 +++ .../state/use-pending-action-deep-link.ts | 8 ++++ packages/widget/src/providers/rainbow-kit.tsx | 5 +++ packages/widget/src/providers/settings.tsx | 16 ++++++- packages/widget/src/providers/wagmi/index.ts | 6 +++ .../src/translation/English/translations.json | 2 +- .../translation/English/utila-variant.json | 9 ++++ .../src/translation/French/translations.json | 2 +- pnpm-lock.yaml | 10 ++--- 25 files changed, 139 insertions(+), 45 deletions(-) create mode 100644 .changeset/pink-garlics-see.md create mode 100644 packages/widget/src/hooks/use-whitelisted-validators.ts create mode 100644 packages/widget/src/translation/English/utila-variant.json diff --git a/.changeset/pink-garlics-see.md b/.changeset/pink-garlics-see.md new file mode 100644 index 00000000..59a77127 --- /dev/null +++ b/.changeset/pink-garlics-see.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +feat: whitelisted validators, hide chain modal prop diff --git a/package.json b/package.json index 7fad4a25..893ceaf1 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "knip": "^5.50.3", "turbo": "^2.5.0" }, - "packageManager": "pnpm@10.10.0", + "packageManager": "pnpm@10.11.0", "pnpm": { "overrides": { "@types/react": "19.0.10", diff --git a/packages/widget/package.json b/packages/widget/package.json index 0e4cea9a..35fe26d4 100644 --- a/packages/widget/package.json +++ b/packages/widget/package.json @@ -73,7 +73,7 @@ "@safe-global/safe-apps-sdk": "^9.1.0", "@stakekit/api-hooks": "0.0.101", "@stakekit/common": "^0.0.48", - "@stakekit/rainbowkit": "^2.2.4", + "@stakekit/rainbowkit": "^2.2.5", "@tanstack/react-query": "^5.74.0", "@tanstack/react-virtual": "^3.13.6", "@testing-library/dom": "^10.4.0", diff --git a/packages/widget/src/App.tsx b/packages/widget/src/App.tsx index a29c2625..e4929150 100644 --- a/packages/widget/src/App.tsx +++ b/packages/widget/src/App.tsx @@ -221,9 +221,9 @@ export type SKAppProps = SettingsProps & (VariantProps | { variant?: never }); export const SKApp = (props: SKAppProps) => { const variantProps: VariantProps = - !props.variant || props.variant === "default" - ? { variant: "default" } - : { variant: props.variant, chainModal: props.chainModal }; + props.variant === "zerion" + ? { variant: props.variant, chainModal: props.chainModal } + : { variant: props.variant ?? "default" }; const [router] = useState(() => createMemoryRouter([{ path: "*", Component: Root }]) diff --git a/packages/widget/src/components/molecules/account-modal/index.tsx b/packages/widget/src/components/molecules/account-modal/index.tsx index 8953e451..a2d7afb7 100644 --- a/packages/widget/src/components/molecules/account-modal/index.tsx +++ b/packages/widget/src/components/molecules/account-modal/index.tsx @@ -49,7 +49,12 @@ export const AccountModal = () => { marginRight="2" className={avatarContainer} > - + {AvatarComponent && ( + + )} diff --git a/packages/widget/src/components/molecules/header/header.tsx b/packages/widget/src/components/molecules/header/header.tsx index 5a5735eb..cd308a53 100644 --- a/packages/widget/src/components/molecules/header/header.tsx +++ b/packages/widget/src/components/molecules/header/header.tsx @@ -23,7 +23,7 @@ export const Header = () => { const { containerRef } = useSyncHeaderHeight(); - const { variant } = useSettings(); + const { variant, hideChainModal } = useSettings(); const { isConnected, isConnecting, connector } = useSKWallet(); @@ -112,7 +112,7 @@ export const Header = () => { animate={{ opacity: 1 }} transition={{ delay: 0.2, duration: 0.2 }} > - + {!hideChainModal && } diff --git a/packages/widget/src/hooks/api/use-activity-actions.ts b/packages/widget/src/hooks/api/use-activity-actions.ts index 6cc00380..29bf9588 100644 --- a/packages/widget/src/hooks/api/use-activity-actions.ts +++ b/packages/widget/src/hooks/api/use-activity-actions.ts @@ -1,4 +1,5 @@ import { getYieldOpportunity } from "@sk-widget/hooks/api/use-yield-opportunity"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import { useSKQueryClient } from "@sk-widget/providers/query-client"; import { useSKWallet } from "@sk-widget/providers/sk-wallet"; import { @@ -14,6 +15,8 @@ export const useActivityActions = () => { const { address, network, isLedgerLive } = useSKWallet(); const queryClient = useSKQueryClient(); + const whitelistedValidatorAddresses = useWhitelistedValidators(); + const query = useInfiniteQuery({ enabled: !!address && !!network, queryKey: getActionListQueryKey({ @@ -44,6 +47,7 @@ export const useActivityActions = () => { yieldId: action.integrationId, queryClient, isLedgerLive, + whitelistedValidatorAddresses, }) .map((yieldData) => ({ actionData: action, yieldData })) .chainLeft(() => EitherAsync(() => Promise.resolve(null))) diff --git a/packages/widget/src/hooks/api/use-multi-yields.ts b/packages/widget/src/hooks/api/use-multi-yields.ts index 2b1f43ad..5d61f311 100644 --- a/packages/widget/src/hooks/api/use-multi-yields.ts +++ b/packages/widget/src/hooks/api/use-multi-yields.ts @@ -2,6 +2,7 @@ import type { InitParams } from "@sk-widget/domain/types/init-params"; import type { PositionsData } from "@sk-widget/domain/types/positions"; import { canBeInitialYield } from "@sk-widget/domain/types/stake"; import { useSavedRef } from "@sk-widget/hooks/use-saved-ref"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import type { YieldDto } from "@stakekit/api-hooks"; import { type QueryClient, hashKey } from "@tanstack/react-query"; import { useSelector } from "@xstate/react"; @@ -59,10 +60,13 @@ export const useMultiYields = (yieldIds: string[]) => { const hashedKey = useMemo(() => hashKey(yieldIds), [yieldIds]); + const whitelistedValidatorAddresses = useWhitelistedValidators(); + useEffect(() => { const sub = multipleYields$({ ...argsRef.current, yieldIds, + whitelistedValidatorAddresses, }) .pipe(repeat({ delay: () => timer(1000 * 60 * 2) })) .subscribe({ @@ -74,7 +78,7 @@ export const useMultiYields = (yieldIds: string[]) => { }); return () => sub.unsubscribe(); - }, [argsRef, yieldIds, hashedKey]); + }, [argsRef, yieldIds, hashedKey, whitelistedValidatorAddresses]); return useSelector(multiYieldsStore, (state) => { const map = state.context.data.get(hashedKey); @@ -102,6 +106,7 @@ const multipleYields$ = (args: { isConnected: boolean; network: SKWallet["network"]; yieldIds: string[]; + whitelistedValidatorAddresses: Set | null; }) => merge( ...args.yieldIds.map((v) => @@ -110,6 +115,7 @@ const multipleYields$ = (args: { isLedgerLive: args.isLedgerLive, yieldId: v, queryClient: args.queryClient, + whitelistedValidatorAddresses: args.whitelistedValidatorAddresses, }) ) ) @@ -138,6 +144,7 @@ const firstEligibleYield$ = (args: { initParams: InitParams; positionsData: PositionsData; tokenBalanceAmount: BigNumber; + whitelistedValidatorAddresses: Set | null; }) => { let defaultYield: YieldDto | null = null; diff --git a/packages/widget/src/hooks/api/use-yield-opportunity.ts b/packages/widget/src/hooks/api/use-yield-opportunity.ts index 09a580e6..08df64b8 100644 --- a/packages/widget/src/hooks/api/use-yield-opportunity.ts +++ b/packages/widget/src/hooks/api/use-yield-opportunity.ts @@ -1,5 +1,5 @@ import { yieldYieldOpportunity } from "@sk-widget/common/private-api"; -import type { YieldDto } from "@stakekit/api-hooks"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import type { QueryClient } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query"; import { EitherAsync } from "purify-ts"; @@ -8,6 +8,7 @@ import { useSKWallet } from "../../providers/sk-wallet"; type Params = { yieldId: string; isLedgerLive: boolean; + whitelistedValidatorAddresses: Set | null; signal?: AbortSignal; }; @@ -21,13 +22,16 @@ const getKey = (params: Params) => [ export const useYieldOpportunity = (integrationId: string | undefined) => { const { isLedgerLive } = useSKWallet(); + const whitelistedValidatorAddresses = useWhitelistedValidators(); + const yieldId = integrationId ?? ""; return useQuery({ - queryKey: getKey({ yieldId, isLedgerLive }), + queryKey: getKey({ yieldId, isLedgerLive, whitelistedValidatorAddresses }), enabled: !!integrationId, staleTime, - queryFn: ({ signal }) => queryFn({ yieldId, isLedgerLive, signal }), + queryFn: ({ signal }) => + queryFn({ yieldId, isLedgerLive, signal, whitelistedValidatorAddresses }), }); }; @@ -57,6 +61,7 @@ const fn = ({ isLedgerLive, yieldId, signal, + whitelistedValidatorAddresses, }: Params & { signal?: AbortSignal; }) => @@ -68,22 +73,18 @@ const fn = ({ }, signal ) - ).mapLeft((e) => { - console.log(e); - return new Error("Could not get yield opportunity"); - }); - -export const setYieldOpportunityInCache = ({ - yieldDto, - isLedgerLive, - queryClient, -}: { - yieldDto: YieldDto; - isLedgerLive: boolean; - queryClient: QueryClient; -}) => { - queryClient.setQueryData( - getKey({ isLedgerLive, yieldId: yieldDto.id }), - yieldDto - ); -}; + ) + .map((y) => + whitelistedValidatorAddresses + ? { + ...y, + validators: y.validators.filter((v) => + whitelistedValidatorAddresses.has(v.address) + ), + } + : y + ) + .mapLeft((e) => { + console.log(e); + return new Error("Could not get yield opportunity"); + }); diff --git a/packages/widget/src/hooks/use-init-params.ts b/packages/widget/src/hooks/use-init-params.ts index b5a862dd..d98b702f 100644 --- a/packages/widget/src/hooks/use-init-params.ts +++ b/packages/widget/src/hooks/use-init-params.ts @@ -1,4 +1,5 @@ import { getAndValidateInitParams } from "@sk-widget/hooks/use-init-query-params"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import { type SettingsContextType, useSettings, @@ -22,6 +23,7 @@ export const useInitParams = (opts?: { const { isLedgerLive } = useSKWallet(); const { externalProviders } = useSettings(); const queryClient = useSKQueryClient(); + const whitelistedValidatorAddresses = useWhitelistedValidators(); return useQuery({ queryKey, @@ -32,6 +34,7 @@ export const useInitParams = (opts?: { isLedgerLive, queryClient, externalProviders, + whitelistedValidatorAddresses, }), select: opts?.select, }); @@ -59,10 +62,12 @@ const fn = ({ isLedgerLive, queryClient, externalProviders, + whitelistedValidatorAddresses, }: { isLedgerLive: boolean; queryClient: QueryClient; externalProviders: SettingsContextType["externalProviders"]; + whitelistedValidatorAddresses: Set | null; }): EitherAsync => EitherAsync.liftEither( getAndValidateInitParams({ @@ -76,6 +81,7 @@ const fn = ({ isLedgerLive, yieldId: yId, queryClient, + whitelistedValidatorAddresses, }) .map((yieldData) => ({ ...val, diff --git a/packages/widget/src/hooks/use-whitelisted-validators.ts b/packages/widget/src/hooks/use-whitelisted-validators.ts new file mode 100644 index 00000000..4cda75ef --- /dev/null +++ b/packages/widget/src/hooks/use-whitelisted-validators.ts @@ -0,0 +1,14 @@ +import { useSettings } from "@sk-widget/providers/settings"; +import { useMemo } from "react"; + +export const useWhitelistedValidators = () => { + const { whitelistedValidatorAddresses } = useSettings(); + + return useMemo( + () => + whitelistedValidatorAddresses + ? new Set(whitelistedValidatorAddresses) + : null, + [whitelistedValidatorAddresses] + ); +}; diff --git a/packages/widget/src/pages/details/earn-page/components/select-token-section/index.tsx b/packages/widget/src/pages/details/earn-page/components/select-token-section/index.tsx index 80029a61..53b5cc9a 100644 --- a/packages/widget/src/pages/details/earn-page/components/select-token-section/index.tsx +++ b/packages/widget/src/pages/details/earn-page/components/select-token-section/index.tsx @@ -64,7 +64,7 @@ export const SelectTokenSection = () => { display="flex" justifyContent="flex-end" alignItems="center" - {...(variant === "default" && { marginRight: "2", marginTop: "2" })} + {...(variant !== "zerion" && { marginRight: "2", marginTop: "2" })} data-rk="stake-token-section-min-max" > { - {variant === "default" && minStakeAmount} + {variant !== "zerion" && minStakeAmount} { ) : ( - {variant === "default" && ( + {variant !== "zerion" && ( {t("details.earn")} @@ -98,7 +98,7 @@ export const SelectYieldSection = () => { - {variant === "default" && } + {variant !== "zerion" && } {variant === "zerion" && ( diff --git a/packages/widget/src/pages/details/earn-page/components/select-yield-section/select-yield-reward-details.tsx b/packages/widget/src/pages/details/earn-page/components/select-yield-section/select-yield-reward-details.tsx index 6f5c7e71..a720d5db 100644 --- a/packages/widget/src/pages/details/earn-page/components/select-yield-section/select-yield-reward-details.tsx +++ b/packages/widget/src/pages/details/earn-page/components/select-yield-section/select-yield-reward-details.tsx @@ -28,7 +28,7 @@ export const SelectYieldRewardDetails = () => { return ( - {variant === "default" && ( + {variant !== "zerion" && ( 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 bcb2379b..4abcb9f9 100644 --- a/packages/widget/src/pages/details/earn-page/earn.page.tsx +++ b/packages/widget/src/pages/details/earn-page/earn.page.tsx @@ -84,7 +84,7 @@ const EarnPageComponent = () => { > - {variant === "default" && } + {variant !== "zerion" && } 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 index 89393742..2a570184 100644 --- 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 @@ -4,6 +4,7 @@ import { getInitialToken } from "@sk-widget/domain/types/stake"; import { getFirstEligibleYield } from "@sk-widget/hooks/api/use-multi-yields"; import { getInitParams } from "@sk-widget/hooks/use-init-params"; import { usePositionsData } from "@sk-widget/hooks/use-positions-data"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import { useSKQueryClient } from "@sk-widget/providers/query-client"; import { useSettings } from "@sk-widget/providers/settings"; import { useSKWallet } from "@sk-widget/providers/sk-wallet"; @@ -31,6 +32,8 @@ export const useInitToken = () => { const { externalProviders, tokensForEnabledYieldsOnly } = useSettings(); + const whitelistedValidatorAddresses = useWhitelistedValidators(); + return useQuery({ staleTime: Number.POSITIVE_INFINITY, queryKey: [ @@ -54,6 +57,7 @@ export const useInitToken = () => { isLedgerLive, queryClient, externalProviders, + whitelistedValidatorAddresses, }).chain((initParams) => EitherAsync.liftEither( getInitialToken({ @@ -77,6 +81,7 @@ export const useInitToken = () => { initParams: initParams, positionsData: positionsData, tokenBalanceAmount: new BigNumber(tokenBalance.amount), + whitelistedValidatorAddresses, }) ) .map(() => token) 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 index 9b5258e7..471890af 100644 --- 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 @@ -3,6 +3,7 @@ import { tokenString } from "@sk-widget/domain"; import { getFirstEligibleYield } from "@sk-widget/hooks/api/use-multi-yields"; import { getInitParams } from "@sk-widget/hooks/use-init-params"; import { usePositionsData } from "@sk-widget/hooks/use-positions-data"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import { useSKQueryClient } from "@sk-widget/providers/query-client"; import { useSettings } from "@sk-widget/providers/settings"; import { useSKWallet } from "@sk-widget/providers/sk-wallet"; @@ -30,6 +31,8 @@ export const useInitYield = ({ const { externalProviders, tokensForEnabledYieldsOnly } = useSettings(); const { data: positionsData } = usePositionsData(); + const whitelistedValidatorAddresses = useWhitelistedValidators(); + return useQuery({ staleTime: Number.POSITIVE_INFINITY, queryKey: [ @@ -65,6 +68,7 @@ export const useInitYield = ({ isLedgerLive, queryClient, externalProviders, + whitelistedValidatorAddresses, }).chain((initParams) => getFirstEligibleYield({ isConnected, @@ -75,6 +79,7 @@ export const useInitYield = ({ initParams: initParams, positionsData: positionsData, tokenBalanceAmount: new BigNumber(val.amount), + whitelistedValidatorAddresses, }) ) ) diff --git a/packages/widget/src/pages/details/earn-page/state/use-pending-action-deep-link.ts b/packages/widget/src/pages/details/earn-page/state/use-pending-action-deep-link.ts index 876c68ec..3930fa8c 100644 --- a/packages/widget/src/pages/details/earn-page/state/use-pending-action-deep-link.ts +++ b/packages/widget/src/pages/details/earn-page/state/use-pending-action-deep-link.ts @@ -4,6 +4,7 @@ import { } from "@sk-widget/domain"; import { getYieldOpportunity } from "@sk-widget/hooks/api/use-yield-opportunity"; import { getInitParams } from "@sk-widget/hooks/use-init-params"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import { preparePendingActionRequestDto } from "@sk-widget/pages/position-details/hooks/utils"; import { useSKQueryClient } from "@sk-widget/providers/query-client"; import { useSettings } from "@sk-widget/providers/settings"; @@ -28,6 +29,8 @@ export const usePendingActionDeepLink = () => { const { externalProviders } = useSettings(); + const whitelistedValidatorAddresses = useWhitelistedValidators(); + return useQuery({ staleTime: Number.POSITIVE_INFINITY, gcTime: Number.POSITIVE_INFINITY, @@ -46,6 +49,7 @@ export const usePendingActionDeepLink = () => { address: addr, queryClient, externalProviders, + whitelistedValidatorAddresses, }) ) ).unsafeCoerce(), @@ -58,17 +62,20 @@ const fn = ({ address, queryClient, externalProviders, + whitelistedValidatorAddresses, }: { isLedgerLive: boolean; address: string; additionalAddresses: AddressWithTokenDtoAdditionalAddresses | null; queryClient: QueryClient; externalProviders: ReturnType["externalProviders"]; + whitelistedValidatorAddresses: Set | null; }) => getInitParams({ isLedgerLive, queryClient, externalProviders, + whitelistedValidatorAddresses, }).chain((val) => { const initQueryParams = Maybe.of(val) .filter( @@ -135,6 +142,7 @@ const fn = ({ isLedgerLive, yieldId: data.yieldId, queryClient, + whitelistedValidatorAddresses, }).map((yieldOp) => ({ ...val, yieldOp })) ) .chain< diff --git a/packages/widget/src/providers/rainbow-kit.tsx b/packages/widget/src/providers/rainbow-kit.tsx index 9f8b7b6e..5ad25540 100644 --- a/packages/widget/src/providers/rainbow-kit.tsx +++ b/packages/widget/src/providers/rainbow-kit.tsx @@ -1,4 +1,5 @@ import { shouldShowDisconnect } from "@sk-widget/domain/types/connectors"; +import { useSettings } from "@sk-widget/providers/settings"; import { vars } from "@sk-widget/styles"; import { id } from "@sk-widget/styles/theme/ids"; import type { DisclaimerComponent } from "@stakekit/rainbowkit"; @@ -36,6 +37,8 @@ export const RainbowKitProviderWithTheme = ({ const { t } = useTranslation(); + const { hideChainModal } = useSettings(); + const chainIdsToUse = useMemo( () => new Set(connectorChains.map((c) => c.id)), [connectorChains] @@ -69,6 +72,8 @@ export const RainbowKitProviderWithTheme = ({ onDisabledChainClick.mutate(disabledChain); }} appInfo={{ disclaimer: Disclamer, appName: "StakeKit" }} + {...(hideChainModal && { avatar: null })} + showRecentTransactions={false} theme={finalTheme} hideDisconnect={hideDisconnect} > diff --git a/packages/widget/src/providers/settings.tsx b/packages/widget/src/providers/settings.tsx index 445e1e0c..04301784 100644 --- a/packages/widget/src/providers/settings.tsx +++ b/packages/widget/src/providers/settings.tsx @@ -1,4 +1,5 @@ import type { Languages, localResources } from "@sk-widget/translation"; +import utilaTranslations from "@sk-widget/translation/English/utila-variant.json"; import type { RecursivePartial } from "@sk-widget/types"; import type { TransactionFormat } from "@stakekit/api-hooks"; import type { PropsWithChildren, ReactNode } from "react"; @@ -19,6 +20,7 @@ export type VariantProps = onSwitchChain: (chainId: number) => void; }) => ReactNode; } + | { variant: "utila" } | { variant: "default" }; export type SettingsProps = { @@ -47,6 +49,8 @@ export type SettingsProps = { customTranslations?: RecursivePartial; tokensForEnabledYieldsOnly?: boolean; preferredTransactionFormat?: TransactionFormat; + hideChainModal?: boolean; + whitelistedValidatorAddresses?: string[]; }; export type SettingsContextType = SettingsProps & VariantProps; @@ -72,12 +76,22 @@ export const SettingsContextProvider = ({ }, [rest.language, i18n]); useLayoutEffect(() => { + if (rest.variant === "utila") { + i18n.addResourceBundle( + "en", + "translation", + utilaTranslations, + true, + true + ); + } + if (rest.customTranslations) { Object.entries(rest.customTranslations).forEach(([lng, val]) => { i18n.addResourceBundle(lng, "translation", val.translation, true, true); }); } - }, [rest.customTranslations, i18n]); + }, [rest.customTranslations, i18n, rest.variant]); return ( {children} diff --git a/packages/widget/src/providers/wagmi/index.ts b/packages/widget/src/providers/wagmi/index.ts index 0e33014b..a7d43cc1 100644 --- a/packages/widget/src/providers/wagmi/index.ts +++ b/packages/widget/src/providers/wagmi/index.ts @@ -2,6 +2,7 @@ import type { CosmosChainsMap } from "@sk-widget/domain/types/chains/cosmos"; import type { EvmChainsMap } from "@sk-widget/domain/types/chains/evm"; import type { MiscChainsMap } from "@sk-widget/domain/types/chains/misc"; import type { SubstrateChainsMap } from "@sk-widget/domain/types/chains/substrate"; +import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; import type { Wallet, WalletList } from "@stakekit/rainbowkit"; import { connectorsForWallets } from "@stakekit/rainbowkit"; import type { QueryClient } from "@tanstack/react-query"; @@ -49,6 +50,7 @@ const buildWagmiConfig = async (opts: { queryClient: QueryClient; isLedgerLive: boolean; isSafe: boolean; + whitelistedValidatorAddresses: Set | null; }): Promise<{ evmConfig: GetEitherAsyncRight>; cosmosConfig: GetEitherAsyncRight>; @@ -83,6 +85,7 @@ const buildWagmiConfig = async (opts: { isLedgerLive: opts.isLedgerLive, queryClient: opts.queryClient, externalProviders: opts.externalProviders?.current, + whitelistedValidatorAddresses: opts.whitelistedValidatorAddresses, }), ]).then(([evm, cosmos, misc, substrate, queryParams]) => evm.chain((e) => @@ -236,6 +239,8 @@ export const useWagmiConfig = () => { const queryClient = useSKQueryClient(); + const whitelistedValidatorAddresses = useWhitelistedValidators(); + const externalProvidersRef = useSavedRef(externalProviders) as | RefObject | RefObject; @@ -255,6 +260,7 @@ export const useWagmiConfig = () => { ...(externalProvidersRef.current && { externalProviders: externalProvidersRef, }), + whitelistedValidatorAddresses, }), }); }; diff --git a/packages/widget/src/translation/English/translations.json b/packages/widget/src/translation/English/translations.json index c449a8ee..0a4761eb 100644 --- a/packages/widget/src/translation/English/translations.json +++ b/packages/widget/src/translation/English/translations.json @@ -43,7 +43,7 @@ "terms_of_use": "By clicking {{action}} you agree to Terms of Use and understand the Risk Disclosures", "in_progress": "In Progress" }, - "no_previous_activity": "No previous activity in StakeKit" + "no_previous_activity": "No previous activity" }, "details": { "earn": "Earn", diff --git a/packages/widget/src/translation/English/utila-variant.json b/packages/widget/src/translation/English/utila-variant.json new file mode 100644 index 00000000..8ca78e95 --- /dev/null +++ b/packages/widget/src/translation/English/utila-variant.json @@ -0,0 +1,9 @@ +{ + "details": { + "earn": "Stake", + "earn_with": "Stake with", + "tabs": { + "earn": "Stake" + } + } +} diff --git a/packages/widget/src/translation/French/translations.json b/packages/widget/src/translation/French/translations.json index 7c5b593d..0850ef1c 100644 --- a/packages/widget/src/translation/French/translations.json +++ b/packages/widget/src/translation/French/translations.json @@ -43,7 +43,7 @@ "terms_of_use": "En cliquant sur {{action}} vous acceptez les Conditions d'utilisation et comprenez les Informations sur les Risques", "in_progress": "En cours" }, - "no_previous_activity": "Aucune activité précédente dans StakeKit" + "no_previous_activity": "Aucune activité précédente" }, "details": { "earn": "Earn", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d164c4a4..41f61e85 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,8 +191,8 @@ importers: specifier: ^0.0.48 version: 0.0.48 '@stakekit/rainbowkit': - specifier: ^2.2.4 - version: 2.2.4(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(wagmi@2.14.16(@tanstack/query-core@5.74.0)(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(zod@3.24.2)) + specifier: ^2.2.5 + version: 2.2.5(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(wagmi@2.14.16(@tanstack/query-core@5.74.0)(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(zod@3.24.2)) '@tanstack/react-query': specifier: ^5.74.0 version: 5.74.0(react@19.1.0) @@ -2559,8 +2559,8 @@ packages: '@stakekit/common@0.0.48': resolution: {integrity: sha512-zqcK6/rviNwmCjvi/7xQzm6j+9WjqmCtU6tK1cTgX/IBFmNLn6HYgEzRhz90+RV5Y3wVTncI/LBA65xXuvHisw==} - '@stakekit/rainbowkit@2.2.4': - resolution: {integrity: sha512-oS1MPY0uAOacieacZWhl6Gg5dLbhGZHJ2hFSSPgMprgNrU3eyS9pNyiZzGnz7MXgkdF44ojI3DMN4tmyFsu5SA==} + '@stakekit/rainbowkit@2.2.5': + resolution: {integrity: sha512-yTSnt+7Kv+uX2Z0Xw0S9mhBXT4nzTYccXg531bhYVFJCDrqgEb0W1UED342fCDMt7u/JmIiTq3zGqRAqEWHqtg==} engines: {node: '>=12.4'} peerDependencies: '@tanstack/react-query': '>=5.0.0' @@ -9115,7 +9115,7 @@ snapshots: '@stakekit/common@0.0.48': {} - '@stakekit/rainbowkit@2.2.4(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(wagmi@2.14.16(@tanstack/query-core@5.74.0)(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(zod@3.24.2))': + '@stakekit/rainbowkit@2.2.5(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(wagmi@2.14.16(@tanstack/query-core@5.74.0)(@tanstack/react-query@5.74.0(react@19.1.0))(@types/react@19.0.10)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.26.5(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.2))(zod@3.24.2))': dependencies: '@tanstack/react-query': 5.74.0(react@19.1.0) '@vanilla-extract/css': 1.15.5 From 2ae9289ae972019e200232b9e2eba94f629076ad Mon Sep 17 00:00:00 2001 From: Petar Todorovic Date: Fri, 16 May 2025 11:36:53 +0200 Subject: [PATCH 2/3] feat: link inline --- README.md | 26 +++++++++++++++++++ .../src/components/atoms/link/styles.css.ts | 1 + .../components/position-balances.tsx | 3 ++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 19a81107..95727c03 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,32 @@ const App = () => { }; ``` +### Options + +```tsx +type SettingsProps = { + apiKey: string; + theme?: ThemeWrapperTheme; + tracking?: { + trackEvent?: (event: TrackEventVal, properties?: Properties) => void; + trackPageView?: (page: TrackPageVal, properties?: Properties) => void; + }; + onMountAnimationComplete?: () => void; + externalProviders?: SKExternalProviders; + disableGasCheck?: boolean; + hideNetworkLogo?: boolean; + disableInitLayoutAnimation?: boolean; + disableResizingInputFontSize?: boolean; + disableAutoScrollToTop?: boolean; + language?: Languages; + customTranslations?: RecursivePartial; + tokensForEnabledYieldsOnly?: boolean; + preferredTransactionFormat?: TransactionFormat; + hideChainModal?: boolean; + whitelistedValidatorAddresses?: string[]; +}; +``` + After this is done, you can start using the widget. ## Style customization diff --git a/packages/widget/src/components/atoms/link/styles.css.ts b/packages/widget/src/components/atoms/link/styles.css.ts index 3c20e564..44e78406 100644 --- a/packages/widget/src/components/atoms/link/styles.css.ts +++ b/packages/widget/src/components/atoms/link/styles.css.ts @@ -2,4 +2,5 @@ import { style } from "@vanilla-extract/css"; export const link = style({ textDecoration: "none", + display: "inline", }); diff --git a/packages/widget/src/pages/position-details/components/position-balances.tsx b/packages/widget/src/pages/position-details/components/position-balances.tsx index 28a0521e..676d4502 100644 --- a/packages/widget/src/pages/position-details/components/position-balances.tsx +++ b/packages/widget/src/pages/position-details/components/position-balances.tsx @@ -18,7 +18,8 @@ export const PositionBalances = ({ const daysRemaining = useMemo(() => { return (yieldBalance.type === "unstaking" || - yieldBalance.type === "unlocking") && + yieldBalance.type === "unlocking" || + yieldBalance.type === "preparing") && yieldBalance.date ? daysUntilDate(new Date(yieldBalance.date)) : null; From 546509a85d9875d37f58fe2033440e53ac16fb39 Mon Sep 17 00:00:00 2001 From: Petar Todorovic Date: Fri, 16 May 2025 12:40:00 +0200 Subject: [PATCH 3/3] fix: position list item amount --- .changeset/plain-otters-allow.md | 5 +++ .../components/positions-list-item.tsx | 4 ++- .../hooks/use-position-list-item.ts | 33 +++++++++++++++++++ .../positions-page/hooks/use-positions.ts | 19 ++++------- 4 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 .changeset/plain-otters-allow.md diff --git a/.changeset/plain-otters-allow.md b/.changeset/plain-otters-allow.md new file mode 100644 index 00000000..fac59a11 --- /dev/null +++ b/.changeset/plain-otters-allow.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +fix: position list item amount diff --git a/packages/widget/src/pages/details/positions-page/components/positions-list-item.tsx b/packages/widget/src/pages/details/positions-page/components/positions-list-item.tsx index 7d7fe3f2..d869770c 100644 --- a/packages/widget/src/pages/details/positions-page/components/positions-list-item.tsx +++ b/packages/widget/src/pages/details/positions-page/components/positions-list-item.tsx @@ -31,6 +31,7 @@ export const PositionsListItem = memo( providersDetails, inactiveValidator, rewardRateAverage, + amount, } = usePositionListItem(item); return ( @@ -158,6 +159,7 @@ export const PositionsListItem = memo( {Maybe.fromRecord({ token: item.token, rewardRateAverage, + amount, }) .map((val) => ( - {item.amount} {val.token.symbol} + {val.amount} {val.token.symbol} )) diff --git a/packages/widget/src/pages/details/positions-page/hooks/use-position-list-item.ts b/packages/widget/src/pages/details/positions-page/hooks/use-position-list-item.ts index f2fced32..1a8beed8 100644 --- a/packages/widget/src/pages/details/positions-page/hooks/use-position-list-item.ts +++ b/packages/widget/src/pages/details/positions-page/hooks/use-position-list-item.ts @@ -1,6 +1,8 @@ +import { equalTokens, getBaseToken } from "@sk-widget/domain"; import { useYieldOpportunity } from "@sk-widget/hooks/api/use-yield-opportunity"; import { useProvidersDetails } from "@sk-widget/hooks/use-provider-details"; import type { usePositions } from "@sk-widget/pages/details/positions-page/hooks/use-positions"; +import { defaultFormattedNumber } from "@sk-widget/utils"; import { getRewardRateFormatted } from "@sk-widget/utils/formatters"; import BigNumber from "bignumber.js"; import { List, Maybe } from "purify-ts"; @@ -53,10 +55,41 @@ export const usePositionListItem = ( [providersDetails] ); + const tokenToDisplay = item.token; + const baseToken = useMemo( + () => integrationData.map((y) => getBaseToken(y)), + [integrationData] + ); + + const amount = useMemo( + () => + Maybe.fromRecord({ + tokenToDisplay, + baseToken, + }) + .map((val) => + item.balancesWithAmount.reduce((acc, b) => { + if (b.token.isPoints) return acc; + + if (equalTokens(b.token, val.tokenToDisplay)) { + return new BigNumber(b.amount).plus(acc); + } + + return new BigNumber(b.amount) + .times(b.pricePerShare) + .dividedBy(val.tokenToDisplay.pricePerShare) + .plus(acc); + }, new BigNumber(0)) + ) + .map(defaultFormattedNumber), + [item.balancesWithAmount, tokenToDisplay, baseToken] + ); + return { integrationData, providersDetails, rewardRateAverage, inactiveValidator, + amount, }; }; diff --git a/packages/widget/src/pages/details/positions-page/hooks/use-positions.ts b/packages/widget/src/pages/details/positions-page/hooks/use-positions.ts index e219a1de..a5601323 100644 --- a/packages/widget/src/pages/details/positions-page/hooks/use-positions.ts +++ b/packages/widget/src/pages/details/positions-page/hooks/use-positions.ts @@ -173,19 +173,13 @@ const positionsTableDataSelector = createSelector( compare(priorityOrder[a.type], priorityOrder[b.type]), value.balances ) - ).map((v) => v.token), + ).map((v) => ({ + ...v.token, + pricePerShare: v.pricePerShare, + })), actionRequired: v.some( (b) => b.type === "locked" || b.type === "unstaked" ), - amount: Just( - v.reduce((acc, b) => { - if (b.token.isPoints) return acc; - - return new BigNumber(b.amount).plus(acc); - }, new BigNumber(0)) - ) - .map(defaultFormattedNumber) - .unsafeCoerce(), pointsRewardTokenBalance: List.find( (v) => !!v.token.isPoints, v @@ -216,10 +210,9 @@ const positionsTableDataSelector = createSelector( allBalances: YieldBalanceDto[]; balanceId: YieldBalanceDto["groupId"]; actionRequired: boolean; - amount: string; pointsRewardTokenBalance: Maybe; hasPendingClaimRewards: boolean; - token: Maybe; + token: Maybe; yieldLabelDto: Maybe; } & ( | { type: "validators"; validatorsAddresses: string[] } @@ -232,7 +225,7 @@ const positionsTableDataSelector = createSelector( ? val.toSorted((a, b) => { if (a.hasPendingClaimRewards) return -1; if (b.hasPendingClaimRewards) return 1; - return BigNumber(b.amount).minus(BigNumber(a.amount)).toNumber(); + return 0; }) : val )