diff --git a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx
index d543fb635ab..afc758e7531 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 const Claimable = 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..c23cfc6cde9 100644
--- a/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx
@@ -12,7 +12,7 @@ const AnimatedImgixImage = Animated.createAnimatedComponent(Image);
const TokenFamilyHeaderAnimationDuration = 200;
const TokenFamilyHeaderHeight = 48;
-const ClaimablesListHeader = ({ total }: { total: string }) => {
+export const ClaimablesListHeader = React.memo(function ClaimablesListHeader({ total }: { total: string }) {
const { colors } = useTheme();
const { isClaimablesOpen, toggleOpenClaimables } = useOpenClaimables();
@@ -84,10 +84,4 @@ const ClaimablesListHeader = ({ total }: { total: string }) => {
);
-};
-
-ClaimablesListHeader.animationDuration = TokenFamilyHeaderAnimationDuration;
-
-ClaimablesListHeader.height = TokenFamilyHeaderHeight;
-
-export default ClaimablesListHeader;
+});
diff --git a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx
index b5805584840..7e14631d279 100644
--- a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx
@@ -35,8 +35,8 @@ import { RemoteCardCarousel } from '@/components/cards/remote-cards';
import WrappedCollectiblesHeader from '../WrappedCollectiblesHeader';
import NFTLoadingSkeleton from '../NFTLoadingSkeleton';
import { NFTEmptyState } from '../NFTEmptyState';
-import Claimable from '../Claimable';
-import ClaimablesListHeader from '../ClaimablesListHeader';
+import { ClaimablesListHeader } from '../ClaimablesListHeader';
+import { Claimable } from '../Claimable';
function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, extendedState: ExtendedState) {
const data = extendedState.additionalData[uid];
@@ -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/resources/addys/claimables/query.ts b/src/resources/addys/claimables/query.ts
index 10aaa2bfa16..137a5846caa 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}`,
},
@@ -30,7 +32,7 @@ export type ClaimablesArgs = {
// Query Key
export const claimablesQueryKey = ({ address, currency }: ClaimablesArgs) =>
- createQueryKey('claimables', { address, currency }, { persisterVersion: 1 });
+ createQueryKey('claimables', { address, currency }, { persisterVersion: 2 });
type ClaimablesQueryKey = ReturnType;
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();