Skip to content

Commit

Permalink
Implement perceived finality (#6037)
Browse files Browse the repository at this point in the history
* feat: perceived finality initial

* fix: update chain id import
  • Loading branch information
derHowie authored Aug 29, 2024
1 parent 156c0ec commit c6edc67
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 13 deletions.
40 changes: 40 additions & 0 deletions src/hooks/useWatchPendingTxs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Address } from 'viem';
import { nftsQueryKey } from '@/resources/nfts';
import { getNftSortForAddress } from './useNFTsSortBy';
import { ChainId } from '@/networks/types';
import { staleBalancesStore } from '@/state/staleBalances';

export const useWatchPendingTransactions = ({ address }: { address: string }) => {
const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({
Expand Down Expand Up @@ -164,6 +165,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) =>
);

const watchPendingTransactions = useCallback(async () => {
const connectedToHardhat = getIsHardhatConnected();
if (!pendingTransactions?.length) return;
const updatedPendingTransactions = await Promise.all(
pendingTransactions.map((tx: RainbowTransaction) => processPendingTransaction(tx))
Expand All @@ -190,6 +192,20 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) =>
const chainIds = RainbowNetworkObjects.filter(networkObject => networkObject.enabled && networkObject.networkType !== 'testnet').map(
networkObject => networkObject.id
);
minedTransactions.forEach(tx => {
if (tx.changes?.length) {
tx.changes?.forEach(change => {
processStaleAsset({ asset: change?.asset, address, transactionHash: tx?.hash });
});
} else if (tx.asset) {
processStaleAsset({ address, asset: tx.asset, transactionHash: tx?.hash });
}
});

queryClient.refetchQueries({
queryKey: userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }),
});

await queryClient.refetchQueries({
queryKey: consolidatedTransactionsQueryKey({
address,
Expand Down Expand Up @@ -217,3 +233,27 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) =>

return { watchPendingTransactions };
};

function processStaleAsset({
asset,
address,
transactionHash,
}: {
asset: RainbowTransaction['asset'];
address: string;
transactionHash: string;
}) {
const { addStaleBalance } = staleBalancesStore.getState();
const chainId = asset?.chainId;
if (asset && typeof chainId === 'number') {
const changedAssetAddress = asset?.address as Address;
addStaleBalance({
address,
chainId,
info: {
address: changedAssetAddress,
transactionHash,
},
});
}
}
47 changes: 34 additions & 13 deletions src/resources/assets/UserAssetsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { filterPositionsData, parseAddressAsset } from './assets';
import { fetchHardhatBalances } from './hardhatAssets';
import { AddysAccountAssetsMeta, AddysAccountAssetsResponse, RainbowAddressAssets } from './types';
import { Network } from '@/networks/types';
import { staleBalancesStore } from '@/state/staleBalances';

// ///////////////////////////////////////////////
// Query Types
Expand All @@ -32,15 +33,26 @@ type UserAssetsQueryKey = ReturnType<typeof userAssetsQueryKey>;
// ///////////////////////////////////////////////
// Query Function

const fetchUserAssetsForChainIds = async (address: string, currency: NativeCurrencyKey, chainIds: number[]) => {
const fetchUserAssetsForChainIds = async ({
address,
currency,
chainIds,
staleBalanceParam,
}: {
address: string;
currency: NativeCurrencyKey;
chainIds: number[];
staleBalanceParam?: string;
}) => {
const chainIdsString = chainIds.join(',');
const url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets`;
let url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets?currency=${currency.toLowerCase()}`;

if (staleBalanceParam) {
url += url + staleBalanceParam;
}

const response = await rainbowFetch(url, {
method: 'get',
params: {
currency: currency.toLowerCase(),
},
headers: {
Authorization: `Bearer ${ADDYS_API_KEY}`,
},
Expand All @@ -66,7 +78,10 @@ async function userAssetsQueryFunction({
network => network.id
);

const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds(address, currency, chainIds);
staleBalancesStore.getState().clearExpiredData(address);
const staleBalanceParam = staleBalancesStore.getState().getStaleBalancesQueryParam(address);

const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam });
let parsedSuccessResults = results;

// grab cached data for chain IDs with errors
Expand Down Expand Up @@ -102,7 +117,7 @@ const retryErroredChainIds = async (
connectedToHardhat: boolean,
erroredChainIds: number[]
) => {
const { meta, results } = await fetchAndParseUserAssetsForChainIds(address, currency, erroredChainIds);
const { meta, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds: erroredChainIds });
let parsedSuccessResults = results;
const successChainIds = meta?.chain_ids;

Expand Down Expand Up @@ -142,12 +157,18 @@ interface AssetsAndMetadata {
results: RainbowAddressAssets;
}

const fetchAndParseUserAssetsForChainIds = async (
address: string,
currency: NativeCurrencyKey,
chainIds: number[]
): Promise<AssetsAndMetadata> => {
const data = await fetchUserAssetsForChainIds(address, currency, chainIds);
const fetchAndParseUserAssetsForChainIds = async ({
address,
currency,
chainIds,
staleBalanceParam,
}: {
address: string;
currency: NativeCurrencyKey;
chainIds: number[];
staleBalanceParam?: string;
}): Promise<AssetsAndMetadata> => {
const data = await fetchUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam });
let parsedSuccessResults = parseUserAssetsByChain(data);

// filter out positions data
Expand Down
165 changes: 165 additions & 0 deletions src/state/staleBalances/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { Address } from 'viem';

import { staleBalancesStore } from '.';
import { DAI_ADDRESS, OP_ADDRESS } from '@/references';
import { ETH_ADDRESS } from '@rainbow-me/swaps';
import { ChainId } from '@/networks/types';

const TEST_ADDRESS_1 = '0xFOO';
const TEST_ADDRESS_2 = '0xBAR';
const THEN = Date.now() - 700000;
const WHEN = Date.now() + 60000;

test('should be able to add asset information to the staleBalances object', async () => {
const { addStaleBalance, staleBalances } = staleBalancesStore.getState();
expect(staleBalances).toStrictEqual({});
addStaleBalance({
address: TEST_ADDRESS_1,
chainId: ChainId.mainnet,
info: {
address: DAI_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: THEN,
},
});
addStaleBalance({
address: TEST_ADDRESS_1,
chainId: ChainId.mainnet,
info: {
address: ETH_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
});
const newStaleBalances = staleBalancesStore.getState().staleBalances;
expect(newStaleBalances).toStrictEqual({
[TEST_ADDRESS_1]: {
[ChainId.mainnet]: {
[DAI_ADDRESS]: {
address: DAI_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: THEN,
},
[ETH_ADDRESS]: {
address: ETH_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
},
},
});
});

test('should generate accurate stale balance query params and clear expired data - case #1', async () => {
const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState();
clearExpiredData(TEST_ADDRESS_1);
const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1);
expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`);
});

test('should be able to remove expired stale balance and preserve unexpired data', async () => {
const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState();
addStaleBalance({
address: TEST_ADDRESS_1,
chainId: ChainId.mainnet,
info: {
address: DAI_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: THEN,
},
});
addStaleBalance({
address: TEST_ADDRESS_1,
chainId: ChainId.mainnet,
info: {
address: ETH_ADDRESS as Address,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
});
clearExpiredData(TEST_ADDRESS_1);
const newStaleBalances = staleBalancesStore.getState().staleBalances;
expect(newStaleBalances).toStrictEqual({
[TEST_ADDRESS_1]: {
[ChainId.mainnet]: {
[ETH_ADDRESS]: {
address: ETH_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
},
},
});
});

test('should preserve data from other addresses when clearing expired data', async () => {
const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState();
addStaleBalance({
address: TEST_ADDRESS_1,
chainId: ChainId.mainnet,
info: {
address: DAI_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: THEN,
},
});
addStaleBalance({
address: TEST_ADDRESS_2,
chainId: ChainId.mainnet,
info: {
address: ETH_ADDRESS as Address,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
});
clearExpiredData(TEST_ADDRESS_1);
const newStaleBalances = staleBalancesStore.getState().staleBalances;
expect(newStaleBalances).toStrictEqual({
[TEST_ADDRESS_1]: {
[ChainId.mainnet]: {
[ETH_ADDRESS]: {
address: ETH_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
},
},
[TEST_ADDRESS_2]: {
[ChainId.mainnet]: {
[ETH_ADDRESS]: {
address: ETH_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
},
},
});
});

test('should generate accurate stale balance query params and clear expired data - case #2', async () => {
const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState();
clearExpiredData(TEST_ADDRESS_2);
const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_2);
expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`);
});

test('should generate accurate stale balance query params and clear expired data - case #3', async () => {
const { addStaleBalance, getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState();
addStaleBalance({
address: TEST_ADDRESS_1,
chainId: ChainId.optimism,
info: {
address: OP_ADDRESS,
transactionHash: '0xFOOBAR',
expirationTime: WHEN,
},
});

clearExpiredData(TEST_ADDRESS_1);
const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1);
expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}&token=${ChainId.optimism}.${OP_ADDRESS}`);

clearExpiredData(TEST_ADDRESS_2);
const queryParam2 = getStaleBalancesQueryParam(TEST_ADDRESS_2);
expect(queryParam2).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`);
});
Loading

0 comments on commit c6edc67

Please sign in to comment.