Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"format": "turbo run format",
"clean": "turbo run clean",
"prepare": "husky",
"check-hygiene": "rev-dep config run --list-all-issues"
"check-hygiene": "rev-dep config run --list-all-issues",
"check": "turbo run lint && pnpm run check-hygiene && turbo run test && turbo run build"
},
"devDependencies": {
"@biomejs/biome": "catalog:",
Expand Down
4 changes: 2 additions & 2 deletions packages/widget/src/common/get-enabled-networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export const getEnabledNetworks = ({
queryKey: [config.appPrefix, "enabled-networks"],
queryFn: async () =>
new Set(
(await apiClient.yield.NetworksControllerGetNetworks(undefined)).map(
(network) => network.id as Networks
(await apiClient.legacy.YieldControllerGetMyNetworks(undefined)).map(
(network) => network as Networks
Comment on lines +20 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the API contract for YieldControllerGetMyNetworks and check Networks type usage

# Check the Networks type definition
echo "=== Networks type definition ==="
rg -n "type Networks|enum Networks|interface Networks" --type=ts -A 5

# Find all usages of getEnabledNetworks to understand how the result is consumed
echo -e "\n=== getEnabledNetworks usage sites ==="
rg -n "getEnabledNetworks" --type=ts -C 3

# Look for the generated API type for YieldControllerGetMyNetworks
echo -e "\n=== YieldControllerGetMyNetworks type definition ==="
rg -n "YieldControllerGetMyNetworks" --type=ts -A 10 -g "generated/**"

Repository: stakekit/widget

Length of output: 10380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo_root="$(pwd)"
echo "Repo root: $repo_root"

# 1) Inspect the exact code around the changed mapping
echo "=== get-enabled-networks.ts (lines ~1-120) ==="
sed -n '1,140p' packages/widget/src/common/get-enabled-networks.ts | nl -ba

# 2) Inspect Networks type + allowed values
echo -e "\n=== Networks type (domain) ==="
sed -n '1,120p' packages/widget/src/domain/types/chains/networks.ts | nl -ba

# 3) Find YieldControllerGetMyNetworks in generated API typings
echo -e "\n=== Search for YieldControllerGetMyNetworks in generated typings ==="
rg -n "YieldControllerGetMyNetworks" packages/widget/src/generated/api -S || true

# 4) If found, print the relevant generated legacy type for its return payload
#    (use a narrower window around the first match)
match_file="$(rg -n "YieldControllerGetMyNetworks" packages/widget/src/generated/api -S -o --files-with-matches | head -n 1 || true)"
if [ -n "${match_file:-}" ]; then
  echo -e "\n=== Context from generated file: $match_file ==="
  # Print a window around the first occurrence
  line_no="$(rg -n "YieldControllerGetMyNetworks" "$match_file" -S | head -n 1 | cut -d: -f1)"
  start=$((line_no-40))
  end=$((line_no+80))
  sed -n "${start},${end}p" "$match_file" | nl -ba
else
  echo -e "\n(No generated typings match for YieldControllerGetMyNetworks under packages/widget/src/generated/api.)"
fi

# 5) Check how getEnabledNetworks result is used (to assess downstream impact)
echo -e "\n=== Downstream usage: where networks are treated as Networks[] ==="
rg -n "getEnabledNetworks\\(|caseOf\\(\\{|\\.caseOf\\(\\{|\bnetworks\b.*Set<|Set<.*Networks" packages/widget/src -S

Repository: stakekit/widget

Length of output: 197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== get-enabled-networks.ts (context) ==="
sed -n '1,220p' packages/widget/src/common/get-enabled-networks.ts | cat -n

echo -e "\n=== Networks type (domain) ==="
sed -n '1,140p' packages/widget/src/domain/types/chains/networks.ts | cat -n

echo -e "\n=== Search for YieldControllerGetMyNetworks in generated typings ==="
rg -n "YieldControllerGetMyNetworks" packages/widget/src/generated/api -S || true

echo -e "\n=== Search for legacy.YieldControllerGetMyNetworks usage ==="
rg -n "legacy\\.YieldControllerGetMyNetworks" packages/widget/src -S || true

match_file="$(rg -n "YieldControllerGetMyNetworks" packages/widget/src/generated/api -S -o --files-with-matches | head -n 1 || true)"
if [ -n "${match_file:-}" ]; then
  echo -e "\n=== Context from generated file: $match_file ==="
  line_no="$(rg -n "YieldControllerGetMyNetworks" "$match_file" -S | head -n 1 | cut -d: -f1)"
  start=$((line_no-60))
  end=$((line_no+120))
  sed -n "${start},${end}p" "$match_file" | cat -n
fi

echo -e "\n=== Downstream usage: networks typed/assumed as Networks[] / Set<Networks> ==="
rg -n "Set<\\s*Networks\\s*>|Networks\\[\\]|ReadonlyArray<\\s*Networks\\s*>|\\bcaseOf\\b\\(" packages/widget/src -S

Repository: stakekit/widget

Length of output: 21728


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo -e "\n=== TokenDto network type (generated api/yield.ts) ==="
rg -n "export type TokenDto|interface TokenDto|type TokenDto" packages/widget/src/generated/api/yield.ts -S | head -n 20
token_line="$(rg -n "type TokenDto" packages/widget/src/generated/api/yield.ts -S | head -n 1 | cut -d: -f1 || true)"
if [ -n "$token_line" ]; then
  start=$((token_line-10))
  end=$((token_line+80))
  sed -n "${start},${end}p" packages/widget/src/generated/api/yield.ts | head -n 120
fi

echo -e "\n=== enabledNetworks consumption sites ==="
rg -n "enabledNetworks\\s*:\\s*Set<Networks>|enabledNetworks\\b" packages/widget/src -S

echo -e "\n=== providers/ethereum/config.ts: getEnabledNetworks Right: networks usage ==="
sed -n '1,120p' packages/widget/src/providers/ethereum/config.ts | cat -n
sed -n '120,240p' packages/widget/src/providers/ethereum/config.ts | cat -n

echo -e "\n=== providers/misc/config.ts (enabledNetworks Set<Networks>) ==="
sed -n '1,120p' packages/widget/src/providers/misc/config.ts | cat -n

echo -e "\n=== providers/substrate/config.ts (networks usage) ==="
sed -n '1,140p' packages/widget/src/providers/substrate/config.ts | cat -n

echo -e "\n=== providers/cosmos/config.ts (networks usage) ==="
sed -n '90,170p' packages/widget/src/providers/cosmos/config.ts | cat -n

Repository: stakekit/widget

Length of output: 224


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo -e "\n=== TokenDto network type (generated api/yield.ts) ==="
token_line="$(rg -n "^export type TokenDto\\b" packages/widget/src/generated/api/yield.ts -S | head -n 1 | cut -d: -f1 || true)"
if [[ -n "${token_line}" && "${token_line}" =~ ^[0-9]+$ ]]; then
  start=$((token_line-5))
  end=$((token_line+140))
  if [ $start -lt 1 ]; then start=1; fi
  sed -n "${start},${end}p" packages/widget/src/generated/api/yield.ts | cat -n
else
  echo "Could not find exact 'export type TokenDto' line in packages/widget/src/generated/api/yield.ts"
  rg -n "type TokenDto" packages/widget/src/generated/api/yield.ts -S | head -n 5
fi

echo -e "\n=== enabledNetworks consumption sites (enabledNetworks: Set<Networks> / enabledNetworks variable) ==="
rg -n "enabledNetworks\\s*:\\s*Set<\\s*Networks\\s*>|enabledNetworks\\b" packages/widget/src -S

echo -e "\n=== providers/misc/config.ts excerpt ==="
sed -n '1,120p' packages/widget/src/providers/misc/config.ts | cat -n

echo -e "\n=== providers/ethereum/config.ts excerpt around Right: (networks) ==="
rg -n "getEnabledNetworks\\(\\{.*\\}\\)\\.caseOf|Right:\\s*\\(networks\\)" packages/widget/src/providers/ethereum/config.ts -S
sed -n '1,120p' packages/widget/src/providers/ethereum/config.ts | cat -n

echo -e "\n=== providers/cosmos/config.ts excerpt around networks usage ==="
sed -n '90,170p' packages/widget/src/providers/cosmos/config.ts | cat -n

echo -e "\n=== providers/substrate/config.ts excerpt around networks usage ==="
sed -n '1,120p' packages/widget/src/providers/substrate/config.ts | cat -n

Repository: stakekit/widget

Length of output: 18123


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo -e "\n=== occurrences of enabledNetworks variable ==="
rg -n "\\benabledNetworks\\b" packages/widget/src -S

echo -e "\n=== networks.has / enabledNetworks.has usages ==="
rg -n "\\b(enabledNetworks|networks)\\.has\\(" packages/widget/src -S

echo -e "\n=== any iteration over Set<Networks> (for..of / [...networks] / Array.from(networks)) ==="
rg -n "for \\(.*of (enabledNetworks|networks)\\)|Array\\.from\\((enabledNetworks|networks)\\)|\\[\\.\\.\\.(enabledNetworks|networks)\\]" packages/widget/src -S

echo -e "\n=== any place passing the Set contents to typed APIs expecting Networks ==="
rg -n "(Networks\\b|skChainName\\b)" packages/widget/src -S | head -n 200

Repository: stakekit/widget

Length of output: 1092


Guard the legacy networks response before casting to Networks

YieldControllerGetMyNetworks is typed in the generated legacy API as ReadonlyArray<string>, and the Networks domain type is TokenDto["network"] (string-literal union). The current (network) => network as Networks cast is unchecked. Since consumers only use the resulting set for Set.has(v.skChainName)-based filtering, invalid strings won’t break execution, but they will silently pollute a value typed as Set<Networks>.

Consider filtering/validating each returned string against the known Networks values before inserting into the Set in packages/widget/src/common/get-enabled-networks.ts (lines 20-21).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widget/src/common/get-enabled-networks.ts` around lines 20 - 21, The
legacy API call YieldControllerGetMyNetworks returns ReadonlyArray<string> but
the code blindly casts each item to Networks; instead, validate each returned
string against the known Networks set before adding to the Set<Networks> to
avoid pollution. In get-enabled-networks.ts, replace the direct cast (network)
=> network as Networks with a guard that checks membership against the canonical
Networks values (e.g., via a isNetwork type-guard or an allowedNetworks
array/Object.values) and only include the item in the Set when the guard returns
true; discard or log invalid strings so the resulting Set has only valid
Networks and consumers can still use Set.has(v.skChainName) safely.

)
),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ComponentProps } from "react";
import { useTranslation } from "react-i18next";
import type { Yield } from "../../../domain/types/yields";
import type { YieldBase } from "../../../domain/types/yields";
import {
capitalizeFirstLetters,
getRewardRateFormatted,
Expand All @@ -17,17 +17,19 @@ import {
selectItemText,
} from "./styles.css";

export const SelectOpportunityListItem = ({
type SelectOpportunityListItemProps<T extends YieldBase> = {
item: T;
onYieldSelect: (item: T) => void;
testId?: string;
selected?: boolean;
};

export const SelectOpportunityListItem = <T extends YieldBase>({
item,
onYieldSelect,
testId,
selected,
}: {
item: Yield;
onYieldSelect: (item: Yield) => void;
testId?: string;
selected?: boolean;
}) => {
}: SelectOpportunityListItemProps<T>) => {
const { t } = useTranslation();

const onItemClick: ComponentProps<typeof SelectModalItem>["onItemClick"] = ({
Expand Down
15 changes: 10 additions & 5 deletions packages/widget/src/domain/types/stake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import type { InitParams } from "./init-params";
import type { PositionsData } from "./positions";
import type { TokenBalanceScanResponseDto } from "./token-balance";
import type { TokenString } from "./tokens";
import { getYieldActionArg, isBittensorStaking, type Yield } from "./yields";
import {
getYieldActionArg,
isBittensorStaking,
type Yield,
type YieldBase,
} from "./yields";

const amountGreaterThanZero = (val: TokenBalanceScanResponseDto) =>
new BigNumber(val.amount).isGreaterThan(0);
Expand Down Expand Up @@ -87,7 +92,7 @@ export const getInitialToken = (args: {

export const canBeInitialYield = (args: {
initQueryParams: Maybe<InitParams>;
yieldDto: Yield;
yieldDto: YieldBase;
tokenBalanceAmount: BigNumber;
positionsData: PositionsData;
}) => {
Expand All @@ -113,7 +118,7 @@ const balanceValidForYield = ({
positionsData,
}: {
tokenBalanceAmount: BigNumber;
yieldDto: Yield;
yieldDto: YieldBase;
positionsData: PositionsData;
}) =>
tokenBalanceAmount.isGreaterThanOrEqualTo(
Expand Down Expand Up @@ -149,7 +154,7 @@ const yieldsWithEnterMinBasedOnPosition = new Map<Networks, Set<Yield["id"]>>([
export const isNetworkWithEnterMinBasedOnPosition = (network: Networks) =>
yieldsWithEnterMinBasedOnPosition.has(network);

const isYieldWithEnterMinBasedOnPosition = (yieldDto: Yield) =>
const isYieldWithEnterMinBasedOnPosition = (yieldDto: YieldBase) =>
Maybe.fromNullable(
yieldsWithEnterMinBasedOnPosition.get(
yieldDto.mechanics.gasFeeToken.network as Networks
Expand All @@ -159,7 +164,7 @@ const isYieldWithEnterMinBasedOnPosition = (yieldDto: Yield) =>
.isJust();

export const getMinStakeAmount = (
yieldDto: Yield,
yieldDto: YieldBase,
positionsData: PositionsData
) => {
const integrationMin = new BigNumber(
Expand Down
45 changes: 19 additions & 26 deletions packages/widget/src/domain/types/yields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import BigNumber from "bignumber.js";
import { Array as EArray, pipe } from "effect";
import type { TFunction } from "i18next";
import { Maybe } from "purify-ts";
import type { YieldDto as OldYieldDto } from "../../generated/api/legacy";
import type {
YieldType as ApiYieldType,
ArgumentFieldDto,
Expand All @@ -15,12 +14,14 @@ import type { SupportedSKChains } from "./chains";
import { EvmNetworks } from "./chains/networks";
import { equalTokens, tokenString } from "./tokens";

export type YieldProviderDetails = ProviderDto;

export type Yield = YieldApiYieldDto & {
__fallback__: OldYieldDto;
provider?: YieldProviderDetails;
};

export type YieldProviderDetails = ProviderDto;
export type YieldBase = Yield;

type YieldRiskRatingTone = "positive" | "warning" | "danger" | "neutral";
type KnownYieldRiskRatingSource = YieldRiskEntryDto["source"];
type YieldRiskRatingSource = KnownYieldRiskRatingSource | (string & {});
Expand Down Expand Up @@ -110,7 +111,7 @@ export const getDashboardYieldCategoryForApiYieldType = (
): DashboardYieldCategory => apiYieldTypeToDashboardCategory[yieldType];

export const getDashboardYieldCategory = (
yieldDto: Yield
yieldDto: YieldBase
): DashboardYieldCategory | null => {
const yieldType = getExtendedYieldType(yieldDto);

Expand Down Expand Up @@ -192,7 +193,7 @@ const secondsToDays = (seconds: number | undefined) => {
};

export const getYieldActionArg = (
yieldDto: Yield,
yieldDto: YieldBase,
type: YieldActionType,
name: YieldArgumentName
): YieldArgumentConfig | null => {
Expand All @@ -213,12 +214,12 @@ export const getYieldActionArg = (
};

export const isYieldActionArgRequired = (
yieldDto: Yield,
yieldDto: YieldBase,
type: YieldActionType,
name: YieldArgumentName
) => !!getYieldActionArg(yieldDto, type, name)?.required;

export const getYieldRewardTokens = (yieldDto: Yield) =>
export const getYieldRewardTokens = (yieldDto: YieldBase) =>
pipe(
yieldDto.rewardRate?.components?.map((component) => component.token) ?? [],
EArray.dedupeWith((a, b) => tokenString(a) === tokenString(b)),
Expand Down Expand Up @@ -277,9 +278,6 @@ export const getYieldCooldownPeriod = (yieldDto: Yield) =>
export const getYieldWarmupPeriod = (yieldDto: Yield) =>
secondsToDays(yieldDto.mechanics.warmupPeriod?.seconds);

export const getYieldCommission = (yieldDto: Yield) =>
yieldDto.__fallback__.metadata.commission;

export const getYieldTvlUsd = (yieldDto: Yield) => {
const tvlUsd = yieldDto.statistics?.tvlUsd;

Expand Down Expand Up @@ -307,10 +305,9 @@ export const getYieldFeePercent = (yieldDto: Yield): number | null => {
export const getYieldLockupPeriod = (yieldDto: Yield) =>
secondsToDays(yieldDto.mechanics.lockupPeriod?.seconds);

export const hasYieldExitSignatureVerification = (yieldDto: Yield) =>
!!yieldDto.__fallback__.args.exit?.args?.signatureVerification?.required;

export const getExtendedYieldType = (yieldDto: Yield): ExtendedYieldType => {
export const getExtendedYieldType = (
yieldDto: YieldBase
): ExtendedYieldType => {
if (isNativeStaking(yieldDto)) {
return "native_staking";
}
Expand All @@ -322,7 +319,7 @@ export const getExtendedYieldType = (yieldDto: Yield): ExtendedYieldType => {
return yieldDto.mechanics.type;
};

export const getYieldOutputToken = (yieldDto: Yield) =>
export const getYieldOutputToken = (yieldDto: YieldBase) =>
Maybe.fromNullable(yieldDto.outputToken).filter(
(outputToken) => !equalTokens(outputToken, yieldDto.token)
);
Expand All @@ -344,7 +341,7 @@ export const isDepositYieldType = (yieldType: ExtendedYieldType) =>
yieldType === "liquidity_pool";

export const getYieldTypeLabels = (
yieldDto: Yield,
yieldDto: YieldBase,
t: TFunction
): YieldTypeLabelsMap[keyof YieldTypeLabelsMap] => {
const map = {
Expand Down Expand Up @@ -426,15 +423,15 @@ const yieldTypesSortRank: { [Key in ExtendedYieldType]: number } = {
concentrated_liquidity_pool: 10,
};

export const getYieldTypesSortRank = (yieldDto: Yield) =>
export const getYieldTypesSortRank = (yieldDto: YieldBase) =>
yieldTypesSortRank[getExtendedYieldType(yieldDto)];

const isEthereumStaking = (yieldDto: Yield) =>
const isEthereumStaking = (yieldDto: YieldBase) =>
yieldDto.mechanics.type === "staking" &&
yieldDto.token.network === EvmNetworks.Ethereum &&
yieldDto.token.symbol === "ETH";

const isNativeStaking = (yieldDto: Yield) =>
const isNativeStaking = (yieldDto: YieldBase) =>
Maybe.fromFalsy(isEthereumStaking(yieldDto))
.chain(() =>
Maybe.fromFalsy(
Expand All @@ -449,22 +446,18 @@ const isNativeStaking = (yieldDto: Yield) =>
.filter((v) => v.isEqualTo(32))
.isJust();

const isPooledStaking = (yieldDto: Yield) =>
const isPooledStaking = (yieldDto: YieldBase) =>
isEthereumStaking(yieldDto) && !isNativeStaking(yieldDto);

export const isYieldWithProviderOptions = (yieldDto: Yield) =>
export const isYieldWithProviderOptions = (yieldDto: YieldBase) =>
!!getYieldActionArg(yieldDto, "enter", "providerId")?.required;

export const getYieldProviderYieldIds = (yieldDto: Yield) =>
export const getYieldProviderYieldIds = (yieldDto: YieldBase) =>
getYieldActionArg(yieldDto, "enter", "providerId")?.options ?? [];

export const isYieldIntegrationAggregator = (yieldDto: Yield) =>
!!yieldDto.__fallback__.metadata.isIntegrationAggregator;

export const isYieldValidatorSelectionRequired = (yieldDto: Yield) =>
!!(
yieldDto.mechanics.requiresValidatorSelection ||
isYieldIntegrationAggregator(yieldDto) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just double check, this validator is not needed anymore?

isYieldActionArgRequired(yieldDto, "enter", "validatorAddress") ||
isYieldActionArgRequired(yieldDto, "enter", "validatorAddresses")
);
Expand Down
12 changes: 9 additions & 3 deletions packages/widget/src/hooks/api/use-dashboard-yield-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
getYieldSummariesQueryKey,
isVisibleYieldSummary,
type YieldSummariesParams,
type YieldSummary,
} from "./use-yield-summaries";

type DashboardCategoryInitialSelection = {
token: TokenDto;
yieldDto: YieldSummary;
yieldId: string;
};

Expand All @@ -31,18 +33,21 @@ const staleTime = 1000 * 60 * 2;
*/
export const useDashboardYieldCatalog = ({
enabled = true,
network,
}: {
enabled?: boolean;
network?: TokenDto["network"] | null;
} = {}) => {
const { network } = useSKWallet();
const { network: walletNetwork } = useSKWallet();
const apiClient = useApiClient();

const probeEnabled = enabled && !!network;
const catalogNetwork = network === null ? null : (network ?? walletNetwork);
const probeEnabled = enabled && (network === null || !!catalogNetwork);

const results = useQueries({
queries: dashboardYieldCategories.map((category) => {
const params: YieldSummariesParams = {
network: network ?? undefined,
...(catalogNetwork ? { network: catalogNetwork } : {}),
types: getApiYieldTypesForDashboardCategory(category),
sort: "rewardRateDesc",
limit: DEFAULT_YIELD_SUMMARIES_PAGE_LIMIT,
Expand Down Expand Up @@ -75,6 +80,7 @@ export const useDashboardYieldCatalog = ({
availableCategories.push(category);
initialSelectionByCategory.set(category, {
token: firstVisible.token,
yieldDto: firstVisible,
yieldId: firstVisible.id,
});
});
Expand Down
Loading