Skip to content

Commit

Permalink
use custom networks assets (#1120)
Browse files Browse the repository at this point in the history
  • Loading branch information
estebanmino authored and BrodyHughes committed Nov 15, 2023
1 parent 01b7092 commit f1c2fb9
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 108 deletions.
84 changes: 84 additions & 0 deletions src/core/resources/assets/assetMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { isValidAddress } from '@ethereumjs/util';
import { useQuery } from '@tanstack/react-query';
import { getProvider } from '@wagmi/core';
import { Address } from 'wagmi';

import {
QueryConfig,
QueryFunctionArgs,
QueryFunctionResult,
createQueryKey,
} from '~/core/react-query';
import { getAssetMetadata } from '~/core/utils/assets';

// ///////////////////////////////////////////////
// Query Types

type AssetMetadataArgs = {
assetAddress?: Address;
chainId: number;
};

// ///////////////////////////////////////////////
// Query Key

export const assetMetadataQueryKey = ({
assetAddress,
chainId,
}: AssetMetadataArgs) =>
createQueryKey(
'assetMetadata',
{ assetAddress, chainId },
{ persisterVersion: 1 },
);

type AssetMetadataQueryKey = ReturnType<typeof assetMetadataQueryKey>;

// ///////////////////////////////////////////////
// Query Function

async function assetMetadataQueryFunction({
queryKey: [{ assetAddress, chainId }],
}: QueryFunctionArgs<typeof assetMetadataQueryKey>) {
if (assetAddress && isValidAddress(assetAddress)) {
const provider = getProvider({ chainId: Number(chainId) });
const metadata = await getAssetMetadata({
address: assetAddress,
provider,
});
return {
address: assetAddress,
symbol: metadata.symbol,
decimals: metadata.decimals,
name: metadata.name,
};
}
}

type AssetMetadataResult = QueryFunctionResult<
typeof assetMetadataQueryFunction
>;

// ///////////////////////////////////////////////
// Query Hook

export function useAssetMetadata(
{ assetAddress, chainId }: AssetMetadataArgs,
config: QueryConfig<
AssetMetadataResult,
Error,
AssetMetadataResult,
AssetMetadataQueryKey
> = {},
) {
return useQuery(
assetMetadataQueryKey({
assetAddress,
chainId,
}),
assetMetadataQueryFunction,
{
...config,
},
);
}
140 changes: 94 additions & 46 deletions src/core/resources/assets/customNetworkAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ import {
queryClient,
} from '~/core/react-query';
import { SupportedCurrencyKey } from '~/core/references';
import { AddressOrEth, ParsedAssetsDictByChain } from '~/core/types/assets';
import { customRPCAssetsStore } from '~/core/state/customRPCAssets';
import {
AddressOrEth,
ParsedAssetsDict,
ParsedUserAsset,
} from '~/core/types/assets';
import { ChainId, ChainName } from '~/core/types/chains';
import { parseUserAssetBalances } from '~/core/utils/assets';
import {
extractFulfilledValue,
getAssetBalance,
parseUserAssetBalances,
} from '~/core/utils/assets';
import { getCustomChains } from '~/core/utils/chains';
import { RainbowError, logger } from '~/logger';

Expand All @@ -24,24 +33,24 @@ export const CUSTOM_NETWORK_ASSETS_STALE_INTERVAL = 30000;
// Query Types

export type CustomNetworkAssetsArgs = {
address?: Address;
address: Address;
currency: SupportedCurrencyKey;
};

type SetCustomNetworkAssetsArgs = {
address?: Address;
address: Address;
currency: SupportedCurrencyKey;
customNetworkAssets?: CustomNetworkAssetsResult;
};

type SetUserDefaultsArgs = {
address?: Address;
address: Address;
currency: SupportedCurrencyKey;
staleTime: number;
};

type FetchCustomNetworkAssetsArgs = {
address?: Address;
address: Address;
currency: SupportedCurrencyKey;
};

Expand Down Expand Up @@ -100,48 +109,87 @@ async function customNetworkAssetsFunction({
const cache = queryClient.getQueryCache();
const cachedCustomNetworkAssets = (cache.find(
customNetworkAssetsKey({ address, currency }),
)?.state?.data || {}) as ParsedAssetsDictByChain;
)?.state?.data || {}) as Record<ChainId | number, ParsedAssetsDict>;
const { customChains } = getCustomChains();
if (customChains.length === 0) {
return cachedCustomNetworkAssets;
}
const { customRPCAssets } = customRPCAssetsStore.getState();

try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const parsedAssetsDict: ParsedAssetsDictByChain = {};
if (address) {
const { customChains } = getCustomChains();
if (customChains.length > 0) {
await Promise.all(
customChains.map(async (chain) => {
const provider = getProvider({ chainId: chain.id });
const nativeAssetBalance = await provider.getBalance(address);
const customNetworkNativeAssetParsed = parseUserAssetBalances({
asset: {
address: AddressZero,
chainId: chain.id,
chainName: chain.name as ChainName,
isNativeAsset: true,
name: chain.nativeCurrency.symbol,
symbol: chain.nativeCurrency.symbol,
uniqueId: `${AddressZero}_${chain.id}`,
decimals: 18,
native: { price: undefined },
bridging: { isBridgeable: false, networks: [] },
mainnetAddress: AddressZero as AddressOrEth,
},
currency,
balance: nativeAssetBalance.toString(), // FORMAT?
});

// TODO - add support for custom network tokens here (BX-1073)

parsedAssetsDict[chain.id as ChainId] = {
[customNetworkNativeAssetParsed.uniqueId]:
customNetworkNativeAssetParsed,
};
const assetsPromises = customChains.map(async (chain) => {
const provider = getProvider({ chainId: chain.id });
const nativeAssetBalance = await provider.getBalance(address);
const customNetworkNativeAssetParsed = parseUserAssetBalances({
asset: {
address: AddressZero,
chainId: chain.id,
chainName: chain.name as ChainName,
isNativeAsset: true,
name: chain.nativeCurrency.symbol,
symbol: chain.nativeCurrency.symbol,
uniqueId: `${AddressZero}_${chain.id}`,
decimals: 18,
native: { price: undefined },
bridging: { isBridgeable: false, networks: [] },
mainnetAddress: AddressZero as AddressOrEth,
},
currency,
balance: nativeAssetBalance.toString(),
});

const chainAssets = customRPCAssets[chain.id] || [];
const chainParsedAssetBalances = await Promise.allSettled(
chainAssets.map((asset) =>
getAssetBalance({
assetAddress: asset.address,
currentAddress: address,
provider,
}),
);
}
return parsedAssetsDict;
}
return cachedCustomNetworkAssets;
),
);

const chainParsedAssets = chainParsedAssetBalances.map((balance, i) => {
const fulfilledBalance = extractFulfilledValue(balance);
return parseUserAssetBalances({
asset: {
...chainAssets[i],
chainId: chain.id,
chainName: chain.name as ChainName,
uniqueId: `${chainAssets[i].address}_${chain.id}`,
mainnetAddress: undefined,
isNativeAsset: false,
native: { price: undefined },
},
currency,
balance: fulfilledBalance || '0',
});
});

return {
chainId: chain.id,
assets: [customNetworkNativeAssetParsed, ...chainParsedAssets],
};
});
const assetsResults = (await Promise.allSettled(assetsPromises))
.map((assets) => extractFulfilledValue(assets))
.filter(Boolean);
const parsedAssetsDict: Record<ChainId | number, ParsedAssetsDict> =
assetsResults.reduce(
(acc, { chainId, assets }) => {
acc[Number(chainId)] = assets.reduce(
(chainAcc, asset) => {
chainAcc[asset.uniqueId] = asset;
return chainAcc;
},
{} as Record<string, ParsedUserAsset>,
);
return acc;
},
{} as Record<ChainId | number, ParsedAssetsDict>,
);

return parsedAssetsDict;
} catch (e) {
logger.error(new RainbowError('customNetworkAssetsFunction: '), {
message: (e as Error)?.message,
Expand Down
2 changes: 1 addition & 1 deletion src/core/state/customRPC/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ChainId } from '~/core/types/chains';

import { customRPCsStore } from '.';

// Dummy CustomRPC data
// Dummy CustomChain data
const TEST_RPC_1: Chain = {
rpcUrls: {
default: { http: ['http://test1.rpc'] },
Expand Down
5 changes: 4 additions & 1 deletion src/core/types/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ export type ParsedSearchAsset = SearchAsset & ParsedUserAsset;

export type ParsedAssetsDict = Record<UniqueId, ParsedUserAsset>;

export type ParsedAssetsDictByChain = Record<ChainId, ParsedAssetsDict>;
export type ParsedAssetsDictByChain = Record<
ChainId | number,
ParsedAssetsDict
>;

export interface ZerionAssetPrice {
value: number;
Expand Down
47 changes: 47 additions & 0 deletions src/core/utils/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,50 @@ export const createAssetQuery = (
.join(',')}
}`;
};

export const getAssetMetadata = async ({
address,
provider,
}: {
address: Address;
provider: Provider;
}) => {
const contract = await getContract({
address,
abi: erc20ABI,
signerOrProvider: provider,
});
const [decimals, symbol, name] = await Promise.allSettled([
contract.decimals(),
contract.symbol(),
contract.name(),
]);

return {
decimals: extractFulfilledValue<number>(decimals),
symbol: extractFulfilledValue<string>(symbol),
name: extractFulfilledValue<string>(name),
};
};

export const getAssetBalance = async ({
assetAddress,
currentAddress,
provider,
}: {
assetAddress: Address;
currentAddress: Address;
provider: Provider;
}) => {
const balance = await getContract({
address: assetAddress,
abi: erc20ABI,
signerOrProvider: provider,
}).balanceOf(currentAddress);

return balance.toString();
};

export const extractFulfilledValue = <T>(
result: PromiseSettledResult<T>,
): T | undefined => (result.status === 'fulfilled' ? result.value : undefined);
6 changes: 6 additions & 0 deletions src/entries/popup/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export function App() {
[customChains],
);

React.useEffect(() => {
if (!isEqual(prevChains, customChains)) {
backgroundMessenger.send('rainbow_updateWagmiClient', null);
}
}, [prevChains, customChains]);

React.useEffect(() => {
// Disable analytics & sentry for e2e and dev mode
if (process.env.IS_TESTING !== 'true' && process.env.IS_DEV !== 'true') {
Expand Down
8 changes: 4 additions & 4 deletions src/entries/popup/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ import { SeedReveal } from './pages/seedReveal';
import { SeedVerify } from './pages/seedVerify';
import { Send } from './pages/send';
import { Currency } from './pages/settings/currency';
import { SettingsCustomRPC } from './pages/settings/customRPC';
import { CustomRPC } from './pages/settings/customRPC/customRPC';
import { SettingsCustomChain } from './pages/settings/customChain';
import { CustomChain } from './pages/settings/customChain/customChain';
import { Language } from './pages/settings/language';
import { SettingsNetworks } from './pages/settings/networks';
import { AutoLockTimer } from './pages/settings/privacy/autoLockTimer';
Expand Down Expand Up @@ -504,7 +504,7 @@ const ROUTE_DATA = [
protectedRoute
background="surfaceSecondary"
>
<SettingsCustomRPC />
<SettingsCustomChain />
</AnimatedRoute>
),
},
Expand All @@ -519,7 +519,7 @@ const ROUTE_DATA = [
protectedRoute
background="surfaceSecondary"
>
<CustomRPC />
<CustomChain />
</AnimatedRoute>
),
},
Expand Down
8 changes: 3 additions & 5 deletions src/entries/popup/hooks/useCustomNetworkAsset.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { useAccount } from 'wagmi';

import { selectUserAssetWithUniqueId } from '~/core/resources/_selectors/assets';
import { useCustomNetworkAssets } from '~/core/resources/assets/customNetworkAssets';
import { useCurrentCurrencyStore } from '~/core/state';
import { useCurrentAddressStore, useCurrentCurrencyStore } from '~/core/state';
import { UniqueId } from '~/core/types/assets';

export function useCustomNetworkAsset(uniqueId?: UniqueId) {
const { address } = useAccount();
const { currentAddress } = useCurrentAddressStore();
const { currentCurrency: currency } = useCurrentCurrencyStore();
return useCustomNetworkAssets(
{
address,
address: currentAddress,
currency,
},
{
Expand Down
Loading

0 comments on commit f1c2fb9

Please sign in to comment.