diff --git a/packages/widget/src/domain/types/yields.ts b/packages/widget/src/domain/types/yields.ts index ac286ce5..93976645 100644 --- a/packages/widget/src/domain/types/yields.ts +++ b/packages/widget/src/domain/types/yields.ts @@ -36,8 +36,23 @@ export type YieldMetadata = Pick< > & { provider?: YieldProviderDetails; }; + +const knownApiYieldTypes = [ + "staking", + "restaking", + "lending", + "vault", + "fixed_yield", + "real_world_asset", + "concentrated_liquidity_pool", + "liquidity_pool", + "liquid_staking", +] as const satisfies ReadonlyArray; + +type KnownApiYieldType = (typeof knownApiYieldTypes)[number]; type LocallyDerivedYieldType = "native_staking" | "pooled_staking"; -export type ExtendedYieldType = ApiYieldType | LocallyDerivedYieldType; +type KnownExtendedYieldType = KnownApiYieldType | LocallyDerivedYieldType; +export type ExtendedYieldType = KnownExtendedYieldType | "unknown"; type YieldActionType = "enter" | "exit"; type YieldArgumentName = ArgumentFieldDto["name"]; @@ -77,29 +92,28 @@ export const dashboardYieldCategories = [ ] as const satisfies ReadonlyArray; /** - * Maps every API `YieldType` to exactly one dashboard category. The - * `satisfies Record` guarantees exhaustiveness: a new server - * yield type fails the build here until it is assigned a category. This mirrors - * `getDashboardYieldCategory` (which classifies hydrated yields) but is keyed by - * the API `type` so it can drive `types[]` query filters. + * Maps locally known API yield types to dashboard categories. Unknown future + * API types are intentionally not included in filtered queries because the app + * cannot infer which dashboard category they belong to. */ const apiYieldTypeToDashboardCategory = { staking: "stake", restaking: "stake", + liquid_staking: "stake", lending: "defi", vault: "defi", fixed_yield: "defi", concentrated_liquidity_pool: "defi", liquidity_pool: "defi", real_world_asset: "rwa", -} as const satisfies Record; +} as const satisfies Record; export const getApiYieldTypesForDashboardCategory = ( category: DashboardYieldCategory -): ApiYieldType[] => +): KnownApiYieldType[] => ( Object.entries(apiYieldTypeToDashboardCategory) as [ - ApiYieldType, + KnownApiYieldType, DashboardYieldCategory, ][] ) @@ -306,6 +320,11 @@ export const getYieldFeePercent = (yieldDto: Yield): number | null => { export const getYieldLockupPeriod = (yieldDto: Yield) => secondsToDays(yieldDto.mechanics.lockupPeriod?.seconds); +const knownApiYieldTypeValues = new Set(knownApiYieldTypes); + +const isKnownApiYieldType = (type: string): type is KnownApiYieldType => + knownApiYieldTypeValues.has(type); + export const getExtendedYieldType = ( yieldDto: YieldBase ): ExtendedYieldType => { @@ -317,7 +336,9 @@ export const getExtendedYieldType = ( return "pooled_staking"; } - return yieldDto.mechanics.type; + const type = yieldDto.mechanics.type as string; + + return isKnownApiYieldType(type) ? type : "unknown"; }; export const getYieldOutputToken = (yieldDto: YieldBase) => @@ -340,6 +361,7 @@ export const hasYieldBearingOutputToken = (yieldDto: YieldBase) => const isStakingYieldType = (yieldType: ExtendedYieldType) => yieldType === "staking" || + yieldType === "liquid_staking" || yieldType === "native_staking" || yieldType === "pooled_staking"; @@ -383,6 +405,12 @@ export const getYieldTypeLabels = ( review: t("yield_types.restaking.review"), cta: t("yield_types.restaking.cta"), }, + liquid_staking: { + type: "liquid_staking", + title: t("yield_types.liquid-staking.title"), + review: t("yield_types.liquid-staking.review"), + cta: t("yield_types.liquid-staking.cta"), + }, fixed_yield: { type: "fixed_yield", title: t("yield_types.fixed_yield.title"), @@ -419,6 +447,12 @@ export const getYieldTypeLabels = ( review: t("yield_types.pooled_staking.review"), cta: t("yield_types.pooled_staking.cta"), }, + unknown: { + type: "unknown", + title: "Yield", + review: "Earn", + cta: "Earn", + }, } satisfies YieldTypeLabelsMap; return map[getExtendedYieldType(yieldDto)]; @@ -427,14 +461,16 @@ export const getYieldTypeLabels = ( const yieldTypesSortRank: { [Key in ExtendedYieldType]: number } = { real_world_asset: 1, staking: 2, - native_staking: 3, - pooled_staking: 4, - restaking: 5, - lending: 6, - vault: 7, - fixed_yield: 8, - liquidity_pool: 9, - concentrated_liquidity_pool: 10, + liquid_staking: 3, + native_staking: 4, + pooled_staking: 5, + restaking: 6, + lending: 7, + vault: 8, + fixed_yield: 9, + liquidity_pool: 10, + concentrated_liquidity_pool: 11, + unknown: 12, }; export const getYieldTypesSortRank = (yieldDto: YieldBase) => diff --git a/packages/widget/src/generated/api/legacy.ts b/packages/widget/src/generated/api/legacy.ts index dbbd7095..293deadf 100644 --- a/packages/widget/src/generated/api/legacy.ts +++ b/packages/widget/src/generated/api/legacy.ts @@ -383,6 +383,7 @@ export type Team = { readonly oavEnabled: boolean; readonly isMfaEnforced: boolean; readonly hyperliquidVerifyByClientOrderId: boolean; + readonly unifiedAccountModeEnabled: boolean; readonly referredBy: string | null; readonly referralCode: string | null; }; @@ -787,6 +788,7 @@ export type YieldProviders = | "lista" | "dolomite" | "midas" + | "concrete" | "dinari" | "ondo" | "superstate" @@ -855,7 +857,8 @@ export type PerpActionTypes = | "approveAgent" | "approveBuilderFee" | "updateMargin" - | "setTpAndSl"; + | "setTpAndSl" + | "setUnifiedAccount"; export type ProgrammaticPerpReportingTransactionDto = { readonly id: string; readonly type: @@ -873,7 +876,8 @@ export type ProgrammaticPerpReportingTransactionDto = { | "ENABLE_DEX_ABSTRACTION" | "APPROVE_AGENT" | "UPDATE_MARGIN" - | "SET_TP_AND_SL"; + | "SET_TP_AND_SL" + | "SET_USER_ABSTRACTION"; readonly status: | "CREATED" | "QUEUED" @@ -3305,6 +3309,7 @@ export type ActionDto = { }; export type ActionGasEstimateDto = { readonly amount: string | null; + readonly entryReserveEstimate?: string; readonly token: TokenDto; readonly gasLimit?: string; readonly transactions: ReadonlyArray; @@ -4139,7 +4144,8 @@ export type ProgrammaticReportingControllerGetPerpActions200 = { | "approveAgent" | "approveBuilderFee" | "updateMargin" - | "setTpAndSl"; + | "setTpAndSl" + | "setUnifiedAccount"; readonly status: | "CANCELED" | "CREATED" @@ -5168,6 +5174,7 @@ export type YieldV2ControllerYieldsParams = { | "lista" | "dolomite" | "midas" + | "concrete" | "dinari" | "ondo" | "superstate" diff --git a/packages/widget/src/generated/api/yield.ts b/packages/widget/src/generated/api/yield.ts index 7c8e5dfc..a0fd7658 100644 --- a/packages/widget/src/generated/api/yield.ts +++ b/packages/widget/src/generated/api/yield.ts @@ -234,6 +234,7 @@ export type RewardDto = { }; readonly yieldSource: | "staking" + | "liquid_staking" | "restaking" | "protocol_incentive" | "campaign_incentive" @@ -257,7 +258,8 @@ export type YieldType = | "fixed_yield" | "real_world_asset" | "concentrated_liquidity_pool" - | "liquidity_pool"; + | "liquidity_pool" + | "liquid_staking"; export type RewardSchedule = | "block" | "hour" @@ -3971,7 +3973,8 @@ export type YieldsControllerGetYieldsParams = { | "fixed_yield" | "real_world_asset" | "concentrated_liquidity_pool" - | "liquidity_pool"; + | "liquidity_pool" + | "liquid_staking"; readonly types?: ReadonlyArray< | "staking" | "restaking" @@ -3981,6 +3984,7 @@ export type YieldsControllerGetYieldsParams = { | "real_world_asset" | "concentrated_liquidity_pool" | "liquidity_pool" + | "liquid_staking" >; readonly hasCooldownPeriod?: boolean; readonly hasWarmupPeriod?: boolean; @@ -4428,6 +4432,7 @@ export type TokensControllerGetTokensParams = { | "real_world_asset" | "concentrated_liquidity_pool" | "liquidity_pool" + | "liquid_staking" >; readonly offset?: number; readonly limit?: number; @@ -4514,6 +4519,7 @@ export type ActionsControllerGetActionsParams = { | "real_world_asset" | "concentrated_liquidity_pool" | "liquidity_pool" + | "liquid_staking" >; readonly network?: | "ethereum" diff --git a/packages/widget/src/hooks/use-yield-meta-info.tsx b/packages/widget/src/hooks/use-yield-meta-info.tsx index 02d05144..fc822438 100644 --- a/packages/widget/src/hooks/use-yield-meta-info.tsx +++ b/packages/widget/src/hooks/use-yield-meta-info.tsx @@ -130,6 +130,7 @@ export const useYieldMetaInfo = ({ switch (yieldType) { case "staking": + case "liquid_staking": case "native_staking": case "pooled_staking": { return { diff --git a/packages/widget/src/translation/English/translations.json b/packages/widget/src/translation/English/translations.json index 35f795ac..cac12c71 100644 --- a/packages/widget/src/translation/English/translations.json +++ b/packages/widget/src/translation/English/translations.json @@ -466,6 +466,7 @@ "staking": "staked", "staking_ethena_usde": "deposited", "liquid-staking": "staked", + "liquid_staking": "staked", "vault": "deposited", "lending": "supplied", "restaking": "restaked", @@ -474,12 +475,14 @@ "fixed_yield": "deposited", "real_world_asset": "deposited", "concentrated_liquidity_pool": "deposited", - "liquidity_pool": "deposited" + "liquidity_pool": "deposited", + "unknown": "earned" }, "unstake": { "staking": "unstaked", "staking_ethena_usde": "withdrawn", "liquid-staking": "unstaked", + "liquid_staking": "unstaked", "vault": "withdrawn", "lending": "withdrawn", "restaking": "unstaked", @@ -488,7 +491,8 @@ "fixed_yield": "withdrawn", "real_world_asset": "withdrawn", "concentrated_liquidity_pool": "withdrawn", - "liquidity_pool": "withdrawn" + "liquidity_pool": "withdrawn", + "unknown": "withdrawn" }, "pending_action": { "stake": "staked", @@ -606,6 +610,11 @@ "review": "Liquid Staking", "cta": "Stake" }, + "liquid_staking": { + "title": "Liquid Staking", + "review": "Liquid Staking", + "cta": "Stake" + }, "vault": { "title": "Vault", "review": "Deposit", @@ -645,26 +654,30 @@ "native_staking": "Staked", "pooled_staking": "Staked", "liquid-staking": "Liquid staked", + "liquid_staking": "Liquid staked", "lending": "Deposited", "vault": "Deposited", "restaking": "Restaked", "fixed_yield": "Deposited", "real_world_asset": "Deposited", "concentrated_liquidity_pool": "Deposited", - "liquidity_pool": "Deposited" + "liquidity_pool": "Deposited", + "unknown": "Yield" }, "unstake_label": { "staking": "Unstake", "native_staking": "Unstake", "pooled_staking": "Unstake", "liquid-staking": "Unstake", + "liquid_staking": "Unstake", "lending": "Withdraw", "vault": "Withdraw", "restaking": "Unstake", "fixed_yield": "Withdraw", "real_world_asset": "Withdraw", "concentrated_liquidity_pool": "Withdraw", - "liquidity_pool": "Withdraw" + "liquidity_pool": "Withdraw", + "unknown": "Manage" }, "balance_type": { "active": "Active", diff --git a/packages/widget/src/translation/French/translations.json b/packages/widget/src/translation/French/translations.json index b62baf28..a508d49a 100644 --- a/packages/widget/src/translation/French/translations.json +++ b/packages/widget/src/translation/French/translations.json @@ -339,6 +339,7 @@ "staking": "staké", "staking_ethena_usde": "déposé", "liquid-staking": "staké", + "liquid_staking": "staké", "vault": "déposé", "lending": "fourni", "restaking": "restaké", @@ -347,12 +348,14 @@ "fixed_yield": "déposé", "real_world_asset": "déposé", "concentrated_liquidity_pool": "déposé", - "liquidity_pool": "déposé" + "liquidity_pool": "déposé", + "unknown": "gagné" }, "unstake": { "staking": "déstaké", "staking_ethena_usde": "retiré", "liquid-staking": "déstaké", + "liquid_staking": "déstaké", "vault": "retiré", "lending": "retiré", "restaking": "déstaké", @@ -361,7 +364,8 @@ "fixed_yield": "retiré", "real_world_asset": "retiré", "concentrated_liquidity_pool": "retiré", - "liquidity_pool": "retiré" + "liquidity_pool": "retiré", + "unknown": "retiré" }, "pending_action": { "stake": "staké", @@ -479,6 +483,11 @@ "review": "Staker", "cta": "Staker" }, + "liquid_staking": { + "title": "Staking Liquide", + "review": "Staker", + "cta": "Staker" + }, "vault": { "title": "Vault", "review": "Déposer", @@ -518,26 +527,30 @@ "native_staking": "Staké", "pooled_staking": "Staké", "liquid-staking": "Staké", + "liquid_staking": "Staké", "lending": "Déposé", "vault": "Déposé", "restaking": "Restaké", "fixed_yield": "Déposé", "real_world_asset": "Déposé", "concentrated_liquidity_pool": "Déposé", - "liquidity_pool": "Déposé" + "liquidity_pool": "Déposé", + "unknown": "Rendement" }, "unstake_label": { "staking": "Déstaker", "native_staking": "Déstaker", "pooled_staking": "Déstaker", "liquid-staking": "Déstaker", + "liquid_staking": "Déstaker", "lending": "Retirer", "vault": "Retirer", "restaking": "Déstaker", "fixed_yield": "Retirer", "real_world_asset": "Retirer", "concentrated_liquidity_pool": "Retirer", - "liquidity_pool": "Retirer" + "liquidity_pool": "Retirer", + "unknown": "Gérer" }, "balance_type": { "active": "Actif", diff --git a/packages/widget/tests/domain/dashboard-yield-category-types.test.ts b/packages/widget/tests/domain/dashboard-yield-category-types.test.ts index b6547e45..7f33cb4a 100644 --- a/packages/widget/tests/domain/dashboard-yield-category-types.test.ts +++ b/packages/widget/tests/domain/dashboard-yield-category-types.test.ts @@ -2,6 +2,8 @@ import { describe, expect, it } from "vitest"; import { dashboardYieldCategories, getApiYieldTypesForDashboardCategory, + getDashboardYieldCategory, + getYieldTypeLabels, getYieldTypesSortRank, type YieldBase, } from "../../src/domain/types/yields"; @@ -15,12 +17,34 @@ const allApiYieldTypes = [ "real_world_asset", "concentrated_liquidity_pool", "liquidity_pool", + "liquid_staking", ] as const; +const makeYield = (type: string): YieldBase => + ({ + mechanics: { + type, + }, + token: { + network: "ethereum", + symbol: "USDC", + }, + }) as YieldBase; + +const t = ((key: string) => { + const values: Record = { + "yield_types.liquid-staking.title": "Liquid Staking", + "yield_types.liquid-staking.review": "Liquid Staking", + "yield_types.liquid-staking.cta": "Stake", + }; + + return values[key] ?? key; +}) as Parameters[1]; + describe("getApiYieldTypesForDashboardCategory", () => { - it("maps stake to staking + restaking", () => { + it("maps stake to staking + restaking + liquid staking", () => { expect(getApiYieldTypesForDashboardCategory("stake").sort()).toEqual( - ["restaking", "staking"].sort() + ["liquid_staking", "restaking", "staking"].sort() ); }); @@ -54,17 +78,6 @@ describe("getApiYieldTypesForDashboardCategory", () => { }); describe("getYieldTypesSortRank", () => { - const makeYield = (type: YieldBase["mechanics"]["type"]): YieldBase => - ({ - mechanics: { - type, - }, - token: { - network: "ethereum", - symbol: "USDC", - }, - }) as YieldBase; - it("ranks RWA yields before other API yield types", () => { const rwaRank = getYieldTypesSortRank(makeYield("real_world_asset")); const otherRanks = allApiYieldTypes @@ -73,4 +86,42 @@ describe("getYieldTypesSortRank", () => { expect(rwaRank).toBeLessThan(Math.min(...otherRanks)); }); + + it("assigns unknown runtime yield types a valid last-place rank", () => { + const unknownRank = getYieldTypesSortRank(makeYield("future_yield_type")); + const knownRanks = allApiYieldTypes.map((type) => + getYieldTypesSortRank(makeYield(type)) + ); + + expect(Number.isFinite(unknownRank)).toBe(true); + expect(unknownRank).toBeGreaterThan(Math.max(...knownRanks)); + }); +}); + +describe("getDashboardYieldCategory", () => { + it("does not assign unknown runtime yield types to a dashboard category", () => { + expect( + getDashboardYieldCategory(makeYield("future_yield_type")) + ).toBeNull(); + }); +}); + +describe("getYieldTypeLabels", () => { + it("uses liquid staking copy for liquid_staking", () => { + expect(getYieldTypeLabels(makeYield("liquid_staking"), t)).toEqual({ + type: "liquid_staking", + title: "Liquid Staking", + review: "Liquid Staking", + cta: "Stake", + }); + }); + + it("uses generic copy for unknown runtime yield types", () => { + expect(getYieldTypeLabels(makeYield("future_yield_type"), t)).toEqual({ + type: "unknown", + title: "Yield", + review: "Earn", + cta: "Earn", + }); + }); }); diff --git a/packages/widget/tests/hooks/activity-actions.test.ts b/packages/widget/tests/hooks/activity-actions.test.ts index af855713..2ca9caed 100644 --- a/packages/widget/tests/hooks/activity-actions.test.ts +++ b/packages/widget/tests/hooks/activity-actions.test.ts @@ -18,7 +18,7 @@ const yieldTypeKey = (values?: ReadonlyArray) => const filterByYieldTypes = new Map([ ["", "all"], - [yieldTypeKey(["staking", "restaking"]), "stake"], + [yieldTypeKey(["staking", "restaking", "liquid_staking"]), "stake"], [ yieldTypeKey([ "lending", @@ -90,7 +90,7 @@ describe("activity action request params", () => { }); it.each([ - ["stake", ["staking", "restaking"]], + ["stake", ["staking", "restaking", "liquid_staking"]], [ "defi", [ @@ -131,7 +131,7 @@ describe("activity action request params", () => { expect(allKey).not.toEqual(stakeKey); expect(stakeKey[1]).toMatchObject({ filter: "stake", - yieldTypes: ["staking", "restaking"], + yieldTypes: ["staking", "restaking", "liquid_staking"], }); }); });