Skip to content

Commit

Permalink
Swaps: Gas fee range to use for native asset maxSwappableAmount buffer (
Browse files Browse the repository at this point in the history
#5881)

* wip

* fix gesture button states

* Safemath pt 2 (#5778)

* add more fns

* accept string or number

* update errors

* fix dynamic island overlap on recieve modal (#5672)

* .

* oop

* oop

* okay ty ben

* change background opacity to 1

* .

* oop

* .

* Gas optimizations (#5779)

* perf

* ✨

* useWhyDidYouUpdate

* EstimatedSwapGasFee

* keepPreviousData

* AnimatedText

* isSameAddress

* fix other networks section (#5784)

* Swaps: fix favorite button press (#5782)

* fix

* android fix

* todo

* remove console logs

* Insufficient Funds

* remove todo

* move cache getter closer to fetcher implentation

* fix

* :)

* 🍕

* or equal 🤌

* remove unused isSameAddress util

* just reordering declarations

* error i18n

* useGasSharedValues

* remove estimating

* fix label flickering

* fix review panel not prompting

* Revert "Lint on pre-commit (#5836)"

This reverts commit d56ed46.

* fix a bunch of shit

* fix?

* opacity

* on review panel we should show fetching status and quote errors too

* remove error

* useUserNativeNetworkAsset

* less or equal

* gas fee range

* fixes

* Fixes for review button states (#5873)

* Fixes APP-1601: adds missing useThreshold for gas fee showing $0.00

* Tweak confirm button prop labels logic and ordering

* Prep work: remove reliance of asset balance display in valueBasedDecimalFormatter

* niceIncrementerFormatter returns asset balance if max swap

* Update formattedInputValue

Instead of using the niceIncrementFormatter for formatting slider-based
values:
for max: use the valueBasedDecimalFormatter on the input value which has
already been set to maxSwappableAmount
for everything else: add commas to the input value as it has already been
formatted using the niceIncrementFormatter

* merge

* weird issues

* fixes

* fix out of sync issue

* update input amount + quote when maxSwappableAmount changes

* fix input formatting logic

* fix wei conversion

---------

Co-authored-by: gregs <[email protected]>
Co-authored-by: Matthew Wall <[email protected]>
Co-authored-by: Bruno Barbieri <[email protected]>
Co-authored-by: brdy <[email protected]>
Co-authored-by: Jin <[email protected]>
  • Loading branch information
6 people authored Jun 22, 2024
1 parent 5aa6434 commit a5087f2
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 34 deletions.
22 changes: 21 additions & 1 deletion src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated';
import { useDebouncedCallback } from 'use-debounce';
import { MAXIMUM_SIGNIFICANT_DECIMALS, SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants';
import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants';
import { RequestNewQuoteParams, inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap';
import {
addCommasToNumber,
Expand Down Expand Up @@ -105,6 +105,8 @@ export function useSwapInputsController({
});
const inputMethod = useSharedValue<inputMethods>('slider');

const maxSwappableAmount = useDerivedValue(() => internalSelectedInputAsset.value?.maxSwappableAmount);

const percentageToSwap = useDerivedValue(() => {
return Math.round(clamp((sliderXPosition.value - SCRUBBER_WIDTH / SLIDER_WIDTH) / SLIDER_WIDTH, 0, 1) * 100) / 100;
});
Expand Down Expand Up @@ -559,6 +561,24 @@ export function useSwapInputsController({
});
};

// update the input amount & quote if swapping max amount & maxSwappableAmount changes
useAnimatedReaction(
() => maxSwappableAmount.value,
maxSwappableAmount => {
const isSwappingMaxBalance = internalSelectedInputAsset.value && inputMethod.value === 'slider' && percentageToSwap.value >= 1;
if (maxSwappableAmount && isSwappingMaxBalance) {
inputValues.modify(prev => {
return {
...prev,
inputAmount: +maxSwappableAmount,
inputNativeValue: +mulWorklet(maxSwappableAmount, inputNativePrice.value),
};
});
fetchQuoteAndAssetPrices();
}
}
);

const quoteFetchingInterval = useAnimatedInterval({
intervalMs: 12_000,
onIntervalWorklet: fetchQuoteAndAssetPrices,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ import { useUserNativeNetworkAsset } from '@/resources/assets/useUserAsset';
import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps';
import { debounce } from 'lodash';
import { useEffect } from 'react';
import { SharedValue, runOnJS, useAnimatedReaction } from 'react-native-reanimated';
import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
import { formatUnits } from 'viem';
import { create } from 'zustand';
import { calculateGasFee } from '../hooks/useEstimatedGasFee';
import { useSelectedGas } from '../hooks/useSelectedGas';
import { useSwapEstimatedGasLimit } from '../hooks/useSwapEstimatedGasLimit';
import { useSwapContext } from './swap-provider';

const BUFFER_RATIO = 0.25;

type InternalSyncedSwapState = {
assetToBuy: ExtendedAnimatedAssetWithColors | undefined;
assetToSell: ExtendedAnimatedAssetWithColors | undefined;
Expand Down Expand Up @@ -84,55 +86,69 @@ const getHasEnoughFundsForGas = (quote: Quote, gasFee: string, nativeNetworkAsse
return lessThanOrEqualToWorklet(totalNativeSpentInTx, userBalance);
};

const BUFFER_FACTOR = 1.3;
function updateMaxSwappableAmount(internalSelectedInputAsset: SharedValue<ExtendedAnimatedAssetWithColors | null>, gasFee: string) {
internalSelectedInputAsset.modify(asset => {
'worklet';

if (!asset?.isNativeAsset) return asset;

const gasFeeNativeCurrency = divWorklet(gasFee, powWorklet(10, asset.decimals));
const gasFeeWithBuffer = toFixedWorklet(mulWorklet(gasFeeNativeCurrency, BUFFER_FACTOR), asset.decimals);
const maxSwappableAmount = subWorklet(asset.balance.amount, gasFeeWithBuffer);

return {
...asset,
maxSwappableAmount: lessThanWorklet(maxSwappableAmount, 0) ? '0' : maxSwappableAmount,
};
});
}

export function SyncGasStateToSharedValues() {
const { hasEnoughFundsForGas, internalSelectedInputAsset, SwapInputController } = useSwapContext();
const { hasEnoughFundsForGas, internalSelectedInputAsset } = useSwapContext();

const { assetToSell, chainId = ChainId.mainnet, quote } = useSyncedSwapQuoteStore();

const gasSettings = useSelectedGas(chainId);
const { data: userNativeNetworkAsset } = useUserNativeNetworkAsset(chainId);
const { data: estimatedGasLimit, isFetching } = useSwapEstimatedGasLimit({ chainId, assetToSell, quote });
const { data: estimatedGasLimit } = useSwapEstimatedGasLimit({ chainId, assetToSell, quote });

const gasFeeRange = useSharedValue<[string, string] | null>(null);

useAnimatedReaction(
() => ({ inputAsset: internalSelectedInputAsset.value, bufferRange: gasFeeRange.value }),
(current, previous) => {
const { inputAsset: currInputAsset, bufferRange: currBufferRange } = current;
const { inputAsset: prevInputAsset, bufferRange: prevBufferRange } = previous || {};

const currBuffer = currBufferRange?.[1];
const prevBuffer = prevBufferRange?.[1];

if (currInputAsset?.chainId !== prevInputAsset?.chainId) {
// reset gas fee range when input chain changes
gasFeeRange.value = null;
} else if (currBuffer && (currBuffer !== prevBuffer || currInputAsset?.uniqueId !== prevInputAsset?.uniqueId)) {
// update maxSwappableAmount when gas fee range is set and there is a change to input asset or gas fee range
internalSelectedInputAsset.modify(asset => {
'worklet';
if (!asset || !asset.isNativeAsset) return asset;
return {
...asset,
maxSwappableAmount: subWorklet(asset.balance.amount, currBuffer),
};
});
}
}
);

useEffect(() => {
hasEnoughFundsForGas.value = undefined;
if (!gasSettings || !estimatedGasLimit || !quote || 'error' in quote) return;
if (!gasSettings || !estimatedGasLimit || !quote || 'error' in quote || !userNativeNetworkAsset) return;

const gasFee = calculateGasFee(gasSettings, estimatedGasLimit);

updateMaxSwappableAmount(internalSelectedInputAsset, gasFee);
const nativeGasFee = divWorklet(gasFee, powWorklet(10, userNativeNetworkAsset.decimals));

const isEstimateOutsideRange = !!(
gasFeeRange.value &&
(lessThanWorklet(nativeGasFee, gasFeeRange.value[0]) || greaterThanWorklet(nativeGasFee, gasFeeRange.value[1]))
);

// If the gas fee range hasn't been set or the estimated fee is outside the range, calculate the range based on the gas fee
if (nativeGasFee && (!gasFeeRange.value || isEstimateOutsideRange)) {
const lowerBound = toFixedWorklet(mulWorklet(nativeGasFee, 1 - BUFFER_RATIO), userNativeNetworkAsset.decimals);
const upperBound = toFixedWorklet(mulWorklet(nativeGasFee, 1 + BUFFER_RATIO), userNativeNetworkAsset.decimals);
gasFeeRange.value = [lowerBound, upperBound];
}

hasEnoughFundsForGas.value = getHasEnoughFundsForGas(quote, gasFee, userNativeNetworkAsset);

return () => {
hasEnoughFundsForGas.value = undefined;
};
}, [
estimatedGasLimit,
gasSettings,
hasEnoughFundsForGas,
quote,
internalSelectedInputAsset,
SwapInputController.inputValues.value.inputAmount,
userNativeNetworkAsset,
isFetching,
]);
}, [estimatedGasLimit, gasFeeRange, gasSettings, hasEnoughFundsForGas, quote, userNativeNetworkAsset]);

return null;
}

0 comments on commit a5087f2

Please sign in to comment.