Skip to content

Commit

Permalink
feat: bitcoin fees query
Browse files Browse the repository at this point in the history
  • Loading branch information
fbwoolf committed Nov 24, 2022
1 parent 23776d4 commit 9451f89
Show file tree
Hide file tree
Showing 35 changed files with 289 additions and 119 deletions.
3 changes: 2 additions & 1 deletion src/app/common/error-formatters.ts
@@ -1,9 +1,10 @@
import BigNumber from 'bignumber.js';

import { SendFormErrorMessages } from '@app/common/error-messages';
import { microStxToStx } from '@app/common/stacks-utils';
import { initBigNumber } from '@app/common/utils';

import { microStxToStx } from './money/unit-conversion';

export function formatPrecisionError(symbol: string, decimals: number) {
const error = SendFormErrorMessages.TooMuchPrecision;
return error.replace('{token}', symbol).replace('{decimals}', String(decimals));
Expand Down
3 changes: 2 additions & 1 deletion src/app/common/hooks/use-send-form-validation.ts
@@ -1,6 +1,5 @@
import { useCallback, useMemo } from 'react';

import { stxToMicroStx } from '@stacks/ui-utils';
import * as yup from 'yup';

import { STX_DECIMALS } from '@shared/constants';
Expand All @@ -24,6 +23,8 @@ import { transactionMemoSchema } from '@app/common/validation/validate-memo';
import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/balance.hooks';
import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';

import { stxToMicroStx } from '../money/unit-conversion';

export function useFungibleTokenAmountSchema(selectedAssetId: string) {
const { selectedAssetBalance } = useSelectedAssetBalance(selectedAssetId);
return useCallback(
Expand Down
24 changes: 24 additions & 0 deletions src/app/common/money/unit-conversion.ts
@@ -0,0 +1,24 @@
import BigNumber from 'bignumber.js';

import { BTC_DECIMALS, STX_DECIMALS } from '@shared/constants';

import { initBigNumber } from '../utils';

function fractionalUnitToUnit(decimals: number) {
return (unit: number | string | BigNumber) => {
const satBigNumber = initBigNumber(unit);
return satBigNumber.shiftedBy(-decimals);
};
}

function unitToFractionalUnit(decimals: number) {
return (unit: number | string | BigNumber) => {
const satBigNumber = initBigNumber(unit);
return satBigNumber.shiftedBy(decimals);
};
}

export const satToBtc = fractionalUnitToUnit(BTC_DECIMALS);

export const microStxToStx = fractionalUnitToUnit(STX_DECIMALS);
export const stxToMicroStx = unitToFractionalUnit(STX_DECIMALS);
12 changes: 2 additions & 10 deletions src/app/common/stacks-utils.ts
Expand Up @@ -6,6 +6,8 @@ import { NetworkConfiguration, STX_DECIMALS } from '@shared/constants';

import { abbreviateNumber, initBigNumber } from '@app/common/utils';

import { microStxToStx } from './money/unit-conversion';

export const stacksValue = ({
value,
fixedDecimals = true,
Expand All @@ -28,11 +30,6 @@ export const stacksValue = ({
}${withTicker ? ' STX' : ''}`;
};

export const microStxToStx = (mStx: number | string | BigNumber) => {
const microStacks = initBigNumber(mStx);
return microStacks.shiftedBy(-STX_DECIMALS);
};

export const ftDecimals = (value: number | string | BigNumber, decimals: number) => {
const amount = initBigNumber(value);
return amount
Expand All @@ -46,11 +43,6 @@ export const ftUnshiftDecimals = (value: number | string | BigNumber, decimals:
return amount.shiftedBy(decimals).toString(10);
};

export const stxToMicroStx = (stx: number | string | BigNumber) => {
const stxBN = initBigNumber(stx);
return stxBN.shiftedBy(STX_DECIMALS);
};

export const validateStacksAddress = (stacksAddress: string): boolean => {
try {
c32addressDecode(stacksAddress);
Expand Down
3 changes: 2 additions & 1 deletion src/app/common/validation/use-fee-schema.ts
Expand Up @@ -7,10 +7,11 @@ import { isNumber } from '@shared/utils';

import { formatInsufficientBalanceError, formatPrecisionError } from '@app/common/error-formatters';
import { SendFormErrorMessages } from '@app/common/error-messages';
import { stxToMicroStx } from '@app/common/stacks-utils';
import { stxAmountSchema } from '@app/common/validation/currency-schema';
import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/balance.hooks';

import { stxToMicroStx } from '../money/unit-conversion';

/**
* @param amountToSend stx amount in µSTX
*/
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/fee-row/components/custom-fee-field.tsx
Expand Up @@ -5,15 +5,15 @@ import { SendFormSelectors } from '@tests-legacy/page-objects/send-form.selector
import BigNumber from 'bignumber.js';
import { useField } from 'formik';

import { FeeEstimate } from '@shared/models/fees-types';
import { StacksFeeEstimate } from '@shared/models/fees/stacks-fees.model';

import { stxToMicroStx } from '@app/common/stacks-utils';
import { stxToMicroStx } from '@app/common/money/unit-conversion';
import { SendFormWarningMessages } from '@app/common/warning-messages';
import { Caption } from '@app/components/typography';

interface CustomFeeFieldProps extends StackProps {
fieldName: string;
lowFeeEstimate: FeeEstimate;
lowFeeEstimate: StacksFeeEstimate;
setFieldWarning: Dispatch<SetStateAction<string | undefined>>;
}
export function CustomFeeField(props: CustomFeeFieldProps) {
Expand Down
7 changes: 4 additions & 3 deletions src/app/components/fee-row/components/fee-estimate-select.tsx
Expand Up @@ -3,14 +3,15 @@ import { Dispatch, SetStateAction, useRef } from 'react';
import { Fade, Stack, color } from '@stacks/ui';
import { SendFormSelectors } from '@tests-legacy/page-objects/send-form.selectors';

import { FeeEstimate, FeeType } from '@shared/models/fees-types';
import { FeeTypes } from '@shared/models/fees/_fees.model';
import { StacksFeeEstimate } from '@shared/models/fees/stacks-fees.model';

import { useOnClickOutside } from '@app/common/hooks/use-onclickoutside';

import { FeeEstimateItem } from './fee-estimate-item';

interface FeeEstimateSelectProps {
items: FeeEstimate[];
items: StacksFeeEstimate[];
onClick: (index: number) => void;
selected: number;
setIsOpen: Dispatch<SetStateAction<boolean>>;
Expand Down Expand Up @@ -50,7 +51,7 @@ export function FeeEstimateSelect(props: FeeEstimateSelectProps) {
visible
/>
))}
<FeeEstimateItem index={FeeType.Custom} onClick={onClick} selected={selected} visible />
<FeeEstimateItem index={FeeTypes.Custom} onClick={onClick} selected={selected} visible />
</Stack>
)}
</Fade>
Expand Down
20 changes: 11 additions & 9 deletions src/app/components/fee-row/fee-row.tsx
Expand Up @@ -6,12 +6,14 @@ import { SendFormSelectors } from '@tests-legacy/page-objects/send-form.selector
import BigNumber from 'bignumber.js';
import { useField } from 'formik';

import { FeeEstimate, FeeType } from '@shared/models/fees-types';
import { FeeTypes } from '@shared/models/fees/_fees.model';
import { StacksFeeEstimate } from '@shared/models/fees/stacks-fees.model';
import { createMoney } from '@shared/models/money.model';
import { isNumber, isString } from '@shared/utils';

import { useConvertStxToFiatAmount } from '@app/common/hooks/use-convert-to-fiat-amount';
import { microStxToStx, stacksValue } from '@app/common/stacks-utils';
import { microStxToStx } from '@app/common/money/unit-conversion';
import { stacksValue } from '@app/common/stacks-utils';
import { openInNewTab } from '@app/common/utils/open-in-new-tab';
import { ErrorLabel } from '@app/components/error-label';
import { SpaceBetween } from '@app/components/space-between';
Expand All @@ -30,7 +32,7 @@ const feesInfo =
const url = 'https://hiro.so/questions/fee-estimates';

interface FeeRowProps {
feeEstimations: FeeEstimate[];
feeEstimations: StacksFeeEstimate[];
feeFieldName: string;
feeTypeFieldName: string;
isSponsored: boolean;
Expand All @@ -41,7 +43,7 @@ export function FeeRow(props: FeeRowProps): JSX.Element {
const [, _, feeTypeHelper] = useField(feeTypeFieldName);
const [fieldWarning, setFieldWarning] = useState<string | undefined>(undefined);
const [isOpen, setIsOpen] = useState(false);
const [selected, setSelected] = useState(FeeType.Middle);
const [selected, setSelected] = useState(FeeTypes.Middle);
const [isCustom, setIsCustom] = useState(false);

const convertStxToUsd = useConvertStxToFiatAmount();
Expand All @@ -55,8 +57,8 @@ export function FeeRow(props: FeeRowProps): JSX.Element {
useEffect(() => {
// Set it to the middle estimation on mount
if (!feeInput.value && !isCustom) {
feeHelper.setValue(microStxToStx(feeEstimations[FeeType.Middle].fee).toNumber());
feeTypeHelper.setValue(FeeType[FeeType.Middle]);
feeHelper.setValue(microStxToStx(feeEstimations[FeeTypes.Middle].fee).toNumber());
feeTypeHelper.setValue(FeeTypes[FeeTypes.Middle]);
}
if (isSponsored) {
feeHelper.setValue(0);
Expand All @@ -68,8 +70,8 @@ export function FeeRow(props: FeeRowProps): JSX.Element {
const handleSelectedItem = useCallback(
(index: number) => {
if (selected !== index) setSelected(index);
feeTypeHelper.setValue(FeeType[index]);
if (index === FeeType.Custom) {
feeTypeHelper.setValue(FeeTypes[index]);
if (index === FeeTypes.Custom) {
feeHelper.setValue('');
setIsCustom(true);
} else {
Expand Down Expand Up @@ -122,7 +124,7 @@ export function FeeRow(props: FeeRowProps): JSX.Element {
{isCustom ? (
<CustomFeeField
fieldName={feeFieldName}
lowFeeEstimate={feeEstimations[FeeType.Low]}
lowFeeEstimate={feeEstimations[FeeTypes.Low]}
setFieldWarning={setFieldWarning}
/>
) : (
Expand Down
@@ -1,15 +1,9 @@
import { truncateMiddle } from '@stacks/ui-utils';
import BigNumber from 'bignumber.js';

import { BTC_DECIMALS } from '@shared/constants';
import { BitcoinTransaction } from '@shared/models/transactions/bitcoin-transaction.model';

import { initBigNumber, sumNumbers } from '@app/common/utils';

const satToBtc = (sat: number | string | BigNumber) => {
const satBigNumber = initBigNumber(sat);
return satBigNumber.shiftedBy(-BTC_DECIMALS);
};
import { satToBtc } from '@app/common/money/unit-conversion';
import { sumNumbers } from '@app/common/utils';

export const getBitcoinTxCaption = (transaction?: BitcoinTransaction) =>
transaction ? truncateMiddle(transaction.txid, 4) : '';
Expand Down
Expand Up @@ -6,7 +6,7 @@ import { useField, useFormikContext } from 'formik';
import { RouteUrls } from '@shared/route-urls';

import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading';
import { stxToMicroStx } from '@app/common/stacks-utils';
import { stxToMicroStx } from '@app/common/money/unit-conversion';
import { useWalletType } from '@app/common/use-wallet-type';
import { useRawTxIdState } from '@app/store/transactions/raw.hooks';

Expand Down
@@ -1,9 +1,9 @@
import { useCallback, useMemo, useState } from 'react';

import { Input, InputGroup, Stack, Text } from '@stacks/ui';
import { microStxToStx, stxToMicroStx } from '@stacks/ui-utils';
import { useField } from 'formik';

import { microStxToStx, stxToMicroStx } from '@app/common/money/unit-conversion';
import { ErrorLabel } from '@app/components/error-label';

import { FeeMultiplier } from './fee-multiplier';
Expand All @@ -19,7 +19,7 @@ export function IncreaseFeeField(props: IncreaseFeeFieldProps): JSX.Element {
const showResetMultiplier = useMemo(() => {
if (modified) return true;
if (!currentFee) return false;
return stxToMicroStx(field.value) !== currentFee;
return stxToMicroStx(field.value).toNumber() !== currentFee;
}, [currentFee, modified, field.value]);

const onSelectMultiplier = useCallback(
Expand Down
Expand Up @@ -7,7 +7,8 @@ import { Formik } from 'formik';
import * as yup from 'yup';

import { useRefreshAllAccountData } from '@app/common/hooks/account/use-refresh-all-account-data';
import { microStxToStx, stacksValue, stxToMicroStx } from '@app/common/stacks-utils';
import { microStxToStx, stxToMicroStx } from '@app/common/money/unit-conversion';
import { stacksValue } from '@app/common/stacks-utils';
import { useWalletType } from '@app/common/use-wallet-type';
import { safelyFormatHexTxid } from '@app/common/utils/safe-handle-txid';
import { useFeeSchema } from '@app/common/validation/use-fee-schema';
Expand Down
Expand Up @@ -3,7 +3,7 @@ import { useContext, useMemo } from 'react';
import { PayloadType, cvToString } from '@stacks/transactions';
import { BigNumber } from 'bignumber.js';

import { microStxToStx } from '@app/common/stacks-utils';
import { microStxToStx } from '@app/common/money/unit-conversion';
import { isSip10Transfer } from '@app/common/transactions/stacks/is-sip-10-transfer';
import { ledgerTxSigningContext } from '@app/features/ledger/flows/tx-signing/ledger-sign-tx.context';
import { ApproveLedgerOperationLayout } from '@app/features/ledger/generic-steps/approve-ledger-operation/approve-ledger-operation.layout';
Expand Down
Expand Up @@ -9,6 +9,7 @@ import { RouteUrls } from '@shared/route-urls';
import { btcAmountSchema } from '@app/common/validation/currency-schema';
import { BtcIcon } from '@app/components/icons/btc-icon';
import { useBitcoinCryptoCurrencyAssetBalance } from '@app/query/bitcoin/address/address.hooks';
import { useBitcoinFeeEstimations } from '@app/query/bitcoin/fees/fee-estimates.hooks';
import { useCurrentAccountBtcAddressState } from '@app/store/accounts/account.hooks';

import { AmountField } from '../../components/amount-field';
Expand All @@ -27,9 +28,12 @@ export function BitcoinCryptoCurrencySendForm({}: BitcoinCryptoCurrencySendFormP
const currentAccountBtcAddress = useCurrentAccountBtcAddressState();
const btcCryptoCurrencyAssetBalance =
useBitcoinCryptoCurrencyAssetBalance(currentAccountBtcAddress);
// TODO: Replace hardcoded number here (200) with the tx byte length
const { data: btcFeeEstimations } = useBitcoinFeeEstimations(200);
const navigate = useNavigate();

logger.debug('btc balance', btcCryptoCurrencyAssetBalance);
logger.debug('btc fees', btcFeeEstimations);

const initialValues = createDefaultInitialFormValues({
memo: '',
Expand Down
4 changes: 2 additions & 2 deletions src/app/pages/send-tokens/components/send-form-inner.tsx
Expand Up @@ -9,7 +9,7 @@ import type {
StacksCryptoCurrencyAssetBalance,
StacksFungibleTokenAssetBalance,
} from '@shared/models/crypto-asset-balance.model';
import { FeeEstimate } from '@shared/models/fees-types';
import { StacksFeeEstimate } from '@shared/models/fees/stacks-fees.model';
import type { SendFormValues } from '@shared/models/form.model';
import { isEmpty, isUndefined } from '@shared/utils';

Expand All @@ -32,7 +32,7 @@ import { RecipientField } from './recipient-field/recipient-field';

interface SendFormInnerProps {
assetError: string | undefined;
feeEstimations: FeeEstimate[];
feeEstimations: StacksFeeEstimate[];
onAssetIdSelected(assetId: string): void;
nonce: number | undefined;
}
Expand Down
Expand Up @@ -2,7 +2,6 @@ import { useEffect, useMemo } from 'react';

import { StacksTransaction } from '@stacks/transactions';
import { Stack } from '@stacks/ui';
import { microStxToStx } from '@stacks/ui-utils';
import BigNumber from 'bignumber.js';
import { useFormikContext } from 'formik';

Expand All @@ -12,6 +11,7 @@ import { createMoney } from '@shared/models/money.model';
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { useConvertStxToFiatAmount } from '@app/common/hooks/use-convert-to-fiat-amount';
import { useDrawers } from '@app/common/hooks/use-drawers';
import { microStxToStx } from '@app/common/money/unit-conversion';
import { BaseDrawer, BaseDrawerProps } from '@app/components/drawer/base-drawer';
import { TransactionFee } from '@app/components/fee-row/components/transaction-fee';
import { SpaceBetween } from '@app/components/space-between';
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/send-tokens/hooks/use-send-form.ts
Expand Up @@ -7,7 +7,7 @@ import { SendFormValues } from '@shared/models/form.model';

import { removeCommas } from '@app/common/crypto-assets/stacks-crypto-asset.utils';
import { useSelectedAssetBalance } from '@app/common/hooks/use-selected-asset-balance';
import { microStxToStx } from '@app/common/stacks-utils';
import { microStxToStx } from '@app/common/money/unit-conversion';
import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/balance.hooks';
import { useCurrentAccountMempoolTransactionsBalance } from '@app/query/stacks/mempool/mempool.hooks';

Expand Down
8 changes: 4 additions & 4 deletions src/app/pages/send-tokens/send-tokens.tsx
Expand Up @@ -6,7 +6,7 @@ import { StacksTransaction } from '@stacks/transactions';
import { Formik } from 'formik';

import { logger } from '@shared/logger';
import { FeeType } from '@shared/models/fees-types';
import { FeeTypes } from '@shared/models/fees/_fees.model';
import { SendFormValues } from '@shared/models/form.model';
import { RouteUrls } from '@shared/route-urls';

Expand All @@ -22,7 +22,7 @@ import { Header } from '@app/components/header';
import { EditNonceDrawer } from '@app/features/edit-nonce-drawer/edit-nonce-drawer';
import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer';
import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate';
import { useFeeEstimations } from '@app/query/stacks/fees/fees.hooks';
import { useStacksFeeEstimations } from '@app/query/stacks/fees/fees.hooks';
import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks';
import {
useGenerateSendFormUnsignedTx,
Expand All @@ -46,7 +46,7 @@ function SendTokensFormBase() {
const signSoftwareWalletTx = useSignTransactionSoftwareWallet();
const txByteLength = useSendFormEstimatedUnsignedTxByteLengthState(selectedAssetId);
const txPayload = useSendFormSerializedUnsignedTxPayloadState(selectedAssetId);
const feeEstimations = useFeeEstimations(txByteLength, txPayload);
const feeEstimations = useStacksFeeEstimations(txByteLength, txPayload);
const { nonce } = useNextNonce();
const analytics = useAnalytics();
const { whenWallet } = useWalletType();
Expand Down Expand Up @@ -95,7 +95,7 @@ function SendTokensFormBase() {
assetId: '',
amount: '',
fee: '',
feeType: FeeType[FeeType.Middle],
feeType: FeeTypes[FeeTypes.Middle],
memo: '',
nonce,
recipientAddressOrBnsName: '',
Expand Down
4 changes: 2 additions & 2 deletions src/app/pages/transaction-request/components/fee-form.tsx
@@ -1,6 +1,6 @@
import { useFormikContext } from 'formik';

import { FeeEstimate } from '@shared/models/fees-types';
import { StacksFeeEstimate } from '@shared/models/fees/stacks-fees.model';
import { SendFormValues, TransactionFormValues } from '@shared/models/form.model';

import { isTxSponsored } from '@app/common/transactions/stacks/transaction.utils';
Expand All @@ -10,7 +10,7 @@ import { MinimalErrorMessage } from '@app/pages/transaction-request/components/m
import { useUnsignedPrepareTransactionDetails } from '@app/store/transactions/transaction.hooks';

interface FeeFormProps {
feeEstimations: FeeEstimate[];
feeEstimations: StacksFeeEstimate[];
}
export function FeeForm({ feeEstimations }: FeeFormProps) {
const { values } = useFormikContext<SendFormValues | TransactionFormValues>();
Expand Down

0 comments on commit 9451f89

Please sign in to comment.