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
102 changes: 8 additions & 94 deletions src/components/transactions/ClaimRewards/ClaimRewardsActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '@aave/contract-helpers';
import { chainId, evmAddress, useUserMeritRewards } from '@aave/react';
import { Trans } from '@lingui/macro';
import { BigNumber, PopulatedTransaction, utils } from 'ethers';
import { BigNumber, PopulatedTransaction } from 'ethers';
import { Reward } from 'src/helpers/types';
import { useTransactionHandler } from 'src/helpers/useTransactionHandler';
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
Expand Down Expand Up @@ -70,9 +70,8 @@ export const ClaimRewardsActions = ({
});
}

// Use complex multicall logic only when needed
// Claim protocol and Merit rewards separately so each claim is sent by the wallet account.
if (isClaimingAll && hasProtocolRewards && hasMeritRewards) {
// Get protocol rewards transaction
const protocolTxns = await claimRewards({
isWrongNetwork,
blocked,
Expand All @@ -84,15 +83,13 @@ export const ClaimRewardsActions = ({
if (!meritClaimRewards?.transaction) {
throw new Error('Merit rewards transaction not available');
}
const multicallTx = await createMulticallTransaction(
protocolTxns,
meritClaimRewards.transaction as unknown as PopulatedTransaction
);

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

return approvalTxns.length > 0 ? [...approvalTxns, multicallTx] : [multicallTx];
return [
...protocolTxns,
convertMeritTransactionToEthereum(
meritClaimRewards.transaction as unknown as PopulatedTransaction
),
];
} else if ((isClaimingAll && !hasProtocolRewards && hasMeritRewards) || isClaimingMeritAll) {
// Only merit rewards - use merit transaction directly
if (!meritClaimRewards?.transaction) {
Expand Down Expand Up @@ -120,89 +117,6 @@ export const ClaimRewardsActions = ({
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 ? (BigNumber.isBigNumber(tx.value) ? tx.value.toString() : tx.value) : '0',
});
}

calls.push({
target: meritTransaction.to,
callData: meritTransaction.data,
value: meritTransaction.value
? BigNumber.isBigNumber(meritTransaction.value)
? meritTransaction.value.toString()
: 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]);

// Calculate total value needed for multicall by summing individual call values
const totalValue = calls.reduce((sum, call) => {
return BigNumber.from(sum).add(BigNumber.from(call.value)).toString();
}, '0');

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

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

return {
gasLimit: estimatedTx.gasLimit?.toString(),
gasPrice: '0', // Legacy field - actual gas pricing handled by wallet (EIP-1559)
};
} catch (error) {
console.warn('Gas estimation failed for multicall, using fallback:', error);
return {
gasLimit: '800000', // Conservative fallback
gasPrice: '0', // Legacy field - actual gas pricing handled by wallet (EIP-1559)
};
}
},
};
};

// Helper function to convert merit transaction to Ethereum format
const convertMeritTransactionToEthereum = (
meritTx: PopulatedTransaction
Expand Down
85 changes: 48 additions & 37 deletions src/helpers/useTransactionHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ import { useShallow } from 'zustand/shallow';

export const MOCK_SIGNED_HASH = 'Signed correctly';

const MAIN_ACTION_TX_TYPES = [
'DLP_ACTION',
'REWARD_ACTION',
'FAUCET_V2_MINT',
'FAUCET_MINT',
'STAKE_ACTION',
'GOV_DELEGATION_ACTION',
'GOVERNANCE_ACTION',
'V3_MIGRATION_ACTION',
];

interface UseTransactionHandlerProps {
handleGetTxns: () => Promise<EthereumTransactionTypeExtended[]>;
handleGetPermitTxns?: (
Expand Down Expand Up @@ -84,7 +95,7 @@ export const useTransactionHandler = ({
);

const [approvalTxes, setApprovalTxes] = useState<EthereumTransactionTypeExtended[] | undefined>();
const [actionTx, setActionTx] = useState<EthereumTransactionTypeExtended | undefined>();
const [actionTxes, setActionTxes] = useState<EthereumTransactionTypeExtended[] | undefined>();
const [usePermit, setUsePermit] = useState(false);
const mounted = useRef(false);

Expand Down Expand Up @@ -313,31 +324,44 @@ export const useTransactionHandler = ({
});
}
}
if ((!usePermit || !approvalTxes) && actionTx) {
if ((!usePermit || !approvalTxes) && actionTxes?.length) {
let errorHandled = false;
try {
setMainTxState({ ...mainTxState, loading: true });
const params = await actionTx.tx();
delete params.gasPrice;
return processTx({
tx: () => sendTx(params),
successCallback: (txnResponse: TransactionResponse) => {
setMainTxState({
txHash: txnResponse.hash,
loading: false,
success: true,
});
setTxError(undefined);
},
errorCallback: (error, hash) => {
const parsedError = getErrorTextFromError(error, TxAction.MAIN_ACTION);
setTxError(parsedError);
setMainTxState({
txHash: hash,
loading: false,

for (let index = 0; index < actionTxes.length; index++) {
const actionTx = actionTxes[index];
const params = await actionTx.tx();
const isLastAction = index === actionTxes.length - 1;
delete params.gasPrice;

await new Promise<void>((resolve, reject) => {
processTx({
tx: () => sendTx(params),
successCallback: (txnResponse: TransactionResponse) => {
setMainTxState({
txHash: txnResponse.hash,
loading: !isLastAction,
success: isLastAction,
});
setTxError(undefined);
resolve();
},
errorCallback: (error, hash) => {
errorHandled = true;
const parsedError = getErrorTextFromError(error, TxAction.MAIN_ACTION);
setTxError(parsedError);
setMainTxState({
txHash: hash,
loading: false,
});
reject(error);
},
});
},
});
});
}
} catch (error) {
if (errorHandled) return;
const parsedError = getErrorTextFromError(error, TxAction.GAS_ESTIMATION, false);
console.error(error, parsedError);
setTxError(parsedError);
Expand Down Expand Up @@ -388,20 +412,7 @@ export const useTransactionHandler = ({
} else {
setApprovalTxes(undefined);
}
setActionTx(
txs.find((tx) =>
[
'DLP_ACTION',
'REWARD_ACTION',
'FAUCET_V2_MINT',
'FAUCET_MINT',
'STAKE_ACTION',
'GOV_DELEGATION_ACTION',
'GOVERNANCE_ACTION',
'V3_MIGRATION_ACTION',
].includes(tx.txType)
)
);
setActionTxes(txs.filter((tx) => MAIN_ACTION_TX_TYPES.includes(tx.txType)));
setMainTxState({
txHash: undefined,
});
Expand Down Expand Up @@ -437,7 +448,7 @@ export const useTransactionHandler = ({
return () => clearTimeout(timeout);
} else {
setApprovalTxes(undefined);
setActionTx(undefined);
setActionTxes(undefined);
}
}, [skip, ...deps, tryPermit, walletApprovalMethodPreference]);

Expand Down