Skip to content

Commit

Permalink
feat: add config for max fee estimations
Browse files Browse the repository at this point in the history
closes #2039
  • Loading branch information
beguene committed Feb 1, 2022
1 parent e943119 commit 0a7a20e
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 58 deletions.
4 changes: 4 additions & 0 deletions config/wallet-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
"activeFiatProviders": {
"transak": { "name": "transak", "enabled": true },
"okcoin": { "name": "okcoin", "enabled": true }
},
"feeEstimations": {
"maxValuesEnabled": true,
"maxValues": [500000, 750000, 2000000]
}
}
21 changes: 21 additions & 0 deletions config/wallet-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@
"transak": { "$ref": "#/$defs/provider" },
"okcoin": { "$ref": "#/$defs/provider" }
}
},
"feeEstimations": {
"type": "object",
"description": "All the active fiat onramp providers that will be displayed in the wallet",
"additionalProperties": false,
"properties": {
"maxValuesEnabled": {
"type": "boolean",
"description": "Whether or not the maximum values are enabled"
},
"maxValues": {
"type": "array",
"description": "Low, middle and high max values for fee estimations",
"minItems": 3,
"maxItems": 3,
"items": {
"type": "number",
"description": "Fee estimation max value"
}
}
}
}
},
"$defs": {
Expand Down
43 changes: 0 additions & 43 deletions src/app/common/transactions/transaction-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import {
} from '@stacks/stacks-blockchain-api-types';
import { getContractName, truncateMiddle } from '@stacks/ui-utils';

import { DEFAULT_FEE_RATE } from '@shared/constants';
import { displayDate, isoDateToLocalDateSafe, todaysIsoDate } from '@app/common/date-utils';
import { stacksValue } from '@app/common/stacks-utils';
import { FeeEstimation } from '@shared/models/fees-types';

type Tx = MempoolTransaction | Transaction;

Expand Down Expand Up @@ -48,47 +46,6 @@ function txHasTime(tx: Tx) {
);
}

const maxValueForLowFeeEstimation = 500000;
const maxValueForMiddleFeeEstimation = 750000;
const maxValueForHighFeeEstimation = 2000000;

export function getFeeEstimationsWithMaxValues(feeEstimations: FeeEstimation[]) {
const feeEstimationsWithMaxValues = [];

if (new BigNumber(feeEstimations[0].fee).isGreaterThan(maxValueForLowFeeEstimation)) {
feeEstimationsWithMaxValues.push({ fee: maxValueForLowFeeEstimation, fee_rate: 0 });
} else {
feeEstimationsWithMaxValues.push(feeEstimations[0]);
}
if (new BigNumber(feeEstimations[1].fee).isGreaterThan(maxValueForMiddleFeeEstimation)) {
feeEstimationsWithMaxValues.push({ fee: maxValueForMiddleFeeEstimation, fee_rate: 0 });
} else {
feeEstimationsWithMaxValues.push(feeEstimations[1]);
}
if (new BigNumber(feeEstimations[2].fee).isGreaterThan(maxValueForHighFeeEstimation)) {
feeEstimationsWithMaxValues.push({ fee: maxValueForHighFeeEstimation, fee_rate: 0 });
} else {
feeEstimationsWithMaxValues.push(feeEstimations[2]);
}

return feeEstimationsWithMaxValues;
}

function calculateFeeFromFeeRate(txBytes: number, feeRate: number) {
return new BigNumber(txBytes).multipliedBy(feeRate);
}

const marginFromDefaultFeeDecimalPercent = 0.1;

export function getDefaultSimulatedFeeEstimations(estimatedByteLength: number): FeeEstimation[] {
const fee = calculateFeeFromFeeRate(estimatedByteLength, DEFAULT_FEE_RATE);
return [
{ fee: fee.multipliedBy(1 - marginFromDefaultFeeDecimalPercent).toNumber(), fee_rate: 0 },
{ fee: fee.toNumber(), fee_rate: 0 },
{ fee: fee.multipliedBy(1 + marginFromDefaultFeeDecimalPercent).toNumber(), fee_rate: 0 },
];
}

export function isAddressTransactionWithTransfers(
transaction: AddressTransactionWithTransfers | Tx
): transaction is AddressTransactionWithTransfers {
Expand Down
15 changes: 15 additions & 0 deletions src/app/common/transactions/use-fee-estimations-max-values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
useConfigFeeEstimationsEnabled,
useConfigFeeEstimationsMaxValues,
} from '@app/query/hiro-config/hiro-config.query';

const defaultFeeEstimationsMaxValues = [500000, 750000, 2000000];

export function useFeeEstimationsMaxValues() {
// Get it first from the config
const configFeeEstimationsEnabled = useConfigFeeEstimationsEnabled();
const configFeeEstimationsMaxValues = useConfigFeeEstimationsMaxValues();
// Only when the remote config file explicitly sets the maxValuesEnabled as false, we return no cap for fees
if (configFeeEstimationsEnabled === false) return;
return configFeeEstimationsMaxValues || defaultFeeEstimationsMaxValues;
}
17 changes: 10 additions & 7 deletions src/app/pages/send-tokens/components/send-form-inner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ import { useDrawers } from '@app/common/hooks/use-drawers';
import { useNextTxNonce } from '@app/common/hooks/account/use-next-tx-nonce';
import { useSelectedAsset } from '@app/common/hooks/use-selected-asset';
import { isEmpty } from '@app/common/utils';
import {
getDefaultSimulatedFeeEstimations,
getFeeEstimationsWithMaxValues,
isTxSponsored,
TransactionFormValues,
} from '@app/common/transactions/transaction-utils';
import { isTxSponsored, TransactionFormValues } from '@app/common/transactions/transaction-utils';
import { ErrorLabel } from '@app/components/error-label';
import { ShowEditNonceAction } from '@app/components/show-edit-nonce';
import { FeeRow } from '@app/components/fee-row/fee-row';
Expand All @@ -35,6 +30,11 @@ import {
} from '@app/store/transactions/transaction.hooks';

import { SendFormMemoWarning } from './memo-warning';
import {
getDefaultSimulatedFeeEstimations,
getFeeEstimationsWithMaxValues,
} from '@shared/transactions/fee-estimations';
import { useFeeEstimationsMaxValues } from '@app/common/transactions/use-fee-estimations-max-values';

interface SendFormInnerProps {
assetError: string | undefined;
Expand All @@ -50,7 +50,9 @@ export function SendFormInner(props: SendFormInnerProps) {
serializedTxPayload,
estimatedTxByteLength
);

const [, setFeeEstimations] = useFeeEstimationsState();
const feeEstimationsMaxValues = useFeeEstimationsMaxValues();
const { selectedAsset } = useSelectedAsset();
const assets = useTransferableAssets();
const analytics = useAnalytics();
Expand All @@ -69,7 +71,8 @@ export function SendFormInner(props: SendFormInnerProps) {
}
if (feeEstimationsResp.estimations && feeEstimationsResp.estimations.length) {
const feeEstimationsWithMaxValues = getFeeEstimationsWithMaxValues(
feeEstimationsResp.estimations
feeEstimationsResp.estimations,
feeEstimationsMaxValues
);
setFeeEstimations(feeEstimationsWithMaxValues);
void analytics.track('use_fee_estimation', {
Expand Down
16 changes: 9 additions & 7 deletions src/app/pages/sign-transaction/components/fee-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import { useEffect } from 'react';
import { useFormikContext } from 'formik';

import { LoadingRectangle } from '@app/components/loading-rectangle';
import {
getDefaultSimulatedFeeEstimations,
getFeeEstimationsWithMaxValues,
isTxSponsored,
TransactionFormValues,
} from '@app/common/transactions/transaction-utils';
import { isTxSponsored, TransactionFormValues } from '@app/common/transactions/transaction-utils';
import { FeeRow } from '@app/components/fee-row/fee-row';
import { MinimalErrorMessage } from '@app/pages/sign-transaction/components/minimal-error-message';
import { useFeeEstimationsQuery } from '@app/query/fees/fees.hooks';
Expand All @@ -18,6 +13,11 @@ import {
} from '@app/store/transactions/transaction.hooks';
import { useFeeEstimationsState } from '@app/store/transactions/fees.hooks';
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import {
getDefaultSimulatedFeeEstimations,
getFeeEstimationsWithMaxValues,
} from '@shared/transactions/fee-estimations';
import { useFeeEstimationsMaxValues } from '@app/common/transactions/use-fee-estimations-max-values';

export function FeeForm(): JSX.Element | null {
const analytics = useAnalytics();
Expand All @@ -33,6 +33,7 @@ export function FeeForm(): JSX.Element | null {
const isSponsored = transaction ? isTxSponsored(transaction) : false;

const [, setFeeEstimations] = useFeeEstimationsState();
const feeEstimationsMaxValues = useFeeEstimationsMaxValues();

useEffect(() => {
if (feeEstimationsResp) {
Expand All @@ -45,7 +46,8 @@ export function FeeForm(): JSX.Element | null {
}
if (feeEstimationsResp.estimations && feeEstimationsResp.estimations.length) {
const feeEstimationsWithMaxValues = getFeeEstimationsWithMaxValues(
feeEstimationsResp.estimations
feeEstimationsResp.estimations,
feeEstimationsMaxValues
);
setFeeEstimations(feeEstimationsWithMaxValues);
void analytics.track('use_fee_estimation', {
Expand Down
23 changes: 22 additions & 1 deletion src/app/query/hiro-config/hiro-config.query.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isUndefined } from '@app/common/utils';
import { GITHUB_ORG, GITHUB_REPO } from '@shared/constants';
import { useQuery } from 'react-query';

Expand All @@ -15,9 +16,15 @@ interface ActiveFiatProviderType {
enabled: boolean;
}

interface FeeEstimationsConfig {
maxValuesEnabled?: boolean;
maxValues?: number[];
}

interface HiroConfig {
messages: any;
activeFiatProviders: Record<string, ActiveFiatProviderType>;
activeFiatProviders?: Record<string, ActiveFiatProviderType>;
feeEstimations?: FeeEstimationsConfig;
}

const GITHUB_PRIMARY_BRANCH = 'dev';
Expand Down Expand Up @@ -54,3 +61,17 @@ export function useHasFiatProviders() {
Object.keys(activeProviders).reduce((acc, key) => activeProviders[key].enabled || acc, false)
);
}

export function useConfigFeeEstimationsEnabled() {
const config = useRemoteHiroConfig();
if (isUndefined(config) || isUndefined(config?.feeEstimations)) return;
return config.feeEstimations.maxValuesEnabled;
}

export function useConfigFeeEstimationsMaxValues() {
const config = useRemoteHiroConfig();
if (typeof config?.feeEstimations === 'undefined') return;
if (!config.feeEstimations.maxValues) return;
if (!Array.isArray(config.feeEstimations.maxValues)) return;
return config.feeEstimations.maxValues;
}
34 changes: 34 additions & 0 deletions src/shared/transactions/fee-estimations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { DEFAULT_FEE_RATE } from '@shared/constants';
import { FeeEstimation } from '@shared/models/fees-types';
import { BigNumber } from 'bignumber.js';

export function getFeeEstimationsWithMaxValues(
feeEstimations: FeeEstimation[],
feeEstimationsMaxValues: number[] | undefined
) {
return feeEstimations.map((feeEstimation, index) => {
if (
feeEstimationsMaxValues &&
new BigNumber(feeEstimation.fee).isGreaterThan(feeEstimationsMaxValues[index])
) {
return { fee: feeEstimationsMaxValues[index], fee_rate: 0 };
} else {
return feeEstimation;
}
});
}

function calculateFeeFromFeeRate(txBytes: number, feeRate: number) {
return new BigNumber(txBytes).multipliedBy(feeRate);
}

const marginFromDefaultFeeDecimalPercent = 0.1;

export function getDefaultSimulatedFeeEstimations(estimatedByteLength: number): FeeEstimation[] {
const fee = calculateFeeFromFeeRate(estimatedByteLength, DEFAULT_FEE_RATE);
return [
{ fee: fee.multipliedBy(1 - marginFromDefaultFeeDecimalPercent).toNumber(), fee_rate: 0 },
{ fee: fee.toNumber(), fee_rate: 0 },
{ fee: fee.multipliedBy(1 + marginFromDefaultFeeDecimalPercent).toNumber(), fee_rate: 0 },
];
}

0 comments on commit 0a7a20e

Please sign in to comment.