Skip to content

Commit

Permalink
Morpho Blue Automation (#3849)
Browse files Browse the repository at this point in the history
* Triggers data in Morpho Blue (#3840)

* add automations to morho settings

* move triggers data from protocol hooks to generic data hook

* update types

* Pass additional parameters to triggers API (#3845)

* pass protocol and pairId

* add additional parameters to create trigger query

* format fix

* Generalize automation methods and interfaces (#3853)

* generalize metadata loading and stop loss

* fix stop loss hook

* bs hook fixes

* auto take profit

* trailing stop loss

* format fix

* typecheck fix

* Update front-end automation interfaces to match lambda (#3862)

* interface update

* tab labels

* update triggers mapping

* omnikit type fixes

* aave type fixes

* fix: dynamically generated emptyConfig

* features in settngs

---------

Co-authored-by: Piotr Konowrocki <[email protected]>
  • Loading branch information
marcinciarka and piotrkonowrocki authored May 7, 2024
1 parent 8b69d03 commit 3dfa2d9
Show file tree
Hide file tree
Showing 86 changed files with 1,752 additions and 1,376 deletions.
8 changes: 0 additions & 8 deletions components/context/PreloadAppDataContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { configCacheTime, getLocalAppConfig, saveConfigToLocalStorage } from 'he
import { LendingProtocol } from 'lendingProtocols'
import type { PropsWithChildren } from 'react'
import React, { useContext, useEffect, useState } from 'react'
import { FeaturesEnum } from 'types/config'

const configFetcher = () =>
fetch(`/api/config`, {
Expand All @@ -14,13 +13,6 @@ const configFetcher = () =>
},
})

export const emptyConfig = {
features: Object.fromEntries(
Object.values(FeaturesEnum).map((feature: FeaturesEnum) => [feature, false]),
) as Record<FeaturesEnum, boolean | {}>,
parameters: {},
} as ConfigResponseType

export const preloadAppDataContext = React.createContext<PreloadAppDataContext | undefined>(
undefined,
)
Expand Down
20 changes: 12 additions & 8 deletions features/aave/components/AaveBorrowPositionData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ export function AaveBorrowPositionData({
}: AaveBorrowPositionDataProps) {
const { t } = useTranslation()
const [collateralToken, debtToken] = getCurrentPositionLibCallData(currentPosition)
const stopLossLambdaData = mapStopLossFromLambda(triggersState?.context.currentTriggers.triggers)
const trailingStopLossLambdaData = mapTrailingStopLossFromLambda(
triggersState?.context.currentTriggers.triggers,
)
const stopLossLambdaData = mapStopLossFromLambda({
protocol: lendingProtocol,
triggers: triggersState?.context.currentTriggers.triggers,
})
const trailingStopLossLambdaData = mapTrailingStopLossFromLambda({
protocol: lendingProtocol,
triggers: triggersState?.context.currentTriggers.triggers,
})
const {
triggerData: {
stopLossTriggerData: { stopLossLevel, isStopLossEnabled },
Expand Down Expand Up @@ -277,7 +281,7 @@ export function AaveBorrowPositionData({
})

const automationData = useMemo(() => {
if (trailingStopLossLambdaData.trailingStopLossTriggerName) {
if (trailingStopLossLambdaData.trailingStopLossData) {
const collateral = amountFromWei(
currentPosition.collateral.amount || zero,
currentPosition.collateral.precision,
Expand All @@ -295,7 +299,7 @@ export function AaveBorrowPositionData({
isTrailingStopLoss: true,
}
}
if (stopLossLambdaData.stopLossTriggerName) {
if (stopLossLambdaData.stopLossData) {
return {
isAutomationAvailable: true,
stopLossLevel: stopLossLambdaData.stopLossLevel?.div(lambdaPercentageDenomination), // still needs to be divided by 100
Expand All @@ -317,11 +321,11 @@ export function AaveBorrowPositionData({
isAutomationAvailable,
isAutomationDataLoaded,
isStopLossEnabled,
stopLossLambdaData.stopLossData,
stopLossLambdaData.stopLossLevel,
stopLossLambdaData.stopLossTriggerName,
stopLossLevel,
trailingStopLossLambdaData.dynamicParams?.executionPrice,
trailingStopLossLambdaData.trailingStopLossTriggerName,
trailingStopLossLambdaData.trailingStopLossData,
])

return (
Expand Down
27 changes: 16 additions & 11 deletions features/aave/components/AaveMultiplyPositionData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,15 @@ export function AaveMultiplyPositionData({
}: AaveMultiplyPositionDataProps) {
const { t } = useTranslation()
const [collateralToken, debtToken] = getCurrentPositionLibCallData(currentPosition)
const stopLossLambdaData = mapStopLossFromLambda(triggersState?.context.currentTriggers.triggers)
const trailingStopLossLambdaData = mapTrailingStopLossFromLambda(
triggersState?.context.currentTriggers.triggers,
)

const stopLossLambdaData = mapStopLossFromLambda({
protocol: lendingProtocol,
triggers: triggersState?.context.currentTriggers.triggers,
})
const trailingStopLossLambdaData = mapTrailingStopLossFromLambda({
protocol: lendingProtocol,
triggers: triggersState?.context.currentTriggers.triggers,
})
const {
triggerData: {
stopLossTriggerData: { stopLossLevel, isStopLossEnabled },
Expand Down Expand Up @@ -269,7 +274,7 @@ export function AaveMultiplyPositionData({
const netValueContentCardAaveData = useAaveCardDataNetValueLending(netValuePnlModalData)

const automationData = useMemo(() => {
if (trailingStopLossLambdaData.trailingStopLossTriggerName) {
if (trailingStopLossLambdaData.trailingStopLossData) {
const collateral = amountFromWei(
currentPosition.collateral.amount || zero,
currentPosition.collateral.precision,
Expand All @@ -287,7 +292,7 @@ export function AaveMultiplyPositionData({
isTrailingStopLoss: true,
}
}
if (stopLossLambdaData.stopLossTriggerName) {
if (stopLossLambdaData.stopLossData) {
return {
isAutomationAvailable: true,
stopLossLevel: stopLossLambdaData.stopLossLevel?.div(lambdaPercentageDenomination), // still needs to be divided by 100
Expand All @@ -309,15 +314,15 @@ export function AaveMultiplyPositionData({
isAutomationAvailable,
isAutomationDataLoaded,
isStopLossEnabled,
stopLossLambdaData.stopLossData,
stopLossLambdaData.stopLossLevel,
stopLossLambdaData.stopLossTriggerName,
stopLossLevel,
trailingStopLossLambdaData.dynamicParams?.executionPrice,
trailingStopLossLambdaData.trailingStopLossTriggerName,
trailingStopLossLambdaData.trailingStopLossData,
])

const trailingStopLossnotifications = useMemo<DetailsSectionNotificationItem[]>(() => {
if (trailingStopLossLambdaData.trailingStopLossTriggerName) {
if (trailingStopLossLambdaData.trailingStopLossData) {
const { executionPrice, originalExecutionPrice } = trailingStopLossLambdaData.dynamicParams
if (executionPrice.gt(originalExecutionPrice)) {
return [
Expand All @@ -344,9 +349,9 @@ export function AaveMultiplyPositionData({
}
return []
}, [
currentPosition.debt.symbol,
priceFormat,
trailingStopLossLambdaData.dynamicParams,
trailingStopLossLambdaData.trailingStopLossTriggerName,
trailingStopLossLambdaData.trailingStopLossData,
])

return (
Expand Down
8 changes: 4 additions & 4 deletions features/aave/hooks/useOptimizationSidebarDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export function useOptimizationSidebarDropdown(
): SidebarSectionHeaderDropdown {
const { t } = useTranslation()

const hasAaveAutoBuyEnabled = state.context.currentTriggers.triggers.aaveBasicBuy !== undefined
const hasSparkAutoBuyEnabled = state.context.currentTriggers.triggers.sparkBasicBuy !== undefined
const hasAaveAutoBuyEnabled = state.context.currentTriggers.triggers.aave3.basicBuy !== undefined
const hasSparkAutoBuyEnabled = state.context.currentTriggers.triggers.spark.basicBuy !== undefined

const hasAavePartialTakeProfitEnabled =
state.context.currentTriggers.triggers.aavePartialTakeProfit !== undefined
state.context.currentTriggers.triggers.aave3.partialTakeProfit !== undefined
const hasSparkPartialTakeProfitEnabled =
state.context.currentTriggers.triggers.sparkPartialTakeProfit !== undefined
state.context.currentTriggers.triggers.spark.partialTakeProfit !== undefined

const currentPanel = useMemo(() => {
return {
Expand Down
21 changes: 12 additions & 9 deletions features/aave/manage/containers/OptimizationControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ export function OptimizationControl({
)

// maps the lambda data
const aaveLikePartialTakeProfitLambdaData = mapPartialTakeProfitFromLambda(
state.context.strategyConfig,
triggersState.context.currentTriggers.triggers,
)
const aaveLikePartialTakeProfitLambdaData = mapPartialTakeProfitFromLambda({
protocol: state.context.strategyConfig.protocol,
strategyConfig: state.context.strategyConfig,
triggers: triggersState.context.currentTriggers.triggers,
})
// calculates the partial take profit params + stores user inputs
const aaveLikePartialTakeProfitParams = getAaveLikePartialTakeProfitParams.manage({
state,
Expand All @@ -120,11 +121,13 @@ export function OptimizationControl({
const dropdown = useOptimizationSidebarDropdown(triggersState, sendTriggerEvent)

const {
isAaveBasicBuyEnabled,
isSparkBasicBuyEnabled,
isAavePartialTakeProfitEnabled,
isSparkPartialTakeProfitEnabled,
} = triggersState.context.currentTriggers.flags
isBasicBuyEnabled: isAaveBasicBuyEnabled,
isPartialTakeProfitEnabled: isAavePartialTakeProfitEnabled,
} = triggersState.context.currentTriggers.flags.aave3
const {
isBasicBuyEnabled: isSparkBasicBuyEnabled,
isPartialTakeProfitEnabled: isSparkPartialTakeProfitEnabled,
} = triggersState.context.currentTriggers.flags.spark

const autoBuyEnabled = isAaveBasicBuyEnabled || isSparkBasicBuyEnabled
const partialTakeProfitEnabled = isAavePartialTakeProfitEnabled || isSparkPartialTakeProfitEnabled
Expand Down
12 changes: 8 additions & 4 deletions features/aave/manage/containers/ProtectionControlWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ export function ProtectionControlWrapper({
const [state, send] = useActor(stateMachine)

const [autoSellState, sendAutoSellEvent] = useActor(triggersState.context.autoSellTrigger)
const stopLossLambdaData = mapStopLossFromLambda(triggersState.context.currentTriggers.triggers)
const trailingStopLossLambdaData = mapTrailingStopLossFromLambda(
triggersState.context.currentTriggers.triggers,
)
const stopLossLambdaData = mapStopLossFromLambda({
protocol: state.context.strategyConfig.protocol,
triggers: triggersState.context.currentTriggers.triggers,
})
const trailingStopLossLambdaData = mapTrailingStopLossFromLambda({
protocol: state.context.strategyConfig.protocol,
triggers: triggersState.context.currentTriggers.triggers,
})
const [stopLossToken, setStopLossToken] = useState<'debt' | 'collateral'>(
stopLossLambdaData.stopLossToken ?? 'debt',
)
Expand Down
150 changes: 77 additions & 73 deletions features/aave/manage/helpers/map-partial-take-profit-from-lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,83 +11,87 @@ import { StrategyType } from 'features/aave/types'
import { formatPercent } from 'helpers/formatters/format'
import type { GetTriggersResponse } from 'helpers/lambda/triggers'
import { nbsp } from 'helpers/nbsp'
import { useMemo } from 'react'
import { LendingProtocol } from 'lendingProtocols'

export const mapPartialTakeProfitFromLambda = (
strategyConfig: IStrategyConfig,
triggers?: GetTriggersResponse['triggers'],
) => {
if (!triggers) {
return {}
}
const isShort = strategyConfig.strategyType === StrategyType.Short
interface MapPartialTakeProfitFromLambdaParams {
poolId?: string
protocol: LendingProtocol
strategyConfig: IStrategyConfig
triggers?: GetTriggersResponse['triggers']
}

const partialTakeProfitTriggersNames = Object.keys(triggers).filter((triggerName) =>
triggerName.includes('PartialTakeProfit'),
)
if (partialTakeProfitTriggersNames.length > 1) {
console.warn(
'Warning: more than one partial take profit trigger found:',
partialTakeProfitTriggersNames,
)
const getTrigger = ({ protocol, triggers, poolId }: MapPartialTakeProfitFromLambdaParams) => {
if (!triggers) return undefined

switch (protocol) {
case LendingProtocol.AaveV3: {
return triggers.aave3.partialTakeProfit
}
case LendingProtocol.MorphoBlue: {
if (`morphoblue-${poolId}` in triggers)
return triggers[`morphoblue-${poolId}`].partialTakeProfit
else return undefined
}
case LendingProtocol.SparkV3: {
return triggers.spark.partialTakeProfit
}
default:
return undefined
}
const partialTakeProfitTriggerName = partialTakeProfitTriggersNames[0] as
| 'sparkPartialTakeProfit'
| 'aavePartialTakeProfit'
const selectedTrigger = triggers[partialTakeProfitTriggerName]
}

const hasStopLoss = hasActiveStopLossFromTriggers({ triggers, protocol: strategyConfig.protocol })
const hasTrailingStopLoss = hasActiveTrailingStopLossFromTriggers({
triggers,
protocol: strategyConfig.protocol,
})
const currentStopLossLevel = useMemo(() => {
return mapStopLossFromLambda(triggers).stopLossLevel
}, [triggers])
const trailingStopLossData = mapTrailingStopLossFromLambda(triggers)
const stopLossTokenLabel = isShort
? `${strategyConfig.tokens.debt}/${strategyConfig.tokens.collateral}`
: `${strategyConfig.tokens.collateral}/${strategyConfig.tokens.debt}`
export const mapPartialTakeProfitFromLambda = ({
poolId,
protocol,
strategyConfig,
triggers,
}: MapPartialTakeProfitFromLambdaParams) => {
if (!triggers) return {}

const triggerLtv = selectedTrigger?.decodedParams.executionLtv
? new BigNumber(Number(selectedTrigger.decodedParams.executionLtv)).div(
lambdaPercentageDenomination,
)
: undefined
const startingTakeProfitPriceLong = selectedTrigger?.decodedParams.executionPrice
? new BigNumber(Number(selectedTrigger.decodedParams.executionPrice)).div(
lambdaPriceDenomination,
)
: undefined
const startingTakeProfitPriceShort = selectedTrigger?.decodedParams.executionPrice
? new BigNumber(lambdaPriceDenomination).div(
new BigNumber(Number(selectedTrigger.decodedParams.executionPrice)),
)
: undefined
const withdrawalLtv =
selectedTrigger?.decodedParams.targetLtv && selectedTrigger?.decodedParams.executionLtv
? new BigNumber(Number(selectedTrigger.decodedParams.targetLtv))
.minus(new BigNumber(Number(selectedTrigger?.decodedParams.executionLtv)))
.div(lambdaPercentageDenomination)
: undefined
const partialTakeProfitToken =
selectedTrigger?.decodedParams.withdrawToDebt === 'true'
? ('debt' as const)
: ('collateral' as const)
const trigger = getTrigger({ poolId, protocol, strategyConfig, triggers })

return {
triggerId: selectedTrigger?.triggerId,
triggerLtv,
startingTakeProfitPrice: isShort ? startingTakeProfitPriceShort : startingTakeProfitPriceLong,
withdrawalLtv,
partialTakeProfitToken,
hasStopLoss: hasStopLoss || hasTrailingStopLoss,
currentStopLossLevel,
currentTrailingDistance: trailingStopLossData.trailingDistance,
stopLossLevelLabel:
hasStopLoss && currentStopLossLevel ? `${formatPercent(currentStopLossLevel)}` : '',
trailingStopLossDistanceLabel: hasTrailingStopLoss
? `${trailingStopLossData.trailingDistance}${nbsp}${stopLossTokenLabel}`
: '',
}
if (trigger) {
const isShort = strategyConfig.strategyType === StrategyType.Short
const hasStopLoss = hasActiveStopLossFromTriggers({ triggers, protocol })
const hasTrailingStopLoss = hasActiveTrailingStopLossFromTriggers({ triggers, protocol })

const currentStopLossLevel = mapStopLossFromLambda({ poolId, protocol, triggers }).stopLossLevel
const trailingStopLossData = mapTrailingStopLossFromLambda({ poolId, protocol, triggers })

const stopLossTokenLabel = isShort
? `${strategyConfig.tokens.debt}/${strategyConfig.tokens.collateral}`
: `${strategyConfig.tokens.collateral}/${strategyConfig.tokens.debt}`

const triggerLtv = new BigNumber(Number(trigger.decodedParams.executionLtv)).div(
lambdaPercentageDenomination,
)
const startingTakeProfitPriceLong = new BigNumber(
Number(trigger.decodedParams.executionPrice),
).div(lambdaPriceDenomination)
const startingTakeProfitPriceShort = new BigNumber(lambdaPriceDenomination).div(
new BigNumber(Number(trigger.decodedParams.executionPrice)),
)
const withdrawalLtv = new BigNumber(Number(trigger.decodedParams.targetLtv))
.minus(new BigNumber(Number(trigger.decodedParams.executionLtv)))
.div(lambdaPercentageDenomination)

const partialTakeProfitToken =
trigger.decodedParams.withdrawToDebt === 'true' ? ('debt' as const) : ('collateral' as const)

return {
triggerId: trigger.triggerId,
triggerLtv,
startingTakeProfitPrice: isShort ? startingTakeProfitPriceShort : startingTakeProfitPriceLong,
withdrawalLtv,
partialTakeProfitToken,
hasStopLoss: hasStopLoss || hasTrailingStopLoss,
currentStopLossLevel,
currentTrailingDistance: trailingStopLossData.trailingDistance,
stopLossLevelLabel:
hasStopLoss && currentStopLossLevel ? `${formatPercent(currentStopLossLevel)}` : '',
trailingStopLossDistanceLabel: hasTrailingStopLoss
? `${trailingStopLossData.trailingDistance}${nbsp}${stopLossTokenLabel}`
: '',
}
} else return {}
}
Loading

0 comments on commit 3dfa2d9

Please sign in to comment.