Skip to content

Commit 063e0d7

Browse files
authored
chore: Fetch currencies from blockchain instead of using hardcoded metadata
* fetch currencies from blockchain instead of using harcoded metadata * handle undefined blockchain data for /gas * implement useAssetRegistryMetadata hook
1 parent 7413027 commit 063e0d7

File tree

6 files changed

+147
-77
lines changed

6 files changed

+147
-77
lines changed

src/hooks/useAssetRegistryMetadata.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useState, useEffect, useCallback } from 'preact/hooks';
2+
import { useNodeInfoState } from '../NodeInfoProvider';
3+
import { AssetId, OrmlTraitsAssetRegistryAssetMetadata } from './useBuyout/types';
4+
import { StorageKey } from '@polkadot/types';
5+
import { Codec } from '@polkadot/types-codec/types';
6+
7+
type CurrencyMetadataType = {
8+
decimals: string;
9+
name: string;
10+
symbol: string;
11+
// There are more coming, but are not used in this context
12+
};
13+
14+
interface UseAssetRegistryMetadata {
15+
getAssetMetadata: (assetId: AssetId) => Promise<OrmlTraitsAssetRegistryAssetMetadata | undefined>;
16+
getNativeAssetMetadata: () => Promise<OrmlTraitsAssetRegistryAssetMetadata | undefined>;
17+
}
18+
19+
function convertToOrmlAssetRegistryAssetMetadata(metadata: [StorageKey, Codec]): OrmlTraitsAssetRegistryAssetMetadata {
20+
const { decimals, name, symbol } = metadata[1].toHuman() as CurrencyMetadataType;
21+
const assetId = (metadata[0].toHuman() as string[])[0] as string;
22+
23+
return {
24+
metadata: { decimals: Number(decimals), name, symbol },
25+
assetId,
26+
};
27+
}
28+
29+
export const useAssetRegistryMetadata = (): UseAssetRegistryMetadata => {
30+
const { api } = useNodeInfoState().state;
31+
const [metadataCache, setMetadataCache] = useState<OrmlTraitsAssetRegistryAssetMetadata[]>([]);
32+
33+
const fetchMetadata = useCallback(async () => {
34+
if (api) {
35+
const fetchedMetadata = await api.query.assetRegistry.metadata.entries();
36+
setMetadataCache(fetchedMetadata.map((m) => convertToOrmlAssetRegistryAssetMetadata(m)));
37+
}
38+
}, [api]);
39+
40+
useEffect(() => {
41+
fetchMetadata().catch(console.error);
42+
}, [fetchMetadata]);
43+
44+
const getAssetMetadata = useCallback(
45+
async (assetId: AssetId): Promise<OrmlTraitsAssetRegistryAssetMetadata | undefined> =>
46+
metadataCache.find(
47+
// When JSON.stringify we check for the same object structure as OrmlTraitsAssetRegistryAssetMetadata.assetId's are really different
48+
(m: OrmlTraitsAssetRegistryAssetMetadata) => JSON.stringify(m.assetId) === JSON.stringify(assetId),
49+
),
50+
[metadataCache],
51+
);
52+
53+
const getNativeAssetMetadata = useCallback(async () => {
54+
return getAssetMetadata('Native');
55+
}, [getAssetMetadata]);
56+
57+
return { getAssetMetadata, getNativeAssetMetadata };
58+
};

src/hooks/useBuyout/index.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ import { doSubmitExtrinsic } from '../../pages/collators/dialogs/helpers';
99
import { useGlobalState } from '../../GlobalStateProvider';
1010

1111
import { OrmlTraitsAssetRegistryAssetMetadata } from './types';
12-
import { getMetadata } from './utils';
1312
import { ToastMessage, showToast } from '../../shared/showToast';
1413
import { PerMill } from '../../shared/parseNumbers/permill';
1514
import { decimalToNative } from '../../shared/parseNumbers/metric';
15+
import { useAssetRegistryMetadata } from '../useAssetRegistryMetadata';
1616

1717
export interface BuyoutSettings {
1818
buyoutNativeToken: {
1919
min: number;
2020
max: number;
2121
};
2222
currencies: OrmlTraitsAssetRegistryAssetMetadata[];
23-
nativeCurrency: OrmlTraitsAssetRegistryAssetMetadata;
23+
nativeCurrency: OrmlTraitsAssetRegistryAssetMetadata | undefined;
2424
sellFee: PerMill;
2525
handleBuyout: (
2626
currency: OrmlTraitsAssetRegistryAssetMetadata,
@@ -55,11 +55,42 @@ function handleBuyoutError(error: string) {
5555

5656
export const useBuyout = (): BuyoutSettings => {
5757
const { api } = useNodeInfoState().state;
58-
const { tenantName } = useGlobalState();
5958
const { walletAccount } = useGlobalState();
6059
const [minimumBuyout, setMinimumBuyout] = useState<number>(0);
6160
const [maximumBuyout, setMaximumBuyout] = useState<number>(0);
6261
const [sellFee, setSellFee] = useState<PerMill>(new PerMill(0));
62+
const { getNativeAssetMetadata, getAssetMetadata } = useAssetRegistryMetadata();
63+
64+
const [nativeCurrency, setNativeCurrency] = useState<OrmlTraitsAssetRegistryAssetMetadata | undefined>(undefined);
65+
const [currencies, setCurrencies] = useState<OrmlTraitsAssetRegistryAssetMetadata[]>([]);
66+
67+
useEffect(() => {
68+
async function fetchAllowedCurrencies() {
69+
const allowedCurrencies = [];
70+
if (api) {
71+
const allowedCurrenciesEntries = await api.query.treasuryBuyoutExtension.allowedCurrencies.entries();
72+
73+
for (const currency of allowedCurrenciesEntries) {
74+
if (currency.length && currency[0] && currency[0].toHuman()) {
75+
const currencyXCMId: { XCM: number } = (currency[0].toHuman() as { XCM: number }[])[0];
76+
const currencyMetadata = await getAssetMetadata(currencyXCMId);
77+
78+
if (currencyMetadata) {
79+
allowedCurrencies.push(currencyMetadata);
80+
}
81+
}
82+
}
83+
}
84+
return allowedCurrencies;
85+
}
86+
87+
async function fetchAndSetCurrencies() {
88+
setCurrencies(await fetchAllowedCurrencies());
89+
setNativeCurrency(await getNativeAssetMetadata());
90+
}
91+
92+
fetchAndSetCurrencies().catch(console.error);
93+
}, [api, getNativeAssetMetadata, getAssetMetadata]);
6394

6495
useEffect(() => {
6596
async function fetchBuyoutLimits() {
@@ -129,9 +160,9 @@ export const useBuyout = (): BuyoutSettings => {
129160
min: minimumBuyout,
130161
max: maximumBuyout,
131162
},
132-
currencies: Object.values(getMetadata(tenantName).currencies),
163+
currencies,
133164
sellFee,
134-
nativeCurrency: getMetadata(tenantName).nativeCurrency.ampe,
165+
nativeCurrency,
135166
handleBuyout,
136167
};
137168
};

src/hooks/useBuyout/types.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
import { SpacewalkPrimitivesAsset } from '@pendulum-chain/types/interfaces';
2+
3+
export type AssetId =
4+
| {
5+
XCM: number;
6+
}
7+
| string
8+
| SpacewalkPrimitivesAsset;
9+
110
export interface OrmlTraitsAssetRegistryAssetMetadata {
211
metadata: {
312
decimals: number;
413
name: string;
514
symbol: string;
6-
existentialDeposit: number;
715
};
8-
assetId:
9-
| {
10-
XCM: number;
11-
}
12-
| string;
16+
assetId: AssetId;
1317
}

src/hooks/useBuyout/utils.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/pages/gas/GasSkeleton.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Skeleton } from '../../components/Skeleton';
2+
3+
export const GasSkeleton = () => (
4+
<div className="w-full h-full flex justify-center items-center">
5+
<div className="w-3/5">
6+
{Array.from({ length: 12 }).map((_, index) => (
7+
<Skeleton className="w-full h-8 mb-2" isLoading={true} key={index} />
8+
))}
9+
</div>
10+
</div>
11+
);

src/pages/gas/index.tsx

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,59 @@ import { OrmlTraitsAssetRegistryAssetMetadata } from '../../hooks/useBuyout/type
99
import { GasForm, IssueFormValues } from './GasForm';
1010
import { calculateForCurrentFromToken, calculatePriceNativeForCurrentFromToken } from './helpers';
1111
import { GasSuccessDialog } from './GasSuccessDialog';
12+
import { GasSkeleton } from './GasSkeleton';
1213

1314
const Gas = () => {
1415
const { currencies, buyoutNativeToken, sellFee, nativeCurrency, handleBuyout } = useBuyout();
1516
const { pricesCache } = usePriceFetcher();
1617

17-
const [selectedFromToken, setSelectedFromToken] = useState<BlockchainAsset | undefined>(currencies[0]);
18+
const [selectedFromToken, setSelectedFromToken] = useState<BlockchainAsset | undefined>(undefined);
1819
const [selectedFromTokenPriceUSD, setSelectedFromTokenPriceUSD] = useState<number>(0);
1920
const [nativeTokenPrice, setNativeTokenPrice] = useState<number>(0);
2021
const [submissionPending, setSubmissionPending] = useState<boolean>(false);
2122
const [confirmationDialogVisible, setConfirmationDialogVisible] = useState<boolean>(false);
2223

2324
useEffect(() => {
2425
const fetchPricesCache = async () => {
25-
const tokensPrices = await pricesCache;
26+
if (nativeCurrency && isOrmlAsset(selectedFromToken)) {
27+
const tokensPrices = await pricesCache;
2628

27-
if (!isEmpty(tokensPrices) && isOrmlAsset(selectedFromToken)) {
28-
setSelectedFromTokenPriceUSD(tokensPrices[selectedFromToken.metadata.symbol]);
29-
const nativeTokenPrice = tokensPrices[nativeCurrency.metadata.symbol];
30-
// We add the sellFee to the native price to already accommodate for it in the calculations
31-
const nativeTokenPriceWithFee = sellFee.addSelfToBase(nativeTokenPrice);
32-
setNativeTokenPrice(nativeTokenPriceWithFee);
29+
if (!isEmpty(tokensPrices)) {
30+
setSelectedFromTokenPriceUSD(tokensPrices[selectedFromToken.metadata.symbol]);
31+
const nativeTokenPrice = tokensPrices[nativeCurrency.metadata.symbol];
32+
// We add the sellFee to the native price to already accommodate for it in the calculations
33+
const nativeTokenPriceWithFee = sellFee.addSelfToBase(nativeTokenPrice);
34+
setNativeTokenPrice(nativeTokenPriceWithFee);
35+
}
3336
}
3437
};
3538

3639
fetchPricesCache().catch(console.error);
37-
}, [nativeCurrency.metadata.symbol, pricesCache, selectedFromToken, sellFee]);
40+
}, [nativeCurrency, pricesCache, selectedFromToken, sellFee]);
41+
42+
useEffect(() => {
43+
if (!selectedFromToken) {
44+
setSelectedFromToken(currencies[0] as OrmlTraitsAssetRegistryAssetMetadata);
45+
}
46+
}, [selectedFromToken, currencies]);
3847

3948
const onSubmit = async (data: IssueFormValues) => {
40-
// If the user has selected the min or max amount by clicking the badge button, we call the buyout extrinsic in the
41-
// direction of the native token being the input token. This way we ensure that the amount is perfectly within the buyout limits and
42-
// the transaction does not fail due to imprecise calculations.
43-
const isExchangeAmount = data.isMin || data.isMax;
44-
const token = isExchangeAmount ? nativeCurrency : (selectedFromToken as OrmlTraitsAssetRegistryAssetMetadata);
45-
const amount = data.isMin ? buyoutNativeToken.min : data.isMax ? buyoutNativeToken.max : Number(data.fromAmount);
49+
if (nativeCurrency && selectedFromToken) {
50+
// If the user has selected the min or max amount by clicking the badge button, we call the buyout extrinsic in the
51+
// direction of the native token being the input token. This way we ensure that the amount is perfectly within the buyout limits and
52+
// the transaction does not fail due to imprecise calculations.
53+
const isExchangeAmount = data.isMin || data.isMax;
54+
const token = isExchangeAmount ? nativeCurrency : (selectedFromToken as OrmlTraitsAssetRegistryAssetMetadata);
55+
const amount = data.isMin ? buyoutNativeToken.min : data.isMax ? buyoutNativeToken.max : Number(data.fromAmount);
4656

47-
handleBuyout(token, amount, setSubmissionPending, setConfirmationDialogVisible, isExchangeAmount);
57+
handleBuyout(token, amount, setSubmissionPending, setConfirmationDialogVisible, isExchangeAmount);
58+
}
4859
};
4960

50-
const selectedTokenDecimals = (selectedFromToken as OrmlTraitsAssetRegistryAssetMetadata).metadata.decimals;
51-
const nativeDecimals = (nativeCurrency as OrmlTraitsAssetRegistryAssetMetadata).metadata.decimals;
61+
const selectedTokenDecimals = (selectedFromToken as OrmlTraitsAssetRegistryAssetMetadata)?.metadata.decimals ?? 0;
62+
const nativeDecimals = (nativeCurrency as OrmlTraitsAssetRegistryAssetMetadata)?.metadata.decimals ?? 0;
63+
64+
if (!selectedFromToken || !nativeCurrency) return <GasSkeleton />;
5265

5366
return (
5467
<div className="h-full flex items-center justify-center mt-4">
@@ -62,7 +75,7 @@ const Gas = () => {
6275
<h1 className="text-[28px] mb-8">Get AMPE</h1>
6376
<GasForm
6477
submissionPending={submissionPending}
65-
currencies={currencies}
78+
currencies={currencies.length ? currencies : []}
6679
selectedFromToken={selectedFromToken}
6780
setSelectedFromToken={setSelectedFromToken}
6881
nativeCurrency={nativeCurrency}

0 commit comments

Comments
 (0)