Skip to content

Commit 784d16e

Browse files
authored
chore: handle new morpho token and wrapper (#4050)
* chore: handle new morpho token and wrapper * chore: remove console logs * chore: remove more logs
1 parent b86d5cc commit 784d16e

File tree

10 files changed

+252
-84
lines changed

10 files changed

+252
-84
lines changed

blockchain/abi/erc20-proxy-actions.json

+28
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,34 @@
2222
"stateMutability": "nonpayable",
2323
"type": "function"
2424
},
25+
{
26+
"inputs": [
27+
{
28+
"internalType": "address",
29+
"name": "oldToken",
30+
"type": "address"
31+
},
32+
{
33+
"internalType": "address",
34+
"name": "newToken",
35+
"type": "address"
36+
},
37+
{
38+
"internalType": "address",
39+
"name": "wrapper",
40+
"type": "address"
41+
},
42+
{
43+
"internalType": "uint256",
44+
"name": "value",
45+
"type": "uint256"
46+
}
47+
],
48+
"name": "approveAndWrap",
49+
"outputs": [],
50+
"stateMutability": "nonpayable",
51+
"type": "function"
52+
},
2553
{
2654
"inputs": [
2755
{

blockchain/better-calls/erc20.ts

+65
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,68 @@ export async function encodeTransferToOwnerProxyAction({
200200
value: '0',
201201
}
202202
}
203+
204+
/**
205+
* Encodes a transaction to approve and wrap an ERC20 token using OpenZeppelin's ERC20Wrapper pattern.
206+
* This function prepares a transaction that will:
207+
* 1. Approve the wrapper contract to spend the old token
208+
* 2. Deposit the old token into the wrapper contract ( from proxy)
209+
* 3.Send the new wrapped token to the owner
210+
*
211+
* The wrapper contract must implement the IERC20Wrapper interface which includes:
212+
* - depositFor(address account, uint256 value)
213+
* - withdrawTo(address account, uint256 value)
214+
*
215+
* @param {object} params - The parameters object
216+
* @param {NetworkIds} params.networkId - The network ID where the transaction will be executed
217+
* @param {string} params.oldToken - The symbol of the token to be wrapped (underlying token)
218+
* @param {string} params.newToken - The symbol of the wrapped token to receive
219+
* @param {string} params.wrapper - The address of the ERC20Wrapper contract
220+
* @param {BigNumber} params.amount - The amount of tokens to wrap
221+
* @returns {Promise<OmniTxData>} The encoded transaction data ready to be executed
222+
* @throws Will throw if the contracts or tokens don't exist in the network configuration
223+
* @throws Will throw if the token addresses cannot be resolved
224+
*/
225+
export async function encodeApproveAndWrapProxyAction({
226+
networkId,
227+
oldToken,
228+
newToken,
229+
wrapper,
230+
amount,
231+
}: {
232+
networkId: NetworkIds
233+
oldToken: string
234+
newToken: string
235+
wrapper: string
236+
amount: BigNumber
237+
}): Promise<OmniTxData> {
238+
const contracts = getNetworkContracts(networkId)
239+
240+
ensureContractsExist(networkId, contracts, ['erc20ProxyActions'])
241+
ensureGivenTokensExist(networkId, contracts, [oldToken, newToken])
242+
243+
const { erc20ProxyActions, tokens } = contracts
244+
245+
const oldTokenAddress = tokens[oldToken].address
246+
const newTokenAddress = tokens[newToken].address
247+
248+
const proxyActionContract = Erc20ProxyActions__factory.connect(
249+
erc20ProxyActions.address,
250+
getRpcProvider(networkId),
251+
)
252+
253+
const amountInWei = amountToWei(amount, oldToken).toFixed()
254+
255+
const encodeFunctionData = proxyActionContract.interface.encodeFunctionData('approveAndWrap', [
256+
oldTokenAddress,
257+
newTokenAddress,
258+
wrapper,
259+
ethers.BigNumber.from(amountInWei),
260+
])
261+
262+
return {
263+
to: erc20ProxyActions.address,
264+
data: encodeFunctionData,
265+
value: '0',
266+
}
267+
}

blockchain/token-metadata-list/token-configs.ts

+9
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,15 @@ export const tokenConfigs: TokenConfig[] = [
997997
iconCircle: morpho_circle_color,
998998
tags: [],
999999
},
1000+
{
1001+
symbol: 'MORPHO_LEGACY',
1002+
precision: 18,
1003+
digits: 5,
1004+
name: 'Legacy Morpho Blue',
1005+
icon: morpho_circle_color,
1006+
iconCircle: morpho_circle_color,
1007+
tags: [],
1008+
},
10001009
{
10011010
symbol: 'RBN',
10021011
precision: 18,

blockchain/tokens/mainnet.ts

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const tokensMainnet = {
7474
LUSD: contractDesc(erc20, mainnet.common.LUSD),
7575
MKR: contractDesc(erc20, mainnet.maker.common.McdGov),
7676
MORPHO: contractDesc(erc20, mainnet.common.MORPHO),
77+
MORPHO_LEGACY: contractDesc(erc20, mainnet.common.MORPHO_LEGACY),
7778
RENBTC: contractDesc(erc20, mainnet.common.RENBTC),
7879
RETH: contractDesc(erc20, mainnet.common.RETH),
7980
RSETH: contractDesc(erc20, mainnet.common.RSETH),

features/notices/VaultsNoticesView.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ export function VaultLiquidatedNotice({
325325
})}
326326
</Text>
327327
<ReclaimCollateralButton {...{ token, id, amount: unlockedCollateral }} />
328+
{console.debug('ReclaimCollateralButton props:', { token, id, amount: unlockedCollateral })}
328329
</>
329330
) : (
330331
fallbackSubheader

features/omni-kit/components/details-section/OmniDetailSectionRewardsClaims.tsx

+132-71
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { Network } from '@oasisdex/dma-library'
33
import { networkIdToLibraryNetwork } from 'actions/aave-like/helpers'
44
import type BigNumber from 'bignumber.js'
55
import { encodeClaimAllRewards, getAllUserRewards } from 'blockchain/better-calls/aave-like-rewards'
6-
import { encodeTransferToOwnerProxyAction, tokenBalance } from 'blockchain/better-calls/erc20'
6+
import {
7+
encodeApproveAndWrapProxyAction,
8+
encodeTransferToOwnerProxyAction,
9+
tokenBalance,
10+
} from 'blockchain/better-calls/erc20'
711
import { NetworkIds } from 'blockchain/networks'
812
import { tokenPriceStore } from 'blockchain/prices.constants'
913
import { getTokenByAddress } from 'blockchain/tokensMetadata'
@@ -17,7 +21,13 @@ import React, { useEffect, useReducer } from 'react'
1721
import { OmniDetailsSectionContentRewardsLoadingState } from './OmniDetailsSectionContentRewardsLoadingState'
1822
import { OmniRewardsClaims } from './OmniRewardsClaims'
1923

20-
const claimableErc20: Record<NetworkIds, string[]> = {
24+
interface OmniDetailSectionRewardsClaimsInternalProps {
25+
isEligibleForErc20Claims: boolean
26+
isEligibleForProtocolRewards: boolean
27+
isEligibleForMorphoLegacy: boolean
28+
}
29+
30+
const claimableErc20ByNetwork: Record<NetworkIds, string[]> = {
2131
[NetworkIds.MAINNET]: ['ENA', 'SENA'],
2232
[NetworkIds.OPTIMISMMAINNET]: [],
2333
[NetworkIds.ARBITRUMMAINNET]: [],
@@ -32,13 +42,21 @@ const claimableErc20: Record<NetworkIds, string[]> = {
3242
[NetworkIds.OPTIMISMGOERLI]: [],
3343
}
3444

45+
const morphoLegacyByNetwork: Partial<Record<NetworkIds, string>> = {
46+
[NetworkIds.MAINNET]: 'MORPHO_LEGACY',
47+
}
48+
3549
type Claim = {
3650
token: string
3751
claimable: BigNumber
3852
tx: OmniTxData
3953
}
4054

41-
const OmniDetailSectionRewardsClaimsInternal: FC = () => {
55+
const OmniDetailSectionRewardsClaimsInternal: FC<OmniDetailSectionRewardsClaimsInternalProps> = ({
56+
isEligibleForErc20Claims,
57+
isEligibleForProtocolRewards,
58+
isEligibleForMorphoLegacy,
59+
}) => {
4260
const {
4361
environment: { dpmProxy, networkId, protocol, quoteAddress },
4462
} = useOmniGeneralContext()
@@ -48,9 +66,9 @@ const OmniDetailSectionRewardsClaimsInternal: FC = () => {
4866
}, [])
4967

5068
useEffect(() => {
51-
if (dpmProxy) {
52-
// Existing ERC20 claims logic
53-
claimableErc20[networkId].forEach((token) => {
69+
if (!dpmProxy) return
70+
if (isEligibleForErc20Claims) {
71+
claimableErc20ByNetwork[networkId].forEach((token) => {
5472
tokenBalance({ token, account: dpmProxy, networkId: networkId })
5573
.then((balance) => {
5674
if (balance.gt(zero)) {
@@ -72,64 +90,93 @@ const OmniDetailSectionRewardsClaimsInternal: FC = () => {
7290
console.error(`Error fetching token balance for ${token}: ${error}`)
7391
})
7492
})
75-
76-
// New Aave and Spark rewards check
77-
if ([LendingProtocol.AaveV3, LendingProtocol.SparkV3].includes(protocol)) {
78-
let rewardsControllerAddress: string | undefined
79-
let poolDataProviderAddress: string | undefined
93+
}
94+
if (isEligibleForMorphoLegacy) {
95+
const morphoLegacyToken = morphoLegacyByNetwork[networkId]
96+
if (morphoLegacyToken) {
8097
const network = networkIdToLibraryNetwork(networkId)
81-
if (
82-
protocol === LendingProtocol.AaveV3 &&
83-
network !== Network.HARDHAT &&
84-
network !== Network.LOCAL &&
85-
network !== Network.TENDERLY
86-
) {
87-
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
88-
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
89-
} else if (
90-
protocol === LendingProtocol.SparkV3 &&
91-
network !== Network.HARDHAT &&
92-
network !== Network.LOCAL &&
93-
network !== Network.TENDERLY
94-
) {
95-
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
96-
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
97-
} else {
98-
console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
99-
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
98+
if (network === Network.MAINNET) {
99+
tokenBalance({ token: morphoLegacyToken, account: dpmProxy, networkId })
100+
.then((balance) => {
101+
if (balance.gt(zero)) {
102+
encodeApproveAndWrapProxyAction({
103+
oldToken: morphoLegacyToken,
104+
newToken: 'MORPHO',
105+
wrapper: ADDRESSES[network].morphoblue.Wrapper,
106+
amount: balance,
107+
networkId,
108+
})
109+
.then((tx) => {
110+
dispatchClaim({ token: 'MORPHO', claimable: balance, tx })
111+
})
112+
.catch((error) => {
113+
console.error(
114+
`Error encoding approve and wrap action for MORPHO_LEGACY: ${error}`,
115+
)
116+
})
117+
}
118+
})
119+
.catch((error) => {
120+
console.error(`Error fetching MORPHO_LEGACY balance: ${error}`)
121+
})
100122
}
123+
}
124+
}
125+
if (isEligibleForProtocolRewards) {
126+
let rewardsControllerAddress: string | undefined
127+
let poolDataProviderAddress: string | undefined
128+
const network = networkIdToLibraryNetwork(networkId)
129+
if (
130+
protocol === LendingProtocol.AaveV3 &&
131+
network !== Network.HARDHAT &&
132+
network !== Network.LOCAL &&
133+
network !== Network.TENDERLY
134+
) {
135+
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
136+
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
137+
} else if (
138+
protocol === LendingProtocol.SparkV3 &&
139+
network !== Network.HARDHAT &&
140+
network !== Network.LOCAL &&
141+
network !== Network.TENDERLY
142+
) {
143+
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
144+
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
145+
} else {
146+
console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
147+
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
148+
}
101149

102-
getAllUserRewards({
103-
networkId,
104-
token: quoteAddress,
105-
account: dpmProxy,
106-
rewardsController: rewardsControllerAddress as string,
107-
poolDataProvider: poolDataProviderAddress as string,
108-
})
109-
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
110-
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
111-
const tx = encodeClaimAllRewards({
112-
networkId,
113-
assets: assets as string[],
114-
dpmAccount: dpmProxy,
115-
rewardsController: rewardsControllerAddress as string,
116-
})
150+
getAllUserRewards({
151+
networkId,
152+
token: quoteAddress,
153+
account: dpmProxy,
154+
rewardsController: rewardsControllerAddress as string,
155+
poolDataProvider: poolDataProviderAddress as string,
156+
})
157+
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
158+
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
159+
const tx = encodeClaimAllRewards({
160+
networkId,
161+
assets: assets as string[],
162+
dpmAccount: dpmProxy,
163+
rewardsController: rewardsControllerAddress as string,
164+
})
117165

118-
rewardsList.forEach((token, index) => {
119-
if (unclaimedAmounts[index].gt(zero)) {
120-
dispatchClaim({
121-
token: getTokenByAddress(token, networkId).symbol,
122-
claimable: unclaimedAmounts[index],
123-
tx,
124-
})
125-
}
126-
})
127-
}
128-
})
129-
.catch((error) => {
130-
console.error(`Error fetching ${protocol} rewards:`, error)
131-
})
132-
}
166+
rewardsList.forEach((token, index) => {
167+
if (unclaimedAmounts[index].gt(zero)) {
168+
dispatchClaim({
169+
token: getTokenByAddress(token, networkId).symbol,
170+
claimable: unclaimedAmounts[index],
171+
tx,
172+
})
173+
}
174+
})
175+
}
176+
})
177+
.catch((error) => {
178+
console.error(`Error fetching ${protocol} rewards:`, error)
179+
})
133180
}
134181
}, [dpmProxy, networkId, protocol, quoteAddress])
135182

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

153200
export const OmniDetailSectionRewardsClaims: FC = () => {
154201
const {
155-
environment: { protocol, collateralToken, quoteToken },
202+
environment: { protocol, collateralToken, quoteToken, networkId },
156203
} = useOmniGeneralContext()
157204

158-
const eligibleTokens = ['SUSDE', 'USDE', 'WETH', 'ETH']
205+
const rewardsEligibleTokens = ['SUSDE', 'USDE', 'WETH', 'ETH']
206+
207+
// Regular ERC20 claims eligibility
208+
const isEligibleForErc20Claims = claimableErc20ByNetwork[networkId].length > 0
209+
210+
// Aave/Spark rewards eligibility
211+
const isEligibleForProtocolRewards =
212+
[LendingProtocol.AaveV3, LendingProtocol.SparkV3].includes(protocol) &&
213+
(rewardsEligibleTokens.includes(collateralToken) || rewardsEligibleTokens.includes(quoteToken))
159214

160-
const isEligible =
161-
[
162-
LendingProtocol.MorphoBlue,
163-
LendingProtocol.Ajna,
164-
LendingProtocol.AaveV3,
165-
LendingProtocol.SparkV3,
166-
].includes(protocol) &&
167-
(eligibleTokens.includes(collateralToken) || eligibleTokens.includes(quoteToken))
215+
// Legacy Morpho claims eligibility
216+
const isEligibleForMorphoLegacy =
217+
networkId === NetworkIds.MAINNET && protocol === LendingProtocol.MorphoBlue
168218

169-
return isEligible ? <OmniDetailSectionRewardsClaimsInternal /> : <></>
219+
const hasAnyEligibleClaims =
220+
isEligibleForErc20Claims || isEligibleForProtocolRewards || isEligibleForMorphoLegacy
221+
222+
return hasAnyEligibleClaims ? (
223+
<OmniDetailSectionRewardsClaimsInternal
224+
isEligibleForErc20Claims={isEligibleForErc20Claims}
225+
isEligibleForProtocolRewards={isEligibleForProtocolRewards}
226+
isEligibleForMorphoLegacy={isEligibleForMorphoLegacy}
227+
/>
228+
) : (
229+
<></>
230+
)
170231
}

0 commit comments

Comments
 (0)