mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-05-13 20:06:51 +08:00
feat: refactor available balance
This commit is contained in:
@@ -17,7 +17,7 @@ export function useTrackSwitchAccount() {
|
||||
if (!accountBalanceCache) return;
|
||||
try {
|
||||
const balances = parseBalanceResponse(accountBalanceCache as any);
|
||||
const hasStxBalance = !!balances?.stx.availableStx.amount.isGreaterThan(0);
|
||||
const hasStxBalance = !!balances?.stx.unlockedStx.amount.isGreaterThan(0);
|
||||
void analytics.track('switch_account', { index, hasStxBalance });
|
||||
} finally {
|
||||
}
|
||||
|
||||
47
src/app/common/hooks/balance/stx/use-stx-balance.ts
Normal file
47
src/app/common/hooks/balance/stx/use-stx-balance.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createMoney } from '@shared/models/money.model';
|
||||
import { isDefined } from '@shared/utils';
|
||||
|
||||
import { baseCurrencyAmountInQuote, subtractMoney } from '@app/common/money/calculate-money';
|
||||
import { i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils';
|
||||
import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks';
|
||||
|
||||
import { useStxOutboundValue } from './use-stx-outbound-value';
|
||||
|
||||
export function useStxBalance() {
|
||||
const anchoredBalanceQuery = useCurrentStacksAccountAnchoredBalances();
|
||||
const totalBalance = anchoredBalanceQuery.data?.stx.balance;
|
||||
const unlockedStxBalance = anchoredBalanceQuery.data?.stx.unlockedStx;
|
||||
|
||||
const stxMarketData = useCryptoCurrencyMarketData('STX');
|
||||
const stxOutboundQuery = useStxOutboundValue();
|
||||
|
||||
const stxEffectiveBalance = isDefined(totalBalance)
|
||||
? subtractMoney(totalBalance, stxOutboundQuery.data)
|
||||
: createMoney(0, 'STX');
|
||||
const stxEffectiveUsdBalance = isDefined(totalBalance)
|
||||
? i18nFormatCurrency(baseCurrencyAmountInQuote(stxEffectiveBalance, stxMarketData))
|
||||
: undefined;
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
anchoredBalanceQuery,
|
||||
stxOutboundQuery,
|
||||
isLoading: anchoredBalanceQuery.isLoading || stxOutboundQuery.isLoading,
|
||||
availableBalance: isDefined(unlockedStxBalance)
|
||||
? subtractMoney(unlockedStxBalance, stxOutboundQuery.data)
|
||||
: createMoney(0, 'STX'),
|
||||
stxEffectiveBalance: createStacksCryptoCurrencyAssetTypeWrapper(stxEffectiveBalance.amount),
|
||||
stxEffectiveUsdBalance,
|
||||
};
|
||||
}, [
|
||||
anchoredBalanceQuery,
|
||||
stxOutboundQuery,
|
||||
unlockedStxBalance,
|
||||
stxEffectiveBalance,
|
||||
stxEffectiveUsdBalance,
|
||||
]);
|
||||
}
|
||||
19
src/app/common/hooks/balance/stx/use-stx-outbound-value.ts
Normal file
19
src/app/common/hooks/balance/stx/use-stx-outbound-value.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { sumMoney } from '@app/common/money/calculate-money';
|
||||
import { useCurrentAccountMempoolTransactionsBalance } from '@app/query/stacks/mempool/mempool.hooks';
|
||||
import { useCurrentAccountMicroblockBalanceQuery } from '@app/query/stacks/microblock/microblock.query';
|
||||
import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
|
||||
|
||||
export function useStxOutboundValue() {
|
||||
const stxAddress = useCurrentAccountStxAddressState();
|
||||
const pendingTxsBalance = useCurrentAccountMempoolTransactionsBalance();
|
||||
const microblockBalanceQuery = useCurrentAccountMicroblockBalanceQuery(stxAddress);
|
||||
|
||||
if (!microblockBalanceQuery.data) {
|
||||
return { ...microblockBalanceQuery, data: pendingTxsBalance };
|
||||
}
|
||||
|
||||
return {
|
||||
...microblockBalanceQuery,
|
||||
data: sumMoney([pendingTxsBalance, microblockBalanceQuery.data]),
|
||||
};
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
|
||||
import { i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
import { useStacksAnchoredCryptoCurrencyAssetBalance } from '@app/query/stacks/balance/stacks-ft-balances.hooks';
|
||||
|
||||
export function useStxAssetBalance(address: string) {
|
||||
const stxMarketData = useCryptoCurrencyMarketData('STX');
|
||||
const { data: balance, isLoading } = useStacksAnchoredCryptoCurrencyAssetBalance(address);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
isLoading,
|
||||
stxAssetBalance: balance,
|
||||
stxUsdBalance: balance
|
||||
? i18nFormatCurrency(baseCurrencyAmountInQuote(balance.balance, stxMarketData))
|
||||
: undefined,
|
||||
}),
|
||||
[balance, isLoading, stxMarketData]
|
||||
);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)
|
||||
if (!balances || !btcBalance) return null;
|
||||
|
||||
// calculate total balance
|
||||
const stxUsdAmount = baseCurrencyAmountInQuote(balances.stx.availableStx, stxMarketData);
|
||||
const stxUsdAmount = baseCurrencyAmountInQuote(balances.stx.balance, stxMarketData);
|
||||
const btcUsdAmount = baseCurrencyAmountInQuote(btcBalance.balance, btcMarketData);
|
||||
|
||||
const totalBalance = { ...stxUsdAmount, amount: stxUsdAmount.amount.plus(btcUsdAmount.amount) };
|
||||
|
||||
@@ -3,26 +3,27 @@ import { useMemo } from 'react';
|
||||
import type { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model';
|
||||
|
||||
import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/bitcoin-balances.query';
|
||||
import {
|
||||
useStacksAnchoredCryptoCurrencyAssetBalance,
|
||||
useTransferableStacksFungibleTokenAssetBalances,
|
||||
} from '@app/query/stacks/balance/stacks-ft-balances.hooks';
|
||||
import { useTransferableStacksFungibleTokenAssetBalances } from '@app/query/stacks/balance/stacks-ft-balances.hooks';
|
||||
import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils';
|
||||
import { useCurrentBtcNativeSegwitAccountAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
|
||||
|
||||
import { useStxBalance } from './balance/stx/use-stx-balance';
|
||||
|
||||
export function useAllTransferableCryptoAssetBalances(): AllTransferableCryptoAssetBalances[] {
|
||||
const account = useCurrentStacksAccount();
|
||||
const currentBtcAddress = useCurrentBtcNativeSegwitAccountAddressIndexZero();
|
||||
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(currentBtcAddress);
|
||||
const { data: stxCryptoCurrencyAssetBalance } = useStacksAnchoredCryptoCurrencyAssetBalance(
|
||||
account?.address ?? ''
|
||||
|
||||
const { availableBalance: availableStxBalance } = useStxBalance();
|
||||
const stxCryptoCurrencyAssetBalance = createStacksCryptoCurrencyAssetTypeWrapper(
|
||||
availableStxBalance.amount
|
||||
);
|
||||
const stacksFtAssetBalances = useTransferableStacksFungibleTokenAssetBalances(
|
||||
account?.address ?? ''
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!stxCryptoCurrencyAssetBalance) return [];
|
||||
return [btcCryptoCurrencyAssetBalance, stxCryptoCurrencyAssetBalance, ...stacksFtAssetBalances];
|
||||
}, [btcCryptoCurrencyAssetBalance, stacksFtAssetBalances, stxCryptoCurrencyAssetBalance]);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,18 @@ import BigNumber from 'bignumber.js';
|
||||
import { MarketData, createMarketData, createMarketPair } from '@shared/models/market.model';
|
||||
import { createMoney, createMoneyFromDecimal } from '@shared/models/money.model';
|
||||
|
||||
import { baseCurrencyAmountInQuote, convertAmountToFractionalUnit } from './calculate-money';
|
||||
import {
|
||||
baseCurrencyAmountInQuote,
|
||||
convertAmountToFractionalUnit,
|
||||
subtractMoney,
|
||||
sumMoney,
|
||||
} from './calculate-money';
|
||||
|
||||
const tenMicroStx = createMoney(10, 'STX');
|
||||
const tenStx = createMoneyFromDecimal(10, 'STX');
|
||||
|
||||
const tenBtc = createMoneyFromDecimal(10, 'BTC');
|
||||
|
||||
const mockWrongMarketData = {
|
||||
pair: createMarketPair('BTC' as any, 'USD'),
|
||||
price: createMoneyFromDecimal(1, 'EUR' as any, 2),
|
||||
@@ -40,3 +47,25 @@ describe(convertAmountToFractionalUnit.name, () => {
|
||||
test('it converts 99 as decimal amount to a fractional unit', () =>
|
||||
expect(convertAmountToFractionalUnit(new BigNumber(99), 6).toNumber()).toEqual(99000000));
|
||||
});
|
||||
|
||||
describe(sumMoney.name, () => {
|
||||
test('it sums two money objects', () => {
|
||||
const result = sumMoney([tenMicroStx, tenMicroStx]);
|
||||
expect(result.amount.toString()).toEqual('20');
|
||||
expect(result.symbol).toEqual(tenMicroStx.symbol);
|
||||
});
|
||||
test('it throws error when summing different currencies', () => {
|
||||
expect(() => sumMoney([tenMicroStx, tenBtc])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe(subtractMoney.name, () => {
|
||||
test('it subtracts two money objects', () => {
|
||||
const result = subtractMoney(tenMicroStx, tenMicroStx);
|
||||
expect(result.amount.toString()).toEqual('0');
|
||||
expect(result.symbol).toEqual(tenMicroStx.symbol);
|
||||
});
|
||||
test('it throws error when subtracting different currencies', () => {
|
||||
expect(() => subtractMoney(tenMicroStx, tenBtc)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MarketData, formatMarketPair } from '@shared/models/market.model';
|
||||
import { Money, createMoney } from '@shared/models/money.model';
|
||||
import { isNumber } from '@shared/utils';
|
||||
|
||||
import { sumNumbers } from '../math/helpers';
|
||||
import { formatMoney } from './format-money';
|
||||
import { isMoney } from './is-money';
|
||||
|
||||
@@ -36,3 +37,16 @@ export function convertAmountToBaseUnit(num: Money | BigNumber, decimals?: numbe
|
||||
if (!isNumber(decimals)) throw new Error('Must define decimal of given currency');
|
||||
return num.shiftedBy(-decimals);
|
||||
}
|
||||
|
||||
export function subtractMoney(xAmount: Money, yAmount: Money) {
|
||||
if (xAmount.symbol !== yAmount.symbol) throw new Error('Cannot subtract different currencies');
|
||||
return createMoney(xAmount.amount.minus(yAmount.amount), xAmount.symbol, xAmount.decimals);
|
||||
}
|
||||
|
||||
export function sumMoney(moneysArr: Money[]) {
|
||||
if (moneysArr.some(item => item.symbol !== moneysArr[0].symbol))
|
||||
throw new Error('Cannot sum different currencies');
|
||||
|
||||
const sum = sumNumbers(moneysArr.map(item => item.amount.toNumber()));
|
||||
return createMoney(sum, moneysArr[0].symbol, moneysArr[0].decimals);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Money, createMoney } from '@shared/models/money.model';
|
||||
import { Money } from '@shared/models/money.model';
|
||||
|
||||
const thinSpace = ' ';
|
||||
|
||||
@@ -27,8 +27,3 @@ export function i18nFormatCurrency(quantity: Money, locale = 'en-US') {
|
||||
export function formatDustUsdAmounts(value: string) {
|
||||
return value.endsWith('0.00') ? '<' + thinSpace + value.replace('0.00', '0.01') : value;
|
||||
}
|
||||
|
||||
export function subtractMoney(xAmount: Money, yAmount: Money) {
|
||||
if (xAmount.symbol !== yAmount.symbol) throw new Error('Cannot subtract different currencies');
|
||||
return createMoney(xAmount.amount.minus(yAmount.amount), xAmount.symbol, xAmount.decimals);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Money } from '@shared/models/money.model';
|
||||
import { isNumber } from '@shared/utils';
|
||||
|
||||
import { countDecimals } from '@app/common/math/helpers';
|
||||
import { subtractMoney } from '@app/common/money/format-money';
|
||||
import {
|
||||
btcToSat,
|
||||
microStxToStx,
|
||||
@@ -80,22 +79,19 @@ export function stxAmountValidator() {
|
||||
.concat(stxAmountPrecisionValidator(formatPrecisionError()));
|
||||
}
|
||||
|
||||
export function stxAvailableBalanceValidator(availableBalance: Money, pendingTxsBalance: Money) {
|
||||
export function stxAvailableBalanceValidator(availableBalance: Money) {
|
||||
return yup
|
||||
.number()
|
||||
.typeError(formatErrorWithSymbol('STX', FormErrorMessages.MustBeNumber))
|
||||
.test({
|
||||
message: formatInsufficientBalanceError(availableBalance, sum =>
|
||||
microStxToStx(subtractMoney(sum, pendingTxsBalance).amount).toString()
|
||||
microStxToStx(sum.amount).toString()
|
||||
),
|
||||
test(value: unknown) {
|
||||
const fee = stxToMicroStx(this.parent.fee);
|
||||
if (!availableBalance || !isNumber(value)) return false;
|
||||
const availableBalanceLessAll = subtractMoney(
|
||||
availableBalance,
|
||||
pendingTxsBalance
|
||||
).amount.minus(fee);
|
||||
return availableBalanceLessAll.isGreaterThanOrEqualTo(stxToMicroStx(value));
|
||||
const availableBalanceLessFee = availableBalance.amount.minus(fee);
|
||||
return availableBalanceLessFee.isGreaterThanOrEqualTo(stxToMicroStx(value));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export function StxBalance(props: BalanceProps) {
|
||||
const balance = useMemo(
|
||||
() =>
|
||||
stacksValue({
|
||||
value: balances?.stx?.availableStx.amount ?? 0,
|
||||
value: balances?.stx?.unlockedStx.amount ?? 0,
|
||||
withTicker: true,
|
||||
}),
|
||||
[balances]
|
||||
|
||||
@@ -48,7 +48,7 @@ export const CryptoCurrencyAssetItemLayout = forwardRefWithAs(
|
||||
const [component, bind] = usePressable(isPressable);
|
||||
|
||||
const amount = balance.decimals
|
||||
? ftDecimals(balance.amount, balance.decimals || 0)
|
||||
? ftDecimals(balance.amount, balance.decimals)
|
||||
: balance.amount.toString();
|
||||
const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace(
|
||||
'{symbol}',
|
||||
@@ -80,7 +80,7 @@ export const CryptoCurrencyAssetItemLayout = forwardRefWithAs(
|
||||
</SpaceBetween>
|
||||
<SpaceBetween height="1.25rem" width="100%">
|
||||
<Caption>{caption}</Caption>
|
||||
{Number(amount) > 0 && address ? <Caption>{usdBalance}</Caption> : null}
|
||||
{balance.amount.toNumber() > 0 && address ? <Caption>{usdBalance}</Caption> : null}
|
||||
{isUnanchored && subBalance ? <SubBalance balance={subBalance} /> : null}
|
||||
</SpaceBetween>
|
||||
</Flag>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Box, Stack, StackProps } from '@stacks/ui';
|
||||
import { HomePageSelectorsLegacy } from '@tests-legacy/page-objects/home.selectors';
|
||||
|
||||
import { useBtcAssetBalance } from '@app/common/hooks/balance/use-btc-balance';
|
||||
import { useStxAssetBalance } from '@app/common/hooks/balance/use-stx-balance';
|
||||
import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance';
|
||||
import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance';
|
||||
import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item';
|
||||
import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-avatar';
|
||||
import { BtcIcon } from '@app/components/icons/btc-icon';
|
||||
@@ -23,11 +23,11 @@ export function BalancesList({ address, ...props }: BalancesListProps) {
|
||||
const { data: stxUnachoredAssetBalance } = useStacksUnanchoredCryptoCurrencyAssetBalance(address);
|
||||
const stacksFtAssetBalances = useStacksFungibleTokenAssetBalancesAnchoredWithMetadata(address);
|
||||
const isBitcoinEnabled = useConfigBitcoinEnabled();
|
||||
const { stxUsdBalance, stxAssetBalance } = useStxAssetBalance(address);
|
||||
const { stxEffectiveBalance, stxEffectiveUsdBalance } = useStxBalance();
|
||||
const { btcAddress, btcAssetBalance, btcUsdBalance } = useBtcAssetBalance();
|
||||
|
||||
// Better handle loading state
|
||||
if (!stxAssetBalance || !stxUnachoredAssetBalance) return <LoadingSpinner />;
|
||||
if (!stxUnachoredAssetBalance) return <LoadingSpinner />;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -46,9 +46,8 @@ export function BalancesList({ address, ...props }: BalancesListProps) {
|
||||
)}
|
||||
|
||||
<CryptoCurrencyAssetItem
|
||||
assetBalance={stxAssetBalance}
|
||||
usdBalance={stxUsdBalance}
|
||||
assetSubBalance={stxUnachoredAssetBalance}
|
||||
assetBalance={stxEffectiveBalance}
|
||||
usdBalance={stxEffectiveUsdBalance}
|
||||
address={address}
|
||||
icon={<StxAvatar {...props} />}
|
||||
/>
|
||||
|
||||
@@ -81,17 +81,17 @@ export function IncreaseFeeForm() {
|
||||
validateOnChange={false}
|
||||
validateOnBlur={false}
|
||||
validateOnMount={false}
|
||||
validationSchema={yup.object({ fee: stxFeeValidator(balances?.stx.availableStx) })}
|
||||
validationSchema={yup.object({ fee: stxFeeValidator(balances?.stx.unlockedStx) })}
|
||||
>
|
||||
{() => (
|
||||
<Stack spacing="extra-loose">
|
||||
{tx && <StacksTransactionItem position="relative" transaction={tx} zIndex={99} />}
|
||||
<Stack spacing="base">
|
||||
<IncreaseFeeField currentFee={fee} />
|
||||
{balances?.stx.availableStx.amount && (
|
||||
{balances?.stx.unlockedStx.amount && (
|
||||
<Caption>
|
||||
Balance:{' '}
|
||||
{stacksValue({ value: balances?.stx.availableStx.amount, fixedDecimals: true })}
|
||||
{stacksValue({ value: balances?.stx.unlockedStx.amount, fixedDecimals: true })}
|
||||
</Caption>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -105,7 +105,7 @@ const ChooseAccountItem = memo((props: ChooseAccountItemProps) => {
|
||||
<AccountBalanceLoading />
|
||||
) : balances ? (
|
||||
<AccountBalanceCaption
|
||||
availableBalance={balances.stx.availableStx}
|
||||
availableBalance={balances.stx.unlockedStx}
|
||||
marketData={stxMarketData}
|
||||
/>
|
||||
) : null
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Box, color } from '@stacks/ui';
|
||||
import { FiInfo } from 'react-icons/fi';
|
||||
|
||||
import { Box, Flex, Stack, Tooltip, color } from '@stacks/ui';
|
||||
|
||||
import { Money } from '@shared/models/money.model';
|
||||
|
||||
@@ -11,7 +13,8 @@ import { PreviewButton } from './preview-button';
|
||||
|
||||
export function FormFooter(props: { balance: Money }) {
|
||||
const { balance } = props;
|
||||
|
||||
const balanceTooltipLabel =
|
||||
'Amount that is immediately available for use after taking into account any pending transactions or holds placed on your account by the protocol.';
|
||||
return (
|
||||
<Box
|
||||
bg={color('bg')}
|
||||
@@ -28,7 +31,20 @@ export function FormFooter(props: { balance: Money }) {
|
||||
<Box mt="loose" px="loose">
|
||||
<PreviewButton />
|
||||
<SpaceBetween>
|
||||
<Caption>Balance</Caption>
|
||||
<Flex alignItems="center">
|
||||
<Caption mr="tight">Available balance</Caption>
|
||||
<Tooltip placement="top" label={balanceTooltipLabel}>
|
||||
<Stack>
|
||||
<Box
|
||||
_hover={{ cursor: 'pointer' }}
|
||||
as={FiInfo}
|
||||
color={color('text-caption')}
|
||||
size="14px"
|
||||
/>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
<Caption>{formatMoney(balance)}</Caption>
|
||||
</SpaceBetween>
|
||||
</Box>
|
||||
|
||||
@@ -6,8 +6,8 @@ import * as yup from 'yup';
|
||||
import { STX_DECIMALS } from '@shared/constants';
|
||||
import { logger } from '@shared/logger';
|
||||
import { StacksSendFormValues } from '@shared/models/form.model';
|
||||
import { createMoney } from '@shared/models/money.model';
|
||||
|
||||
import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance';
|
||||
import { convertAmountToBaseUnit } from '@app/common/money/calculate-money';
|
||||
import { useWalletType } from '@app/common/use-wallet-type';
|
||||
import {
|
||||
@@ -17,9 +17,7 @@ import {
|
||||
import { stxFeeValidator } from '@app/common/validation/forms/fee-validators';
|
||||
import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate';
|
||||
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
|
||||
import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks';
|
||||
import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks';
|
||||
import { useCurrentAccountMempoolTransactionsBalance } from '@app/query/stacks/mempool/mempool.hooks';
|
||||
import {
|
||||
useGenerateStxTokenTransferUnsignedTx,
|
||||
useStxTokenTransferUnsignedTxState,
|
||||
@@ -29,29 +27,25 @@ import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
|
||||
import { useStacksCommonSendForm } from '../stacks/use-stacks-common-send-form';
|
||||
|
||||
export function useStxSendForm() {
|
||||
const { data: balances } = useCurrentStacksAccountAnchoredBalances();
|
||||
const unsignedTx = useStxTokenTransferUnsignedTxState();
|
||||
const { data: stxFees } = useCalculateStacksTxFees(unsignedTx);
|
||||
|
||||
const generateTx = useGenerateStxTokenTransferUnsignedTx();
|
||||
const pendingTxsBalance = useCurrentAccountMempoolTransactionsBalance();
|
||||
const { onFormStateChange } = useUpdatePersistedSendFormValues();
|
||||
|
||||
const { whenWallet } = useWalletType();
|
||||
const ledgerNavigate = useLedgerNavigate();
|
||||
const sendFormNavigate = useSendFormNavigate();
|
||||
|
||||
const availableStxBalance = balances?.stx.availableStx ?? createMoney(0, 'STX');
|
||||
const { availableBalance: availableStxBalance } = useStxBalance();
|
||||
|
||||
const sendMaxBalance = useMemo(
|
||||
() =>
|
||||
convertAmountToBaseUnit(
|
||||
availableStxBalance.amount
|
||||
.minus(pendingTxsBalance.amount)
|
||||
.minus(stxFees?.estimates[1].fee.amount || 0),
|
||||
availableStxBalance.amount.minus(stxFees?.estimates[1].fee.amount || 0),
|
||||
STX_DECIMALS
|
||||
),
|
||||
[availableStxBalance, pendingTxsBalance, stxFees]
|
||||
[availableStxBalance, stxFees]
|
||||
);
|
||||
|
||||
const { initialValues, checkFormValidation, recipient, memo, nonce } = useStacksCommonSendForm({
|
||||
@@ -67,9 +61,7 @@ export function useStxSendForm() {
|
||||
stxFees,
|
||||
|
||||
validationSchema: yup.object({
|
||||
amount: stxAmountValidator().concat(
|
||||
stxAvailableBalanceValidator(availableStxBalance, pendingTxsBalance)
|
||||
),
|
||||
amount: stxAmountValidator().concat(stxAvailableBalanceValidator(availableStxBalance)),
|
||||
fee: stxFeeValidator(availableStxBalance),
|
||||
recipient,
|
||||
memo,
|
||||
|
||||
@@ -73,7 +73,7 @@ export const StxTransferInsufficientFundsErrorMessage = memo(props => {
|
||||
<Caption>
|
||||
{balance
|
||||
? stacksValue({
|
||||
value: balance.stx.availableStx.amount,
|
||||
value: balance.stx.unlockedStx.amount,
|
||||
withTicker: true,
|
||||
})
|
||||
: '--'}
|
||||
|
||||
@@ -34,13 +34,13 @@ export function useTransactionError() {
|
||||
}
|
||||
|
||||
if (balances) {
|
||||
const zeroBalance = balances?.stx.availableStx.amount.toNumber() === 0;
|
||||
const zeroBalance = balances?.stx.unlockedStx.amount.toNumber() === 0;
|
||||
|
||||
if (transactionRequest.txType === TransactionTypes.STXTransfer) {
|
||||
if (zeroBalance) return TransactionErrorReason.StxTransferInsufficientFunds;
|
||||
|
||||
const transferAmount = new BigNumber(transactionRequest.amount);
|
||||
if (transferAmount.gte(balances?.stx.availableStx.amount))
|
||||
if (transferAmount.gte(balances?.stx.unlockedStx.amount))
|
||||
return TransactionErrorReason.StxTransferInsufficientFunds;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export function useTransactionError() {
|
||||
|
||||
if (transactionRequest.fee) {
|
||||
const feeValue = microStxToStx(transactionRequest.fee);
|
||||
if (feeValue.gte(balances?.stx.availableStx.amount))
|
||||
if (feeValue.gte(balances?.stx.unlockedStx.amount))
|
||||
return TransactionErrorReason.FeeInsufficientFunds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ function TransactionRequestBase() {
|
||||
|
||||
const validationSchema = !transactionRequest.sponsored
|
||||
? yup.object({
|
||||
fee: stxFeeValidator(stacksBalances?.stx.availableStx),
|
||||
fee: stxFeeValidator(stacksBalances?.stx.unlockedStx),
|
||||
nonce: nonceValidator,
|
||||
})
|
||||
: null;
|
||||
|
||||
@@ -24,17 +24,13 @@ import {
|
||||
export function useStacksAnchoredCryptoCurrencyAssetBalance(address: string) {
|
||||
return useAnchoredStacksAccountBalanceQuery(address, {
|
||||
select: resp =>
|
||||
createStacksCryptoCurrencyAssetTypeWrapper(
|
||||
parseBalanceResponse(resp).stx.availableStx.amount
|
||||
),
|
||||
createStacksCryptoCurrencyAssetTypeWrapper(parseBalanceResponse(resp).stx.unlockedStx.amount),
|
||||
});
|
||||
}
|
||||
export function useStacksUnanchoredCryptoCurrencyAssetBalance(address: string) {
|
||||
return useUnanchoredStacksAccountBalanceQuery(address, {
|
||||
select: resp =>
|
||||
createStacksCryptoCurrencyAssetTypeWrapper(
|
||||
parseBalanceResponse(resp).stx.availableStx.amount
|
||||
),
|
||||
createStacksCryptoCurrencyAssetTypeWrapper(parseBalanceResponse(resp).stx.unlockedStx.amount),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,11 @@ export function parseBalanceResponse(balances: AddressBalanceResponse) {
|
||||
])
|
||||
) as Record<AccountBalanceStxKeys, Money>;
|
||||
|
||||
const stx: AccountStxBalanceBigNumber & { availableStx: Money } = {
|
||||
const stx: AccountStxBalanceBigNumber & { unlockedStx: Money } = {
|
||||
...balances.stx,
|
||||
...stxMoney,
|
||||
availableStx: createMoney(stxMoney.balance.amount.minus(stxMoney.locked.amount), 'STX'),
|
||||
balance: createMoney(stxMoney.balance.amount, 'STX'),
|
||||
unlockedStx: createMoney(stxMoney.balance.amount.minus(stxMoney.locked.amount), 'STX'),
|
||||
};
|
||||
return { ...balances, stx };
|
||||
}
|
||||
|
||||
28
src/app/query/stacks/microblock/microblock.query.ts
Normal file
28
src/app/query/stacks/microblock/microblock.query.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { TokenTransferTransaction } from '@stacks/stacks-blockchain-api-types';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { createMoney } from '@shared/models/money.model';
|
||||
|
||||
import { sumNumbers } from '@app/common/math/helpers';
|
||||
import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
|
||||
|
||||
export function useCurrentAccountMicroblockBalanceQuery(address: string) {
|
||||
const client = useStacksClientUnanchored();
|
||||
|
||||
async function accountMicroblockFetcher() {
|
||||
const txs = await client.microblocksApi.getUnanchoredTxs({});
|
||||
return txs.results as TokenTransferTransaction[];
|
||||
}
|
||||
return useQuery({
|
||||
enabled: !!address,
|
||||
queryKey: ['account-microblock', address],
|
||||
queryFn: accountMicroblockFetcher,
|
||||
select: resp => {
|
||||
const senderMicroblockTxs = resp.filter(tx => tx.sender_address === address);
|
||||
return createMoney(
|
||||
sumNumbers(senderMicroblockTxs.map(tx => Number(tx.token_transfer.amount))),
|
||||
'STX'
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user