Skip to content

Commit

Permalink
fetch assets from batching server (#542)
Browse files Browse the repository at this point in the history
* fetch assets from batching server

* fix lint

* fetch prices from batching server

* use tanstack react query for usePriceFetcher

* simplify the code, download the price only from the batching server

* update readme.md

* update readme.md

* fix portfolio table sorting, fix table cells width

* fix tables styles

* add BATCHING_SERVER_URL

* Amened README.md

---------

Co-authored-by: Marcel Ebert <[email protected]>
  • Loading branch information
Sharqiewicz and ebma authored Sep 12, 2024
1 parent 375f530 commit eb11d06
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 93 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Yarn version specified in the `packageManager` field.
**Important:** Modern [Yarn](https://yarnpkg.com/getting-started/install) releases should not be installed globally or
via npm - use Corepack instead.

**Note:** If you are using Volta to manage your Node.js versions, you need to follow the
instructions [here](https://yarnpkg.com/corepack#volta).
**Note:** If you are using Volta to manage your Node.js versions, you need to follow the instructions
[here](https://yarnpkg.com/corepack#volta).

### `yarn install`

Expand All @@ -48,7 +48,15 @@ We call on `version.cjs` to show the commit version on the sidebar.\
We also create a file, on the fly, a file named `_redirects` that will serve the index.html instead of giving a 404 no
matter what URL the browser requests.

## Fixing type issues
## Troubleshooting

### Missing price information

If you are missing the price information about the assets on the dashboard page, you are probably experiencing a CORS
problem with the batching server. If you want to fetch prices locally, you can use the proxy server available
at [pendulum-tools](https://github.com/pendulum-chain/pendulum-tools). Change url in `src/hooks/usePriceFetcher.ts` file to `http://localhost:3000`

### Fixing type issues

If you encounter issues with the IDE not detecting the type overwrites of the `@pendulum-chain/types` package properly,
make sure that all the `@polkadot/xxx` packages match the same version used in the types package. It is also important
Expand Down
22 changes: 18 additions & 4 deletions src/components/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ export type TableProps<T> = {
fontSize?: string;
/** Sets the global font size for the Table. */
rowCallback?: (row: Row<T>, index: number) => void;
/** If true, the table will have fixed columns */
tableFixed?: boolean;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const defaultData: any[] = [];
const loading = <>{repeat(<Skeleton className="mb-2 h-8" />, 6)}</>;

const Table = <T,>({
data = defaultData,
Expand All @@ -73,6 +76,7 @@ const Table = <T,>({
fontSize,
title,
rowCallback,
tableFixed,
}: TableProps<T>): JSX.Element | null => {
const totalCount = data.length;

Expand All @@ -96,7 +100,7 @@ const Table = <T,>({
: undefined;
}, [sortBy]);

const { getHeaderGroups, getRowModel, getPageCount, nextPage, previousPage, setGlobalFilter, getState } =
const { getHeaderGroups, getRowModel, getPageCount, nextPage, previousPage, setGlobalFilter, getState, setSorting } =
useReactTable({
columns: tableColumns,
data: tableData,
Expand Down Expand Up @@ -133,7 +137,7 @@ const Table = <T,>({
} font-semibold ${className})`}
>
{title && <div className="bg-base-200 px-4 py-6 text-lg">{title}</div>}
<table className="table w-full">
<table className={`table w-full ${tableFixed ? 'table-fixed' : ''}`}>
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} className="table-border border-b">
Expand All @@ -144,7 +148,17 @@ const Table = <T,>({
key={header.id}
colSpan={header.colSpan}
className={`${isSortable ? 'cursor-pointer' : ''}`}
onClick={header.column.getToggleSortingHandler()}
onClick={() => {
if (isSortable) {
const currentSort = header.column.getIsSorted();
setSorting([
{
id: header.column.id,
desc: currentSort === 'asc',
},
]);
}
}}
>
<div
className={`flex flex-row items-center font-normal ${
Expand Down Expand Up @@ -181,7 +195,7 @@ const Table = <T,>({
key={cell.id}
className={`${cell.column.columnDef.meta?.className || ''} ${
(index % 2 ? evenRowsClassname : oddRowsClassname) || 'bg-base-200'
}`}
} ${tableFixed ? 'table-fixed' : ''}`}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
Expand Down
31 changes: 15 additions & 16 deletions src/components/nabla/Swap/To.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Button } from 'react-daisyui';
import { useFormContext } from 'react-hook-form';
import Big from 'big.js';

import pendulumIcon from '../../../assets/pendulum-icon.svg';
import { UseTokenOutAmountResult } from '../../../hooks/nabla/useTokenOutAmount';
import useBoolean from '../../../hooks/useBoolean';
import { NumberLoader } from '../../Loader';
Expand Down Expand Up @@ -48,9 +47,9 @@ export function To({
}, [toAmountQuote.data?.amountOut.preciseString, setValue]);

return (
<div className="rounded-lg bg-base-300 px-4 py-3 border border-transparent">
<div className="w-full flex justify-between">
<div className="flex-grow text-4xl text-[inherit] font-outfit overflow-x-auto overflow-y-hidden mr-2">
<div className="rounded-lg border border-transparent bg-base-300 px-4 py-3">
<div className="flex w-full justify-between">
<div className="font-outfit mr-2 flex-grow overflow-x-auto overflow-y-hidden text-4xl text-[inherit]">
{toAmountQuote.isLoading ? (
<NumberLoader />
) : toAmountQuote.data !== undefined ? (
Expand All @@ -62,27 +61,27 @@ export function To({
className="hover:opacity-80"
title="Refresh"
>
<ArrowPathRoundedSquareIcon className="w-7 h-7" />
<ArrowPathRoundedSquareIcon className="h-7 w-7" />
</button>
) : (
<>0</>
)}
</div>
<Button
size="xs"
className="rounded-full h-7 min-h-none border-0 bg-neutral-200 dark:bg-neutral-700 pl-0 pr-1 flex items-center mt-0.5 text-sm font-medium"
className="min-h-none mt-0.5 flex h-7 items-center rounded-full border-0 bg-neutral-200 pl-0 pr-1 text-sm font-medium dark:bg-neutral-700"
onClick={onOpenSelector}
type="button"
>
<span className="rounded-full bg-[rgba(0,0,0,0.15)] h-full p-px mr-1">
<span className="mr-1 h-full rounded-full bg-[rgba(0,0,0,0.15)] p-px">
<img src={getIcon(toToken?.symbol)} alt={toToken?.name} className="h-full w-auto" />
</span>
<strong className="font-bold">{toToken?.symbol || 'Select'}</strong>
<ChevronDownIcon className="w-4 h-4 inline ml-px" />
<ChevronDownIcon className="ml-px inline h-4 w-4" />
</Button>
</div>
<div className="flex justify-between items-center mt-1 dark:text-neutral-300 text-neutral-500">
<div className="text-sm mt-px">{toToken ? <NablaTokenPrice address={toToken.id} fallback="$ -" /> : '$ -'}</div>
<div className="mt-1 flex items-center justify-between text-neutral-500 dark:text-neutral-300">
<div className="mt-px text-sm">{toToken ? <NablaTokenPrice address={toToken.id} fallback="$ -" /> : '$ -'}</div>
{walletAccount && (
<div className="flex gap-1 text-sm">
Balance:{' '}
Expand All @@ -95,13 +94,13 @@ export function To({
</div>
)}
</div>
<div className="mt-4 h-px -mx-4 bg-[rgba(0,0,0,0.15)]" />
<div className="-mx-4 mt-4 h-px bg-[rgba(0,0,0,0.15)]" />
<div
className={`collapse overflow-visible dark:text-neutral-300 text-neutral-500 -mx-4 text-sm${
isOpen ? ' collapse-open' : ''
className={`collapse -mx-4 overflow-visible text-neutral-500 dark:text-neutral-300 text-sm${
isOpen ? 'collapse-open' : ''
}`}
>
<div className="collapse-title cursor-pointer flex justify-between px-4 pt-3 pb-0" onClick={toggle}>
<div className="collapse-title flex cursor-pointer justify-between px-4 pb-0 pt-3" onClick={toggle}>
<div className="flex items-center">
{fromToken !== undefined &&
toToken !== undefined &&
Expand All @@ -115,9 +114,9 @@ export function To({
<div>
<div
title="More info"
className="w-6 h-6 ml-1 flex items-center justify-center rounded-full bg-blackAlpha-200 dark:bg-whiteAlpha-200 hover:opacity-80"
className="ml-1 flex h-6 w-6 items-center justify-center rounded-full bg-blackAlpha-200 hover:opacity-80 dark:bg-whiteAlpha-200"
>
<ChevronDownIcon className="w-5 h-5 inline" />
<ChevronDownIcon className="inline h-5 w-5" />
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useAssetRegistryMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const useAssetRegistryMetadata = (): UseAssetRegistryMetadata => {
const fetchMetadata = useCallback(async () => {
if (api) {
const fetchedMetadata = await api.query.assetRegistry.metadata.entries();
return fetchedMetadata.map((m) => convertToOrmlAssetRegistryAssetMetadata(m));
return fetchedMetadata.map(convertToOrmlAssetRegistryAssetMetadata);
}

return [];
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ function useBalances() {
);

useEffect(() => {
const getTokensBalances = async () => {
if (!walletAccount) return [];
const getTokensBalances = async (): Promise<void> => {
if (!walletAccount) return Promise.resolve();

const assets = getAllAssetsMetadata();
const walletAddress = ss58Format ? getAddressForFormat(walletAccount.address, ss58Format) : walletAccount.address;
Expand Down Expand Up @@ -74,7 +74,7 @@ function useBalances() {
}),
);

return setBalances(tokensBalances);
setBalances(tokensBalances);
};

getTokensBalances().catch(console.error);
Expand Down
97 changes: 44 additions & 53 deletions src/hooks/usePriceFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useCallback, useEffect, useState } from 'preact/compat';
import { SpacewalkPrimitivesCurrencyId } from '@polkadot/types/lookup';
import { useCallback } from 'preact/compat';
import { useQuery } from '@tanstack/react-query';
import { isEqual } from 'lodash';
import { TenantName } from '../models/Tenant';
import useSwitchChain from './useSwitchChain';
import { useNodeInfoState } from '../NodeInfoProvider';
import { nativeToDecimal } from '../shared/parseNumbers/metric';
import { SpacewalkPrimitivesCurrencyId } from '@polkadot/types/lookup';
import { useAssetRegistryMetadata } from './useAssetRegistryMetadata';
import { isEqual } from 'lodash';
import { BATCHING_SERVER_URL } from '../shared/constants';

export interface DiaKeys {
blockchain: string;
Expand All @@ -20,68 +21,58 @@ function diaKeysToString(diaKeys: DiaKeys) {
type PricesCache = { [diaKeys: string]: number };

export const usePriceFetcher = () => {
const [pricesCache, setPricesCache] = useState<PricesCache>({});
const { currentTenant } = useSwitchChain();
const { api } = useNodeInfoState().state;
const { getAllAssetsMetadata } = useAssetRegistryMetadata();
const allAssetsMetadata = getAllAssetsMetadata();
const diaKeys = allAssetsMetadata.map((asset) => asset.metadata.additional.diaKeys);

useEffect(() => {
if (!api) return;

const fetchPrices = async () => {
const allPrices = await api.query.diaOracleModule.coinInfosMap.entries();
const getPriceFromBatchingServer = useCallback(async () => {
try {
const response = await fetch(BATCHING_SERVER_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(diaKeys.map(({ blockchain, symbol }) => ({ blockchain, symbol }))),
});

const prices = allPrices.map(([key, value]) => {
const keyJson = key.toHuman() as unknown as DiaKeys[];
const assetKeys = keyJson[0];
const priceRaw = value.price;
const price = nativeToDecimal(priceRaw.toString()).toNumber();
if (!response.ok) {
console.error('Batching server response was not ok:', response.status, response.statusText);
return {};
}

return { assetKeys, price };
});
const batchingServerPrices = await response.json();

setPricesCache((prev) => {
const newPricesCache = { ...prev };
prices.forEach(({ assetKeys, price }) => {
newPricesCache[diaKeysToString(assetKeys)] = price;
});
return batchingServerPrices.reduce(
(acc: PricesCache, { symbol, price, blockchain }: { symbol: string; price: number; blockchain: string }) => {
acc[diaKeysToString({ symbol, blockchain })] = nativeToDecimal(price.toString()).toNumber();
return acc;
},
{},
);
} catch (error) {
console.error('Error fetching prices from batching server:', error);
return {};
}
}, [diaKeys]);

return newPricesCache;
});
};
const { data: pricesCache = {} } = useQuery({
queryKey: ['prices', allAssetsMetadata],
queryFn: getPriceFromBatchingServer,
});

fetchPrices().catch(console.error);
}, [api]);
const getTokenPriceForKeys = useCallback((asset: DiaKeys) => pricesCache[diaKeysToString(asset)] || 0, [pricesCache]);

const getTokenPriceForKeys = useCallback(
async (asset: DiaKeys) => {
try {
const diaKeys = diaKeysToString(asset);
const cachedAssetPrice = pricesCache[diaKeys];
if (cachedAssetPrice) return cachedAssetPrice;
} catch (e) {
console.error(e);
}
return 0;
},
[pricesCache],
const handleNativeTokenPrice = useCallback(
() => (currentTenant === TenantName.Pendulum ? pricesCache['Pendulum:PEN'] : pricesCache['Amplitude:AMPE']),
[currentTenant, pricesCache],
);

const handleNativeTokenPrice = useCallback(() => {
if (currentTenant === TenantName.Pendulum) return pricesCache['Pendulum:PEN'];
return pricesCache['Amplitude:AMPE'];
}, [currentTenant, pricesCache]);

const getTokenPriceForCurrency = useCallback(
async (currency: SpacewalkPrimitivesCurrencyId) => {
(currency: SpacewalkPrimitivesCurrencyId) => {
if (currency.toHuman() === 'Native') return handleNativeTokenPrice();
const asset = getAllAssetsMetadata().find((asset) => {
return isEqual(asset.currencyId.toHuman(), currency.toHuman());
});
if (!asset) {
return 0;
}
return getTokenPriceForKeys(asset.metadata.additional.diaKeys);

const asset = getAllAssetsMetadata().find((asset) => isEqual(asset.currencyId.toHuman(), currency.toHuman()));

return asset ? getTokenPriceForKeys(asset.metadata.additional.diaKeys) : 0;
},
[getAllAssetsMetadata, getTokenPriceForKeys, handleNativeTokenPrice],
);
Expand Down
11 changes: 6 additions & 5 deletions src/pages/dashboard/Portfolio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,25 @@ function Portfolio() {
}, []);

return (
<div className="card portfolio rounded-md bg-base-100">
<div className="p-4 flex flex-row justify-between">
<div className="font-bold text-xl">Wallet</div>
<div className="portfolio card rounded-md bg-base-100">
<div className="flex flex-row justify-between p-4">
<div className="text-xl font-bold">Wallet</div>
<div className="text-xl" title={accountTotalBalance.toString()}>
$ {accountTotalBalance.toPrecision(2)}
</div>
</div>
{walletAccount && (
<Table
className="bg-base-100 text-md"
className="text-md bg-base-100"
data={balances}
columns={columns}
isLoading={!balances}
sortBy={{ amount: SortingOrder.DESC, token: SortingOrder.ASC }}
sortBy={{ usdValue: SortingOrder.DESC }}
search={false}
pageSize={8}
oddRowsClassname="odd-rows bg-table-row border-b-base-300 table-border"
evenRowsClassname="border-b-base-300 table-border"
tableFixed
/>
)}
{!walletAccount && <div className="p-5"> You need to connect a wallet in order to see your Portfolio. </div>}
Expand Down
Loading

0 comments on commit eb11d06

Please sign in to comment.