Skip to content

Commit

Permalink
Feature: get balances on interval (#66)
Browse files Browse the repository at this point in the history
* underline footer links on hover

* useBalancePolling hook
  • Loading branch information
steezeburger authored Nov 15, 2024
1 parent f35ca28 commit 48d439a
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 80 deletions.
116 changes: 64 additions & 52 deletions web/src/features/EthWallet/hooks/useEvmChainSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import { NotificationType, useNotifications } from "features/Notifications";
import { useEthWallet } from "features/EthWallet/hooks/useEthWallet";
import EthWalletConnector from "features/EthWallet/components/EthWalletConnector/EthWalletConnector";
import {
AstriaErc20WithdrawerService,
type AstriaErc20WithdrawerService,
getAstriaWithdrawerService,
} from "features/EthWallet/services/AstriaWithdrawerService/AstriaWithdrawerService";
import { formatBalance } from "features/EthWallet/utils/utils";
import { useBalancePolling } from "features/GetBalancePolling";

export function useEvmChainSelection(evmChains: EvmChains) {
const { addNotification } = useNotifications();
Expand All @@ -37,72 +38,83 @@ export function useEvmChainSelection(evmChains: EvmChains) {
null,
);

const [evmBalance, setEvmBalance] = useState<string | null>(null);
const [isLoadingEvmBalance, setIsLoadingEvmBalance] =
useState<boolean>(false);

const resetState = useCallback(() => {
setSelectedEvmChain(null);
setSelectedEvmCurrency(null);
setEvmAccountAddress(null);
setEvmBalance(null);
setIsLoadingEvmBalance(false);
}, []);

useEffect(() => {
async function getAndSetBalance() {
if (
!provider ||
!userAccount ||
!selectedEvmChain ||
!selectedEvmCurrency ||
!evmAccountAddress
) {
return;
}
if (!evmCurrencyBelongsToChain(selectedEvmCurrency, selectedEvmChain)) {
return;
}
setIsLoadingEvmBalance(true);
try {
const contractAddress =
selectedEvmCurrency.erc20ContractAddress ||
selectedEvmCurrency.nativeTokenWithdrawerContractAddress ||
"";
const withdrawerSvc = getAstriaWithdrawerService(
const getBalanceCallback = useCallback(async () => {
if (
!provider ||
!userAccount ||
!selectedEvmChain ||
!selectedEvmCurrency ||
!evmAccountAddress
) {
console.log(
"provider, userAccount, chain, currency, or address is null",
{
provider,
contractAddress,
Boolean(selectedEvmCurrency.erc20ContractAddress),
);
if (withdrawerSvc instanceof AstriaErc20WithdrawerService) {
const balanceRes = await withdrawerSvc.getBalance(evmAccountAddress);
const balanceStr = formatBalance(
balanceRes.toString(),
selectedEvmCurrency.coinDecimals,
);
const balance = `${balanceStr} ${selectedEvmCurrency.coinDenom}`;
setEvmBalance(balance);
} else {
// for native token balance
const balance = `${userAccount.balance} ${selectedEvmCurrency.coinDenom}`;
setEvmBalance(balance);
}
setIsLoadingEvmBalance(false);
} catch (e) {
console.error("Failed to get balance from EVM", e);
setIsLoadingEvmBalance(false);
}
userAccount,
selectedEvmChain,
selectedEvmCurrency,
evmAccountAddress,
},
);
return null;
}
if (!evmCurrencyBelongsToChain(selectedEvmCurrency, selectedEvmChain)) {
return null;
}
if (selectedEvmCurrency.erc20ContractAddress) {
const withdrawerSvc = getAstriaWithdrawerService(
provider,
selectedEvmCurrency.erc20ContractAddress,
true,
) as AstriaErc20WithdrawerService;
const balanceRes = await withdrawerSvc.getBalance(evmAccountAddress);
const balanceStr = formatBalance(
balanceRes.toString(),
selectedEvmCurrency.coinDecimals,
);
return `${balanceStr} ${selectedEvmCurrency.coinDenom}`;
}

getAndSetBalance().then((_) => {});
return `${userAccount.balance} ${selectedEvmCurrency.coinDenom}`;
}, [
selectedEvmChain,
selectedEvmCurrency,
provider,
userAccount,
selectedEvmChain,
selectedEvmCurrency,
evmAccountAddress,
]);

const pollingConfig = useMemo(
() => ({
enabled: Boolean(
provider &&
userAccount &&
selectedEvmChain &&
selectedEvmCurrency &&
evmAccountAddress,
),
intervalMS: 10_000,
onError: (error: Error) => {
console.error("Failed to get balance from EVM wallet", error);
},
}),
[
provider,
userAccount,
selectedEvmChain,
selectedEvmCurrency,
evmAccountAddress,
],
);
const { balance: evmBalance, isLoading: isLoadingEvmBalance } =
useBalancePolling(getBalanceCallback, pollingConfig);

const selectedEvmChainNativeToken = useMemo(() => {
return selectedEvmChain?.currencies[0];
}, [selectedEvmChain]);
Expand Down
57 changes: 57 additions & 0 deletions web/src/features/GetBalancePolling/hooks/useGetBalancePolling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useCallback, useEffect, useState } from "react";

interface PollingConfig {
enabled: boolean;
intervalMS?: number;
onError?: (error: Error) => void;
}

/**
* Hook to poll for balance at a given interval
* @param fetchBalance
* @param config
*/
export default function useBalancePolling<T>(
fetchBalance: () => Promise<T>,
config: PollingConfig,
) {
const [balance, setBalance] = useState<T | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);

const getBalance = useCallback(async () => {
if (!config.enabled) return;

setIsLoading(true);
try {
const result = await fetchBalance();
setBalance(result);
setError(null);
} catch (e) {
const error =
e instanceof Error ? e : new Error("Failed to fetch balance");
setError(error);
config.onError?.(error);
} finally {
setIsLoading(false);
}
}, [fetchBalance, config.enabled, config.onError]);

useEffect(() => {
getBalance().then((_) => {});

// setup polling if enabled
if (config.enabled && config.intervalMS) {
const intervalId = setInterval(getBalance, config.intervalMS);
return () => clearInterval(intervalId);
}
return undefined;
}, [getBalance, config.enabled, config.intervalMS]);

return {
balance,
isLoading,
error,
refetch: getBalance,
};
}
3 changes: 3 additions & 0 deletions web/src/features/GetBalancePolling/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import useBalancePolling from "./hooks/useGetBalancePolling";

export { useBalancePolling };
49 changes: 21 additions & 28 deletions web/src/features/KeplrWallet/hooks/useIbcChainSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getBalanceFromKeplr,
getKeplrFromWindow,
} from "features/KeplrWallet/services/ibc";
import { useBalancePolling } from "features/GetBalancePolling";

/**
* Custom hook to manage the selection of an IBC chain and currency.
Expand All @@ -34,43 +35,35 @@ export function useIbcChainSelection(ibcChains: IbcChains) {
null,
);

const [ibcBalance, setIbcBalance] = useState<string | null>(null);
const [isLoadingIbcBalance, setIsLoadingIbcBalance] =
useState<boolean>(false);

const resetState = useCallback(() => {
setSelectedIbcChain(null);
setSelectedIbcCurrency(null);
setIbcAccountAddress(null);
setIbcBalance(null);
setIsLoadingIbcBalance(false);
}, []);

useEffect(() => {
async function getAndSetBalance() {
if (!selectedIbcChain || !selectedIbcCurrency) {
return;
}
if (!ibcCurrencyBelongsToChain(selectedIbcCurrency, selectedIbcChain)) {
return;
}
setIsLoadingIbcBalance(true);
try {
const balance = await getBalanceFromKeplr(
selectedIbcChain,
selectedIbcCurrency,
);
setIbcBalance(balance);
setIsLoadingIbcBalance(false);
} catch (e) {
console.error("Failed to get balance from Keplr", e);
setIsLoadingIbcBalance(false);
}
const getBalanceCallback = useCallback(async () => {
if (!selectedIbcChain || !selectedIbcCurrency) {
return null;
}

getAndSetBalance().then((_) => {});
if (!ibcCurrencyBelongsToChain(selectedIbcCurrency, selectedIbcChain)) {
return null;
}
return getBalanceFromKeplr(selectedIbcChain, selectedIbcCurrency);
}, [selectedIbcChain, selectedIbcCurrency]);

const pollingConfig = useMemo(
() => ({
enabled: !!selectedIbcChain && !!selectedIbcCurrency,
intervalMS: 10_000,
onError: (error: Error) => {
console.error("Failed to get balance from Keplr", error);
},
}),
[selectedIbcChain, selectedIbcCurrency],
);
const { balance: ibcBalance, isLoading: isLoadingIbcBalance } =
useBalancePolling(getBalanceCallback, pollingConfig);

useEffect(() => {
async function getAddress() {
if (!selectedIbcChain) {
Expand Down
3 changes: 3 additions & 0 deletions web/src/styles/footer-customizations.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
.content {
a {
color: $astria-orange-soft;
&:hover {
text-decoration: underline;
}
}
}
}

0 comments on commit 48d439a

Please sign in to comment.