Skip to content

Commit

Permalink
Merge pull request #3937 from hirosystems/release/utxo-filtering
Browse files Browse the repository at this point in the history
Release/utxo filtering
  • Loading branch information
fbwoolf authored Jun 27, 2023
2 parents fe3f627 + 76515b1 commit 4cf7354
Show file tree
Hide file tree
Showing 40 changed files with 624 additions and 176 deletions.
11 changes: 9 additions & 2 deletions src/app/common/hooks/balance/btc/use-btc-balance.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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(
() => ({
Expand Down
1 change: 1 addition & 0 deletions src/app/common/hooks/balance/stx/use-stx-balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function useStxBalance() {
const stxEffectiveBalance = isDefined(totalBalance)
? subtractMoney(totalBalance, stxOutboundQuery.data)
: createMoney(0, 'STX');

const stxEffectiveUsdBalance = isDefined(totalBalance)
? i18nFormatCurrency(baseCurrencyAmountInQuote(stxEffectiveBalance, stxMarketData))
: undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/hooks/use-on-mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 9 additions & 5 deletions src/app/common/transactions/bitcoin/use-generate-bitcoin-tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -18,7 +18,6 @@ interface GenerateNativeSegwitTxValues {
recipient: string;
}
export function useGenerateSignedNativeSegwitTx() {
const { data: utxos } = useSpendableCurrentNativeSegwitAccountUtxos();
const {
address,
publicKeychain: currentAddressIndexKeychain,
Expand All @@ -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 {
Expand Down Expand Up @@ -87,6 +91,6 @@ export function useGenerateSignedNativeSegwitTx() {
return null;
}
},
[address, currentAddressIndexKeychain?.publicKey, networkMode, sign, utxos]
[address, currentAddressIndexKeychain?.publicKey, networkMode, sign]
);
}
14 changes: 10 additions & 4 deletions src/app/common/validation/forms/amount-validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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()
Expand All @@ -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;
Expand Down
16 changes: 9 additions & 7 deletions src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import {
determineUtxosForSpend,
determineUtxosForSpendAll,
} from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
import { useSpendableNativeSegwitUtxos } from '@app/query/bitcoin/address/use-spendable-native-segwit-utxos';
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 { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';

import { FeesListItem } from './bitcoin-fees-list';

Expand All @@ -32,11 +31,14 @@ interface UseBitcoinFeesListArgs {
amount: number;
isSendingMax?: boolean;
recipient: string;
utxos: UtxoResponseItem[];
}
export function useBitcoinFeesList({ amount, isSendingMax, recipient }: UseBitcoinFeesListArgs) {
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
const { data: utxos } = useSpendableNativeSegwitUtxos(currentAccountBtcAddress);

export function useBitcoinFeesList({
amount,
isSendingMax,
recipient,
utxos,
}: UseBitcoinFeesListArgs) {
const btcMarketData = useCryptoCurrencyMarketData('BTC');
const { data: feeRates, isLoading } = useAverageBitcoinFeeRates();

Expand All @@ -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();

Expand Down
10 changes: 6 additions & 4 deletions src/app/features/activity-list/activity-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,9 +24,6 @@ interface BitcoinCustomFeeProps {
customFeeInitialValue: string;
setCustomFeeInitialValue: Dispatch<SetStateAction<string>>;
}

const feeInputLabel = 'sats/vB';

export function BitcoinCustomFee({
onChooseFee,
recipient,
Expand All @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,21 @@ 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/use-spendable-native-segwit-utxos';
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;
amount: number;
}

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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ export function ConnectLedgerErrorLayout(props: ConnectLedgerErrorLayoutProps) {
<Link
display="inline"
fontSize={1}
onClick={() => openInNewTab('https://hirowallet.gitbook.io')}
onClick={() =>
openInNewTab(
'https://hirowallet.gitbook.io/guides/securing-the-wallet/using-ledger-with-hiro'
)
}
>
Support Page
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
8 changes: 8 additions & 0 deletions src/app/pages/rpc-send-transfer/use-rpc-send-transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -25,18 +27,24 @@ 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: () =>
navigate(RouteUrls.RpcSendTransferChooseFee, {
state: {
address,
amount,
utxos,
},
}),
ledger: noop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Loading

0 comments on commit 4cf7354

Please sign in to comment.