mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-04-29 05:05:32 +08:00
refactor: send form utxos
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import { createMoney } from '@shared/models/money.model';
|
||||
|
||||
import { baseCurrencyAmountInQuote, subtractMoney } from '@app/common/money/calculate-money';
|
||||
import { i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { createBitcoinCryptoCurrencyAssetTypeWrapper } from '@app/query/bitcoin/address/address.utils';
|
||||
@@ -10,8 +14,11 @@ import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/marke
|
||||
export function useBtcAssetBalance(btcAddress: string) {
|
||||
const btcMarketData = useCryptoCurrencyMarketData('BTC');
|
||||
const btcAssetBalance = useNativeSegwitBalance(btcAddress);
|
||||
const pendingBalance = useBitcoinPendingTransactionsBalance(btcAddress);
|
||||
const availableBalance = subtractMoney(btcAssetBalance.balance, pendingBalance);
|
||||
const { data: pendingBalance } = useBitcoinPendingTransactionsBalance(btcAddress);
|
||||
const availableBalance = subtractMoney(
|
||||
btcAssetBalance.balance,
|
||||
pendingBalance ?? createMoney(new BigNumber(0), 'BTC')
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
|
||||
import { isFunction } from '@shared/utils';
|
||||
|
||||
export function useOnMount(effect: () => void | (() => void)) {
|
||||
export function useOnMount(effect: () => void | (() => void) | Promise<unknown>) {
|
||||
useEffect(() => {
|
||||
const fn = effect();
|
||||
return () => (isFunction(fn) ? fn() : undefined);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
determineUtxosForSpend,
|
||||
determineUtxosForSpendAll,
|
||||
} from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
|
||||
import { useSpendableCurrentNativeSegwitAccountUtxos } from '@app/query/bitcoin/address/use-spendable-native-segwit-utxos';
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
@@ -18,7 +18,6 @@ interface GenerateNativeSegwitTxValues {
|
||||
recipient: string;
|
||||
}
|
||||
export function useGenerateSignedNativeSegwitTx() {
|
||||
const { data: utxos } = useSpendableCurrentNativeSegwitAccountUtxos();
|
||||
const {
|
||||
address,
|
||||
publicKeychain: currentAddressIndexKeychain,
|
||||
@@ -28,8 +27,13 @@ export function useGenerateSignedNativeSegwitTx() {
|
||||
const networkMode = useBitcoinScureLibNetworkConfig();
|
||||
|
||||
return useCallback(
|
||||
(values: GenerateNativeSegwitTxValues, feeRate: number, isSendingMax?: boolean) => {
|
||||
if (!utxos) return;
|
||||
(
|
||||
values: GenerateNativeSegwitTxValues,
|
||||
feeRate: number,
|
||||
utxos: UtxoResponseItem[],
|
||||
isSendingMax?: boolean
|
||||
) => {
|
||||
if (!utxos.length) return;
|
||||
if (!feeRate) return;
|
||||
|
||||
try {
|
||||
@@ -87,6 +91,6 @@ export function useGenerateSignedNativeSegwitTx() {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[address, currentAddressIndexKeychain?.publicKey, networkMode, sign, utxos]
|
||||
[address, currentAddressIndexKeychain?.publicKey, networkMode, sign]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
satToBtc,
|
||||
stxToMicroStx,
|
||||
} from '@app/common/money/unit-conversion';
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
|
||||
import { formatInsufficientBalanceError, formatPrecisionError } from '../../error-formatters';
|
||||
import { FormErrorMessages } from '../../error-messages';
|
||||
@@ -27,14 +28,19 @@ function amountValidator() {
|
||||
}
|
||||
|
||||
interface BtcInsufficientBalanceValidatorArgs {
|
||||
recipient: string;
|
||||
calcMaxSpend(recipient: string): {
|
||||
calcMaxSpend(
|
||||
recipient: string,
|
||||
utxos: UtxoResponseItem[]
|
||||
): {
|
||||
spendableBitcoin: BigNumber;
|
||||
};
|
||||
recipient: string;
|
||||
utxos: UtxoResponseItem[];
|
||||
}
|
||||
export function btcInsufficientBalanceValidator({
|
||||
recipient,
|
||||
calcMaxSpend,
|
||||
recipient,
|
||||
utxos,
|
||||
}: BtcInsufficientBalanceValidatorArgs) {
|
||||
return yup
|
||||
.number()
|
||||
@@ -43,7 +49,7 @@ export function btcInsufficientBalanceValidator({
|
||||
message: FormErrorMessages.InsufficientFunds,
|
||||
test(value) {
|
||||
if (!value) return false;
|
||||
const maxSpend = calcMaxSpend(recipient);
|
||||
const maxSpend = calcMaxSpend(recipient, utxos);
|
||||
if (!maxSpend) return false;
|
||||
const desiredSpend = new BigNumber(value);
|
||||
if (desiredSpend.isGreaterThan(maxSpend.spendableBitcoin)) return false;
|
||||
|
||||
@@ -11,10 +11,9 @@ import {
|
||||
determineUtxosForSpend,
|
||||
determineUtxosForSpendAll,
|
||||
} from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
|
||||
import { useSpendableNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
import { FeesListItem } from './bitcoin-fees-list';
|
||||
|
||||
@@ -32,11 +31,14 @@ interface UseBitcoinFeesListArgs {
|
||||
amount: number;
|
||||
isSendingMax?: boolean;
|
||||
recipient: string;
|
||||
utxos: UtxoResponseItem[];
|
||||
}
|
||||
export function useBitcoinFeesList({ amount, isSendingMax, recipient }: UseBitcoinFeesListArgs) {
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const { data: utxos } = useSpendableNativeSegwitUtxos(nativeSegwitSigner.address);
|
||||
|
||||
export function useBitcoinFeesList({
|
||||
amount,
|
||||
isSendingMax,
|
||||
recipient,
|
||||
utxos,
|
||||
}: UseBitcoinFeesListArgs) {
|
||||
const btcMarketData = useCryptoCurrencyMarketData('BTC');
|
||||
const { data: feeRates, isLoading } = useAverageBitcoinFeeRates();
|
||||
|
||||
@@ -47,7 +49,7 @@ export function useBitcoinFeesList({ amount, isSendingMax, recipient }: UseBitco
|
||||
)}`;
|
||||
}
|
||||
|
||||
if (!feeRates || !utxos || !utxos.length) return [];
|
||||
if (!feeRates || !utxos.length) return [];
|
||||
|
||||
const satAmount = btcToSat(amount).toNumber();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useGetBitcoinTransactionsByAddressQuery } from '@app/query/bitcoin/addr
|
||||
import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query';
|
||||
import { useStacksPendingTransactions } from '@app/query/stacks/mempool/mempool.hooks';
|
||||
import { useGetAccountTransactionsWithTransfersQuery } from '@app/query/stacks/transactions/transactions-with-transfers.query';
|
||||
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useSubmittedTransactions } from '@app/store/submitted-transactions/submitted-transactions.selectors';
|
||||
|
||||
import { convertBitcoinTxsToListType, convertStacksTxsToListType } from './activity-list.utils';
|
||||
@@ -16,10 +16,12 @@ import { SubmittedTransactionList } from './components/submitted-transaction-lis
|
||||
import { TransactionList } from './components/transaction-list/transaction-list';
|
||||
|
||||
export function ActivityList() {
|
||||
const bitcoinAddress = useCurrentAccountNativeSegwitAddressIndexZero();
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const { isInitialLoading: isInitialLoadingBitcoinTransactions, data: bitcoinTransactions } =
|
||||
useGetBitcoinTransactionsByAddressQuery(bitcoinAddress);
|
||||
const bitcoinPendingTxs = useBitcoinPendingTransactions(bitcoinAddress);
|
||||
useGetBitcoinTransactionsByAddressQuery(nativeSegwitSigner.address);
|
||||
const { data: bitcoinPendingTxs = [] } = useBitcoinPendingTransactions(
|
||||
nativeSegwitSigner.address
|
||||
);
|
||||
const {
|
||||
isInitialLoading: isInitialLoadingStacksTransactions,
|
||||
data: stacksTransactionsWithTransfers,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AvailableBalance } from '@app/components/available-balance';
|
||||
import { OnChooseFeeArgs } from '@app/components/bitcoin-fees-list/bitcoin-fees-list';
|
||||
import { BitcoinCustomFee } from '@app/features/bitcoin-choose-fee/bitcoin-custom-fee/bitcoin-custom-fee';
|
||||
import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/bitcoin-balances.query';
|
||||
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
import { BitcoinChooseFeeLayout } from './components/bitcoin-choose-fee.layout';
|
||||
import { ChooseFeeSubtitle } from './components/choose-fee-subtitle';
|
||||
@@ -38,8 +38,8 @@ export function BitcoinChooseFee({
|
||||
isLoading,
|
||||
recommendedFeeRate,
|
||||
}: BitcoinChooseFeeProps) {
|
||||
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
|
||||
const btcBalance = useNativeSegwitBalance(currentAccountBtcAddress);
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const btcBalance = useNativeSegwitBalance(nativeSegwitSigner.address);
|
||||
const hasAmount = amount.amount.isGreaterThan(0);
|
||||
const [customFeeInitialValue, setCustomFeeInitialValue] = useState(recommendedFeeRate);
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import { TextInputField } from '../../../components/text-input-field';
|
||||
import { BitcoinCustomFeeFiat } from './bitcoin-custom-fee-fiat';
|
||||
import { useBitcoinCustomFee } from './hooks/use-bitcoin-custom-fee';
|
||||
|
||||
const feeInputLabel = 'sats/vB';
|
||||
|
||||
interface BitcoinCustomFeeProps {
|
||||
onChooseFee({ feeRate, feeValue, time, isCustomFee }: OnChooseFeeArgs): Promise<void>;
|
||||
onValidateBitcoinSpend(value: number): boolean;
|
||||
@@ -22,9 +24,6 @@ interface BitcoinCustomFeeProps {
|
||||
customFeeInitialValue: string;
|
||||
setCustomFeeInitialValue: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
const feeInputLabel = 'sats/vB';
|
||||
|
||||
export function BitcoinCustomFee({
|
||||
onChooseFee,
|
||||
recipient,
|
||||
@@ -44,7 +43,7 @@ export function BitcoinCustomFee({
|
||||
if (!isValid) return;
|
||||
await onChooseFee({ feeRate: Number(feeRate), feeValue, time: '', isCustomFee: true });
|
||||
},
|
||||
[onChooseFee, onValidateBitcoinSpend, getCustomFeeValues]
|
||||
[getCustomFeeValues, onValidateBitcoinSpend, onChooseFee]
|
||||
);
|
||||
|
||||
const validationSchema = yup.object({
|
||||
|
||||
@@ -5,9 +5,8 @@ import { createMoney } from '@shared/models/money.model';
|
||||
import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
|
||||
import { i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { determineUtxosForSpend } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
|
||||
import { useSpendableNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useSpendableCurrentNativeSegwitAccountUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
interface UseBitcoinCustomFeeArgs {
|
||||
recipient: string;
|
||||
@@ -15,15 +14,12 @@ interface UseBitcoinCustomFeeArgs {
|
||||
}
|
||||
|
||||
export function useBitcoinCustomFee({ recipient, amount }: UseBitcoinCustomFeeArgs) {
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const currentAccountBtcAddress = nativeSegwitSigner.address;
|
||||
|
||||
const { data: utxos } = useSpendableNativeSegwitUtxos(currentAccountBtcAddress);
|
||||
const { data: utxos = [] } = useSpendableCurrentNativeSegwitAccountUtxos();
|
||||
const btcMarketData = useCryptoCurrencyMarketData('BTC');
|
||||
|
||||
return useCallback(
|
||||
(feeRate: number) => {
|
||||
if (!feeRate || !utxos || !utxos.length) return { fee: 0, fiatFeeValue: '' };
|
||||
if (!feeRate || !utxos.length) return { fee: 0, fiatFeeValue: '' };
|
||||
|
||||
const determineUtxosArgs = {
|
||||
amount,
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list';
|
||||
import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee';
|
||||
import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend';
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
|
||||
import { formFeeRowValue } from '../../common/send/utils';
|
||||
import { useRpcSendTransferState } from './rpc-send-transfer-container';
|
||||
@@ -25,29 +26,32 @@ function useRpcSendTransferFeeState() {
|
||||
const location = useLocation();
|
||||
const amount = get(location.state, 'amount');
|
||||
const amountAsMoney = createMoney(Number(amount), 'BTC');
|
||||
const utxos = get(location.state, 'utxos') as UtxoResponseItem[];
|
||||
|
||||
return {
|
||||
address: get(location.state, 'address') as string,
|
||||
amountAsMoney,
|
||||
utxos,
|
||||
};
|
||||
}
|
||||
|
||||
export function RpcSendTransferChooseFee() {
|
||||
const { selectedFeeType, setSelectedFeeType } = useRpcSendTransferState();
|
||||
const { address, amountAsMoney } = useRpcSendTransferFeeState();
|
||||
const { address, amountAsMoney, utxos } = useRpcSendTransferFeeState();
|
||||
const navigate = useNavigate();
|
||||
const { whenWallet } = useWalletType();
|
||||
const generateTx = useGenerateSignedNativeSegwitTx();
|
||||
const { feesList, isLoading } = useBitcoinFeesList({
|
||||
amount: Number(amountAsMoney.amount),
|
||||
recipient: address,
|
||||
utxos,
|
||||
});
|
||||
const recommendedFeeRate = feesList[1]?.feeRate.toString() || '';
|
||||
|
||||
const { showInsufficientBalanceError, onValidateBitcoinFeeSpend } = useValidateBitcoinSpend();
|
||||
|
||||
async function previewTransfer({ feeRate, feeValue, time, isCustomFee }: OnChooseFeeArgs) {
|
||||
const resp = generateTx({ amount: amountAsMoney, recipient: address }, feeRate);
|
||||
const resp = generateTx({ amount: amountAsMoney, recipient: address }, feeRate, utxos);
|
||||
|
||||
if (!resp) return logger.error('Attempted to generate raw tx, but no tx exists');
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
|
||||
import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
|
||||
import { formatMoney, formatMoneyPadded, i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { satToBtc } from '@app/common/money/unit-conversion';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/use-current-account-native-segwit-utxos';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
@@ -5,8 +5,10 @@ import { RouteUrls } from '@shared/route-urls';
|
||||
import { noop } from '@shared/utils';
|
||||
|
||||
import { useDefaultRequestParams } from '@app/common/hooks/use-default-request-search-params';
|
||||
import { useOnMount } from '@app/common/hooks/use-on-mount';
|
||||
import { initialSearchParams } from '@app/common/initial-search-params';
|
||||
import { useWalletType } from '@app/common/use-wallet-type';
|
||||
import { useSpendableCurrentNativeSegwitAccountUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
|
||||
export function useRpcSendTransferRequestParams() {
|
||||
const defaultParams = useDefaultRequestParams();
|
||||
@@ -25,11 +27,16 @@ export function useRpcSendTransfer() {
|
||||
const navigate = useNavigate();
|
||||
const { whenWallet } = useWalletType();
|
||||
const { address, amount, origin } = useRpcSendTransferRequestParams();
|
||||
const { data: utxos = [], refetch } = useSpendableCurrentNativeSegwitAccountUtxos();
|
||||
|
||||
// Forcing a refetch to ensure UTXOs are fresh
|
||||
useOnMount(() => refetch());
|
||||
|
||||
return {
|
||||
address,
|
||||
amount,
|
||||
origin,
|
||||
utxos,
|
||||
async onChooseTransferFee() {
|
||||
whenWallet({
|
||||
software: () =>
|
||||
@@ -37,6 +44,7 @@ export function useRpcSendTransfer() {
|
||||
state: {
|
||||
address,
|
||||
amount,
|
||||
utxos,
|
||||
},
|
||||
}),
|
||||
ledger: noop,
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as btc from '@scure/btc-signer';
|
||||
import { logger } from '@shared/logger';
|
||||
import { OrdinalSendFormValues } from '@shared/models/form.model';
|
||||
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/use-current-account-native-segwit-utxos';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';
|
||||
import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain';
|
||||
import { useCurrentAccountNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createMoney } from '@shared/models/money.model';
|
||||
import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
|
||||
import { formatMoneyPadded, i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { FeesListItem } from '@app/components/bitcoin-fees-list/bitcoin-fees-list';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/use-current-account-native-segwit-utxos';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks';
|
||||
import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
|
||||
@@ -11,7 +11,7 @@ import { BaseDrawer } from '@app/components/drawer/base-drawer';
|
||||
import { InfoCard, InfoCardRow, InfoCardSeparator } from '@app/components/info-card/info-card';
|
||||
import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/use-current-account-native-segwit-utxos';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useAppDispatch } from '@app/store';
|
||||
import { inscriptionSent } from '@app/store/ordinals/ordinals.slice';
|
||||
|
||||
|
||||
@@ -7,20 +7,17 @@ import { createMoney } from '@shared/models/money.model';
|
||||
|
||||
import { satToBtc } from '@app/common/money/unit-conversion';
|
||||
import { BtcSizeFeeEstimator } from '@app/common/transactions/bitcoin/fees/btc-size-fee-estimator';
|
||||
import { useSpendableNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance/bitcoin-balances.query';
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
export function useCalculateMaxBitcoinSpend() {
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const balance = useCurrentNativeSegwitAddressBalance();
|
||||
const { data: utxos } = useSpendableNativeSegwitUtxos(nativeSegwitSigner.address);
|
||||
const { data: feeRates } = useAverageBitcoinFeeRates();
|
||||
|
||||
return useCallback(
|
||||
(address = '', feeRate?: number) => {
|
||||
if (!utxos || !feeRates)
|
||||
(address = '', utxos: UtxoResponseItem[], feeRate?: number) => {
|
||||
if (!utxos.length || !feeRates)
|
||||
return {
|
||||
spendAllFee: 0,
|
||||
amount: createMoney(0, 'BTC'),
|
||||
@@ -45,6 +42,6 @@ export function useCalculateMaxBitcoinSpend() {
|
||||
spendableBitcoin: satToBtc(spendableAmount),
|
||||
};
|
||||
},
|
||||
[balance.amount, feeRates, utxos]
|
||||
[balance.amount, feeRates]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { LoadingSpinner } from '@app/components/loading-spinner';
|
||||
import { ModalHeader } from '@app/components/modal-header';
|
||||
import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee';
|
||||
import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend';
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/use-brc-20';
|
||||
|
||||
import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container';
|
||||
@@ -32,18 +33,20 @@ function useBrc20ChooseFeeState() {
|
||||
tick: get(location.state, 'tick') as string,
|
||||
amount: get(location.state, 'amount') as string,
|
||||
recipient: get(location.state, 'recipient') as string,
|
||||
utxos: get(location.state, 'utxos') as UtxoResponseItem[],
|
||||
};
|
||||
}
|
||||
|
||||
export function BrcChooseFee() {
|
||||
const navigate = useNavigate();
|
||||
const { amount, recipient, tick } = useBrc20ChooseFeeState();
|
||||
const { amount, recipient, tick, utxos } = useBrc20ChooseFeeState();
|
||||
const generateTx = useGenerateSignedNativeSegwitTx();
|
||||
const { selectedFeeType, setSelectedFeeType } = useSendBitcoinAssetContextState();
|
||||
const { initiateTransfer } = useBrc20Transfers();
|
||||
const { feesList, isLoading } = useBitcoinFeesList({
|
||||
amount: Number(amount),
|
||||
recipient,
|
||||
utxos,
|
||||
});
|
||||
const recommendedFeeRate = feesList[1]?.feeRate.toString() || '';
|
||||
|
||||
@@ -72,7 +75,8 @@ export function BrcChooseFee() {
|
||||
amount: serviceFeeAsMoney,
|
||||
recipient: serviceFeeRecipient,
|
||||
},
|
||||
feeRate
|
||||
feeRate,
|
||||
utxos
|
||||
);
|
||||
|
||||
if (!resp) return logger.error('Attempted to generate raw tx, but no tx exists');
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '@app/components/info-card/info-card';
|
||||
import { ModalHeader } from '@app/components/modal-header';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/use-current-account-native-segwit-utxos';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/use-brc-20';
|
||||
import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction';
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { createMoney } from '@shared/models/money.model';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { noop } from '@shared/utils';
|
||||
|
||||
import { useOnMount } from '@app/common/hooks/use-on-mount';
|
||||
import { unitToFractionalUnit } from '@app/common/money/unit-conversion';
|
||||
import { useWalletType } from '@app/common/use-wallet-type';
|
||||
import {
|
||||
@@ -19,7 +20,8 @@ import {
|
||||
import { brc20TokenAmountValidator } from '@app/common/validation/forms/amount-validators';
|
||||
import { currencyAmountValidator } from '@app/common/validation/forms/currency-validators';
|
||||
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
|
||||
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useSpendableCurrentNativeSegwitAccountUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
|
||||
|
||||
import { createDefaultInitialFormValues } from '../../send-form.utils';
|
||||
@@ -41,11 +43,15 @@ export function useBrc20SendForm({ balance, tick, decimals }: UseBrc20SendFormAr
|
||||
const { whenWallet } = useWalletType();
|
||||
const navigate = useNavigate();
|
||||
const currentNetwork = useCurrentNetwork();
|
||||
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const { data: utxos = [], refetch } = useSpendableCurrentNativeSegwitAccountUtxos();
|
||||
|
||||
// Forcing a refetch to ensure UTXOs are fresh
|
||||
useOnMount(() => refetch());
|
||||
|
||||
// TODO: change recipient to that one user iputs
|
||||
const initialValues = createDefaultInitialFormValues({
|
||||
recipient: currentAccountBtcAddress,
|
||||
recipient: nativeSegwitSigner.address,
|
||||
amount: '',
|
||||
symbol: tick,
|
||||
});
|
||||
@@ -73,7 +79,7 @@ export function useBrc20SendForm({ balance, tick, decimals }: UseBrc20SendFormAr
|
||||
whenWallet({
|
||||
software: () =>
|
||||
navigate(RouteUrls.SendBrc20ChooseFee.replace(':ticker', tick), {
|
||||
state: { ...values, tick, hasHeaderTitle: true },
|
||||
state: { ...values, tick, utxos, hasHeaderTitle: true },
|
||||
}),
|
||||
ledger: noop,
|
||||
})();
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoi
|
||||
import { ModalHeader } from '@app/components/modal-header';
|
||||
import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee';
|
||||
import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend';
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
|
||||
import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container';
|
||||
import { useBtcChooseFee } from './use-btc-choose-fee';
|
||||
@@ -20,16 +21,18 @@ export function useBtcChooseFeeState() {
|
||||
return {
|
||||
isSendingMax: get(location.state, 'isSendingMax') as boolean,
|
||||
txValues: get(location.state, 'values') as BitcoinSendFormValues,
|
||||
utxos: get(location.state, 'utxos') as UtxoResponseItem[],
|
||||
};
|
||||
}
|
||||
|
||||
export function BtcChooseFee() {
|
||||
const { isSendingMax, txValues } = useBtcChooseFeeState();
|
||||
const { isSendingMax, txValues, utxos } = useBtcChooseFeeState();
|
||||
const { selectedFeeType, setSelectedFeeType } = useSendBitcoinAssetContextState();
|
||||
const { feesList, isLoading } = useBitcoinFeesList({
|
||||
amount: Number(txValues.amount),
|
||||
isSendingMax,
|
||||
recipient: txValues.recipient,
|
||||
utxos,
|
||||
});
|
||||
const recommendedFeeRate = feesList[1]?.feeRate.toString() || '';
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from '@app/components/info-card/info-card';
|
||||
import { ModalHeader } from '@app/components/modal-header';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/use-current-account-native-segwit-utxos';
|
||||
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useBitcoinBroadcastTransaction } from '@app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BtcIcon } from '@app/components/icons/btc-icon';
|
||||
import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer';
|
||||
import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/bitcoin-balances.query';
|
||||
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
|
||||
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
import { AmountField } from '../../components/amount-field';
|
||||
import { FormFooter } from '../../components/form-footer';
|
||||
@@ -28,8 +28,8 @@ export function BtcSendForm() {
|
||||
const routeState = useSendFormRouteState();
|
||||
const btcMarketData = useCryptoCurrencyMarketData('BTC');
|
||||
|
||||
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
|
||||
const btcBalance = useNativeSegwitBalance(currentAccountBtcAddress);
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const btcBalance = useNativeSegwitBalance(nativeSegwitSigner.address);
|
||||
|
||||
const {
|
||||
calcMaxSpend,
|
||||
@@ -39,6 +39,7 @@ export function BtcSendForm() {
|
||||
isSendingMax,
|
||||
onFormStateChange,
|
||||
onSetIsSendingMax,
|
||||
utxos,
|
||||
validationSchema,
|
||||
} = useBtcSendForm();
|
||||
|
||||
@@ -56,7 +57,7 @@ export function BtcSendForm() {
|
||||
>
|
||||
{props => {
|
||||
onFormStateChange(props.values);
|
||||
const sendMaxCalculation = calcMaxSpend(props.values.recipient);
|
||||
const sendMaxCalculation = calcMaxSpend(props.values.recipient, utxos);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@@ -88,7 +89,7 @@ export function BtcSendForm() {
|
||||
<Outlet />
|
||||
|
||||
{/* This is for testing purposes only, to make sure the form is ready to be submitted */}
|
||||
{calcMaxSpend(props.values.recipient).spendableBitcoin.toNumber() > 0 ? (
|
||||
{calcMaxSpend(props.values.recipient, utxos).spendableBitcoin.toNumber() > 0 ? (
|
||||
<Box data-testid={SendCryptoAssetSelectors.SendPageReady}></Box>
|
||||
) : null}
|
||||
</Form>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
|
||||
import { useBtcChooseFeeState } from './btc-choose-fee';
|
||||
|
||||
export function useBtcChooseFee() {
|
||||
const { isSendingMax, txValues } = useBtcChooseFeeState();
|
||||
const { isSendingMax, txValues, utxos } = useBtcChooseFeeState();
|
||||
const navigate = useNavigate();
|
||||
const { whenWallet } = useWalletType();
|
||||
const sendFormNavigate = useSendFormNavigate();
|
||||
@@ -38,13 +38,14 @@ export function useBtcChooseFee() {
|
||||
{
|
||||
amount: isSendingMax
|
||||
? createMoney(
|
||||
btcToSat(calcMaxSpend(txValues.recipient, feeRate).spendableBitcoin),
|
||||
btcToSat(calcMaxSpend(txValues.recipient, utxos, feeRate).spendableBitcoin),
|
||||
'BTC'
|
||||
)
|
||||
: amountAsMoney,
|
||||
recipient: txValues.recipient,
|
||||
},
|
||||
feeRate,
|
||||
utxos,
|
||||
isSendingMax
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { BitcoinSendFormValues } from '@shared/models/form.model';
|
||||
import { noop } from '@shared/utils';
|
||||
|
||||
import { formatPrecisionError } from '@app/common/error-formatters';
|
||||
import { useOnMount } from '@app/common/hooks/use-on-mount';
|
||||
import { useWalletType } from '@app/common/use-wallet-type';
|
||||
import {
|
||||
btcAddressNetworkValidator,
|
||||
@@ -23,8 +24,9 @@ import {
|
||||
currencyAmountValidator,
|
||||
} from '@app/common/validation/forms/currency-validators';
|
||||
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
|
||||
import { useSpendableCurrentNativeSegwitAccountUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
|
||||
import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/bitcoin-balances.query';
|
||||
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
|
||||
|
||||
import { useCalculateMaxBitcoinSpend } from '../../family/bitcoin/hooks/use-calculate-max-spend';
|
||||
@@ -34,13 +36,17 @@ export function useBtcSendForm() {
|
||||
const [isSendingMax, setIsSendingMax] = useState(false);
|
||||
const formRef = useRef<FormikProps<BitcoinSendFormValues>>(null);
|
||||
const currentNetwork = useCurrentNetwork();
|
||||
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
|
||||
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(currentAccountBtcAddress);
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const { data: utxos = [], refetch } = useSpendableCurrentNativeSegwitAccountUtxos();
|
||||
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(nativeSegwitSigner.address);
|
||||
const { whenWallet } = useWalletType();
|
||||
const sendFormNavigate = useSendFormNavigate();
|
||||
const calcMaxSpend = useCalculateMaxBitcoinSpend();
|
||||
const { onFormStateChange } = useUpdatePersistedSendFormValues();
|
||||
|
||||
// Forcing a refetch to ensure UTXOs are fresh
|
||||
useOnMount(() => refetch());
|
||||
|
||||
return {
|
||||
calcMaxSpend,
|
||||
currentNetwork,
|
||||
@@ -50,6 +56,7 @@ export function useBtcSendForm() {
|
||||
onSetIsSendingMax(value: boolean) {
|
||||
setIsSendingMax(value);
|
||||
},
|
||||
utxos,
|
||||
validationSchema: yup.object({
|
||||
amount: yup
|
||||
.number()
|
||||
@@ -60,17 +67,18 @@ export function useBtcSendForm() {
|
||||
.concat(currencyAmountValidator())
|
||||
.concat(
|
||||
btcInsufficientBalanceValidator({
|
||||
calcMaxSpend,
|
||||
// TODO: investigate yup features for cross-field validation
|
||||
// to prevent need to access form via ref
|
||||
recipient: formRef.current?.values.recipient ?? '',
|
||||
calcMaxSpend,
|
||||
utxos,
|
||||
})
|
||||
),
|
||||
recipient: yup
|
||||
.string()
|
||||
.concat(btcAddressValidator())
|
||||
.concat(btcAddressNetworkValidator(currentNetwork.chain.bitcoin.network))
|
||||
.concat(notCurrentAddressValidator(currentAccountBtcAddress || ''))
|
||||
.concat(notCurrentAddressValidator(nativeSegwitSigner.address || ''))
|
||||
.required('Enter a bitcoin address'),
|
||||
}),
|
||||
|
||||
@@ -83,7 +91,7 @@ export function useBtcSendForm() {
|
||||
await formikHelpers.validateForm();
|
||||
|
||||
whenWallet({
|
||||
software: () => sendFormNavigate.toChooseTransactionFee(isSendingMax, values),
|
||||
software: () => sendFormNavigate.toChooseTransactionFee(isSendingMax, utxos, values),
|
||||
ledger: noop,
|
||||
})();
|
||||
},
|
||||
|
||||
@@ -7,6 +7,8 @@ import { StacksTransaction } from '@stacks/transactions';
|
||||
import { BitcoinSendFormValues } from '@shared/models/form.model';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
|
||||
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
|
||||
|
||||
interface ConfirmationRouteState {
|
||||
decimals?: number;
|
||||
token?: string;
|
||||
@@ -36,10 +38,15 @@ export function useSendFormNavigate() {
|
||||
backToSendForm(state: any) {
|
||||
return navigate('../', { relative: 'path', replace: true, state });
|
||||
},
|
||||
toChooseTransactionFee(isSendingMax: boolean, values: BitcoinSendFormValues) {
|
||||
toChooseTransactionFee(
|
||||
isSendingMax: boolean,
|
||||
utxos: UtxoResponseItem[],
|
||||
values: BitcoinSendFormValues
|
||||
) {
|
||||
return navigate('choose-fee', {
|
||||
state: {
|
||||
isSendingMax,
|
||||
utxos,
|
||||
values,
|
||||
hasHeaderTitle: true,
|
||||
},
|
||||
|
||||
@@ -1,40 +1,66 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { createMoney } from '@shared/models/money.model';
|
||||
import { BitcoinTransaction } from '@shared/models/transactions/bitcoin-transaction.model';
|
||||
|
||||
import { sumNumbers } from '@app/common/math/helpers';
|
||||
|
||||
import { useGetBitcoinTransactionsByAddressQuery } from './transactions-by-address.query';
|
||||
|
||||
function useFilterAddressPendingTransactions() {
|
||||
return useCallback((txs: BitcoinTransaction[]) => {
|
||||
return txs.filter(tx => !tx.status.confirmed);
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function useBitcoinPendingTransactions(address: string) {
|
||||
const filterPendingTransactions = useFilterAddressPendingTransactions();
|
||||
|
||||
return useGetBitcoinTransactionsByAddressQuery(address, {
|
||||
select(txs) {
|
||||
return txs.filter(tx => !tx.status.confirmed);
|
||||
return filterPendingTransactions(txs);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useBitcoinPendingTransactionsBalance(address: string) {
|
||||
const { data: pendingTransactions = [] } = useBitcoinPendingTransactions(address);
|
||||
export function useBitcoinPendingTransactionsInputs(address: string) {
|
||||
const filterPendingTransactions = useFilterAddressPendingTransactions();
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
createMoney(
|
||||
sumNumbers(
|
||||
pendingTransactions
|
||||
.flatMap(tx => tx.vout.filter(output => output.scriptpubkey_address !== address))
|
||||
.map(vout => vout.value)
|
||||
),
|
||||
'BTC'
|
||||
),
|
||||
[address, pendingTransactions]
|
||||
return useGetBitcoinTransactionsByAddressQuery(address, {
|
||||
select(txs) {
|
||||
return filterPendingTransactions(txs).flatMap(tx => tx.vin.map(input => input));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function useFilterAddressPendingTxsOutputs(address: string) {
|
||||
return useCallback(
|
||||
(pendingTxs: BitcoinTransaction[]) => {
|
||||
return pendingTxs.flatMap(tx => {
|
||||
const inputsFromAddress = tx.vin.filter(
|
||||
input => input.prevout.scriptpubkey_address === address
|
||||
);
|
||||
// Output is possibly change, so we only subtract the value if the address
|
||||
// is funding the tx and sending utxos to a different address
|
||||
return tx.vout.filter(
|
||||
output => inputsFromAddress.length && output.scriptpubkey_address !== address
|
||||
);
|
||||
});
|
||||
},
|
||||
[address]
|
||||
);
|
||||
}
|
||||
|
||||
export function useBitcoinPendingTransactionsInputs(address: string) {
|
||||
const { data: pendingTransactions = [] } = useBitcoinPendingTransactions(address);
|
||||
export function useBitcoinPendingTransactionsBalance(address: string) {
|
||||
const filterPendingTransactions = useFilterAddressPendingTransactions();
|
||||
const filterPendingTxsOutputs = useFilterAddressPendingTxsOutputs(address);
|
||||
|
||||
return useMemo(() => {
|
||||
return pendingTransactions.flatMap(tx => tx.vin.map(input => input));
|
||||
}, [pendingTransactions]);
|
||||
return useGetBitcoinTransactionsByAddressQuery(address, {
|
||||
select(txs) {
|
||||
return createMoney(
|
||||
sumNumbers(filterPendingTxsOutputs(filterPendingTransactions(txs)).map(vout => vout.value)),
|
||||
'BTC'
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
import { UtxoResponseItem } from '../bitcoin-client';
|
||||
import { useInscriptionByAddressQuery } from '../ordinals/use-inscriptions.query';
|
||||
import { useBitcoinPendingTransactionsInputs } from './transactions-by-address.hooks';
|
||||
import { useGetUtxosByAddressQuery } from './utxos-by-address.query';
|
||||
@@ -13,39 +16,69 @@ export function useCurrentNativeSegwitUtxos() {
|
||||
return useGetUtxosByAddressQuery(nativeSegwitSigner.address);
|
||||
}
|
||||
|
||||
export function useSpendableNativeSegwitUtxos(address: string) {
|
||||
function useFilterAddressNativeSegwitInscriptions(address: string) {
|
||||
const {
|
||||
data: inscriptions,
|
||||
hasNextPage: hasMoreInscriptionsToLoad,
|
||||
isLoading: isLoadingInscriptions,
|
||||
} = useInscriptionByAddressQuery(address);
|
||||
const pendingInputs = useBitcoinPendingTransactionsInputs(address);
|
||||
|
||||
return useGetUtxosByAddressQuery(address, {
|
||||
select(utxos) {
|
||||
// While infinite query check has more data to load, or Stamps are loading
|
||||
// assume nothing is spendable
|
||||
return useCallback(
|
||||
(utxos: UtxoResponseItem[]) => {
|
||||
// While infinite query checks if has more data to load, or Stamps
|
||||
// are loading, assume nothing is spendable
|
||||
if (hasMoreInscriptionsToLoad || isLoadingInscriptions) return [];
|
||||
|
||||
const inscribedUtxos = inscriptions?.pages.flatMap(page => page.results) ?? [];
|
||||
|
||||
return (
|
||||
utxos
|
||||
.filter(
|
||||
utxo =>
|
||||
!inscribedUtxos.some(
|
||||
inscription =>
|
||||
utxo.txid === inscription.tx_id && utxo.vout === Number(inscription.offset)
|
||||
)
|
||||
return utxos.filter(
|
||||
utxo =>
|
||||
!inscribedUtxos.some(
|
||||
inscription =>
|
||||
utxo.txid === inscription.tx_id && utxo.vout === Number(inscription.offset)
|
||||
)
|
||||
// Safety check to make sure we don't reuse utxos in a pending tx
|
||||
.filter(utxo => !pendingInputs.find(input => input.txid === utxo.txid))
|
||||
);
|
||||
},
|
||||
[hasMoreInscriptionsToLoad, inscriptions?.pages, isLoadingInscriptions]
|
||||
);
|
||||
}
|
||||
|
||||
function useFilterAddressNativeSegwitPendingTxsUtxos(address: string) {
|
||||
const { data: pendingInputs = [] } = useBitcoinPendingTransactionsInputs(address);
|
||||
|
||||
return useCallback(
|
||||
(utxos: UtxoResponseItem[]) => {
|
||||
return utxos.filter(
|
||||
utxo =>
|
||||
!pendingInputs.find(
|
||||
input => input.prevout.scriptpubkey_address === address && input.txid === utxo.txid
|
||||
)
|
||||
);
|
||||
},
|
||||
[address, pendingInputs]
|
||||
);
|
||||
}
|
||||
|
||||
export function useAllSpendableNativeSegwitUtxos(address: string) {
|
||||
const filterOutInscriptions = useFilterAddressNativeSegwitInscriptions(address);
|
||||
return useGetUtxosByAddressQuery(address, {
|
||||
select(utxos) {
|
||||
return filterOutInscriptions(utxos);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function useSpendableAndNotPendingNativeSegwitUtxos(address: string) {
|
||||
const filterOutInscriptions = useFilterAddressNativeSegwitInscriptions(address);
|
||||
const filterOutPendingTxsUtxos = useFilterAddressNativeSegwitPendingTxsUtxos(address);
|
||||
|
||||
return useGetUtxosByAddressQuery(address, {
|
||||
select(utxos) {
|
||||
return filterOutPendingTxsUtxos(filterOutInscriptions(utxos));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSpendableCurrentNativeSegwitAccountUtxos() {
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
return useSpendableNativeSegwitUtxos(nativeSegwitSigner.address);
|
||||
return useSpendableAndNotPendingNativeSegwitUtxos(nativeSegwitSigner.address);
|
||||
}
|
||||
|
||||
@@ -3,20 +3,21 @@ import { useMemo } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import { createMoney } from '@shared/models/money.model';
|
||||
import { isDefined } from '@shared/utils';
|
||||
import { isDefined, isUndefined } from '@shared/utils';
|
||||
|
||||
import { sumNumbers } from '@app/common/math/helpers';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
import { createBitcoinCryptoCurrencyAssetTypeWrapper } from '../address/address.utils';
|
||||
import { useSpendableNativeSegwitUtxos } from '../address/utxos-by-address.hooks';
|
||||
import { useAllSpendableNativeSegwitUtxos } from '../address/utxos-by-address.hooks';
|
||||
import { useOrdinalsAwareUtxoQueries } from '../ordinals/ordinals-aware-utxo.query';
|
||||
import { useTaprootAccountUtxosQuery } from '../ordinals/use-taproot-address-utxos.query';
|
||||
|
||||
function useGetBitcoinBalanceByAddress(address: string) {
|
||||
const utxos = useSpendableNativeSegwitUtxos(address).data;
|
||||
const { data: utxos } = useAllSpendableNativeSegwitUtxos(address);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!utxos) return createMoney(new BigNumber(0), 'BTC');
|
||||
if (isUndefined(utxos)) return createMoney(new BigNumber(0), 'BTC');
|
||||
return createMoney(sumNumbers(utxos.map(utxo => utxo.value)), 'BTC');
|
||||
}, [utxos]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user