diff --git a/src/components/pages/vaults/components/detail/VaultStrategiesSection.tsx b/src/components/pages/vaults/components/detail/VaultStrategiesSection.tsx
index 1df8e4912..b9a365bb9 100644
--- a/src/components/pages/vaults/components/detail/VaultStrategiesSection.tsx
+++ b/src/components/pages/vaults/components/detail/VaultStrategiesSection.tsx
@@ -243,6 +243,7 @@ export function VaultStrategiesSection({ currentVault }: { currentVault: TKongVa
variant={vaultVariant}
apr={strategy.estimatedAPY}
netApr={strategy.netAPR}
+ katRewardsAPR={strategy.katRewardsAPR}
fees={fees}
/>
))}
@@ -301,6 +302,7 @@ export function VaultStrategiesSection({ currentVault }: { currentVault: TKongVa
variant={vaultVariant}
apr={strategy.estimatedAPY}
netApr={strategy.netAPR}
+ katRewardsAPR={strategy.katRewardsAPR}
fees={fees}
/>
))}
diff --git a/src/components/pages/vaults/components/detail/VaultsListStrategy.tsx b/src/components/pages/vaults/components/detail/VaultsListStrategy.tsx
index 033065edb..c768465ec 100644
--- a/src/components/pages/vaults/components/detail/VaultsListStrategy.tsx
+++ b/src/components/pages/vaults/components/detail/VaultsListStrategy.tsx
@@ -1,5 +1,6 @@
import type { TKongVaultApr, TKongVaultStrategy } from '@pages/vaults/domain/kongVaultSelectors'
import { TokenLogo } from '@shared/components/TokenLogo'
+import { Tooltip } from '@shared/components/Tooltip'
import { IconChevron } from '@shared/icons/IconChevron'
import { IconCopy } from '@shared/icons/IconCopy'
import { IconLinkOut } from '@shared/icons/IconLinkOut'
@@ -26,6 +27,7 @@ export function VaultsListStrategy({
variant = 'v3',
apr,
netApr,
+ katRewardsAPR,
fees,
totalValueUsd
}: {
@@ -40,6 +42,7 @@ export function VaultsListStrategy({
variant: 'v2' | 'v3'
apr: number | null | undefined
netApr: number | null | undefined
+ katRewardsAPR?: number | null
fees: TKongVaultApr['fees']
totalValueUsd: number
}): ReactElement {
@@ -47,12 +50,39 @@ export function VaultsListStrategy({
const isInactive = status === 'not_active'
const isUnallocated = status === 'unallocated'
const shouldShowPlaceholders = isInactive || isUnallocated
- const displayApr = apr ?? netApr ?? 0
+ const hasKatRewards = typeof katRewardsAPR === 'number' && katRewardsAPR > 0
+ const baseApr = apr ?? netApr ?? 0
+ const displayApr = hasKatRewards ? baseApr + (katRewardsAPR ?? 0) : baseApr
const lastReportTime = details?.lastReport ? formatDuration(details.lastReport * 1000 - Date.now(), true) : 'N/A'
let apyContent: ReactElement | string = '-'
if (shouldShowPlaceholders) {
apyContent = '-'
+ } else if (hasKatRewards) {
+ const tooltipContent = (
+
+
+ {'Base APY: '}
+ {formatStrategiesApy(baseApr)}
+
+
+ {'KAT Rewards APR: '}
+ {formatStrategiesApy(katRewardsAPR)}
+
+
+ )
+ apyContent = (
+
+
+ {'⚔️'}
+ {formatStrategiesApy(displayApr)}
+
+
+ )
} else {
apyContent = formatStrategiesApy(displayApr)
}
diff --git a/src/components/pages/vaults/domain/kongVaultSelectors.test.ts b/src/components/pages/vaults/domain/kongVaultSelectors.test.ts
index 787a499d9..9e299a524 100644
--- a/src/components/pages/vaults/domain/kongVaultSelectors.test.ts
+++ b/src/components/pages/vaults/domain/kongVaultSelectors.test.ts
@@ -205,4 +205,139 @@ describe('getVaultStrategies', () => {
expect(strategies[0]?.estimatedAPY).toBeUndefined()
})
+
+ it('uses oracle apy as base for katana strategies — estimated apr is KAT rewards only', () => {
+ const strategies = getVaultStrategies(vault, {
+ totalAssets: '1000000',
+ composition: [
+ {
+ address: '0x8888888888888888888888888888888888888888',
+ name: 'Morpho Strategy',
+ status: 'active',
+ totalDebt: '500000',
+ currentDebt: '500000',
+ performance: {
+ estimated: {
+ apr: 0.0028,
+ type: 'katana-estimated-apr'
+ },
+ oracle: {
+ apr: 0.03,
+ apy: 0.04
+ }
+ }
+ }
+ ]
+ } as any)
+
+ // estimatedAPY should be oracle.apy (base yield), not estimated.apr (KAT rewards)
+ expect(strategies[0]?.estimatedAPY).toBe(0.04)
+ expect(strategies[0]?.katRewardsAPR).toBe(0.0028)
+ })
+
+ it('leaves estimatedAPY undefined for katana strategies when neither estimated.apy nor oracle.apy exists', () => {
+ const strategies = getVaultStrategies(vault, {
+ totalAssets: '1000000',
+ composition: [
+ {
+ address: '0x8888888888888888888888888888888888888888',
+ name: 'Morpho Strategy',
+ status: 'active',
+ totalDebt: '500000',
+ currentDebt: '500000',
+ performance: {
+ estimated: {
+ apr: 0.0028,
+ type: 'katana-estimated-apr'
+ }
+ }
+ }
+ ]
+ } as any)
+
+ expect(strategies[0]?.estimatedAPY).toBeUndefined()
+ expect(strategies[0]?.katRewardsAPR).toBe(0.0028)
+ })
+
+ it('reads katRewardsAPR from estimated components when apr is omitted', () => {
+ const strategies = getVaultStrategies(vault, {
+ totalAssets: '1000000',
+ composition: [
+ {
+ address: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
+ name: 'Katana Strategy with Components',
+ status: 'active',
+ totalDebt: '500000',
+ currentDebt: '500000',
+ performance: {
+ estimated: {
+ type: 'katana-estimated-apr',
+ components: {
+ katRewardsAPR: 0.002978698024448475
+ }
+ },
+ oracle: {
+ apr: 0.013945609013431531,
+ apy: 0.014041406702504533
+ }
+ }
+ }
+ ]
+ } as any)
+
+ expect(strategies[0]?.estimatedAPY).toBe(0.014041406702504533)
+ expect(strategies[0]?.katRewardsAPR).toBe(0.002978698024448475)
+ })
+
+ it('does not set katRewardsAPR for non-katana strategies', () => {
+ const strategies = getVaultStrategies(vault, {
+ totalAssets: '1000000',
+ composition: [
+ {
+ address: '0x9999999999999999999999999999999999999999',
+ name: 'Regular Strategy',
+ status: 'active',
+ totalDebt: '500000',
+ currentDebt: '500000',
+ performance: {
+ estimated: {
+ apr: 0.05,
+ apy: 0.06,
+ type: 'yvusd-estimated-apr',
+ components: {}
+ }
+ }
+ }
+ ]
+ } as any)
+
+ expect(strategies[0]?.estimatedAPY).toBe(0.06)
+ expect(strategies[0]?.katRewardsAPR).toBeUndefined()
+ })
+
+ it('prefers estimated apy over estimated apr even for katana strategies', () => {
+ const strategies = getVaultStrategies(vault, {
+ totalAssets: '1000000',
+ composition: [
+ {
+ address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ name: 'Katana Strategy with APY',
+ status: 'active',
+ totalDebt: '500000',
+ currentDebt: '500000',
+ performance: {
+ estimated: {
+ apr: 0.003,
+ apy: 0.05,
+ type: 'katana-estimated-apr',
+ components: {}
+ }
+ }
+ }
+ ]
+ } as any)
+
+ expect(strategies[0]?.estimatedAPY).toBe(0.05)
+ expect(strategies[0]?.katRewardsAPR).toBe(0.003)
+ })
})
diff --git a/src/components/pages/vaults/domain/kongVaultSelectors.ts b/src/components/pages/vaults/domain/kongVaultSelectors.ts
index 74d638145..ddd178526 100644
--- a/src/components/pages/vaults/domain/kongVaultSelectors.ts
+++ b/src/components/pages/vaults/domain/kongVaultSelectors.ts
@@ -333,6 +333,7 @@ export type TKongVaultStrategy = {
description: string
netAPR: number | null
estimatedAPY?: number | null
+ katRewardsAPR?: number | null
status: 'active' | 'not_active' | 'unallocated'
details?: {
totalDebt: string
@@ -757,9 +758,22 @@ const mapSnapshotComposition = (
if (estimatedApy !== null) {
return estimatedApy
}
+ // For Katana strategies, estimated.apr is KAT rewards (additive incentive),
+ // NOT the base yield — so skip straight to oracle.apy for the base value.
+ // KAT rewards are captured separately in katRewardsAPR below.
const oracleApy = pickNumberOrNull(entry.performance?.oracle?.apy)
return oracleApy === null ? undefined : oracleApy
})()
+ const katRewardsAPR = (() => {
+ const estimatedType = entry.performance?.estimated?.type ?? ''
+ if (!estimatedType.includes('katana')) {
+ return undefined
+ }
+ return (
+ pickNumberOrNull(entry.performance?.estimated?.components?.katRewardsAPR, entry.performance?.estimated?.apr) ??
+ undefined
+ )
+ })()
const resolvedApr = hasAllocation
? pickNumberOrNull(
entry.performance?.historical?.net,
@@ -773,6 +787,7 @@ const mapSnapshotComposition = (
description: '',
netAPR: resolvedApr,
estimatedAPY,
+ katRewardsAPR,
status,
details: {
totalDebt,
diff --git a/src/components/shared/utils/schemas/kongVaultSnapshotSchema.ts b/src/components/shared/utils/schemas/kongVaultSnapshotSchema.ts
index 2514e8165..3b6c56193 100644
--- a/src/components/shared/utils/schemas/kongVaultSnapshotSchema.ts
+++ b/src/components/shared/utils/schemas/kongVaultSnapshotSchema.ts
@@ -88,8 +88,18 @@ const snapshotRiskSchema = z
.default(null)
const estimatedAprSchema = z.object({
- apr: z.union([z.number(), z.string()]).transform((value) => Number(value)),
- apy: z.union([z.number(), z.string()]).transform((value) => Number(value)),
+ apr: z
+ .union([z.number(), z.string()])
+ .transform((value) => Number(value))
+ .optional()
+ .nullable()
+ .catch(null),
+ apy: z
+ .union([z.number(), z.string()])
+ .transform((value) => Number(value))
+ .optional()
+ .nullable()
+ .catch(null),
type: z.string().optional().default('').catch(''),
components: z
.object({
@@ -107,6 +117,7 @@ const estimatedAprSchema = z.object({
katanaBonusAPY: nullableNumberSchema.nullish(),
katanaNativeYield: nullableNumberSchema.nullish(),
katanaAppRewardsAPR: nullableNumberSchema.nullish(),
+ katRewardsAPR: nullableNumberSchema.nullish(),
steerPointsPerDollar: nullableNumberSchema.nullish(),
fixedRateKatanaRewards: nullableNumberSchema.nullish(),
FixedRateKatanaRewards: nullableNumberSchema.nullish()