1- import { ProtocolAction } from '@aave/contract-helpers' ;
1+ import {
2+ eEthereumTxType ,
3+ EthereumTransactionTypeExtended ,
4+ ProtocolAction ,
5+ } from '@aave/contract-helpers' ;
6+ import { chainId , evmAddress , useUserMeritRewards } from '@aave/react' ;
27import { Trans } from '@lingui/macro' ;
8+ import { BigNumber , PopulatedTransaction , utils } from 'ethers' ;
39import { Reward } from 'src/helpers/types' ;
410import { useTransactionHandler } from 'src/helpers/useTransactionHandler' ;
511import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider' ;
12+ import { useWeb3Context } from 'src/libs/hooks/useWeb3Context' ;
613import { useRootStore } from 'src/store/root' ;
14+ import { useShallow } from 'zustand/shallow' ;
715
816import { TxActionsWrapper } from '../TxActionsWrapper' ;
17+ import { ControllerIdentifier , RewardSymbol } from './constants' ;
918
1019export type ClaimRewardsActionsProps = {
1120 isWrongNetwork : boolean ;
@@ -21,6 +30,17 @@ export const ClaimRewardsActions = ({
2130 const claimRewards = useRootStore ( ( state ) => state . claimRewards ) ;
2231 const { reserves } = useAppDataContext ( ) ;
2332
33+ const { currentAccount } = useWeb3Context ( ) ;
34+
35+ const [ currentMarketData , estimateGasLimit ] = useRootStore (
36+ useShallow ( ( store ) => [ store . currentMarketData , store . estimateGasLimit ] )
37+ ) ;
38+
39+ const { data : meritClaimRewards } = useUserMeritRewards ( {
40+ user : evmAddress ( currentAccount ) ,
41+ chainId : chainId ( currentMarketData . chainId ) ,
42+ } ) ;
43+
2444 const { action, loadingTxns, mainTxState, requiresApproval } = useTransactionHandler ( {
2545 protocolAction : ProtocolAction . claimRewards ,
2646 eventTxInfo : {
@@ -29,12 +49,202 @@ export const ClaimRewardsActions = ({
2949 } ,
3050 tryPermit : false ,
3151 handleGetTxns : async ( ) => {
32- return claimRewards ( { isWrongNetwork, blocked, selectedReward, formattedReserves : reserves } ) ;
52+ // Check if we need to claim both protocol and merit rewards
53+ const isClaimingAll = selectedReward . symbol === RewardSymbol . ALL ;
54+ const isClaimingMeritAll = selectedReward . symbol === RewardSymbol . MERIT_ALL ;
55+ const isClaimingProtocolAll = selectedReward . symbol === RewardSymbol . PROTOCOL_ALL ;
56+ const hasProtocolRewards =
57+ selectedReward . incentiveControllerAddress !== ControllerIdentifier . MERIT_REWARD ;
58+ const hasMeritRewards =
59+ meritClaimRewards ?. claimable && meritClaimRewards . claimable . length > 0 ;
60+ const isIndividualProtocolReward =
61+ hasProtocolRewards && ! isClaimingAll && ! isClaimingProtocolAll && ! isClaimingMeritAll ;
62+
63+ // Use simple approach for individual protocol rewards (most common case)
64+ if ( isIndividualProtocolReward ) {
65+ return claimRewards ( {
66+ isWrongNetwork,
67+ blocked,
68+ selectedReward,
69+ formattedReserves : reserves ,
70+ } ) ;
71+ }
72+
73+ // Use complex multicall logic only when needed
74+ if ( isClaimingAll && hasProtocolRewards && hasMeritRewards ) {
75+ // Get protocol rewards transaction
76+ const protocolTxns = await claimRewards ( {
77+ isWrongNetwork,
78+ blocked,
79+ selectedReward,
80+ formattedReserves : reserves ,
81+ } ) ;
82+
83+ // Create multicall transaction that includes both protocol and merit claims
84+ if ( ! meritClaimRewards ?. transaction ) {
85+ throw new Error ( 'Merit rewards transaction not available' ) ;
86+ }
87+ const multicallTx = await createMulticallTransaction (
88+ protocolTxns ,
89+ meritClaimRewards . transaction as unknown as PopulatedTransaction
90+ ) ;
91+
92+ // Check if there are approval transactions that need to be handled separately
93+ const approvalTxns = protocolTxns . filter ( ( tx ) => tx . txType === 'ERC20_APPROVAL' ) ;
94+
95+ return approvalTxns . length > 0 ? [ ...approvalTxns , multicallTx ] : [ multicallTx ] ;
96+ } else if ( ( isClaimingAll && ! hasProtocolRewards && hasMeritRewards ) || isClaimingMeritAll ) {
97+ // Only merit rewards - use merit transaction directly
98+ if ( ! meritClaimRewards ?. transaction ) {
99+ throw new Error ( 'Merit rewards transaction not available' ) ;
100+ }
101+
102+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
103+ // @ts -ignore
104+ return [
105+ convertMeritTransactionToEthereum (
106+ meritClaimRewards . transaction as unknown as PopulatedTransaction
107+ ) ,
108+ ] ;
109+ } else {
110+ // Protocol-all or other cases - use existing protocol logic
111+ return claimRewards ( {
112+ isWrongNetwork,
113+ blocked,
114+ selectedReward,
115+ formattedReserves : reserves ,
116+ } ) ;
117+ }
33118 } ,
34119 skip : Object . keys ( selectedReward ) . length === 0 || blocked ,
35- deps : [ selectedReward ] ,
120+ deps : [ selectedReward , meritClaimRewards ] ,
36121 } ) ;
37122
123+ // Helper function to create multicall transaction
124+ const createMulticallTransaction = async (
125+ protocolTxns : EthereumTransactionTypeExtended [ ] ,
126+ meritTransaction : PopulatedTransaction
127+ ) : Promise < EthereumTransactionTypeExtended > => {
128+ // Multicall3 contract address (same across chains)
129+ const multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11' ;
130+
131+ const calls = [ ] ;
132+
133+ for ( const txExt of protocolTxns ) {
134+ if ( txExt . txType === 'ERC20_APPROVAL' ) continue ; // Skip approvals for multicall
135+
136+ const tx = await txExt . tx ( ) ;
137+ calls . push ( {
138+ target : tx . to ,
139+ callData : tx . data ,
140+ value : tx . value ? ( BigNumber . isBigNumber ( tx . value ) ? tx . value . toString ( ) : tx . value ) : '0' ,
141+ } ) ;
142+ }
143+
144+ calls . push ( {
145+ target : meritTransaction . to ,
146+ callData : meritTransaction . data ,
147+ value : meritTransaction . value
148+ ? BigNumber . isBigNumber ( meritTransaction . value )
149+ ? meritTransaction . value . toString ( )
150+ : meritTransaction . value
151+ : '0' ,
152+ } ) ;
153+
154+ const multicallInterface = new utils . Interface ( [
155+ 'function aggregate3Value((address target, bool allowFailure, uint256 value, bytes callData)[] calls) payable returns ((bool success, bytes returnData)[])' ,
156+ ] ) ;
157+
158+ const callsWithFailure = calls . map ( ( call ) => [
159+ call . target ,
160+ false , // allowFailure = false
161+ call . value ,
162+ call . callData ,
163+ ] ) ;
164+
165+ const data = multicallInterface . encodeFunctionData ( 'aggregate3Value' , [ callsWithFailure ] ) ;
166+
167+ // Calculate total value needed for multicall by summing individual call values
168+ const totalValue = calls . reduce ( ( sum , call ) => {
169+ return BigNumber . from ( sum ) . add ( BigNumber . from ( call . value ) ) . toString ( ) ;
170+ } , '0' ) ;
171+
172+ return {
173+ txType : eEthereumTxType . DLP_ACTION ,
174+ tx : async ( ) => ( {
175+ to : multicallAddress ,
176+ from : currentAccount ,
177+ data,
178+ value : totalValue ,
179+ } ) ,
180+ gas : async ( ) => {
181+ try {
182+ const tx = {
183+ to : multicallAddress ,
184+ from : currentAccount ,
185+ data,
186+ value : BigNumber . from ( totalValue ) ,
187+ } ;
188+
189+ const estimatedTx = await estimateGasLimit ( tx , currentMarketData . chainId ) ;
190+
191+ return {
192+ gasLimit : estimatedTx . gasLimit ?. toString ( ) ,
193+ gasPrice : '0' , // Legacy field - actual gas pricing handled by wallet (EIP-1559)
194+ } ;
195+ } catch ( error ) {
196+ console . warn ( 'Gas estimation failed for multicall, using fallback:' , error ) ;
197+ return {
198+ gasLimit : '800000' , // Conservative fallback
199+ gasPrice : '0' , // Legacy field - actual gas pricing handled by wallet (EIP-1559)
200+ } ;
201+ }
202+ } ,
203+ } ;
204+ } ;
205+
206+ // Helper function to convert merit transaction to Ethereum format
207+ const convertMeritTransactionToEthereum = (
208+ meritTx : PopulatedTransaction
209+ ) : EthereumTransactionTypeExtended => {
210+ return {
211+ txType : eEthereumTxType . DLP_ACTION ,
212+ tx : async ( ) => ( {
213+ to : meritTx . to ,
214+ from : meritTx . from || currentAccount ,
215+ data : meritTx . data ,
216+ value : meritTx . value
217+ ? BigNumber . isBigNumber ( meritTx . value )
218+ ? meritTx . value . toString ( )
219+ : meritTx . value
220+ : '0' ,
221+ } ) ,
222+ gas : async ( ) => {
223+ try {
224+ const tx = {
225+ to : meritTx . to ,
226+ from : meritTx . from || currentAccount ,
227+ data : meritTx . data ,
228+ value : meritTx . value ,
229+ } ;
230+
231+ const estimatedTx = await estimateGasLimit ( tx , currentMarketData . chainId ) ;
232+
233+ return {
234+ gasLimit : estimatedTx . gasLimit ?. toString ( ) ,
235+ gasPrice : '0' , // Legacy field - actual gas pricing handled by wallet (EIP-1559)
236+ } ;
237+ } catch ( error ) {
238+ console . warn ( 'Gas estimation failed for merit transaction, using fallback:' , error ) ;
239+ return {
240+ gasLimit : '400000' ,
241+ gasPrice : '0' , // Legacy field - actual gas pricing handled by wallet (EIP-1559)
242+ } ;
243+ }
244+ } ,
245+ } ;
246+ } ;
247+
38248 return (
39249 < TxActionsWrapper
40250 requiresApproval = { requiresApproval }
@@ -43,8 +253,12 @@ export const ClaimRewardsActions = ({
43253 mainTxState = { mainTxState }
44254 handleAction = { action }
45255 actionText = {
46- selectedReward . symbol === 'all' ? (
256+ selectedReward . symbol === RewardSymbol . ALL ? (
47257 < Trans > Claim all</ Trans >
258+ ) : selectedReward . symbol === RewardSymbol . MERIT_ALL ? (
259+ < Trans > Claim all merit rewards</ Trans >
260+ ) : selectedReward . symbol === RewardSymbol . PROTOCOL_ALL ? (
261+ < Trans > Claim all protocol rewards</ Trans >
48262 ) : (
49263 < Trans > Claim { selectedReward . symbol } </ Trans >
50264 )
0 commit comments