Skip to content
Open
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"dependencies": {
"@aave/contract-helpers": "1.36.1",
"@aave/math-utils": "1.36.1",
"@aave/react": "^0.4.0",
"@aave/react": "canary",
Copy link
Contributor

Choose a reason for hiding this comment

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

would rather a specific version to avoid issues on updates

"@amplitude/analytics-browser": "^2.13.0",
"@bgd-labs/aave-address-book": "^4.25.1",
"@cowprotocol/app-data": "^3.1.0",
Expand Down Expand Up @@ -156,4 +156,4 @@
"budgetPercentIncreaseRed": 20,
"showDetails": true
}
}
}
201 changes: 198 additions & 3 deletions src/components/transactions/ClaimRewards/ClaimRewardsActions.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { ProtocolAction } from '@aave/contract-helpers';
import {
eEthereumTxType,
EthereumTransactionTypeExtended,
ProtocolAction,
} from '@aave/contract-helpers';
import { useMeritClaimRewards } from '@aave/react';
import { Trans } from '@lingui/macro';
import { BigNumber, PopulatedTransaction, utils } from 'ethers';
import { Reward } from 'src/helpers/types';
import { useTransactionHandler } from 'src/helpers/useTransactionHandler';
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
import { useShallow } from 'zustand/shallow';

import { TxActionsWrapper } from '../TxActionsWrapper';

Expand All @@ -21,6 +29,17 @@ export const ClaimRewardsActions = ({
const claimRewards = useRootStore((state) => state.claimRewards);
const { reserves } = useAppDataContext();

const { currentAccount } = useWeb3Context();

const [currentMarketData, estimateGasLimit] = useRootStore(
useShallow((store) => [store.currentMarketData, store.estimateGasLimit])
);

const { data: meritClaimRewards } = useMeritClaimRewards({
user: currentAccount,
chainId: currentMarketData.chainId,
});

const { action, loadingTxns, mainTxState, requiresApproval } = useTransactionHandler({
protocolAction: ProtocolAction.claimRewards,
eventTxInfo: {
Expand All @@ -29,12 +48,184 @@ export const ClaimRewardsActions = ({
},
tryPermit: false,
handleGetTxns: async () => {
return claimRewards({ isWrongNetwork, blocked, selectedReward, formattedReserves: reserves });
// Check if we need to claim both protocol and merit rewards
const isClaimingAll = selectedReward.symbol === 'all';
const isClaimingMeritAll = selectedReward.symbol === 'merit-all';
const isClaimingProtocolAll = selectedReward.symbol === 'protocol-all';
const hasProtocolRewards = selectedReward.incentiveControllerAddress !== 'MERIT_REWARD';
Comment on lines +52 to +54
Copy link
Contributor

Choose a reason for hiding this comment

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

we are using those keys in multiple places i'd say to use an enum

const hasMeritRewards = meritClaimRewards?.rewards && meritClaimRewards.rewards.length > 0;
const isIndividualProtocolReward =
hasProtocolRewards && !isClaimingAll && !isClaimingProtocolAll && !isClaimingMeritAll;

// Use simple approach for individual protocol rewards (most common case)
if (isIndividualProtocolReward) {
return claimRewards({
isWrongNetwork,
blocked,
selectedReward,
formattedReserves: reserves,
});
}

// Use complex multicall logic only when needed
if (isClaimingAll && hasProtocolRewards && hasMeritRewards) {
// Get protocol rewards transaction
const protocolTxns = await claimRewards({
isWrongNetwork,
blocked,
selectedReward,
formattedReserves: reserves,
});

// Create multicall transaction that includes both protocol and merit claims
if (!meritClaimRewards?.transaction) {
throw new Error('Merit rewards transaction not available');
}
const multicallTx = await createMulticallTransaction(
protocolTxns,
meritClaimRewards.transaction
);

// Check if there are any approval transactions that need to be handled separately
const approvalTxns = protocolTxns.filter((tx) => tx.txType === 'ERC20_APPROVAL');

return approvalTxns.length > 0 ? [...approvalTxns, multicallTx] : [multicallTx];
} else if ((isClaimingAll && !hasProtocolRewards && hasMeritRewards) || isClaimingMeritAll) {
// Only merit rewards - use merit transaction directly
if (!meritClaimRewards?.transaction) {
throw new Error('Merit rewards transaction not available');
}
return [convertMeritTransactionToEthereum(meritClaimRewards.transaction)];
} else {
// Protocol-all or other cases - use existing protocol logic
return claimRewards({
isWrongNetwork,
blocked,
selectedReward,
formattedReserves: reserves,
});
}
},
skip: Object.keys(selectedReward).length === 0 || blocked,
deps: [selectedReward],
deps: [selectedReward, meritClaimRewards],
});

// Helper function to create multicall transaction
const createMulticallTransaction = async (
protocolTxns: EthereumTransactionTypeExtended[],
meritTransaction: PopulatedTransaction
): Promise<EthereumTransactionTypeExtended> => {
// Multicall3 contract address (same across chains)
const multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11';

const calls = [];

for (const txExt of protocolTxns) {
if (txExt.txType === 'ERC20_APPROVAL') continue; // Skip approvals for multicall

const tx = await txExt.tx();
calls.push({
target: tx.to,
callData: tx.data,
value: tx.value || '0',
});
Copy link
Contributor

Choose a reason for hiding this comment

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

just double checking we are sending tx.value here and in return we set value to zero

}

calls.push({
target: meritTransaction.to,
callData: meritTransaction.data,
value: meritTransaction.value || '0',
});

const multicallInterface = new utils.Interface([
'function aggregate3Value((address target, bool allowFailure, uint256 value, bytes callData)[] calls) payable returns ((bool success, bytes returnData)[])',
]);

const callsWithFailure = calls.map((call) => [
call.target,
false, // allowFailure = false
call.value,
call.callData,
]);

const data = multicallInterface.encodeFunctionData('aggregate3Value', [callsWithFailure]);

return {
txType: eEthereumTxType.DLP_ACTION,
tx: async () => ({
to: multicallAddress,
from: currentAccount,
data,
value: '0',
}),
gas: async () => {
try {
const tx = {
to: multicallAddress,
from: currentAccount,
data,
value: BigNumber.from('0'),
};

const estimatedTx = await estimateGasLimit(tx, currentMarketData.chainId);

return {
gasLimit: estimatedTx.gasLimit?.toString(),
gasPrice: '0',
};
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure where is this being used by pointing out gasPrice 0

} catch (error) {
console.warn('Gas estimation failed for multicall, using fallback:', error);
return {
gasLimit: '800000', // Conservative fallback
gasPrice: '0',
};
}
},
};
};

// Helper function to convert merit transaction to Ethereum format
const convertMeritTransactionToEthereum = (
meritTx: PopulatedTransaction
): EthereumTransactionTypeExtended => {
return {
txType: eEthereumTxType.DLP_ACTION,
tx: async () => ({
to: meritTx.to,
from: meritTx.from || currentAccount,
data: meritTx.data,
value: meritTx.value
? BigNumber.isBigNumber(meritTx.value)
? meritTx.value.toString()
: meritTx.value
: '0',
}),
gas: async () => {
try {
const tx = {
to: meritTx.to,
from: meritTx.from || currentAccount,
data: meritTx.data,
value: meritTx.value,
};

const estimatedTx = await estimateGasLimit(tx, currentMarketData.chainId);

return {
gasLimit: estimatedTx.gasLimit?.toString(),
gasPrice: '0',
};
} catch (error) {
console.warn('Gas estimation failed for merit transaction, using fallback:', error);
return {
gasLimit: '400000',
gasPrice: '0',
};
}
},
};
};

return (
<TxActionsWrapper
requiresApproval={requiresApproval}
Expand All @@ -45,6 +236,10 @@ export const ClaimRewardsActions = ({
actionText={
selectedReward.symbol === 'all' ? (
<Trans>Claim all</Trans>
) : selectedReward.symbol === 'merit-all' ? (
<Trans>Claim all merit rewards</Trans>
) : selectedReward.symbol === 'protocol-all' ? (
<Trans>Claim all protocol rewards</Trans>
) : (
<Trans>Claim {selectedReward.symbol}</Trans>
)
Expand Down
Loading
Loading