Skip to content
Draft
35 changes: 19 additions & 16 deletions composables/borrow/useBorrowForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import type { TxPlan } from '~/entities/txPlan'
import { getPlanHookDisabledWarning, getUtilisationWarning, getBorrowCapWarning, getSupplyCapWarning } from '~/composables/useVaultWarnings'
import { getVaultTags, isVaultRestrictedByCountry, isAssetBlockedByCountry } from '~/composables/useGeoBlock'
import { useSwapQuotesParallel } from '~/composables/useSwapQuotesParallel'
import { getNetAPY, getProjectedRates } from '~/entities/vault'
import { getNetAPY, getProjectedRatesBatch } from '~/entities/vault'
import { findBlockingDisabledOp, OP_BORROW, OP_DEPOSIT, OP_SKIM, OP_TRANSFER, type PlannedOp } from '~/utils/vault-hooks'

export interface UseBorrowFormOptions {
Expand Down Expand Up @@ -531,26 +531,29 @@ export const useBorrowForm = (options: UseBorrowFormOptions) => {
: valueToNano(collateralAmount.value || '0', collateralVault.value.decimals)
const borrowAmountNano = valueToNano(borrowAmount.value || '0', borrowVault.value.decimals)

const [collateralProjected, borrowProjected, collateralUsdValue, borrowUsdValue] = await Promise.all([
getProjectedRates(
collateralVault.value.address,
collateralVault.value.interestRateInfo.cash,
collateralVault.value.interestRateInfo.borrows,
collateralAmountNano,
0n,
),
getProjectedRates(
borrowVault.value.address,
borrowVault.value.interestRateInfo.cash,
borrowVault.value.interestRateInfo.borrows,
-borrowAmountNano,
borrowAmountNano,
),
const [projectedRates, collateralUsdValue, borrowUsdValue] = await Promise.all([
getProjectedRatesBatch([
{
vaultAddress: collateralVault.value.address,
currentCash: collateralVault.value.interestRateInfo.cash,
currentBorrows: collateralVault.value.interestRateInfo.borrows,
cashDelta: collateralAmountNano,
borrowsDelta: 0n,
},
{
vaultAddress: borrowVault.value.address,
currentCash: borrowVault.value.interestRateInfo.cash,
currentBorrows: borrowVault.value.interestRateInfo.borrows,
cashDelta: -borrowAmountNano,
borrowsDelta: borrowAmountNano,
},
]),
borrowNeedsSwap.value && borrowSwapAssetUsdPrice.value
? Promise.resolve((+collateralAmount.value || 0) * borrowSwapAssetUsdPrice.value)
: getAssetUsdValueOrZero(collateralAmountNano, collateralVault.value!, 'off-chain'),
getAssetUsdValueOrZero(borrowAmountNano, borrowVault.value!, 'off-chain'),
])
const [collateralProjected, borrowProjected] = projectedRates

if (asyncEstimatesGuard.isStale(gen)) return

Expand Down
26 changes: 13 additions & 13 deletions composables/borrow/useMultiplyForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
type Vault,
type ProjectedRates,
convertAssetsToShares,
getProjectedRates,
getProjectedRatesBatch,
getRoe,
} from '~/entities/vault'
import {
getAssetUsdValue,
Expand All @@ -27,7 +28,7 @@ import { buildSwapRouteItems } from '~/utils/swapRouteItems'
import { formatSmartAmount, trimTrailingZeros } from '~/utils/string-utils'
import { nanoToValue } from '~/utils/crypto-utils'
import { computeMultipliedPriceImpact } from '~/utils/priceImpact'
import { calculateRoe, computeNextHealth, computeLiquidationPrice } from '~/utils/repayUtils'
import { computeNextHealth, computeLiquidationPrice } from '~/utils/repayUtils'
import { computeMaxMultiplier, computeMinMultiplier, computeWeightedSupplyApy, computeLeverageDebt } from '~/utils/multiply-math'
import type { TxPlan } from '~/entities/txPlan'
import { getPlanHookDisabledWarning, getUtilisationWarning, getBorrowCapWarning } from '~/composables/useVaultWarnings'
Expand Down Expand Up @@ -302,20 +303,20 @@ export const useMultiplyForm = (options: UseMultiplyFormOptions) => {

if (supplyAndLongSameVault) {
// Combined delta for supply + long vault
const [combined, shortResult] = await Promise.all([
getProjectedRates(supply.address, supply.interestRateInfo.cash, supply.interestRateInfo.borrows, supplyNano + swapOut, 0n),
getProjectedRates(short.address, short.interestRateInfo.cash, short.interestRateInfo.borrows, -debtNano, debtNano),
const [combined, shortResult] = await getProjectedRatesBatch([
{ vaultAddress: supply.address, currentCash: supply.interestRateInfo.cash, currentBorrows: supply.interestRateInfo.borrows, cashDelta: supplyNano + swapOut, borrowsDelta: 0n },
{ vaultAddress: short.address, currentCash: short.interestRateInfo.cash, currentBorrows: short.interestRateInfo.borrows, cashDelta: -debtNano, borrowsDelta: debtNano },
])
if (projectedRatesGuard.isStale(gen)) return
projectedSupplyRates.value = combined
projectedLongRates.value = combined
projectedBorrowRates.value = shortResult
}
else {
const [supplyResult, shortResult, longResult] = await Promise.all([
getProjectedRates(supply.address, supply.interestRateInfo.cash, supply.interestRateInfo.borrows, supplyNano, 0n),
getProjectedRates(short.address, short.interestRateInfo.cash, short.interestRateInfo.borrows, -debtNano, debtNano),
getProjectedRates(long.address, long.interestRateInfo.cash, long.interestRateInfo.borrows, swapOut, 0n),
const [supplyResult, shortResult, longResult] = await getProjectedRatesBatch([
{ vaultAddress: supply.address, currentCash: supply.interestRateInfo.cash, currentBorrows: supply.interestRateInfo.borrows, cashDelta: supplyNano, borrowsDelta: 0n },
{ vaultAddress: short.address, currentCash: short.interestRateInfo.cash, currentBorrows: short.interestRateInfo.borrows, cashDelta: -debtNano, borrowsDelta: debtNano },
{ vaultAddress: long.address, currentCash: long.interestRateInfo.cash, currentBorrows: long.interestRateInfo.borrows, cashDelta: swapOut, borrowsDelta: 0n },
])
if (projectedRatesGuard.isStale(gen)) return
projectedSupplyRates.value = supplyResult
Expand Down Expand Up @@ -373,8 +374,7 @@ export const useMultiplyForm = (options: UseMultiplyFormOptions) => {
// --- ROE ---
const multiplyRoeBefore = computed(() => {
if (isMultiplyQuoteLoading.value) return null
if (multiplySupplyValueUsd.value === null) return null
return 0
return null
})

const multiplyRoeAfter = computed(() => {
Expand All @@ -385,10 +385,10 @@ export const useMultiplyForm = (options: UseMultiplyFormOptions) => {
|| multiplyWeightedSupplyApy.value === null
|| multiplyBorrowApy.value === null
) return null
return calculateRoe(
return getRoe(
multiplyTotalSupplyUsd.value,
multiplyBorrowValueUsd.value,
multiplyWeightedSupplyApy.value,
multiplyBorrowValueUsd.value,
multiplyBorrowApy.value,
)
})
Expand Down
51 changes: 18 additions & 33 deletions composables/position/useCollateralForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ import { OperationReviewModal, SwapTokenSelector, SlippageSettingsModal } from '
import { useToast } from '~/components/ui/composables/useToast'
import { eulerAccountLensABI } from '~/entities/euler/abis'
import {
getNetAPY,
getProjectedRates,
getNetAPYFromWeightedSupplySnapshot,
isEVKVault,
type Vault,
type SecuritizeVault,
type VaultAsset,
} from '~/entities/vault'
import {
getAssetUsdValueOrZero,
getCollateralUsdValueOrZero,
} from '~/services/pricing/priceProvider'
import type { TxPlan } from '~/entities/txPlan'
import { isAnyVaultBlockedByCountry, isVaultRestrictedByCountry, isAssetBlockedByCountry, isAssetRestrictedByCountry } from '~/composables/useGeoBlock'
Expand Down Expand Up @@ -126,6 +124,7 @@ export const useCollateralForm = (options: UseCollateralFormOptions) => {
const { getOrFetch } = useVaultRegistry()
const { eulerLensAddresses, isReady: isEulerAddressesReady, loadEulerConfig } = useEulerAddresses()
const { client: rpcClient } = useRpcClient()
const { getCollateralApySnapshot } = usePositionCollateralApy()

// --- Shared reactive state ---
const isLoading = ref(false)
Expand Down Expand Up @@ -189,11 +188,6 @@ export const useCollateralForm = (options: UseCollateralFormOptions) => {
borrowVault.value?.asset.address,
))

const getCollateralValueUsdLocal = async (amt: bigint) => {
if (!borrowVault.value || !collateralVault.value) return 0
return getCollateralUsdValueOrZero(amt, borrowVault.value, collateralVault.value as Vault, 'off-chain')
}

const netAPY = ref(0)

watchEffect(async () => {
Expand All @@ -202,13 +196,13 @@ export const useCollateralForm = (options: UseCollateralFormOptions) => {
return
}

const [collateralUsd, borrowedUsd] = await Promise.all([
getCollateralValueUsdLocal(collateralAssets.value),
const [collateralSnapshot, borrowedUsd] = await Promise.all([
getCollateralApySnapshot(position.value, borrowVault.value),
getAssetUsdValueOrZero(position.value.borrowed ?? 0n, borrowVault.value, 'off-chain'),
])

netAPY.value = getNetAPY(
collateralUsd,
netAPY.value = getNetAPYFromWeightedSupplySnapshot(
collateralSnapshot,
collateralSupplyApy.value,
borrowedUsd,
borrowApy.value,
Expand Down Expand Up @@ -530,7 +524,7 @@ export const useCollateralForm = (options: UseCollateralFormOptions) => {

const asyncEstimatesGuard = createRaceGuard()
const updateAsyncEstimates = useDebounceFn(async () => {
if (!collateralVault.value || !borrowVault.value) {
if (!position.value || !collateralVault.value || !borrowVault.value) {
isEstimatesLoading.value = false
return
}
Expand All @@ -539,31 +533,22 @@ export const useCollateralForm = (options: UseCollateralFormOptions) => {
const amountNano = valueToNano(amount.value, collateralVault.value.decimals)
const cashDelta = options.mode === 'supply' ? amountNano : -amountNano

const [projected, collateralUsd, borrowedUsd] = await Promise.all([
getProjectedRates(
collateralVault.value.address,
collateralVault.value.interestRateInfo.cash,
collateralVault.value.interestRateInfo.borrows,
cashDelta,
0n,
),
getCollateralValueUsdLocal(
options.mode === 'supply'
? collateralAssets.value + amountNano
: collateralAssets.value - amountNano,
),
const [collateralSnapshot, borrowedUsd] = await Promise.all([
getCollateralApySnapshot(position.value, borrowVault.value, {
deltas: [{
vaultAddress: collateralVault.value.address,
assetsDelta: cashDelta,
projectRates: true,
}],
}),
getAssetUsdValueOrZero(position.value!.borrowed || 0n, borrowVault.value!, 'off-chain'),
])

if (asyncEstimatesGuard.isStale(gen)) return

const projectedSupplyApy = projected
? withIntrinsicSupplyApy(nanoToValue(projected.supplyAPY, 25), collateralVault.value?.asset.address)
: collateralSupplyApy.value

estimateNetAPY.value = getNetAPY(
collateralUsd,
projectedSupplyApy,
estimateNetAPY.value = getNetAPYFromWeightedSupplySnapshot(
collateralSnapshot,
collateralSupplyApy.value,
borrowedUsd,
borrowApy.value,
collateralSupplyRewardApy.value || null,
Expand Down
44 changes: 33 additions & 11 deletions composables/repay/useCollateralSwapRepay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useModal } from '~/components/ui/composables/useModal'
import { OperationReviewModal } from '#components'
import { useToast } from '~/components/ui/composables/useToast'
import { getCashLimitedWithdrawAmount, isEVKVault, type Vault } from '~/entities/vault'
import { getAssetUsdValue, getAssetOraclePrice, conservativePriceRatioNumber } from '~/services/pricing/priceProvider'
import { getAssetOraclePrice, conservativePriceRatioNumber } from '~/services/pricing/priceProvider'
import type { AccountBorrowPosition } from '~/entities/account'
import type { TxPlan } from '~/entities/txPlan'
import { SwapperMode } from '~/entities/swap'
Expand Down Expand Up @@ -65,6 +65,7 @@ export const useCollateralSwapRepay = (options: UseCollateralSwapRepayOptions) =
const { client: rpcClient } = useRpcClient()
const { withIntrinsicSupplyApy, withIntrinsicBorrowApy } = useIntrinsicApy()
const { getSupplyRewardApy, getBorrowRewardApy } = useRewardsApy()
const { getCollateralApySnapshot } = usePositionCollateralApy()

// --- Source vault state ---
const sourceVault: Ref<Vault | undefined> = ref()
Expand Down Expand Up @@ -171,21 +172,41 @@ export const useCollateralSwapRepay = (options: UseCollateralSwapRepayOptions) =
return nanoToValue(position.value.liquidationLTV, 2)
})

// --- 4th USD watcher: next collateral value ---
const nextCollateralUsdGuard = createRaceGuard()
// --- Collateral portfolio value/APY ---
const collateralPortfolioGuard = createRaceGuard()
const weightedCollateralSupplyApy = ref<number | null>(null)
const nextWeightedCollateralSupplyApy = ref<number | null>(null)
const collateralValueUsd = ref<number | null>(null)
const nextCollateralValueUsd = ref<number | null>(null)

watchEffect(async () => {
if (!sourceVault.value || core.spent.value === null) {
if (!position.value || !borrowVault.value || !sourceVault.value) {
weightedCollateralSupplyApy.value = null
nextWeightedCollateralSupplyApy.value = null
collateralValueUsd.value = null
nextCollateralValueUsd.value = null
return
}
const gen = nextCollateralUsdGuard.next()
const nextAssets = sourceAssets.value - core.spent.value
const result = (await getAssetUsdValue(nextAssets > 0n ? nextAssets : 0n, sourceVault.value, 'off-chain')) ?? null
if (nextCollateralUsdGuard.isStale(gen)) return
nextCollateralValueUsd.value = result
const gen = collateralPortfolioGuard.next()
const spent = core.spent.value ?? 0n
const [currentSnapshot, nextSnapshot] = await Promise.all([
getCollateralApySnapshot(position.value, borrowVault.value),
getCollateralApySnapshot(position.value, borrowVault.value, {
deltas: [{
vaultAddress: sourceVault.value.address,
assetsDelta: -spent,
projectRates: spent > 0n,
}],
}),
])
if (collateralPortfolioGuard.isStale(gen)) return
weightedCollateralSupplyApy.value = currentSnapshot.weightedSupplyApy
nextWeightedCollateralSupplyApy.value = nextSnapshot.weightedSupplyApy
collateralValueUsd.value = currentSnapshot.supplyUsd
nextCollateralValueUsd.value = nextSnapshot.supplyUsd
})
const effectiveCollateralSupplyApy = computed(() => weightedCollateralSupplyApy.value ?? collateralSupplyApy.value)
Comment thread
Seranged marked this conversation as resolved.
const effectiveNextCollateralSupplyApy = computed(() => nextWeightedCollateralSupplyApy.value ?? effectiveCollateralSupplyApy.value)

// --- Health metrics ---
const health = useRepayHealthMetrics({
Expand All @@ -195,9 +216,10 @@ export const useCollateralSwapRepay = (options: UseCollateralSwapRepayOptions) =
priceRatio,
nextLiquidationLtv,
collateralAmountAfter,
collateralSupplyApy,
collateralSupplyApy: effectiveCollateralSupplyApy,
nextCollateralSupplyApy: effectiveNextCollateralSupplyApy,
borrowApy,
collateralValueUsd: core.sourceValueUsd,
collateralValueUsd,
nextCollateralValueUsd,
borrowValueUsd: core.borrowValueUsd,
nextBorrowValueUsd: core.nextBorrowValueUsd,
Expand Down
56 changes: 53 additions & 3 deletions composables/repay/useRepayHealthMetrics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Ref, ComputedRef } from 'vue'
import type { AccountBorrowPosition } from '~/entities/account'
import { getProjectedRates, getRoe } from '~/entities/vault'
import { nanoToValue } from '~/utils/crypto-utils'
import { calculateRoe, computeNextLtv, computeNextHealth, computeLiquidationPrice } from '~/utils/repayUtils'
import { computeNextLtv, computeNextHealth, computeLiquidationPrice } from '~/utils/repayUtils'
import { createRaceGuard } from '~/utils/race-guard'

interface UseRepayHealthMetricsOptions {
position: Ref<AccountBorrowPosition | undefined>
Expand All @@ -11,6 +13,7 @@ interface UseRepayHealthMetricsOptions {
nextLiquidationLtv: ComputedRef<number | null>
collateralAmountAfter: ComputedRef<number | null>
collateralSupplyApy: ComputedRef<number | null>
nextCollateralSupplyApy?: ComputedRef<number | null>
borrowApy: ComputedRef<number | null>
collateralValueUsd: Ref<number | null>
nextCollateralValueUsd: Ref<number | null>
Expand All @@ -27,6 +30,7 @@ export const useRepayHealthMetrics = (options: UseRepayHealthMetricsOptions) =>
nextLiquidationLtv,
collateralAmountAfter,
collateralSupplyApy,
nextCollateralSupplyApy,
borrowApy,
collateralValueUsd,
nextCollateralValueUsd,
Expand Down Expand Up @@ -69,11 +73,57 @@ export const useRepayHealthMetrics = (options: UseRepayHealthMetricsOptions) =>
const nextLiquidationPrice = computed(() =>
computeLiquidationPrice(priceRatio.value, nextHealth.value))

const projectedBorrowApy = ref<number | null>(null)
const projectedBorrowApyGuard = createRaceGuard()

watchEffect(async () => {
const gen = projectedBorrowApyGuard.next()
const vault = borrowVault.value
const currentPosition = position.value
const repaid = debtRepaid.value
const currentBorrowApy = borrowApy.value

if (!vault || !currentPosition || repaid === null) {
projectedBorrowApy.value = null
return
}

try {
const repayAmount = repaid > currentPosition.borrowed
? currentPosition.borrowed
: repaid

const projected = await getProjectedRates(
vault.address,
vault.interestRateInfo.cash,
vault.interestRateInfo.borrows,
repayAmount,
-repayAmount,
)

if (projectedBorrowApyGuard.isStale(gen)) return

if (!projected) {
projectedBorrowApy.value = null
return
}

const currentRaw = nanoToValue(vault.interestRateInfo.borrowAPY || 0n, 25)
const projectedRaw = nanoToValue(projected.borrowAPY, 25)
projectedBorrowApy.value = (currentBorrowApy ?? 0) + (projectedRaw - currentRaw)
}
catch {
if (!projectedBorrowApyGuard.isStale(gen)) {
projectedBorrowApy.value = null
}
}
})
Comment thread
Seranged marked this conversation as resolved.

const roeBefore = computed(() =>
calculateRoe(collateralValueUsd.value, borrowValueUsd.value, collateralSupplyApy.value, borrowApy.value))
getRoe(collateralValueUsd.value, collateralSupplyApy.value, borrowValueUsd.value, borrowApy.value))

const roeAfter = computed(() =>
calculateRoe(nextCollateralValueUsd.value, nextBorrowValueUsd.value, collateralSupplyApy.value, borrowApy.value))
getRoe(nextCollateralValueUsd.value, nextCollateralSupplyApy?.value ?? collateralSupplyApy.value, nextBorrowValueUsd.value, projectedBorrowApy.value ?? borrowApy.value))

return {
currentHealth,
Expand Down
Loading