Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: handle new morpho token and wrapper #4050

Merged
merged 4 commits into from
Nov 12, 2024
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
28 changes: 28 additions & 0 deletions blockchain/abi/erc20-proxy-actions.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,34 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "oldToken",
"type": "address"
},
{
"internalType": "address",
"name": "newToken",
"type": "address"
},
{
"internalType": "address",
"name": "wrapper",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approveAndWrap",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
Comment on lines +25 to +52
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Wrapper contract integration not found

No implementation of the wrapper contract was detected in the codebase. Please ensure that the wrapper contract is properly implemented and integrated.

🔗 Analysis chain

Implementation looks good with security considerations.

The approveAndWrap function ABI is well-structured for the MORPHO token migration process. The parameter ordering and types are appropriate for the intended functionality.

Let's verify the wrapper contract integration:

Security Considerations:

  1. Ensure the wrapper contract is audited and properly verified on Etherscan
  2. Verify that the wrapper contract follows the check-effects-interactions pattern
  3. Consider adding explicit approval checks before wrapping
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the wrapper contract implementation and integration
# Expected: Find wrapper contract implementation and any direct interactions with it

# Search for wrapper contract implementation
ast-grep --pattern 'contract $_ is $_* {
  $$$
  function wrap($$$) {
    $$$
  }
  $$$
}'

# Search for direct wrapper contract interactions
rg -A 5 'wrapper.*wrap'

Length of output: 475

{
"inputs": [
{
Expand Down
65 changes: 65 additions & 0 deletions blockchain/better-calls/erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,68 @@ export async function encodeTransferToOwnerProxyAction({
value: '0',
}
}

/**
* Encodes a transaction to approve and wrap an ERC20 token using OpenZeppelin's ERC20Wrapper pattern.
* This function prepares a transaction that will:
* 1. Approve the wrapper contract to spend the old token
* 2. Deposit the old token into the wrapper contract ( from proxy)
* 3.Send the new wrapped token to the owner
*
* The wrapper contract must implement the IERC20Wrapper interface which includes:
* - depositFor(address account, uint256 value)
* - withdrawTo(address account, uint256 value)
*
* @param {object} params - The parameters object
* @param {NetworkIds} params.networkId - The network ID where the transaction will be executed
* @param {string} params.oldToken - The symbol of the token to be wrapped (underlying token)
* @param {string} params.newToken - The symbol of the wrapped token to receive
* @param {string} params.wrapper - The address of the ERC20Wrapper contract
* @param {BigNumber} params.amount - The amount of tokens to wrap
* @returns {Promise<OmniTxData>} The encoded transaction data ready to be executed
* @throws Will throw if the contracts or tokens don't exist in the network configuration
* @throws Will throw if the token addresses cannot be resolved
*/
export async function encodeApproveAndWrapProxyAction({
networkId,
oldToken,
newToken,
wrapper,
amount,
}: {
networkId: NetworkIds
oldToken: string
newToken: string
wrapper: string
amount: BigNumber
}): Promise<OmniTxData> {
const contracts = getNetworkContracts(networkId)

ensureContractsExist(networkId, contracts, ['erc20ProxyActions'])
ensureGivenTokensExist(networkId, contracts, [oldToken, newToken])

Comment on lines +225 to +242
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add essential input validation.

The function should validate the wrapper address format and verify that required contracts exist.

 export async function encodeApproveAndWrapProxyAction({
   networkId,
   oldToken,
   newToken,
   wrapper,
   amount,
 }: {
   networkId: NetworkIds
   oldToken: string
   newToken: string
   wrapper: string
   amount: BigNumber
 }): Promise<OmniTxData> {
+  if (!ethers.utils.isAddress(wrapper)) {
+    throw new Error('Invalid wrapper address format')
+  }
+
   const contracts = getNetworkContracts(networkId)
 
   ensureContractsExist(networkId, contracts, ['erc20ProxyActions'])
   ensureGivenTokensExist(networkId, contracts, [oldToken, newToken])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function encodeApproveAndWrapProxyAction({
networkId,
oldToken,
newToken,
wrapper,
amount,
}: {
networkId: NetworkIds
oldToken: string
newToken: string
wrapper: string
amount: BigNumber
}): Promise<OmniTxData> {
const contracts = getNetworkContracts(networkId)
ensureContractsExist(networkId, contracts, ['erc20ProxyActions'])
ensureGivenTokensExist(networkId, contracts, [oldToken, newToken])
export async function encodeApproveAndWrapProxyAction({
networkId,
oldToken,
newToken,
wrapper,
amount,
}: {
networkId: NetworkIds
oldToken: string
newToken: string
wrapper: string
amount: BigNumber
}): Promise<OmniTxData> {
if (!ethers.utils.isAddress(wrapper)) {
throw new Error('Invalid wrapper address format')
}
const contracts = getNetworkContracts(networkId)
ensureContractsExist(networkId, contracts, ['erc20ProxyActions'])
ensureGivenTokensExist(networkId, contracts, [oldToken, newToken])

const { erc20ProxyActions, tokens } = contracts

const oldTokenAddress = tokens[oldToken].address
const newTokenAddress = tokens[newToken].address

const proxyActionContract = Erc20ProxyActions__factory.connect(
erc20ProxyActions.address,
getRpcProvider(networkId),
)

const amountInWei = amountToWei(amount, oldToken).toFixed()

const encodeFunctionData = proxyActionContract.interface.encodeFunctionData('approveAndWrap', [
oldTokenAddress,
newTokenAddress,
wrapper,
ethers.BigNumber.from(amountInWei),
])

return {
to: erc20ProxyActions.address,
data: encodeFunctionData,
value: '0',
}
}
9 changes: 9 additions & 0 deletions blockchain/token-metadata-list/token-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,15 @@ export const tokenConfigs: TokenConfig[] = [
iconCircle: morpho_circle_color,
tags: [],
},
{
symbol: 'MORPHO_LEGACY',
precision: 18,
digits: 5,
name: 'Legacy Morpho Blue',
icon: morpho_circle_color,
iconCircle: morpho_circle_color,
tags: [],
},
{
symbol: 'RBN',
precision: 18,
Expand Down
1 change: 1 addition & 0 deletions blockchain/tokens/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const tokensMainnet = {
LUSD: contractDesc(erc20, mainnet.common.LUSD),
MKR: contractDesc(erc20, mainnet.maker.common.McdGov),
MORPHO: contractDesc(erc20, mainnet.common.MORPHO),
MORPHO_LEGACY: contractDesc(erc20, mainnet.common.MORPHO_LEGACY),
RENBTC: contractDesc(erc20, mainnet.common.RENBTC),
RETH: contractDesc(erc20, mainnet.common.RETH),
RSETH: contractDesc(erc20, mainnet.common.RSETH),
Expand Down
1 change: 1 addition & 0 deletions features/notices/VaultsNoticesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export function VaultLiquidatedNotice({
})}
</Text>
<ReclaimCollateralButton {...{ token, id, amount: unlockedCollateral }} />
{console.debug('ReclaimCollateralButton props:', { token, id, amount: unlockedCollateral })}
</>
) : (
fallbackSubheader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Network } from '@oasisdex/dma-library'
import { networkIdToLibraryNetwork } from 'actions/aave-like/helpers'
import type BigNumber from 'bignumber.js'
import { encodeClaimAllRewards, getAllUserRewards } from 'blockchain/better-calls/aave-like-rewards'
import { encodeTransferToOwnerProxyAction, tokenBalance } from 'blockchain/better-calls/erc20'
import {
encodeApproveAndWrapProxyAction,
encodeTransferToOwnerProxyAction,
tokenBalance,
} from 'blockchain/better-calls/erc20'
import { NetworkIds } from 'blockchain/networks'
import { tokenPriceStore } from 'blockchain/prices.constants'
import { getTokenByAddress } from 'blockchain/tokensMetadata'
Expand All @@ -17,7 +21,13 @@ import React, { useEffect, useReducer } from 'react'
import { OmniDetailsSectionContentRewardsLoadingState } from './OmniDetailsSectionContentRewardsLoadingState'
import { OmniRewardsClaims } from './OmniRewardsClaims'

const claimableErc20: Record<NetworkIds, string[]> = {
interface OmniDetailSectionRewardsClaimsInternalProps {
isEligibleForErc20Claims: boolean
isEligibleForProtocolRewards: boolean
isEligibleForMorphoLegacy: boolean
}

const claimableErc20ByNetwork: Record<NetworkIds, string[]> = {
[NetworkIds.MAINNET]: ['ENA', 'SENA'],
[NetworkIds.OPTIMISMMAINNET]: [],
[NetworkIds.ARBITRUMMAINNET]: [],
Expand All @@ -32,13 +42,21 @@ const claimableErc20: Record<NetworkIds, string[]> = {
[NetworkIds.OPTIMISMGOERLI]: [],
}

const morphoLegacyByNetwork: Partial<Record<NetworkIds, string>> = {
[NetworkIds.MAINNET]: 'MORPHO_LEGACY',
}

type Claim = {
token: string
claimable: BigNumber
tx: OmniTxData
}

const OmniDetailSectionRewardsClaimsInternal: FC = () => {
const OmniDetailSectionRewardsClaimsInternal: FC<OmniDetailSectionRewardsClaimsInternalProps> = ({
isEligibleForErc20Claims,
isEligibleForProtocolRewards,
isEligibleForMorphoLegacy,
}) => {
const {
environment: { dpmProxy, networkId, protocol, quoteAddress },
} = useOmniGeneralContext()
Expand All @@ -48,9 +66,9 @@ const OmniDetailSectionRewardsClaimsInternal: FC = () => {
}, [])

useEffect(() => {
if (dpmProxy) {
// Existing ERC20 claims logic
claimableErc20[networkId].forEach((token) => {
if (!dpmProxy) return
if (isEligibleForErc20Claims) {
claimableErc20ByNetwork[networkId].forEach((token) => {
tokenBalance({ token, account: dpmProxy, networkId: networkId })
.then((balance) => {
if (balance.gt(zero)) {
Expand All @@ -72,64 +90,93 @@ const OmniDetailSectionRewardsClaimsInternal: FC = () => {
console.error(`Error fetching token balance for ${token}: ${error}`)
})
})

// New Aave and Spark rewards check
if ([LendingProtocol.AaveV3, LendingProtocol.SparkV3].includes(protocol)) {
let rewardsControllerAddress: string | undefined
let poolDataProviderAddress: string | undefined
}
if (isEligibleForMorphoLegacy) {
const morphoLegacyToken = morphoLegacyByNetwork[networkId]
if (morphoLegacyToken) {
const network = networkIdToLibraryNetwork(networkId)
if (
protocol === LendingProtocol.AaveV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
} else if (
protocol === LendingProtocol.SparkV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
} else {
console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
if (network === Network.MAINNET) {
tokenBalance({ token: morphoLegacyToken, account: dpmProxy, networkId })
.then((balance) => {
if (balance.gt(zero)) {
encodeApproveAndWrapProxyAction({
oldToken: morphoLegacyToken,
newToken: 'MORPHO',
wrapper: ADDRESSES[network].morphoblue.Wrapper,
amount: balance,
networkId,
})
.then((tx) => {
dispatchClaim({ token: 'MORPHO', claimable: balance, tx })
})
.catch((error) => {
console.error(
`Error encoding approve and wrap action for MORPHO_LEGACY: ${error}`,
)
})
}
})
.catch((error) => {
console.error(`Error fetching MORPHO_LEGACY balance: ${error}`)
})
}
}
}
Comment on lines +94 to +124
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and type safety in Morpho legacy token handling

The Morpho legacy token handling could be improved with better type safety and error handling.

Consider this implementation:

 if (isEligibleForMorphoLegacy) {
   const morphoLegacyToken = morphoLegacyByNetwork[networkId]
-  if (morphoLegacyToken) {
+  if (!morphoLegacyToken) {
+    logger.warn(`No Morpho legacy token configured for network ${networkId}`)
+    return
+  }
   const network = networkIdToLibraryNetwork(networkId)
-  if (network === Network.MAINNET) {
-    tokenBalance({ token: morphoLegacyToken, account: dpmProxy, networkId })
-      .then((balance) => {
-        if (balance.gt(zero)) {
-          encodeApproveAndWrapProxyAction({
-            oldToken: morphoLegacyToken,
-            newToken: 'MORPHO',
-            wrapper: ADDRESSES[network].morphoblue.Wrapper,
-            amount: balance,
-            networkId,
-          })
-            .then((tx) => {
-              dispatchClaim({ token: 'MORPHO', claimable: balance, tx })
-            })
-            .catch((error) => {
-              console.error(
-                `Error encoding approve and wrap action for MORPHO_LEGACY: ${error}`,
-              )
-            })
-        }
-      })
-      .catch((error) => {
-        console.error(`Error fetching MORPHO_LEGACY balance: ${error}`)
-      })
+  if (network !== Network.MAINNET) {
+    logger.warn(`Morpho legacy token wrapping is only supported on mainnet`)
+    return
   }
+  try {
+    const balance = await tokenBalance({ token: morphoLegacyToken, account: dpmProxy, networkId })
+    if (balance.gt(zero)) {
+      try {
+        const tx = await encodeApproveAndWrapProxyAction({
+          oldToken: morphoLegacyToken,
+          newToken: 'MORPHO',
+          wrapper: ADDRESSES[network].morphoblue.Wrapper,
+          amount: balance,
+          networkId,
+        })
+        dispatchClaim({ token: 'MORPHO', claimable: balance, tx })
+      } catch (error) {
+        logger.error(`Error encoding approve and wrap action for MORPHO_LEGACY:`, error)
+      }
+    }
+  } catch (error) {
+    logger.error(`Error fetching MORPHO_LEGACY balance:`, error)
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isEligibleForMorphoLegacy) {
const morphoLegacyToken = morphoLegacyByNetwork[networkId]
if (morphoLegacyToken) {
const network = networkIdToLibraryNetwork(networkId)
if (
protocol === LendingProtocol.AaveV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
} else if (
protocol === LendingProtocol.SparkV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
} else {
console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
if (network === Network.MAINNET) {
tokenBalance({ token: morphoLegacyToken, account: dpmProxy, networkId })
.then((balance) => {
if (balance.gt(zero)) {
encodeApproveAndWrapProxyAction({
oldToken: morphoLegacyToken,
newToken: 'MORPHO',
wrapper: ADDRESSES[network].morphoblue.Wrapper,
amount: balance,
networkId,
})
.then((tx) => {
dispatchClaim({ token: 'MORPHO', claimable: balance, tx })
})
.catch((error) => {
console.error(
`Error encoding approve and wrap action for MORPHO_LEGACY: ${error}`,
)
})
}
})
.catch((error) => {
console.error(`Error fetching MORPHO_LEGACY balance: ${error}`)
})
}
}
}
if (isEligibleForMorphoLegacy) {
const morphoLegacyToken = morphoLegacyByNetwork[networkId]
if (!morphoLegacyToken) {
logger.warn(`No Morpho legacy token configured for network ${networkId}`)
return
}
const network = networkIdToLibraryNetwork(networkId)
if (network !== Network.MAINNET) {
logger.warn(`Morpho legacy token wrapping is only supported on mainnet`)
return
}
try {
const balance = await tokenBalance({ token: morphoLegacyToken, account: dpmProxy, networkId })
if (balance.gt(zero)) {
try {
const tx = await encodeApproveAndWrapProxyAction({
oldToken: morphoLegacyToken,
newToken: 'MORPHO',
wrapper: ADDRESSES[network].morphoblue.Wrapper,
amount: balance,
networkId,
})
dispatchClaim({ token: 'MORPHO', claimable: balance, tx })
} catch (error) {
logger.error(`Error encoding approve and wrap action for MORPHO_LEGACY:`, error)
}
}
} catch (error) {
logger.error(`Error fetching MORPHO_LEGACY balance:`, error)
}
}

if (isEligibleForProtocolRewards) {
let rewardsControllerAddress: string | undefined
let poolDataProviderAddress: string | undefined
const network = networkIdToLibraryNetwork(networkId)
if (
protocol === LendingProtocol.AaveV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
} else if (
protocol === LendingProtocol.SparkV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
} else {
console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
}

getAllUserRewards({
networkId,
token: quoteAddress,
account: dpmProxy,
rewardsController: rewardsControllerAddress as string,
poolDataProvider: poolDataProviderAddress as string,
})
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
const tx = encodeClaimAllRewards({
networkId,
assets: assets as string[],
dpmAccount: dpmProxy,
rewardsController: rewardsControllerAddress as string,
})
getAllUserRewards({
networkId,
token: quoteAddress,
account: dpmProxy,
rewardsController: rewardsControllerAddress as string,
poolDataProvider: poolDataProviderAddress as string,
})
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
const tx = encodeClaimAllRewards({
networkId,
assets: assets as string[],
dpmAccount: dpmProxy,
rewardsController: rewardsControllerAddress as string,
})

rewardsList.forEach((token, index) => {
if (unclaimedAmounts[index].gt(zero)) {
dispatchClaim({
token: getTokenByAddress(token, networkId).symbol,
claimable: unclaimedAmounts[index],
tx,
})
}
})
}
})
.catch((error) => {
console.error(`Error fetching ${protocol} rewards:`, error)
})
}
rewardsList.forEach((token, index) => {
if (unclaimedAmounts[index].gt(zero)) {
dispatchClaim({
token: getTokenByAddress(token, networkId).symbol,
claimable: unclaimedAmounts[index],
tx,
})
}
})
}
})
.catch((error) => {
console.error(`Error fetching ${protocol} rewards:`, error)
})
Comment on lines +125 to +179
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve protocol rewards handling with early validation and proper error handling

The protocol rewards handling could be improved with early validation and proper error handling.

Consider this implementation:

 if (isEligibleForProtocolRewards) {
+  const network = networkIdToLibraryNetwork(networkId)
+  if ([Network.HARDHAT, Network.LOCAL, Network.TENDERLY].includes(network)) {
+    logger.warn(`Protocol rewards not supported on network: ${network}`)
+    return
+  }
+
   let rewardsControllerAddress: string | undefined
   let poolDataProviderAddress: string | undefined
-  const network = networkIdToLibraryNetwork(networkId)
-  if (
-    protocol === LendingProtocol.AaveV3 &&
-    network !== Network.HARDHAT &&
-    network !== Network.LOCAL &&
-    network !== Network.TENDERLY
-  ) {
-    rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
-    poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
-  } else if (
-    protocol === LendingProtocol.SparkV3 &&
-    network !== Network.HARDHAT &&
-    network !== Network.LOCAL &&
-    network !== Network.TENDERLY
-  ) {
-    rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
-    poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
-  } else {
-    console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
+
+  switch (protocol) {
+    case LendingProtocol.AaveV3:
+      rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
+      poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
+      break
+    case LendingProtocol.SparkV3:
+      rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
+      poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
+      break
+    default:
+      logger.warn(`Unsupported protocol for rewards: ${protocol}`)
+      return
   }
+
+  if (!rewardsControllerAddress || !poolDataProviderAddress) {
+    logger.error(`Missing contract addresses for protocol ${protocol} on network ${network}`)
     throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isEligibleForProtocolRewards) {
let rewardsControllerAddress: string | undefined
let poolDataProviderAddress: string | undefined
const network = networkIdToLibraryNetwork(networkId)
if (
protocol === LendingProtocol.AaveV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
} else if (
protocol === LendingProtocol.SparkV3 &&
network !== Network.HARDHAT &&
network !== Network.LOCAL &&
network !== Network.TENDERLY
) {
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
} else {
console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
}
getAllUserRewards({
networkId,
token: quoteAddress,
account: dpmProxy,
rewardsController: rewardsControllerAddress as string,
poolDataProvider: poolDataProviderAddress as string,
})
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
const tx = encodeClaimAllRewards({
networkId,
assets: assets as string[],
dpmAccount: dpmProxy,
rewardsController: rewardsControllerAddress as string,
})
getAllUserRewards({
networkId,
token: quoteAddress,
account: dpmProxy,
rewardsController: rewardsControllerAddress as string,
poolDataProvider: poolDataProviderAddress as string,
})
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
const tx = encodeClaimAllRewards({
networkId,
assets: assets as string[],
dpmAccount: dpmProxy,
rewardsController: rewardsControllerAddress as string,
})
rewardsList.forEach((token, index) => {
if (unclaimedAmounts[index].gt(zero)) {
dispatchClaim({
token: getTokenByAddress(token, networkId).symbol,
claimable: unclaimedAmounts[index],
tx,
})
}
})
}
})
.catch((error) => {
console.error(`Error fetching ${protocol} rewards:`, error)
})
}
rewardsList.forEach((token, index) => {
if (unclaimedAmounts[index].gt(zero)) {
dispatchClaim({
token: getTokenByAddress(token, networkId).symbol,
claimable: unclaimedAmounts[index],
tx,
})
}
})
}
})
.catch((error) => {
console.error(`Error fetching ${protocol} rewards:`, error)
})
if (isEligibleForProtocolRewards) {
const network = networkIdToLibraryNetwork(networkId)
if ([Network.HARDHAT, Network.LOCAL, Network.TENDERLY].includes(network)) {
logger.warn(`Protocol rewards not supported on network: ${network}`)
return
}
let rewardsControllerAddress: string | undefined
let poolDataProviderAddress: string | undefined
switch (protocol) {
case LendingProtocol.AaveV3:
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
break
case LendingProtocol.SparkV3:
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
break
default:
logger.warn(`Unsupported protocol for rewards: ${protocol}`)
return
}
if (!rewardsControllerAddress || !poolDataProviderAddress) {
logger.error(`Missing contract addresses for protocol ${protocol} on network ${network}`)
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
}
getAllUserRewards({
networkId,
token: quoteAddress,
account: dpmProxy,
rewardsController: rewardsControllerAddress as string,
poolDataProvider: poolDataProviderAddress as string,
})
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
const tx = encodeClaimAllRewards({
networkId,
assets: assets as string[],
dpmAccount: dpmProxy,
rewardsController: rewardsControllerAddress as string,
})
rewardsList.forEach((token, index) => {
if (unclaimedAmounts[index].gt(zero)) {
dispatchClaim({
token: getTokenByAddress(token, networkId).symbol,
claimable: unclaimedAmounts[index],
tx,
})
}
})
}
})
.catch((error) => {
console.error(`Error fetching ${protocol} rewards:`, error)
})

}
}, [dpmProxy, networkId, protocol, quoteAddress])

Expand All @@ -152,19 +199,33 @@ const OmniDetailSectionRewardsClaimsInternal: FC = () => {

export const OmniDetailSectionRewardsClaims: FC = () => {
const {
environment: { protocol, collateralToken, quoteToken },
environment: { protocol, collateralToken, quoteToken, networkId },
} = useOmniGeneralContext()

const eligibleTokens = ['SUSDE', 'USDE', 'WETH', 'ETH']
const rewardsEligibleTokens = ['SUSDE', 'USDE', 'WETH', 'ETH']

// Regular ERC20 claims eligibility
const isEligibleForErc20Claims = claimableErc20ByNetwork[networkId].length > 0

// Aave/Spark rewards eligibility
const isEligibleForProtocolRewards =
[LendingProtocol.AaveV3, LendingProtocol.SparkV3].includes(protocol) &&
(rewardsEligibleTokens.includes(collateralToken) || rewardsEligibleTokens.includes(quoteToken))

const isEligible =
[
LendingProtocol.MorphoBlue,
LendingProtocol.Ajna,
LendingProtocol.AaveV3,
LendingProtocol.SparkV3,
].includes(protocol) &&
(eligibleTokens.includes(collateralToken) || eligibleTokens.includes(quoteToken))
// Legacy Morpho claims eligibility
const isEligibleForMorphoLegacy =
networkId === NetworkIds.MAINNET && protocol === LendingProtocol.MorphoBlue

return isEligible ? <OmniDetailSectionRewardsClaimsInternal /> : <></>
const hasAnyEligibleClaims =
isEligibleForErc20Claims || isEligibleForProtocolRewards || isEligibleForMorphoLegacy

return hasAnyEligibleClaims ? (
<OmniDetailSectionRewardsClaimsInternal
isEligibleForErc20Claims={isEligibleForErc20Claims}
isEligibleForProtocolRewards={isEligibleForProtocolRewards}
isEligibleForMorphoLegacy={isEligibleForMorphoLegacy}
/>
) : (
<></>
)
}
Loading
Loading