diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9446638f981..80f5b25a2b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/)
## [1.9.40] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.40)
-### Fixed
+### Fixed
- Fixed a bug with speed up and cancel (#6133)
diff --git a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx
index d543fb635ab..b9cda34cc2d 100644
--- a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx
@@ -5,9 +5,13 @@ import { useClaimables } from '@/resources/addys/claimables/query';
import { FasterImageView } from '@candlefinance/faster-image';
import { ButtonPressAnimation } from '@/components/animations';
import { deviceUtils } from '@/utils';
+import Routes from '@/navigation/routesNames';
+import { ExtendedState } from './core/RawRecyclerList';
-export default function Claimable({ uniqueId }: { uniqueId: string }) {
+export default React.memo(function Claimable({ uniqueId, extendedState }: { uniqueId: string; extendedState: ExtendedState }) {
const { accountAddress, nativeCurrency } = useAccountSettings();
+ const { navigate } = extendedState;
+
const { data = [] } = useClaimables(
{
address: accountAddress,
@@ -25,6 +29,7 @@ export default function Claimable({ uniqueId }: { uniqueId: string }) {
return (
navigate(Routes.CLAIM_CLAIMABLE_PANEL, { claimable })}
scaleTo={0.96}
paddingHorizontal="20px"
justifyContent="space-between"
@@ -68,4 +73,4 @@ export default function Claimable({ uniqueId }: { uniqueId: string }) {
);
-}
+});
diff --git a/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx b/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx
index 964a58af799..9e2896b130f 100644
--- a/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx
@@ -90,4 +90,4 @@ ClaimablesListHeader.animationDuration = TokenFamilyHeaderAnimationDuration;
ClaimablesListHeader.height = TokenFamilyHeaderHeight;
-export default ClaimablesListHeader;
+export default React.memo(ClaimablesListHeader);
diff --git a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx
index b5805584840..e6bd18abcc3 100644
--- a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx
@@ -175,7 +175,7 @@ function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, exten
case CellType.CLAIMABLE: {
const { uniqueId } = data as ClaimableExtraData;
- return ;
+ return ;
}
case CellType.LOADING_ASSETS:
diff --git a/src/helpers/buildWalletSections.tsx b/src/helpers/buildWalletSections.tsx
index a612150f20d..833b154fad7 100644
--- a/src/helpers/buildWalletSections.tsx
+++ b/src/helpers/buildWalletSections.tsx
@@ -1,13 +1,10 @@
import { createSelector } from 'reselect';
import { buildBriefCoinsList, buildBriefUniqueTokenList } from './assets';
import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities';
-import { queryClient } from '@/react-query';
-import { positionsQueryKey } from '@/resources/defi/PositionsQuery';
import store from '@/redux/store';
import { ClaimableExtraData, PositionExtraData } from '@/components/asset-list/RecyclerAssetList2/core/ViewTypes';
import { DEFI_POSITIONS, CLAIMABLES, ExperimentalValue } from '@/config/experimental';
import { RainbowPositions } from '@/resources/defi/types';
-import { claimablesQueryKey } from '@/resources/addys/claimables/query';
import { Claimable } from '@/resources/addys/claimables/types';
import { add, convertAmountToNativeDisplay } from './utilities';
import { RainbowConfig } from '@/model/remoteConfig';
@@ -60,20 +57,24 @@ const isFetchingNftsSelector = (state: any) => state.isFetchingNfts;
const listTypeSelector = (state: any) => state.listType;
const remoteConfigSelector = (state: any) => state.remoteConfig;
const experimentalConfigSelector = (state: any) => state.experimentalConfig;
+const positionsSelector = (state: any) => state.positions;
+const claimablesSelector = (state: any) => state.claimables;
const buildBriefWalletSections = (
balanceSectionData: any,
uniqueTokenFamiliesSection: any,
remoteConfig: RainbowConfig,
- experimentalConfig: Record
+ experimentalConfig: Record,
+ positions: RainbowPositions | undefined,
+ claimables: Claimable[] | undefined
) => {
const { balanceSection, isEmpty, isLoadingUserAssets } = balanceSectionData;
const positionsEnabled = experimentalConfig[DEFI_POSITIONS] && !IS_TEST;
const claimablesEnabled = (remoteConfig.claimables || experimentalConfig[CLAIMABLES]) && !IS_TEST;
- const positionSection = positionsEnabled ? withPositionsSection(isLoadingUserAssets) : [];
- const claimablesSection = claimablesEnabled ? withClaimablesSection(isLoadingUserAssets) : [];
+ const positionSection = positionsEnabled ? withPositionsSection(positions, isLoadingUserAssets) : [];
+ const claimablesSection = claimablesEnabled ? withClaimablesSection(claimables, isLoadingUserAssets) : [];
const sections = [balanceSection, claimablesSection, positionSection, uniqueTokenFamiliesSection];
const filteredSections = sections.filter(section => section.length !== 0).flat(1);
@@ -84,10 +85,7 @@ const buildBriefWalletSections = (
};
};
-const withPositionsSection = (isLoadingUserAssets: boolean) => {
- const { accountAddress: address, nativeCurrency: currency } = store.getState().settings;
- const positionsObj: RainbowPositions | undefined = queryClient.getQueryData(positionsQueryKey({ address, currency }));
-
+const withPositionsSection = (positionsObj: RainbowPositions | undefined, isLoadingUserAssets: boolean) => {
const result: PositionExtraData[] = [];
const sortedPositions = positionsObj?.positions?.sort((a, b) => (a.totals.totals.amount > b.totals.totals.amount ? -1 : 1));
sortedPositions?.forEach((position, index) => {
@@ -118,9 +116,8 @@ const withPositionsSection = (isLoadingUserAssets: boolean) => {
return [];
};
-const withClaimablesSection = (isLoadingUserAssets: boolean) => {
- const { accountAddress: address, nativeCurrency: currency } = store.getState().settings;
- const claimables: Claimable[] | undefined = queryClient.getQueryData(claimablesQueryKey({ address, currency }));
+const withClaimablesSection = (claimables: Claimable[] | undefined, isLoadingUserAssets: boolean) => {
+ const { nativeCurrency: currency } = store.getState().settings;
const result: ClaimableExtraData[] = [];
let totalNativeValue = '0';
@@ -285,6 +282,13 @@ const briefBalanceSectionSelector = createSelector(
);
export const buildBriefWalletSectionsSelector = createSelector(
- [briefBalanceSectionSelector, (state: any) => briefUniqueTokenDataSelector(state), remoteConfigSelector, experimentalConfigSelector],
+ [
+ briefBalanceSectionSelector,
+ (state: any) => briefUniqueTokenDataSelector(state),
+ remoteConfigSelector,
+ experimentalConfigSelector,
+ positionsSelector,
+ claimablesSelector,
+ ],
buildBriefWalletSections
);
diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts
index f974ee2673c..91e7298151e 100644
--- a/src/hooks/useWalletSectionsData.ts
+++ b/src/hooks/useWalletSectionsData.ts
@@ -14,6 +14,8 @@ import useNftSort from './useNFTsSortBy';
import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames';
import { useRemoteConfig } from '@/model/remoteConfig';
import { RainbowContext } from '@/helpers/RainbowContext';
+import { usePositions } from '@/resources/defi/PositionsQuery';
+import { useClaimables } from '@/resources/addys/claimables/query';
export default function useWalletSectionsData({
type,
@@ -35,6 +37,8 @@ export default function useWalletSectionsData({
address: accountAddress,
sortBy: nftSort,
});
+ const { data: positions } = usePositions({ address: accountAddress, currency: nativeCurrency });
+ const { data: claimables } = useClaimables({ address: accountAddress, currency: nativeCurrency });
const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames();
@@ -76,6 +80,8 @@ export default function useWalletSectionsData({
nftSort,
remoteConfig,
experimentalConfig,
+ positions,
+ claimables,
};
const { briefSectionsData, isEmpty } = buildBriefWalletSectionsSelector(accountInfo);
@@ -110,6 +116,8 @@ export default function useWalletSectionsData({
nftSort,
remoteConfig,
experimentalConfig,
+ positions,
+ claimables,
]);
return walletSections;
}
diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx
index 007652f2302..e66ca433ef5 100644
--- a/src/navigation/Routes.android.tsx
+++ b/src/navigation/Routes.android.tsx
@@ -90,6 +90,7 @@ import { SwapScreen } from '@/__swaps__/screens/Swap/Swap';
import { useRemoteConfig } from '@/model/remoteConfig';
import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel';
import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel';
+import { ClaimClaimablePanel } from '@/screens/claimables/ClaimClaimablePanel';
import { RootStackParamList } from './types';
const Stack = createStackNavigator();
@@ -247,6 +248,7 @@ function BSNavigator() {
+
{swapsV2Enabled && }
diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx
index 4f1a8e96407..1ea1c9553ae 100644
--- a/src/navigation/Routes.ios.tsx
+++ b/src/navigation/Routes.ios.tsx
@@ -38,7 +38,6 @@ import {
hardwareWalletTxNavigatorConfig,
consoleSheetConfig,
customGasSheetConfig,
- dappBrowserControlPanelConfig,
defaultScreenStackOptions,
ensAdditionalRecordsSheetConfig,
ensConfirmRegisterSheetConfig,
@@ -50,6 +49,7 @@ import {
nftOffersSheetConfig,
nftSingleOfferSheetConfig,
pairHardwareWalletNavigatorConfig,
+ panelConfig,
profileConfig,
profilePreviewConfig,
qrScannerConfig,
@@ -104,6 +104,7 @@ import { useRemoteConfig } from '@/model/remoteConfig';
import CheckIdentifierScreen from '@/screens/CheckIdentifierScreen';
import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel';
import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel';
+import { ClaimClaimablePanel } from '@/screens/claimables/ClaimClaimablePanel';
import { RootStackParamList } from './types';
const Stack = createStackNavigator();
@@ -285,8 +286,9 @@ function NativeStackNavigator() {
-
-
+
+
+
{swapsV2Enabled && }
diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx
index 09b893005bb..fbe583c44b8 100644
--- a/src/navigation/config.tsx
+++ b/src/navigation/config.tsx
@@ -248,7 +248,7 @@ export const consoleSheetConfig = {
}),
};
-export const dappBrowserControlPanelConfig = {
+export const panelConfig = {
options: ({ route: { params = {} } }) => ({
...buildCoolModalConfig({
...params,
diff --git a/src/navigation/routesNames.ts b/src/navigation/routesNames.ts
index 1e5693b4c27..b9ca655e1eb 100644
--- a/src/navigation/routesNames.ts
+++ b/src/navigation/routesNames.ts
@@ -108,6 +108,7 @@ const Routes = {
SETTINGS_SECTION_PRIVACY: 'PrivacySection',
DAPP_BROWSER_CONTROL_PANEL: 'DappBrowserControlPanel',
CLAIM_REWARDS_PANEL: 'ClaimRewardsPanel',
+ CLAIM_CLAIMABLE_PANEL: 'ClaimClaimablePanel',
} as const;
export const NATIVE_ROUTES = [
diff --git a/src/raps/actions/claim.ts b/src/raps/actions/claimRewards.ts
similarity index 81%
rename from src/raps/actions/claim.ts
rename to src/raps/actions/claimRewards.ts
index 4cb029a4df8..763b1369f7f 100644
--- a/src/raps/actions/claim.ts
+++ b/src/raps/actions/claimRewards.ts
@@ -15,10 +15,10 @@ const DO_FAKE_CLAIM = false;
// This action is used to claim the rewards of the user
// by making an api call to the backend which would use a relayer
// to do the claim and send the funds to the user
-export async function claim({ parameters, wallet, baseNonce }: ActionProps<'claim'>) {
+export async function claimRewards({ parameters, wallet, baseNonce }: ActionProps<'claimRewards'>) {
const { address } = parameters;
if (!address) {
- throw new Error('[CLAIM]: missing address');
+ throw new Error('[CLAIM-REWARDS]: missing address');
}
// when DO_FAKE_CLAIM is true, we use mock data (can do as many as we want)
// otherwise we do a real claim (can be done once, then backend needs to reset it)
@@ -29,7 +29,7 @@ export async function claim({ parameters, wallet, baseNonce }: ActionProps<'clai
if (!txHash) {
// If there's no transaction hash the relayer didn't submit the transaction
// so we can't contnue
- throw new Error('[CLAIM]: missing tx hash from backend');
+ throw new Error('[CLAIM-REWARDS]: missing tx hash from backend');
}
// We need to make sure the transaction is mined
@@ -37,7 +37,7 @@ export async function claim({ parameters, wallet, baseNonce }: ActionProps<'clai
const claimTx = await wallet?.provider?.getTransaction(txHash);
if (!claimTx) {
// If we can't get the transaction we can't continue
- throw new Error('[CLAIM]: tx not found');
+ throw new Error('[CLAIM-REWARDS]: tx not found');
}
// then we wait for the receipt of the transaction
@@ -45,14 +45,14 @@ export async function claim({ parameters, wallet, baseNonce }: ActionProps<'clai
const receipt = await claimTx?.wait();
if (!receipt) {
// If we can't get the receipt we can't continue
- throw new Error('[CLAIM]: tx not mined');
+ throw new Error('[CLAIM-REWARDS]: tx not mined');
}
// finally we check if the transaction was successful
const success = receipt?.status === 1;
if (!success) {
// The transaction failed, we can't continue
- throw new Error('[CLAIM]: claim tx failed onchain');
+ throw new Error('[CLAIM-REWARDS]: claim rewards tx failed onchain');
}
// If the transaction was successful we can return the hash
diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimRewardsBridge.ts
similarity index 89%
rename from src/raps/actions/claimBridge.ts
rename to src/raps/actions/claimRewardsBridge.ts
index d4f9addd1e5..b8b8c946003 100644
--- a/src/raps/actions/claimBridge.ts
+++ b/src/raps/actions/claimRewardsBridge.ts
@@ -16,12 +16,12 @@ import { ChainId } from '@/chains/types';
import { chainsName } from '@/chains';
// This action is used to bridge the claimed funds to another chain
-export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps<'claimBridge'>) {
+export async function claimRewardsBridge({ parameters, wallet, baseNonce }: ActionProps<'claimRewardsBridge'>) {
const { address, toChainId, sellAmount, chainId } = parameters;
// Check if the address and toChainId are valid
// otherwise we can't continue
if (!toChainId || !address) {
- throw new RainbowError('claimBridge: error getClaimBridgeQuote');
+ throw new RainbowError('claimRewardsBridge: error getClaimBridgeQuote');
}
let maxBridgeableAmount = sellAmount;
@@ -44,7 +44,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps
// if we don't get a quote or there's an error we can't continue
if (!claimBridgeQuote || (claimBridgeQuote as QuoteError)?.error) {
- throw new Error('[CLAIM-BRIDGE]: error getting getClaimBridgeQuote');
+ throw new Error('[CLAIM-REWARDS-BRIDGE]: error getting getClaimBridgeQuote');
}
let bridgeQuote = claimBridgeQuote as CrosschainQuote;
@@ -81,7 +81,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps
if (lessThan(subtract(balance.toString(), sellAmount), gasFeeInWei)) {
// if the balance is less than the gas fee we can't continue
if (lessThan(sellAmount, gasFeeInWei)) {
- throw new Error('[CLAIM-BRIDGE]: error insufficient funds to pay gas fee');
+ throw new Error('[CLAIM-REWARDS-BRIDGE]: error insufficient funds to pay gas fee');
} else {
// otherwie we bridge the maximum amount we can afford
maxBridgeableAmount = subtract(sellAmount, gasFeeInWei);
@@ -104,7 +104,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps
});
if (!newQuote || (newQuote as QuoteError)?.error) {
- throw new Error('[CLAIM-BRIDGE]: error getClaimBridgeQuote (new)');
+ throw new Error('[CLAIM-REWARDS-BRIDGE]: error getClaimBridgeQuote (new)');
}
bridgeQuote = newQuote as CrosschainQuote;
@@ -126,7 +126,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps
}
if (!gasLimit) {
- throw new Error('[CLAIM-BRIDGE]: error estimating gas or using default gas limit');
+ throw new Error('[CLAIM-REWARDS-BRIDGE]: error estimating gas or using default gas limit');
}
// we need to bump the base nonce to next available one
@@ -147,11 +147,11 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps
try {
swap = await executeCrosschainSwap(swapParams);
} catch (e) {
- throw new Error('[CLAIM-BRIDGE]: crosschainSwap error');
+ throw new Error('[CLAIM-REWARDS-BRIDGE]: crosschainSwap error');
}
if (!swap) {
- throw new Error('[CLAIM-BRIDGE]: executeCrosschainSwap returned undefined');
+ throw new Error('[CLAIM-REWARDS-BRIDGE]: executeCrosschainSwap returned undefined');
}
const typedAssetToBuy: ParsedAddressAsset = {
diff --git a/src/raps/actions/claimTransactionClaimable.ts b/src/raps/actions/claimTransactionClaimable.ts
new file mode 100644
index 00000000000..3c85149dec6
--- /dev/null
+++ b/src/raps/actions/claimTransactionClaimable.ts
@@ -0,0 +1,42 @@
+import { ActionPropsV2 } from '../references';
+import { sendTransaction } from '@/model/wallet';
+import { getProvider } from '@/handlers/web3';
+import { RainbowError } from '@/logger';
+import { addNewTransaction } from '@/state/pendingTransactions';
+import { NewTransaction } from '@/entities';
+import { chainsName } from '@/chains';
+
+export async function claimTransactionClaimable({ parameters, wallet }: ActionPropsV2<'claimTransactionClaimableAction'>) {
+ const { claimTx } = parameters;
+
+ const provider = getProvider({ chainId: claimTx.chainId });
+ const result = await sendTransaction({ transaction: claimTx, existingWallet: wallet, provider });
+
+ if (!result?.result || !!result.error || !result.result.hash) {
+ throw new RainbowError('[CLAIM-TRANSACTION-CLAIMABLE]: failed to execute claim transaction');
+ }
+
+ const transaction = {
+ amount: result.result.value.toString(),
+ gasLimit: result.result.gasLimit,
+ from: result.result.from ?? null,
+ to: result.result.to ?? null,
+ chainId: result.result.chainId,
+ hash: result.result.hash,
+ network: chainsName[result.result.chainId],
+ status: 'pending',
+ type: 'send',
+ nonce: result.result.nonce,
+ } satisfies NewTransaction;
+
+ addNewTransaction({
+ address: claimTx.from,
+ chainId: claimTx.chainId,
+ transaction,
+ });
+
+ return {
+ nonce: result.result.nonce,
+ hash: result.result.hash,
+ };
+}
diff --git a/src/raps/actions/index.ts b/src/raps/actions/index.ts
index 5801ce411b2..6a27273e5f3 100644
--- a/src/raps/actions/index.ts
+++ b/src/raps/actions/index.ts
@@ -1,3 +1,3 @@
export { estimateSwapGasLimit, executeSwap, swap } from './swap';
export { assetNeedsUnlocking, estimateApprove, executeApprove, unlock } from './unlock';
-export { claim } from './claim';
+export { claimRewards } from './claimRewards';
diff --git a/src/raps/claimAndBridge.ts b/src/raps/claimRewardsAndBridge.ts
similarity index 58%
rename from src/raps/claimAndBridge.ts
rename to src/raps/claimRewardsAndBridge.ts
index 5f611eb51ad..d7580dc6c31 100644
--- a/src/raps/claimAndBridge.ts
+++ b/src/raps/claimRewardsAndBridge.ts
@@ -1,17 +1,17 @@
import { createNewAction, createNewRap } from './common';
-import { RapAction, RapClaimActionParameters } from './references';
+import { RapAction, RapClaimRewardsActionParameters } from './references';
-export const createClaimAndBridgeRap = async (claimParameters: RapClaimActionParameters) => {
- let actions: RapAction<'claim' | 'claimBridge'>[] = [];
+export const createClaimRewardsAndBridgeRap = async (claimParameters: RapClaimRewardsActionParameters) => {
+ let actions: RapAction<'claimRewards' | 'claimRewardsBridge'>[] = [];
const { assetToSell, sellAmount, assetToBuy, meta, chainId, toChainId, address, gasParams } = claimParameters;
- const claim = createNewAction('claim', claimParameters);
+ const claim = createNewAction('claimRewards', claimParameters);
actions = actions.concat(claim);
// if we need the bridge
if (chainId !== toChainId && toChainId !== undefined) {
// create a bridge rap
- const bridge = createNewAction('claimBridge', {
+ const bridge = createNewAction('claimRewardsBridge', {
address,
chainId,
toChainId,
@@ -21,7 +21,7 @@ export const createClaimAndBridgeRap = async (claimParameters: RapClaimActionPar
assetToBuy,
quote: undefined,
gasParams,
- } satisfies RapClaimActionParameters);
+ } satisfies RapClaimRewardsActionParameters);
actions = actions.concat(bridge);
}
diff --git a/src/raps/claimTransactionClaimable.ts b/src/raps/claimTransactionClaimable.ts
new file mode 100644
index 00000000000..8cf7be94e60
--- /dev/null
+++ b/src/raps/claimTransactionClaimable.ts
@@ -0,0 +1,13 @@
+import { createNewActionV2, createNewRapV2 } from './common';
+import { RapActionV2, RapParameters } from './references';
+
+export async function createClaimTransactionClaimableRap(parameters: Extract) {
+ let actions: RapActionV2<'claimTransactionClaimableAction'>[] = [];
+
+ const claim = createNewActionV2('claimTransactionClaimableAction', parameters.claimTransactionClaimableActionParameters);
+ actions = actions.concat(claim);
+
+ // create the overall rap
+ const newRap = createNewRapV2(actions);
+ return newRap;
+}
diff --git a/src/raps/common.ts b/src/raps/common.ts
index 477a0f68b46..ed2d75c158d 100644
--- a/src/raps/common.ts
+++ b/src/raps/common.ts
@@ -1,5 +1,5 @@
import { MMKV } from 'react-native-mmkv';
-import { RapAction, RapActionParameterMap, RapActionTypes } from './references';
+import { RapAction, RapActionParameterMap, RapActionParameterMapV2, RapActionTypes, RapActionTypesV2, RapActionV2 } from './references';
import { STORAGE_IDS } from '@/model/mmkv';
import { logger } from '@/logger';
import { EthereumAddress, LegacyGasFeeParamsBySpeed, LegacySelectedGasFee, Records, SelectedGasFee, GasFeeParamsBySpeed } from '@/entities';
@@ -75,12 +75,27 @@ export function createNewAction(type: T, parameters: R
return newAction;
}
+export function createNewActionV2(type: T, parameters: RapActionParameterMapV2[T]): RapActionV2 {
+ const newAction = {
+ parameters,
+ transaction: { confirmed: null, hash: null },
+ type,
+ };
+ return newAction;
+}
+
export function createNewRap(actions: RapAction[]) {
return {
actions,
};
}
+export function createNewRapV2(actions: RapActionV2[]) {
+ return {
+ actions,
+ };
+}
+
export function createNewENSRap(actions: RapENSAction[]) {
return {
actions,
diff --git a/src/raps/execute.ts b/src/raps/execute.ts
index 3ee2c9ec9b4..fe1512342d4 100644
--- a/src/raps/execute.ts
+++ b/src/raps/execute.ts
@@ -5,11 +5,13 @@ import { Signer } from '@ethersproject/abstract-signer';
import { RainbowError, logger } from '@/logger';
-import { claim, swap, unlock } from './actions';
+import { claimRewards, swap, unlock } from './actions';
import { crosschainSwap } from './actions/crosschainSwap';
-import { claimBridge } from './actions/claimBridge';
+import { claimRewardsBridge } from './actions/claimRewardsBridge';
import {
ActionProps,
+ ActionPropsV2,
+ RapResponse,
Rap,
RapAction,
RapActionResponse,
@@ -17,21 +19,29 @@ import {
RapActionTypes,
RapSwapActionParameters,
RapTypes,
+ RapActionTypesV2,
+ RapActionV2,
+ RapV2,
+ RapParameters,
+ RapActionResponseV2,
+ RapResponseV2,
} from './references';
import { createUnlockAndCrosschainSwapRap } from './unlockAndCrosschainSwap';
-import { createClaimAndBridgeRap } from './claimAndBridge';
+import { createClaimRewardsAndBridgeRap } from './claimRewardsAndBridge';
import { createUnlockAndSwapRap } from './unlockAndSwap';
import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities';
import { Screens, TimeToSignOperation, performanceTracking } from '@/state/performance/performance';
import { swapsStore } from '@/state/swaps/swapsStore';
+import { createClaimTransactionClaimableRap } from './claimTransactionClaimable';
+import { claimTransactionClaimable } from './actions/claimTransactionClaimable';
export function createSwapRapByType(
type: T,
swapParameters: RapSwapActionParameters
): Promise<{ actions: RapAction[] }> {
switch (type) {
- case 'claimBridge':
- return createClaimAndBridgeRap(swapParameters as RapSwapActionParameters<'claimBridge'>);
+ case 'claimRewardsBridge':
+ return createClaimRewardsAndBridgeRap(swapParameters as RapSwapActionParameters<'claimRewardsBridge'>);
case 'crosschainSwap':
return createUnlockAndCrosschainSwapRap(swapParameters as RapSwapActionParameters<'crosschainSwap'>);
case 'swap':
@@ -41,16 +51,25 @@ export function createSwapRapByType(
}
}
+export function createRap(parameters: RapParameters): Promise<{ actions: RapActionV2[] }> {
+ switch (parameters.type) {
+ case 'claimTransactionClaimableRap':
+ return createClaimTransactionClaimableRap(parameters);
+ default:
+ return Promise.resolve({ actions: [] });
+ }
+}
+
function typeAction(type: T, props: ActionProps) {
switch (type) {
- case 'claim':
- return () => claim(props as ActionProps<'claim'>);
+ case 'claimRewards':
+ return () => claimRewards(props as ActionProps<'claimRewards'>);
case 'unlock':
return () => unlock(props as ActionProps<'unlock'>);
case 'swap':
return () => swap(props as ActionProps<'swap'>);
- case 'claimBridge':
- return () => claimBridge(props as ActionProps<'claimBridge'>);
+ case 'claimRewardsBridge':
+ return () => claimRewardsBridge(props as ActionProps<'claimRewardsBridge'>);
case 'crosschainSwap':
return () => crosschainSwap(props as ActionProps<'crosschainSwap'>);
default:
@@ -59,6 +78,15 @@ function typeAction(type: T, props: ActionProps) {
}
}
+function typeActionV2(type: T, props: ActionPropsV2) {
+ switch (type) {
+ case 'claimTransactionClaimableAction':
+ return () => claimTransactionClaimable(props as ActionPropsV2<'claimTransactionClaimableAction'>);
+ default:
+ throw new RainbowError(`[raps/execute]: typeActionV2 - unknown type ${type}`);
+ }
+}
+
export async function executeAction({
action,
wallet,
@@ -104,11 +132,47 @@ export async function executeAction({
}
}
+export async function executeActionV2({
+ action,
+ wallet,
+ rap,
+ nonceToUse,
+ rapName,
+}: {
+ action: RapActionV2;
+ wallet: Signer;
+ rap: RapV2;
+ nonceToUse: number | undefined;
+ rapName: string;
+}): Promise {
+ const { type, parameters } = action;
+ try {
+ const actionProps = {
+ wallet,
+ currentRap: rap,
+ parameters,
+ nonceToUse,
+ };
+ const { nonce, hash } = await typeActionV2(type, actionProps)();
+ return { nonce, errorMessage: null, hash };
+ } catch (error) {
+ logger.error(new RainbowError(`[raps/execute]: ${rapName} - error execute action`), {
+ message: (error as Error)?.message,
+ });
+ return { nonce: null, errorMessage: String(error), hash: null };
+ }
+}
+
function getRapFullName(actions: RapAction[]) {
const actionTypes = actions.map(action => action.type);
return actionTypes.join(' + ');
}
+function getRapFullNameV2(actions: RapActionV2[]) {
+ const actionTypes = actions.map(action => action.type);
+ return actionTypes.join(' + ');
+}
+
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
const waitForNodeAck = async (hash: string, provider: Signer['provider']): Promise => {
@@ -125,14 +189,43 @@ const waitForNodeAck = async (hash: string, provider: Signer['provider']): Promi
});
};
+const executeRap = async (wallet: Signer, rap: RapV2): Promise => {
+ const { actions } = rap;
+ const rapName = getRapFullNameV2(rap.actions);
+ let nonceToUse: number | undefined;
+
+ while (actions.length) {
+ const action = actions.shift();
+
+ if (!action) break;
+
+ const { nonce, errorMessage, hash } = await executeActionV2({
+ action,
+ wallet,
+ rap,
+ nonceToUse,
+ rapName,
+ });
+
+ if (errorMessage) return { errorMessage };
+
+ if (typeof nonce === 'number') {
+ actions.length >= 1 && hash && (await waitForNodeAck(hash, wallet.provider));
+ nonceToUse = nonce + 1;
+ }
+ }
+
+ return { errorMessage: null };
+};
+
export const walletExecuteRap = async (
wallet: Signer,
type: RapTypes,
- parameters: RapSwapActionParameters<'swap' | 'crosschainSwap' | 'claimBridge'>
-): Promise<{ nonce: number | undefined; errorMessage: string | null }> => {
- // NOTE: We don't care to track claimBridge raps
+ parameters: RapSwapActionParameters<'swap' | 'crosschainSwap' | 'claimRewardsBridge'>
+): Promise => {
+ // NOTE: We don't care to track claimRewardsBridge raps
const rap =
- type === 'claimBridge'
+ type === 'claimRewardsBridge'
? await createSwapRapByType(type, parameters)
: await performanceTracking.getState().executeFn({
fn: createSwapRapByType,
@@ -188,3 +281,8 @@ export const walletExecuteRap = async (
}
return { nonce, errorMessage };
};
+
+export async function walletExecuteRapV2(wallet: Signer, rapParameters: RapParameters): Promise {
+ const rap = await createRap(rapParameters);
+ return executeRap(wallet, rap);
+}
diff --git a/src/raps/references.ts b/src/raps/references.ts
index 259b8fe330c..28010e693f4 100644
--- a/src/raps/references.ts
+++ b/src/raps/references.ts
@@ -5,6 +5,7 @@ import { Address } from 'viem';
import { ParsedAsset } from '@/__swaps__/types/assets';
import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities';
import { ChainId } from '@/chains/types';
+import { TransactionRequest } from '@ethersproject/abstract-provider';
export enum SwapModalField {
input = 'inputAmount',
@@ -38,10 +39,10 @@ export type SwapMetadata = {
export type QuoteTypeMap = {
swap: Quote;
crosschainSwap: CrosschainQuote;
- claimBridge: undefined;
+ claimRewardsBridge: undefined;
};
-export interface RapSwapActionParameters {
+export interface RapSwapActionParameters {
amount?: string | null;
sellAmount: string;
buyAmount?: string;
@@ -67,7 +68,7 @@ export interface RapUnlockActionParameters {
chainId: number;
}
-export interface RapClaimActionParameters {
+export interface RapClaimRewardsActionParameters {
address?: Address;
assetToSell: ParsedAsset;
sellAmount: string;
@@ -79,10 +80,43 @@ export interface RapClaimActionParameters {
gasParams: TransactionGasParamAmounts | LegacyTransactionGasParamAmounts;
}
+export type TransactionClaimableTxPayload = TransactionRequest &
+ (
+ | {
+ to: string;
+ from: string;
+ nonce: number;
+ gasLimit: string;
+ maxFeePerGas: string;
+ maxPriorityFeePerGas: string;
+ data: string;
+ value: '0x0';
+ chainId: number;
+ }
+ | {
+ to: string;
+ from: string;
+ nonce: number;
+ gasLimit: string;
+ gasPrice: string;
+ data: string;
+ value: '0x0';
+ chainId: number;
+ }
+ );
+
+export interface ClaimTransactionClaimableActionParameters {
+ claimTx: TransactionClaimableTxPayload;
+}
+
+export interface ClaimTransactionClaimableRapParameters {
+ claim: ClaimTransactionClaimableActionParameters;
+}
+
export type RapActionParameters =
| RapSwapActionParameters<'swap'>
| RapSwapActionParameters<'crosschainSwap'>
- | RapClaimActionParameters
+ | RapClaimRewardsActionParameters
| RapUnlockActionParameters;
export interface RapActionTransaction {
@@ -93,8 +127,21 @@ export type RapActionParameterMap = {
swap: RapSwapActionParameters<'swap'>;
crosschainSwap: RapSwapActionParameters<'crosschainSwap'>;
unlock: RapUnlockActionParameters;
- claim: RapClaimActionParameters;
- claimBridge: RapClaimActionParameters;
+ claimRewards: RapClaimRewardsActionParameters;
+ claimRewardsBridge: RapClaimRewardsActionParameters;
+};
+
+export type RapActionParameterMapV2 = {
+ claimTransactionClaimableAction: ClaimTransactionClaimableActionParameters;
+};
+
+export type RapParameterMapV2 = {
+ claimTransactionClaimableRap: ClaimTransactionClaimableRapParameters;
+};
+
+export type RapParameters = {
+ type: 'claimTransactionClaimableRap';
+ claimTransactionClaimableActionParameters: ClaimTransactionClaimableActionParameters;
};
export interface RapAction {
@@ -103,39 +150,73 @@ export interface RapAction {
type: T;
}
+export interface RapActionV2 {
+ parameters: RapActionParameterMapV2[T];
+ transaction: RapActionTransaction;
+ type: T;
+}
+
export interface Rap {
- actions: RapAction<'swap' | 'crosschainSwap' | 'unlock' | 'claim' | 'claimBridge'>[];
+ actions: RapAction<'swap' | 'crosschainSwap' | 'unlock' | 'claimRewards' | 'claimRewardsBridge'>[];
+}
+
+export interface RapV2 {
+ actions: RapActionV2<'claimTransactionClaimableAction'>[];
}
export enum rapActions {
swap = 'swap',
crosschainSwap = 'crosschainSwap',
unlock = 'unlock',
- claim = 'claim',
- claimBridge = 'claimBridge',
+ claimRewards = 'claimRewards',
+ claimRewardsBridge = 'claimRewardsBridge',
+}
+
+export enum rapActionsV2 {
+ claimTransactionClaimableAction = 'claimTransactionClaimableAction',
}
export type RapActionTypes = keyof typeof rapActions;
+export type RapActionTypesV2 = keyof typeof rapActionsV2;
+
export enum rapTypes {
swap = 'swap',
crosschainSwap = 'crosschainSwap',
- claimBridge = 'claimBridge',
+ claimRewardsBridge = 'claimRewardsBridge',
+}
+
+export enum rapTypesV2 {
+ claimTransactionClaimableRap = 'claimTransactionClaimableRap',
+ claimSponsoredClaimableRap = 'claimSponsoredClaimableRap',
}
export type RapTypes = keyof typeof rapTypes;
+export type RapTypesV2 = keyof typeof rapTypesV2;
+
export interface RapActionResponse {
baseNonce?: number | null;
errorMessage: string | null;
hash?: string | null;
}
+export interface RapActionResponseV2 {
+ nonce: number | null;
+ errorMessage: string | null;
+ hash: string | null;
+}
+
export interface RapActionResult {
nonce?: number | undefined;
hash?: string | undefined;
}
+export interface RapActionResultV2 {
+ nonce: number | null;
+ hash: string | null;
+}
+
export interface ActionProps {
baseNonce?: number;
index: number;
@@ -146,7 +227,23 @@ export interface ActionProps {
gasFeeParamsBySpeed: GasFeeParamsBySpeed | LegacyGasFeeParamsBySpeed;
}
+export interface ActionPropsV2 {
+ nonceToUse?: number;
+ parameters: RapActionParameterMapV2[T];
+ wallet: Signer;
+ currentRap: RapV2;
+}
+
export interface WalletExecuteRapProps {
- rapActionParameters: RapSwapActionParameters<'swap' | 'crosschainSwap' | 'claimBridge'>;
+ rapActionParameters: RapSwapActionParameters<'swap' | 'crosschainSwap' | 'claimRewardsBridge'>;
type: RapTypes;
}
+
+export interface RapResponse {
+ nonce: number | undefined;
+ errorMessage: string | null;
+}
+
+export interface RapResponseV2 {
+ errorMessage: string | null;
+}
diff --git a/src/rapsV2/actions/claimTransactionClaimableAction.ts b/src/rapsV2/actions/claimTransactionClaimableAction.ts
new file mode 100644
index 00000000000..80f3f088ca2
--- /dev/null
+++ b/src/rapsV2/actions/claimTransactionClaimableAction.ts
@@ -0,0 +1,42 @@
+import { ActionProps } from '../references';
+import { sendTransaction } from '@/model/wallet';
+import { getProvider } from '@/handlers/web3';
+import { RainbowError } from '@/logger';
+import { addNewTransaction } from '@/state/pendingTransactions';
+import { NewTransaction } from '@/entities';
+import { chainsName } from '@/chains';
+
+export async function claimTransactionClaimable({ parameters, wallet }: ActionProps<'claimTransactionClaimableAction'>) {
+ const { claimTx } = parameters;
+
+ const provider = getProvider({ chainId: claimTx.chainId });
+ const result = await sendTransaction({ transaction: claimTx, existingWallet: wallet, provider });
+
+ if (!result?.result || !!result.error || !result.result.hash) {
+ throw new RainbowError('[CLAIM-TRANSACTION-CLAIMABLE]: failed to execute claim transaction');
+ }
+
+ const transaction = {
+ amount: result.result.value.toString(),
+ gasLimit: result.result.gasLimit,
+ from: result.result.from ?? null,
+ to: result.result.to ?? null,
+ chainId: result.result.chainId,
+ hash: result.result.hash,
+ network: chainsName[result.result.chainId],
+ status: 'pending',
+ type: 'send',
+ nonce: result.result.nonce,
+ } satisfies NewTransaction;
+
+ addNewTransaction({
+ address: claimTx.from,
+ chainId: claimTx.chainId,
+ transaction,
+ });
+
+ return {
+ nonce: result.result.nonce,
+ hash: result.result.hash,
+ };
+}
diff --git a/src/rapsV2/actions/index.ts b/src/rapsV2/actions/index.ts
new file mode 100644
index 00000000000..75323a946a5
--- /dev/null
+++ b/src/rapsV2/actions/index.ts
@@ -0,0 +1 @@
+export { claimTransactionClaimable } from './claimTransactionClaimableAction';
diff --git a/src/rapsV2/claimTransactionClaimableRap.ts b/src/rapsV2/claimTransactionClaimableRap.ts
new file mode 100644
index 00000000000..f91b2b6950a
--- /dev/null
+++ b/src/rapsV2/claimTransactionClaimableRap.ts
@@ -0,0 +1,13 @@
+import { createNewAction, createNewRap } from './common';
+import { RapAction, RapParameters } from './references';
+
+export async function createClaimTransactionClaimableRap(parameters: Extract) {
+ let actions: RapAction<'claimTransactionClaimableAction'>[] = [];
+
+ const claim = createNewAction('claimTransactionClaimableAction', parameters.claimTransactionClaimableActionParameters);
+ actions = actions.concat(claim);
+
+ // create the overall rap
+ const newRap = createNewRap(actions);
+ return newRap;
+}
diff --git a/src/rapsV2/common.ts b/src/rapsV2/common.ts
new file mode 100644
index 00000000000..506a165adcd
--- /dev/null
+++ b/src/rapsV2/common.ts
@@ -0,0 +1,20 @@
+import { RapAction, RapActionParameterMap, RapActionTypes } from './references';
+
+export interface RapActionTransaction {
+ hash: string | null;
+}
+
+export function createNewAction(type: T, parameters: RapActionParameterMap[T]): RapAction {
+ const newAction = {
+ parameters,
+ transaction: { confirmed: null, hash: null },
+ type,
+ };
+ return newAction;
+}
+
+export function createNewRap(actions: RapAction[]) {
+ return {
+ actions,
+ };
+}
diff --git a/src/rapsV2/execute.ts b/src/rapsV2/execute.ts
new file mode 100644
index 00000000000..cdbdc1ca8c7
--- /dev/null
+++ b/src/rapsV2/execute.ts
@@ -0,0 +1,121 @@
+/* eslint-disable no-await-in-loop */
+/* eslint-disable no-async-promise-executor */
+/* eslint-disable no-promise-executor-return */
+import { Signer } from '@ethersproject/abstract-signer';
+
+import { RainbowError, logger } from '@/logger';
+
+import { ActionProps, RapResponse, Rap, RapAction, RapActionResponse, RapActionTypes, RapParameters } from './references';
+import { createClaimTransactionClaimableRap } from './claimTransactionClaimableRap';
+import { claimTransactionClaimable } from './actions/claimTransactionClaimableAction';
+import { delay } from '@/utils/delay';
+
+// get the rap by type
+export function createRap(parameters: RapParameters): Promise<{ actions: RapAction[] }> {
+ switch (parameters.type) {
+ case 'claimTransactionClaimableRap':
+ return createClaimTransactionClaimableRap(parameters);
+ default:
+ return Promise.resolve({ actions: [] });
+ }
+}
+
+// get the action executable by type
+function getActionExecutableByType(type: T, props: ActionProps) {
+ switch (type) {
+ case 'claimTransactionClaimableAction':
+ return () => claimTransactionClaimable(props);
+ default:
+ throw new RainbowError(`[rapsV2/execute]: T - unknown action type ${type}`);
+ }
+}
+
+// executes a single action in the rap
+// if the action executes a tx on-chain, it will return the nonce it used
+// if an error occurs, we return the error message
+export async function executeAction({
+ action,
+ wallet,
+ rap,
+ nonceToUse,
+ rapName,
+}: {
+ action: RapAction;
+ wallet: Signer;
+ rap: Rap;
+ nonceToUse: number | undefined;
+ rapName: string;
+}): Promise {
+ const { type, parameters } = action;
+ try {
+ const actionProps = {
+ wallet,
+ currentRap: rap,
+ parameters,
+ nonceToUse,
+ };
+ const { nonce, hash } = await getActionExecutableByType(type, actionProps)();
+ return { nonce, errorMessage: null, hash };
+ } catch (error) {
+ logger.error(new RainbowError(`[rapsV2/execute]: ${rapName} - error execute action`), {
+ message: (error as Error)?.message,
+ });
+ return { nonce: null, errorMessage: String(error), hash: null };
+ }
+}
+
+function getRapFullName(actions: RapAction[]) {
+ const actionTypes = actions.map(action => action.type);
+ return actionTypes.join(' + ');
+}
+
+const waitForNodeAck = async (hash: string, provider: Signer['provider']): Promise => {
+ return new Promise(async resolve => {
+ const tx = await provider?.getTransaction(hash);
+ // This means the node is aware of the tx, we're good to go
+ if ((tx && tx.blockNumber === null) || (tx && tx?.blockNumber && tx?.blockNumber > 0)) {
+ resolve();
+ } else {
+ // Wait for 1 second and try again
+ await delay(1000);
+ return waitForNodeAck(hash, provider);
+ }
+ });
+};
+
+// goes through each action in the rap and executes it
+// if an action executes a tx on-chain, increment the nonceToUse for the next tx
+// if an action fails, it will return the error message
+const executeRap = async (wallet: Signer, rap: Rap): Promise => {
+ const { actions } = rap;
+ const rapName = getRapFullName(rap.actions);
+ let nonceToUse: number | undefined;
+
+ while (actions.length) {
+ const action = actions.shift();
+
+ if (!action) break;
+
+ const { nonce, errorMessage, hash } = await executeAction({
+ action,
+ wallet,
+ rap,
+ nonceToUse,
+ rapName,
+ });
+
+ if (errorMessage) return { errorMessage };
+
+ if (typeof nonce === 'number') {
+ actions.length >= 1 && hash && (await waitForNodeAck(hash, wallet.provider));
+ nonceToUse = nonce + 1;
+ }
+ }
+
+ return { errorMessage: null };
+};
+
+export async function walletExecuteRap(wallet: Signer, rapParameters: RapParameters): Promise {
+ const rap = await createRap(rapParameters);
+ return executeRap(wallet, rap);
+}
diff --git a/src/rapsV2/references.ts b/src/rapsV2/references.ts
new file mode 100644
index 00000000000..2f3acc7007f
--- /dev/null
+++ b/src/rapsV2/references.ts
@@ -0,0 +1,83 @@
+import { Signer } from '@ethersproject/abstract-signer';
+import { TransactionRequest } from '@ethersproject/abstract-provider';
+
+// supports legacy and new gas types
+export type TransactionClaimableTxPayload = TransactionRequest &
+ (
+ | {
+ to: string;
+ from: string;
+ nonce: number;
+ gasLimit: string;
+ maxFeePerGas: string;
+ maxPriorityFeePerGas: string;
+ data: string;
+ value: '0x0';
+ chainId: number;
+ }
+ | {
+ to: string;
+ from: string;
+ nonce: number;
+ gasLimit: string;
+ gasPrice: string;
+ data: string;
+ value: '0x0';
+ chainId: number;
+ }
+ );
+
+export interface ClaimTransactionClaimableActionParameters {
+ claimTx: TransactionClaimableTxPayload;
+}
+
+export interface RapActionTransaction {
+ hash: string | null;
+}
+
+export type RapActionParameterMap = {
+ claimTransactionClaimableAction: ClaimTransactionClaimableActionParameters;
+};
+
+export type RapParameters = {
+ type: 'claimTransactionClaimableRap';
+ claimTransactionClaimableActionParameters: ClaimTransactionClaimableActionParameters;
+};
+
+export interface RapAction {
+ parameters: RapActionParameterMap[T];
+ transaction: RapActionTransaction;
+ type: T;
+}
+
+export interface Rap {
+ actions: RapAction<'claimTransactionClaimableAction'>[];
+}
+
+export enum rapActions {
+ claimTransactionClaimableAction = 'claimTransactionClaimableAction',
+}
+
+export type RapActionTypes = keyof typeof rapActions;
+
+export enum rapTypes {
+ claimTransactionClaimableRap = 'claimTransactionClaimableRap',
+}
+
+export type RapTypes = keyof typeof rapTypes;
+
+export interface RapActionResponse {
+ nonce: number | null | undefined;
+ errorMessage: string | null;
+ hash: string | null | undefined;
+}
+
+export interface ActionProps {
+ nonceToUse: number | undefined;
+ parameters: RapActionParameterMap[T];
+ wallet: Signer;
+}
+
+export interface RapResponse {
+ errorMessage: string | null;
+}
diff --git a/src/resources/addys/claimables/query.ts b/src/resources/addys/claimables/query.ts
index 10aaa2bfa16..7dda29953aa 100644
--- a/src/resources/addys/claimables/query.ts
+++ b/src/resources/addys/claimables/query.ts
@@ -11,8 +11,10 @@ import { CLAIMABLES, useExperimentalFlag } from '@/config';
import { IS_TEST } from '@/env';
import { SUPPORTED_CHAIN_IDS } from '@/chains';
-const addysHttp = new RainbowFetchClient({
- baseURL: 'https://addys.p.rainbow.me/v3',
+export const ADDYS_BASE_URL = 'https://addys.p.rainbow.me/v3';
+
+export const addysHttp = new RainbowFetchClient({
+ baseURL: ADDYS_BASE_URL,
headers: {
Authorization: `Bearer ${ADDYS_API_KEY}`,
},
diff --git a/src/resources/addys/claimables/types.ts b/src/resources/addys/claimables/types.ts
index 814a4bfc869..28538833b69 100644
--- a/src/resources/addys/claimables/types.ts
+++ b/src/resources/addys/claimables/types.ts
@@ -1,6 +1,6 @@
-import { ChainId } from '@rainbow-me/swaps';
import { Address } from 'viem';
import { AddysAsset, AddysConsolidatedError, AddysResponseStatus } from '../types';
+import { ChainId } from '@/chains/types';
interface Colors {
primary: string;
@@ -28,20 +28,33 @@ interface DApp {
colors: Colors;
}
-type ClaimableType = 'transaction' | 'sponsored';
-
-export interface AddysClaimable {
+interface AddysBaseClaimable {
name: string;
unique_id: string;
- type: ClaimableType;
+ type: string;
network: ChainId;
asset: AddysAsset;
amount: string;
dapp: DApp;
- claim_action_type?: string | null;
+}
+
+interface AddysTransactionClaimable extends AddysBaseClaimable {
+ claim_action_type: 'transaction';
+ claim_action: ClaimActionTransaction[];
+}
+
+interface AddysSponsoredClaimable extends AddysBaseClaimable {
+ claim_action_type: 'sponsored';
+ claim_action: ClaimActionSponsored[];
+}
+
+interface AddysUnsupportedClaimable extends AddysBaseClaimable {
+ claim_action_type?: 'unknown' | null;
claim_action?: ClaimAction[];
}
+export type AddysClaimable = AddysTransactionClaimable | AddysSponsoredClaimable | AddysUnsupportedClaimable;
+
interface ConsolidatedClaimablesPayloadResponse {
claimables: AddysClaimable[];
}
@@ -61,8 +74,13 @@ export interface ConsolidatedClaimablesResponse {
payload: ConsolidatedClaimablesPayloadResponse;
}
-// will add more attributes as needed
-export interface Claimable {
+interface BaseClaimable {
+ asset: {
+ iconUrl: string;
+ name: string;
+ symbol: string;
+ };
+ chainId: ChainId;
name: string;
uniqueId: string;
iconUrl: string;
@@ -71,3 +89,41 @@ export interface Claimable {
nativeAsset: { amount: string; display: string };
};
}
+
+export interface TransactionClaimable extends BaseClaimable {
+ type: 'transaction';
+ action: { to: Address; data: string };
+}
+
+export interface SponsoredClaimable extends BaseClaimable {
+ type: 'sponsored';
+ action: { url: string; method: string };
+}
+
+export type Claimable = TransactionClaimable | SponsoredClaimable;
+
+interface ClaimTransactionStatus {
+ network: ChainId;
+ transaction_hash: string;
+ explorer_url: string;
+ sponsored_status: string;
+}
+
+interface ClaimPayloadResponse {
+ success: boolean;
+ claimable: Claimable | null;
+ claim_transaction_status: ClaimTransactionStatus | null;
+}
+
+interface ClaimMetadataResponse {
+ address: string;
+ chain_id: ChainId;
+ currency: string;
+ claim_type: string;
+ error: string;
+}
+
+export interface ClaimResponse {
+ metadata: ClaimMetadataResponse;
+ payload: ClaimPayloadResponse;
+}
diff --git a/src/resources/addys/claimables/utils.ts b/src/resources/addys/claimables/utils.ts
index 30b0395cc87..71b6122a3f1 100644
--- a/src/resources/addys/claimables/utils.ts
+++ b/src/resources/addys/claimables/utils.ts
@@ -1,17 +1,50 @@
import { NativeCurrencyKey } from '@/entities';
import { AddysClaimable, Claimable } from './types';
-import { convertRawAmountToBalance, convertRawAmountToNativeDisplay, greaterThan, lessThan } from '@/helpers/utilities';
+import { convertRawAmountToBalance, convertRawAmountToNativeDisplay, greaterThan } from '@/helpers/utilities';
export const parseClaimables = (claimables: AddysClaimable[], currency: NativeCurrencyKey): Claimable[] => {
return claimables
- .map(claimable => ({
- name: claimable.name,
- uniqueId: claimable.unique_id,
- iconUrl: claimable.dapp.icon_url,
- value: {
- claimAsset: convertRawAmountToBalance(claimable.amount, claimable.asset),
- nativeAsset: convertRawAmountToNativeDisplay(claimable.amount, claimable.asset.decimals, claimable.asset.price.value, currency),
- },
- }))
+ .map(claimable => {
+ if (
+ !(claimable.claim_action_type === 'transaction' || claimable.claim_action_type === 'sponsored') ||
+ !claimable.claim_action?.length
+ ) {
+ return undefined;
+ }
+
+ const baseClaimable = {
+ asset: {
+ iconUrl: claimable.asset.icon_url,
+ name: claimable.asset.name,
+ symbol: claimable.asset.symbol,
+ },
+ chainId: claimable.network,
+ name: claimable.name,
+ uniqueId: claimable.unique_id,
+ iconUrl: claimable.dapp.icon_url,
+ value: {
+ claimAsset: convertRawAmountToBalance(claimable.amount, claimable.asset),
+ nativeAsset: convertRawAmountToNativeDisplay(claimable.amount, claimable.asset.decimals, claimable.asset.price.value, currency),
+ },
+ };
+
+ if (claimable.claim_action_type === 'transaction') {
+ return {
+ ...baseClaimable,
+ type: 'transaction' as const,
+ action: {
+ to: claimable.claim_action[0].address_to,
+ data: claimable.claim_action[0].calldata,
+ },
+ };
+ } else if (claimable.claim_action_type === 'sponsored') {
+ return {
+ ...baseClaimable,
+ type: 'sponsored' as const,
+ action: { method: claimable.claim_action[0].method, url: claimable.claim_action[0].url },
+ };
+ }
+ })
+ .filter((c): c is Claimable => !!c)
.sort((a, b) => (greaterThan(a.value.claimAsset.amount ?? '0', b.value.claimAsset.amount ?? '0') ? -1 : 1));
};
diff --git a/src/resources/defi/PositionsQuery.ts b/src/resources/defi/PositionsQuery.ts
index 46e935d00ed..0bd90cffb14 100644
--- a/src/resources/defi/PositionsQuery.ts
+++ b/src/resources/defi/PositionsQuery.ts
@@ -8,6 +8,8 @@ import { ADDYS_API_KEY } from 'react-native-dotenv';
import { AddysPositionsResponse, PositionsArgs } from './types';
import { parsePositions } from './utils';
import { SUPPORTED_CHAIN_IDS } from '@/chains';
+import { DEFI_POSITIONS, useExperimentalFlag } from '@/config';
+import { IS_TEST } from '@/env';
export const buildPositionsUrl = (address: string) => {
const networkString = SUPPORTED_CHAIN_IDS.join(',');
@@ -77,5 +79,9 @@ export async function fetchPositions(
// Query Hook
export function usePositions({ address, currency }: PositionsArgs, config: QueryConfig = {}) {
- return useQuery(positionsQueryKey({ address, currency }), positionsQueryFunction, { ...config, enabled: !!address });
+ const positionsEnabled = useExperimentalFlag(DEFI_POSITIONS);
+ return useQuery(positionsQueryKey({ address, currency }), positionsQueryFunction, {
+ ...config,
+ enabled: !!(address && positionsEnabled && !IS_TEST),
+ });
}
diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx
index 7b00c9e94e7..391dae64608 100644
--- a/src/screens/WalletScreen/index.tsx
+++ b/src/screens/WalletScreen/index.tsx
@@ -22,13 +22,11 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { analyticsV2 } from '@/analytics';
import { AppState } from '@/redux/store';
import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom';
-import { usePositions } from '@/resources/defi/PositionsQuery';
import styled from '@/styled-thing';
import { IS_ANDROID } from '@/env';
import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync';
import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync';
import { UserAssetsSync } from '@/state/sync/UserAssetsSync';
-import { useClaimables } from '@/resources/addys/claimables/query';
import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener';
import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents';
@@ -44,9 +42,7 @@ const WalletScreen: React.FC = ({ navigation, route }) => {
const removeFirst = useRemoveFirst();
const [initialized, setInitialized] = useState(!!params?.initialized);
const initializeWallet = useInitializeWallet();
- const { network: currentNetwork, accountAddress, appIcon, nativeCurrency } = useAccountSettings();
- usePositions({ address: accountAddress, currency: nativeCurrency });
- useClaimables({ address: accountAddress, currency: nativeCurrency });
+ const { network: currentNetwork, accountAddress, appIcon } = useAccountSettings();
const loadAccountLateData = useLoadAccountLateData();
const loadGlobalLateData = useLoadGlobalLateData();
diff --git a/src/screens/claimables/ClaimClaimablePanel.tsx b/src/screens/claimables/ClaimClaimablePanel.tsx
new file mode 100644
index 00000000000..8d940e09bbc
--- /dev/null
+++ b/src/screens/claimables/ClaimClaimablePanel.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { RouteProp, useRoute } from '@react-navigation/native';
+import { Claimable } from '@/resources/addys/claimables/types';
+import { ClaimingTransactionClaimable } from './ClaimingTransactionClaimable';
+import { ClaimingSponsoredClaimable } from './ClaimingSponsoredClaimable';
+
+type RouteParams = {
+ ClaimClaimablePanelParams: { claimable: Claimable };
+};
+
+export const ClaimClaimablePanel = () => {
+ const {
+ params: { claimable },
+ } = useRoute>();
+
+ return claimable.type === 'transaction' ? (
+
+ ) : (
+
+ );
+};
diff --git a/src/screens/claimables/ClaimingClaimableSharedUI.tsx b/src/screens/claimables/ClaimingClaimableSharedUI.tsx
new file mode 100644
index 00000000000..4f1df19f903
--- /dev/null
+++ b/src/screens/claimables/ClaimingClaimableSharedUI.tsx
@@ -0,0 +1,225 @@
+import React, { useMemo } from 'react';
+import { AccentColorProvider, Bleed, Box, Inline, Text, TextShadow, globalColors, useColorMode } from '@/design-system';
+import * as i18n from '@/languages';
+import { ListHeader, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel';
+import { safeAreaInsetValues } from '@/utils';
+import { View } from 'react-native';
+import { IS_IOS } from '@/env';
+import { ButtonPressAnimation } from '@/components/animations';
+import { SponsoredClaimable, TransactionClaimable } from '@/resources/addys/claimables/types';
+import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon';
+import { useTheme } from '@/theme';
+import { FasterImageView } from '@candlefinance/faster-image';
+import { chainsLabel } from '@/chains';
+import { useNavigation } from '@/navigation';
+import { TextColor } from '@/design-system/color/palettes';
+
+export type ClaimStatus = 'idle' | 'claiming' | 'success' | 'error';
+
+export const ClaimingClaimableSharedUI = ({
+ claim,
+ claimable,
+ claimStatus,
+ hasSufficientFunds,
+ isGasReady,
+ isTransactionReady,
+ nativeCurrencyGasFeeDisplay,
+ setClaimStatus,
+}:
+ | {
+ claim: () => void;
+ claimable: TransactionClaimable;
+ claimStatus: ClaimStatus;
+ hasSufficientFunds: boolean;
+ isGasReady: boolean;
+ isTransactionReady: boolean;
+ nativeCurrencyGasFeeDisplay: string;
+ setClaimStatus: React.Dispatch>;
+ }
+ | {
+ claim: () => void;
+ claimable: SponsoredClaimable;
+ claimStatus: ClaimStatus;
+ hasSufficientFunds?: never;
+ isGasReady?: never;
+ isTransactionReady?: never;
+ nativeCurrencyGasFeeDisplay?: never;
+ setClaimStatus: React.Dispatch>;
+ }) => {
+ const { isDarkMode } = useColorMode();
+ const theme = useTheme();
+ const { goBack } = useNavigation();
+
+ const isButtonDisabled =
+ claimStatus === 'claiming' || (claimStatus !== 'success' && claimable.type === 'transaction' && !isTransactionReady);
+ const shouldShowClaimText =
+ (claimStatus === 'idle' || claimStatus === 'claiming') && (claimable.type !== 'transaction' || hasSufficientFunds);
+
+ const buttonLabel = useMemo(() => {
+ switch (claimStatus) {
+ case 'idle':
+ if (shouldShowClaimText) {
+ return `Claim ${claimable.value.claimAsset.display}`;
+ } else {
+ return 'Insufficient Funds';
+ }
+ case 'claiming':
+ return `Claim ${claimable.value.claimAsset.display}`;
+ case 'success':
+ return i18n.t(i18n.l.button.done);
+ case 'error':
+ default:
+ return i18n.t(i18n.l.points.points.try_again);
+ }
+ }, [claimStatus, claimable.value.claimAsset.display, shouldShowClaimText]);
+
+ const panelTitle = useMemo(() => {
+ switch (claimStatus) {
+ case 'idle':
+ return 'Claim';
+ case 'claiming':
+ return 'Claiming...';
+ case 'success':
+ return 'Claimed!';
+ case 'error':
+ default:
+ return i18n.t(i18n.l.points.points.error_claiming);
+ }
+ }, [claimStatus]);
+
+ const panelTitleColor: TextColor = useMemo(() => {
+ switch (claimStatus) {
+ case 'idle':
+ case 'claiming':
+ return 'label';
+ case 'success':
+ return 'green';
+ case 'error':
+ default:
+ return 'red';
+ }
+ }, [claimStatus]);
+
+ return (
+ <>
+
+
+
+
+
+
+ {panelTitle}
+
+
+
+ }
+ showBackButton={false}
+ />
+
+
+
+
+
+
+
+
+
+
+ {claimable.value.nativeAsset.display}
+
+
+
+
+ {/* TODO: needs shimmer when claimStatus === 'claiming' */}
+ {
+ if (claimStatus === 'idle' || claimStatus === 'error') {
+ setClaimStatus('claiming');
+ claim();
+ } else if (claimStatus === 'success') {
+ goBack();
+ }
+ }}
+ >
+
+
+
+ {shouldShowClaimText && (
+
+
+
+
+
+ )}
+
+
+ {buttonLabel}
+
+
+
+
+
+
+ {claimable.type === 'transaction' &&
+ (isGasReady ? (
+
+
+
+
+
+ {`${nativeCurrencyGasFeeDisplay} to claim on ${chainsLabel[claimable.chainId]}`}
+
+
+ ) : (
+
+ Calculating gas fee...
+
+ ))}
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/screens/claimables/ClaimingSponsoredClaimable.tsx b/src/screens/claimables/ClaimingSponsoredClaimable.tsx
new file mode 100644
index 00000000000..3ace6ca3361
--- /dev/null
+++ b/src/screens/claimables/ClaimingSponsoredClaimable.tsx
@@ -0,0 +1,84 @@
+import React, { useState } from 'react';
+import { useAccountSettings } from '@/hooks';
+import { ClaimResponse, SponsoredClaimable } from '@/resources/addys/claimables/types';
+import { ADDYS_BASE_URL, addysHttp, claimablesQueryKey, useClaimables } from '@/resources/addys/claimables/query';
+import { useMutation } from '@tanstack/react-query';
+import { loadWallet } from '@/model/wallet';
+import { getProvider } from '@/handlers/web3';
+import { logger, RainbowError } from '@/logger';
+import { queryClient } from '@/react-query';
+import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI';
+
+export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: SponsoredClaimable }) => {
+ const { accountAddress, nativeCurrency } = useAccountSettings();
+
+ const { refetch } = useClaimables({ address: accountAddress, currency: nativeCurrency });
+
+ const [claimStatus, setClaimStatus] = useState('idle');
+
+ const { mutate: claimClaimable } = useMutation({
+ mutationFn: async () => {
+ const provider = getProvider({ chainId: claimable.chainId });
+ const wallet = await loadWallet({
+ address: accountAddress,
+ showErrorIfNotLoaded: true,
+ provider,
+ });
+
+ if (!wallet) {
+ // Biometrics auth failure (retry possible)
+ setClaimStatus('error');
+ return;
+ }
+
+ const path = claimable.action.url.replace(ADDYS_BASE_URL, '');
+ let response: { data: ClaimResponse };
+
+ if (claimable.action.method === 'GET') {
+ try {
+ response = await addysHttp.get(path);
+ } catch (e) {
+ setClaimStatus('error');
+ logger.error(new RainbowError('[ClaimSponsoredClaimable]: failed to execute sponsored claim api call'));
+ return;
+ }
+ } else {
+ try {
+ response = await addysHttp.post(path);
+ } catch (e) {
+ setClaimStatus('error');
+ logger.error(new RainbowError('[ClaimSponsoredClaimable]: failed to execute sponsored claim api call'));
+ return;
+ }
+ }
+
+ if (!response.data.payload.success) {
+ setClaimStatus('error');
+ logger.warn('[ClaimSponsoredClaimable]: sponsored claim api call returned unsuccessful response');
+ } else {
+ setClaimStatus('success');
+ // Clear and refresh claimables data
+ queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency }));
+ refetch();
+ }
+ },
+ onError: e => {
+ setClaimStatus('error');
+ logger.error(new RainbowError('[ClaimingSponsoredClaimable]: Failed to claim claimable due to unhandled error'), {
+ message: (e as Error)?.message,
+ });
+ },
+ onSuccess: () => {
+ if (claimStatus === 'claiming') {
+ logger.error(
+ new RainbowError('[ClaimingSponsoredClaimable]: claim function completed but never resolved status to success or error state')
+ );
+ setClaimStatus('error');
+ }
+ },
+ });
+
+ return (
+
+ );
+};
diff --git a/src/screens/claimables/ClaimingTransactionClaimable.tsx b/src/screens/claimables/ClaimingTransactionClaimable.tsx
new file mode 100644
index 00000000000..1dd828ef27e
--- /dev/null
+++ b/src/screens/claimables/ClaimingTransactionClaimable.tsx
@@ -0,0 +1,178 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { useAccountSettings, useGas } from '@/hooks';
+import { ethereumUtils } from '@/utils';
+import { TransactionClaimable } from '@/resources/addys/claimables/types';
+import { claimablesQueryKey, useClaimables } from '@/resources/addys/claimables/query';
+import { useMutation } from '@tanstack/react-query';
+import { loadWallet } from '@/model/wallet';
+import { estimateGasWithPadding, getProvider } from '@/handlers/web3';
+import { parseGasParamsForTransaction } from '@/parsers';
+import { getNextNonce } from '@/state/nonces';
+import { needsL1SecurityFeeChains } from '@/chains';
+import { logger, RainbowError } from '@/logger';
+import { convertAmountToNativeDisplay } from '@/helpers/utilities';
+import { queryClient } from '@/react-query';
+import { TransactionClaimableTxPayload } from '@/raps/references';
+import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI';
+import { walletExecuteRap } from '@/rapsV2/execute';
+
+export const ClaimingTransactionClaimable = ({ claimable }: { claimable: TransactionClaimable }) => {
+ const { accountAddress, nativeCurrency } = useAccountSettings();
+ const { isGasReady, isSufficientGas, isValidGas, selectedGasFee, startPollingGasFees, stopPollingGasFees, updateTxFee } = useGas();
+
+ const [baseTxPayload, setBaseTxPayload] = useState<
+ Omit | undefined
+ >();
+ const [txPayload, setTxPayload] = useState();
+ const [claimStatus, setClaimStatus] = useState('idle');
+
+ const { refetch } = useClaimables({ address: accountAddress, currency: nativeCurrency });
+
+ const provider = useMemo(() => getProvider({ chainId: claimable.chainId }), [claimable.chainId]);
+
+ const buildTxPayload = useCallback(async () => {
+ const payload = {
+ value: '0x0' as const,
+ data: claimable.action.data,
+ from: accountAddress,
+ chainId: claimable.chainId,
+ nonce: await getNextNonce({ address: accountAddress, chainId: claimable.chainId }),
+ to: claimable.action.to,
+ };
+
+ setBaseTxPayload(payload);
+ }, [accountAddress, claimable.action.to, claimable.action.data, claimable.chainId, setBaseTxPayload]);
+
+ useEffect(() => {
+ buildTxPayload();
+ }, [buildTxPayload]);
+
+ useEffect(() => {
+ startPollingGasFees();
+ return () => {
+ stopPollingGasFees();
+ };
+ }, [startPollingGasFees, stopPollingGasFees]);
+
+ const estimateGas = useCallback(async () => {
+ if (!baseTxPayload) {
+ logger.error(new RainbowError('[ClaimingTransactionClaimable]: attempted to estimate gas without a tx payload'));
+ return;
+ }
+
+ const gasParams = parseGasParamsForTransaction(selectedGasFee);
+ const updatedTxPayload = { ...baseTxPayload, ...gasParams };
+
+ const gasLimit = await estimateGasWithPadding(updatedTxPayload, null, null, provider);
+
+ if (!gasLimit) {
+ updateTxFee(null, null);
+ logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to estimate gas limit'));
+ return;
+ }
+
+ if (needsL1SecurityFeeChains.includes(claimable.chainId)) {
+ const l1SecurityFee = await ethereumUtils.calculateL1FeeOptimism(
+ // @ts-expect-error - type mismatch, but this tx request structure is the same as in SendSheet.js
+ {
+ to: claimable.action.to,
+ from: accountAddress,
+ value: '0x0',
+ data: claimable.action.data,
+ },
+ provider
+ );
+
+ if (!l1SecurityFee) {
+ updateTxFee(null, null);
+ logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to calculate L1 security fee'));
+ return;
+ }
+
+ updateTxFee(gasLimit, null, l1SecurityFee);
+ } else {
+ updateTxFee(gasLimit, null);
+ }
+
+ setTxPayload({ ...updatedTxPayload, gasLimit });
+ }, [baseTxPayload, selectedGasFee, provider, claimable.chainId, claimable.action.to, claimable.action.data, updateTxFee, accountAddress]);
+
+ useEffect(() => {
+ if (baseTxPayload) {
+ estimateGas();
+ }
+ }, [baseTxPayload, estimateGas, selectedGasFee]);
+
+ const isTransactionReady = !!(isGasReady && isSufficientGas && isValidGas && txPayload);
+
+ const nativeCurrencyGasFeeDisplay = useMemo(
+ () => convertAmountToNativeDisplay(selectedGasFee?.gasFee?.estimatedFee?.native?.value?.amount, nativeCurrency),
+ [nativeCurrency, selectedGasFee?.gasFee?.estimatedFee?.native?.value?.amount]
+ );
+
+ const { mutate: claimClaimable } = useMutation({
+ mutationFn: async () => {
+ if (!txPayload) {
+ setClaimStatus('error');
+ logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to missing tx payload'));
+ return;
+ }
+
+ const wallet = await loadWallet({
+ address: accountAddress,
+ showErrorIfNotLoaded: false,
+ provider,
+ });
+
+ if (!wallet) {
+ // Biometrics auth failure (retry possible)
+ setClaimStatus('error');
+ return;
+ }
+
+ const { errorMessage } = await walletExecuteRap(wallet, {
+ type: 'claimTransactionClaimableRap',
+ claimTransactionClaimableActionParameters: { claimTx: txPayload },
+ });
+
+ if (errorMessage) {
+ setClaimStatus('error');
+ logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to rap error'), {
+ message: errorMessage,
+ });
+ } else {
+ setClaimStatus('success');
+ // Clear and refresh claimables data
+ queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency }));
+ refetch();
+ }
+ },
+ onError: e => {
+ setClaimStatus('error');
+ logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to unhandled error'), {
+ message: (e as Error)?.message,
+ });
+ },
+ onSuccess: () => {
+ if (claimStatus === 'claiming') {
+ logger.error(
+ new RainbowError('[ClaimingTransactionClaimable]: claim function completed but never resolved status to success or error state')
+ );
+ setClaimStatus('error');
+ }
+ },
+ });
+
+ return (
+
+ );
+};
diff --git a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx
index d39b3619b3c..576131ba849 100644
--- a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx
+++ b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx
@@ -258,7 +258,7 @@ const ClaimingRewards = ({
// @ts-expect-error - collision between old gas types and new
gasFeeParamsBySpeed,
gasParams,
- } satisfies RapSwapActionParameters<'claimBridge'>;
+ } satisfies RapSwapActionParameters<'claimRewardsBridge'>;
const provider = getProvider({ chainId: ChainId.optimism });
const wallet = await loadWallet({
@@ -275,13 +275,13 @@ const ClaimingRewards = ({
try {
const { errorMessage, nonce: bridgeNonce } = await walletExecuteRap(
wallet,
- 'claimBridge',
+ 'claimRewardsBridge',
// @ts-expect-error - collision between old gas types and new
actionParams
);
if (errorMessage) {
- if (errorMessage.includes('[CLAIM]')) {
+ if (errorMessage.includes('[CLAIM-REWARDS]')) {
// Claim error (retry possible)
setClaimStatus('error');
} else {