Skip to content

Commit ab9f85a

Browse files
committed
chore: handle new morpho token and wrapper
1 parent 155f1dd commit ab9f85a

File tree

10 files changed

+261
-83
lines changed

10 files changed

+261
-83
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

+136-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,97 @@ 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+
console.log('morphoLegacyToken', morphoLegacyToken)
97+
if (morphoLegacyToken) {
98+
console.log('morphoLegacyToken', morphoLegacyToken)
8099
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}`)
100+
console.log('network', network)
101+
if (network === Network.MAINNET) {
102+
tokenBalance({ token: morphoLegacyToken, account: dpmProxy, networkId })
103+
.then((balance) => {
104+
console.log('balance mmm', balance)
105+
if (balance.gt(zero)) {
106+
encodeApproveAndWrapProxyAction({
107+
oldToken: morphoLegacyToken,
108+
newToken: 'MORPHO',
109+
wrapper: ADDRESSES[network].morphoblue.Wrapper,
110+
amount: balance,
111+
networkId,
112+
})
113+
.then((tx) => {
114+
dispatchClaim({ token: 'MORPHO', claimable: balance, tx })
115+
})
116+
.catch((error) => {
117+
console.error(
118+
`Error encoding approve and wrap action for MORPHO_LEGACY: ${error}`,
119+
)
120+
})
121+
}
122+
})
123+
.catch((error) => {
124+
console.error(`Error fetching MORPHO_LEGACY balance: ${error}`)
125+
})
100126
}
127+
}
128+
}
129+
if (isEligibleForProtocolRewards) {
130+
let rewardsControllerAddress: string | undefined
131+
let poolDataProviderAddress: string | undefined
132+
const network = networkIdToLibraryNetwork(networkId)
133+
if (
134+
protocol === LendingProtocol.AaveV3 &&
135+
network !== Network.HARDHAT &&
136+
network !== Network.LOCAL &&
137+
network !== Network.TENDERLY
138+
) {
139+
rewardsControllerAddress = ADDRESSES[network].aave.v3.RewardsController
140+
poolDataProviderAddress = ADDRESSES[network].aave.v3.PoolDataProvider
141+
} else if (
142+
protocol === LendingProtocol.SparkV3 &&
143+
network !== Network.HARDHAT &&
144+
network !== Network.LOCAL &&
145+
network !== Network.TENDERLY
146+
) {
147+
rewardsControllerAddress = ADDRESSES[network].spark.RewardsController
148+
poolDataProviderAddress = ADDRESSES[network].spark.PoolDataProvider
149+
} else {
150+
console.warn(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
151+
throw new Error(`Unsupported protocol or network for rewards: ${protocol} on ${network}`)
152+
}
101153

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-
})
154+
getAllUserRewards({
155+
networkId,
156+
token: quoteAddress,
157+
account: dpmProxy,
158+
rewardsController: rewardsControllerAddress as string,
159+
poolDataProvider: poolDataProviderAddress as string,
160+
})
161+
.then(async ({ rewardsList, unclaimedAmounts, assets }) => {
162+
if (unclaimedAmounts.some((amount) => amount.gt(zero))) {
163+
const tx = encodeClaimAllRewards({
164+
networkId,
165+
assets: assets as string[],
166+
dpmAccount: dpmProxy,
167+
rewardsController: rewardsControllerAddress as string,
168+
})
117169

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-
}
170+
rewardsList.forEach((token, index) => {
171+
if (unclaimedAmounts[index].gt(zero)) {
172+
dispatchClaim({
173+
token: getTokenByAddress(token, networkId).symbol,
174+
claimable: unclaimedAmounts[index],
175+
tx,
176+
})
177+
}
178+
})
179+
}
180+
})
181+
.catch((error) => {
182+
console.error(`Error fetching ${protocol} rewards:`, error)
183+
})
133184
}
134185
}, [dpmProxy, networkId, protocol, quoteAddress])
135186

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

153204
export const OmniDetailSectionRewardsClaims: FC = () => {
154205
const {
155-
environment: { protocol, collateralToken, quoteToken },
206+
environment: { protocol, collateralToken, quoteToken, networkId },
156207
} = useOmniGeneralContext()
157208

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

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))
219+
// Legacy Morpho claims eligibility
220+
const isEligibleForMorphoLegacy =
221+
networkId === NetworkIds.MAINNET && protocol === LendingProtocol.MorphoBlue
168222

169-
return isEligible ? <OmniDetailSectionRewardsClaimsInternal /> : <></>
223+
const hasAnyEligibleClaims =
224+
isEligibleForErc20Claims || isEligibleForProtocolRewards || isEligibleForMorphoLegacy
225+
226+
return hasAnyEligibleClaims ? (
227+
<OmniDetailSectionRewardsClaimsInternal
228+
isEligibleForErc20Claims={isEligibleForErc20Claims}
229+
isEligibleForProtocolRewards={isEligibleForProtocolRewards}
230+
isEligibleForMorphoLegacy={isEligibleForMorphoLegacy}
231+
/>
232+
) : (
233+
<></>
234+
)
170235
}

0 commit comments

Comments
 (0)