Commit 8c372c61 authored by Jack Short's avatar Jack Short Committed by GitHub

fix: fixing pwat routing edge case (#5987)

* feat: implementing graphql endpoint

* changing from hook to function call

* initial gql routing works

* feat: initial pwatRouting setup

* sending correct amount

* removing console

* it is working

* sufficient balance

* 0 if no inputCurrency

* removing value to send if erc20

* removing console

* permit2 optional flag

* removing not necessary stuff

* mobile fixes

* overlay needs to be here

* changing swap amount to pool reserves

* refactoring routing logic

* no route found button state

* better price loading for insufficient liquidity

* refactoring graphql routing code

* overflow

* initial comments

* resetting bag status on input currency change

* locking

* done

* remove helper text for eth

* fix: pay with any token routing bug

* reordering button

* zindex

* price updated

* keeping debounce
parent 8c0998bd
...@@ -66,7 +66,7 @@ const BagContainer = styled.div<{ raiseZIndex: boolean; isProfilePage: boolean } ...@@ -66,7 +66,7 @@ const BagContainer = styled.div<{ raiseZIndex: boolean; isProfilePage: boolean }
border-radius: 16px; border-radius: 16px;
box-shadow: ${({ theme }) => theme.shallowShadow}; box-shadow: ${({ theme }) => theme.shallowShadow};
z-index: ${({ raiseZIndex, isProfilePage }) => z-index: ${({ raiseZIndex, isProfilePage }) =>
raiseZIndex ? (isProfilePage ? Z_INDEX.modalOverTooltip : Z_INDEX.modalBackdrop) : 3}; raiseZIndex ? (isProfilePage ? Z_INDEX.modalOverTooltip : Z_INDEX.modalBackdrop + 2) : 3};
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
right: 0px; right: 0px;
...@@ -232,7 +232,8 @@ const Bag = () => { ...@@ -232,7 +232,8 @@ const Bag = () => {
const { route, routeResponse } = buildRouteResponse(data.nftRoute, !!tokenTradeInput) const { route, routeResponse } = buildRouteResponse(data.nftRoute, !!tokenTradeInput)
const updatedAssets = combineBuyItemsWithTxRoute(itemsToBuy, route) const { hasPriceAdjustment, updatedAssets } = combineBuyItemsWithTxRoute(itemsToBuy, route)
const shouldRefetchCalldata = hasPriceAdjustment && !!tokenTradeInput
const fetchedPriceChangedAssets = updatedAssets const fetchedPriceChangedAssets = updatedAssets
.filter((asset) => asset.updatedPriceInfo) .filter((asset) => asset.updatedPriceInfo)
...@@ -266,9 +267,13 @@ const Bag = () => { ...@@ -266,9 +267,13 @@ const Bag = () => {
if (hasAssets) { if (hasAssets) {
if (!shouldReview) { if (!shouldReview) {
purchaseAssets(routeResponse) if (shouldRefetchCalldata) {
setBagStatus(BagStatus.CONFIRMING_IN_WALLET) setBagStatus(BagStatus.CONFIRM_QUOTE)
shouldLock = true } else {
purchaseAssets(routeResponse)
setBagStatus(BagStatus.CONFIRMING_IN_WALLET)
shouldLock = true
}
} else if (!hasAssetsInReview) setBagStatus(BagStatus.CONFIRM_REVIEW) } else if (!hasAssetsInReview) setBagStatus(BagStatus.CONFIRM_REVIEW)
else { else {
setBagStatus(BagStatus.IN_REVIEW) setBagStatus(BagStatus.IN_REVIEW)
...@@ -289,7 +294,7 @@ const Bag = () => { ...@@ -289,7 +294,7 @@ const Bag = () => {
}) })
) )
const updatedAssets = combineBuyItemsWithTxRoute(itemsToBuy, routeData.route) const { updatedAssets } = combineBuyItemsWithTxRoute(itemsToBuy, routeData.route)
const fetchedPriceChangedAssets = updatedAssets const fetchedPriceChangedAssets = updatedAssets
.filter((asset) => asset.updatedPriceInfo) .filter((asset) => asset.updatedPriceInfo)
...@@ -405,12 +410,7 @@ const Bag = () => { ...@@ -405,12 +410,7 @@ const Bag = () => {
{isProfilePage ? <ProfileBagContent /> : <BagContent />} {isProfilePage ? <ProfileBagContent /> : <BagContent />}
</Column> </Column>
{hasAssetsToShow && !isProfilePage && ( {hasAssetsToShow && !isProfilePage && (
<BagFooter <BagFooter totalEthPrice={totalEthPrice} fetchAssets={fetchAssets} eventProperties={eventProperties} />
totalEthPrice={totalEthPrice}
bagStatus={bagStatus}
fetchAssets={fetchAssets}
eventProperties={eventProperties}
/>
)} )}
{isSellingAssets && isProfilePage && ( {isSellingAssets && isProfilePage && (
<Box <Box
......
...@@ -77,11 +77,11 @@ const TotalColumn = styled(Column)` ...@@ -77,11 +77,11 @@ const TotalColumn = styled(Column)`
const WarningIcon = styled(AlertTriangle)` const WarningIcon = styled(AlertTriangle)`
width: 14px; width: 14px;
margin-right: 4px; margin-right: 4px;
color: ${({ theme }) => theme.accentWarning}; color: inherit;
` `
const WarningText = styled(ThemedText.BodyPrimary)` const WarningText = styled(ThemedText.BodyPrimary)<{ $color: string }>`
align-items: center; align-items: center;
color: ${({ theme }) => theme.accentWarning}; color: ${({ $color }) => $color};
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-bottom: 10px !important; margin-bottom: 10px !important;
...@@ -173,22 +173,22 @@ const ActionButton = ({ ...@@ -173,22 +173,22 @@ const ActionButton = ({
) )
} }
const Warning = ({ children }: PropsWithChildren<unknown>) => { interface HelperTextProps {
color: string
}
const Warning = ({ color, children }: PropsWithChildren<HelperTextProps>) => {
if (!children) { if (!children) {
return null return null
} }
return ( return (
<WarningText fontSize="14px" lineHeight="20px"> <WarningText fontSize="14px" lineHeight="20px" $color={color}>
<WarningIcon /> <WarningIcon />
{children} {children}
</WarningText> </WarningText>
) )
} }
interface HelperTextProps {
color: string
}
const Helper = ({ children, color }: PropsWithChildren<HelperTextProps>) => { const Helper = ({ children, color }: PropsWithChildren<HelperTextProps>) => {
if (!children) { if (!children) {
return null return null
...@@ -281,7 +281,6 @@ const FiatValue = ({ ...@@ -281,7 +281,6 @@ const FiatValue = ({
interface BagFooterProps { interface BagFooterProps {
totalEthPrice: BigNumber totalEthPrice: BigNumber
bagStatus: BagStatus
fetchAssets: () => void fetchAssets: () => void
eventProperties: Record<string, unknown> eventProperties: Record<string, unknown>
} }
...@@ -293,7 +292,7 @@ const PENDING_BAG_STATUSES = [ ...@@ -293,7 +292,7 @@ const PENDING_BAG_STATUSES = [
BagStatus.PROCESSING_TRANSACTION, BagStatus.PROCESSING_TRANSACTION,
] ]
export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperties }: BagFooterProps) => { export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFooterProps) => {
const toggleWalletModal = useToggleWalletModal() const toggleWalletModal = useToggleWalletModal()
const theme = useTheme() const theme = useTheme()
const { account, chainId, connector } = useWeb3React() const { account, chainId, connector } = useWeb3React()
...@@ -309,11 +308,13 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -309,11 +308,13 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
const { const {
isLocked: bagIsLocked, isLocked: bagIsLocked,
bagStatus,
setBagExpanded, setBagExpanded,
setBagStatus, setBagStatus,
} = useBag( } = useBag(
({ isLocked, setBagExpanded, setBagStatus }) => ({ ({ isLocked, bagStatus, setBagExpanded, setBagStatus }) => ({
isLocked, isLocked,
bagStatus,
setBagExpanded, setBagExpanded,
setBagStatus, setBagStatus,
}), }),
...@@ -389,116 +390,131 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -389,116 +390,131 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
setBagStatus(BagStatus.ADDING_TO_BAG) setBagStatus(BagStatus.ADDING_TO_BAG)
}, [inputCurrency, setBagStatus]) }, [inputCurrency, setBagStatus])
const { buttonText, buttonTextColor, disabled, warningText, helperText, helperTextColor, handleClick, buttonColor } = const {
useMemo(() => { buttonText,
let handleClick = fetchAssets buttonTextColor,
let buttonText = <Trans>Something went wrong</Trans> disabled,
let disabled = true warningText,
let warningText = undefined warningTextColor,
let helperText = undefined helperText,
let helperTextColor = theme.textSecondary helperTextColor,
let buttonColor = theme.accentAction handleClick,
let buttonTextColor = theme.accentTextLightPrimary buttonColor,
} = useMemo(() => {
if (connected && chainId !== SupportedChainId.MAINNET) { let handleClick = fetchAssets
handleClick = () => switchChain(connector, SupportedChainId.MAINNET) let buttonText = <Trans>Something went wrong</Trans>
buttonText = <Trans>Switch networks</Trans> let disabled = true
disabled = false let warningText = undefined
warningText = <Trans>Wrong network</Trans> let warningTextColor = theme.accentWarning
} else if (sufficientBalance === false) { let helperText = undefined
buttonText = <Trans>Pay</Trans> let helperTextColor = theme.textSecondary
disabled = true let buttonColor = theme.accentAction
warningText = <Trans>Insufficient funds</Trans> let buttonTextColor = theme.accentTextLightPrimary
} else if (bagStatus === BagStatus.WARNING) {
warningText = <Trans>Something went wrong. Please try again.</Trans> if (connected && chainId !== SupportedChainId.MAINNET) {
} else if (!connected) { handleClick = () => switchChain(connector, SupportedChainId.MAINNET)
handleClick = () => { buttonText = <Trans>Switch networks</Trans>
toggleWalletModal() disabled = false
setBagExpanded({ bagExpanded: false }) warningText = <Trans>Wrong network</Trans>
} } else if (sufficientBalance === false) {
disabled = false buttonText = <Trans>Pay</Trans>
buttonText = <Trans>Connect wallet</Trans> disabled = true
} else if (usingPayWithAnyToken && tradeState !== TradeState.VALID) { warningText = <Trans>Insufficient funds</Trans>
disabled = true } else if (bagStatus === BagStatus.WARNING) {
buttonText = <Trans>Fetching Route</Trans> warningText = <Trans>Something went wrong. Please try again.</Trans>
} else if (!connected) {
if (tradeState === TradeState.INVALID) { handleClick = () => {
buttonText = <Trans>Pay</Trans> toggleWalletModal()
} setBagExpanded({ bagExpanded: false })
}
if (tradeState === TradeState.NO_ROUTE_FOUND) { disabled = false
buttonText = <Trans>Insufficient liquidity</Trans> buttonText = <Trans>Connect wallet</Trans>
buttonColor = theme.backgroundInteractive } else if (bagStatus === BagStatus.FETCHING_FINAL_ROUTE || bagStatus === BagStatus.CONFIRMING_IN_WALLET) {
buttonTextColor = theme.textPrimary disabled = true
helperText = <Trans>Insufficient pool liquidity to complete transaction</Trans> buttonText = <Trans>Proceed in wallet</Trans>
} } else if (bagStatus === BagStatus.PROCESSING_TRANSACTION) {
} else if (allowance.state === AllowanceState.REQUIRED || allowance.state === AllowanceState.LOADING) { disabled = true
handleClick = () => updateAllowance() buttonText = <Trans>Transaction pending</Trans>
disabled = isAllowancePending || isApprovalLoading || allowance.state === AllowanceState.LOADING } else if (usingPayWithAnyToken && tradeState !== TradeState.VALID) {
disabled = true
if (allowance.state === AllowanceState.LOADING) { buttonText = <Trans>Fetching Route</Trans>
buttonText = <Trans>Loading Allowance</Trans>
} else if (isAllowancePending) { if (tradeState === TradeState.INVALID) {
buttonText = <Trans>Approve in your wallet</Trans>
} else if (isApprovalLoading) {
buttonText = <Trans>Approval pending</Trans>
} else {
helperText = <Trans>An approval is needed to use this token. </Trans>
buttonText = <Trans>Approve</Trans>
}
} else if (bagStatus === BagStatus.FETCHING_FINAL_ROUTE || bagStatus === BagStatus.CONFIRMING_IN_WALLET) {
disabled = true
buttonText = <Trans>Proceed in wallet</Trans>
} else if (bagStatus === BagStatus.PROCESSING_TRANSACTION) {
disabled = true
buttonText = <Trans>Transaction pending</Trans>
} else if (priceImpactWarning && priceImpactColor) {
disabled = false
buttonColor = priceImpactColor
helperText = <Trans>Price impact warning</Trans>
helperTextColor = priceImpactColor
buttonText = <Trans>Pay Anyway</Trans>
} else if (sufficientBalance === true) {
disabled = false
buttonText = <Trans>Pay</Trans> buttonText = <Trans>Pay</Trans>
helperText = usingPayWithAnyToken ? (
<Trans>Refunds for unavailable items will be given in ETH</Trans>
) : undefined
} }
return { if (tradeState === TradeState.NO_ROUTE_FOUND) {
buttonText, buttonText = <Trans>Insufficient liquidity</Trans>
buttonTextColor, buttonColor = theme.backgroundInteractive
disabled, buttonTextColor = theme.textPrimary
warningText, helperText = <Trans>Insufficient pool liquidity to complete transaction</Trans>
helperText,
helperTextColor,
handleClick,
buttonColor,
} }
}, [ } else if (allowance.state === AllowanceState.REQUIRED || allowance.state === AllowanceState.LOADING) {
fetchAssets, handleClick = () => updateAllowance()
theme.textSecondary, disabled = isAllowancePending || isApprovalLoading || allowance.state === AllowanceState.LOADING
theme.accentAction,
theme.accentTextLightPrimary, if (allowance.state === AllowanceState.LOADING) {
theme.backgroundInteractive, buttonText = <Trans>Loading Allowance</Trans>
theme.textPrimary, } else if (isAllowancePending) {
connected, buttonText = <Trans>Approve in your wallet</Trans>
chainId, } else if (isApprovalLoading) {
sufficientBalance, buttonText = <Trans>Approval pending</Trans>
bagStatus, } else {
usingPayWithAnyToken, helperText = <Trans>An approval is needed to use this token. </Trans>
tradeState, buttonText = <Trans>Approve</Trans>
allowance.state, }
priceImpactWarning, } else if (bagStatus === BagStatus.CONFIRM_QUOTE) {
priceImpactColor, disabled = false
connector, warningTextColor = theme.accentAction
toggleWalletModal, warningText = <Trans>Price updated</Trans>
setBagExpanded, buttonText = <Trans>Pay</Trans>
isAllowancePending, } else if (priceImpactWarning && priceImpactColor) {
isApprovalLoading, disabled = false
updateAllowance, buttonColor = priceImpactColor
]) helperText = <Trans>Price impact warning</Trans>
helperTextColor = priceImpactColor
buttonText = <Trans>Pay Anyway</Trans>
} else if (sufficientBalance === true) {
disabled = false
buttonText = <Trans>Pay</Trans>
helperText = usingPayWithAnyToken ? <Trans>Refunds for unavailable items will be given in ETH</Trans> : undefined
}
return {
buttonText,
buttonTextColor,
disabled,
warningText,
warningTextColor,
helperText,
helperTextColor,
handleClick,
buttonColor,
}
}, [
fetchAssets,
theme.accentWarning,
theme.textSecondary,
theme.accentAction,
theme.accentTextLightPrimary,
theme.backgroundInteractive,
theme.textPrimary,
connected,
chainId,
sufficientBalance,
bagStatus,
usingPayWithAnyToken,
tradeState,
allowance.state,
priceImpactWarning,
priceImpactColor,
connector,
toggleWalletModal,
setBagExpanded,
isAllowancePending,
isApprovalLoading,
updateAllowance,
])
const traceEventProperties = { const traceEventProperties = {
usd_value: usdcValue?.toExact(), usd_value: usdcValue?.toExact(),
...@@ -574,7 +590,7 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -574,7 +590,7 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
properties={{ ...traceEventProperties }} properties={{ ...traceEventProperties }}
shouldLogImpression={connected && !disabled} shouldLogImpression={connected && !disabled}
> >
<Warning>{warningText}</Warning> <Warning color={warningTextColor}>{warningText}</Warning>
<Helper color={helperTextColor}>{helperText}</Helper> <Helper color={helperTextColor}>{helperText}</Helper>
<ActionButton <ActionButton
onClick={handleClick} onClick={handleClick}
......
...@@ -16,6 +16,6 @@ export const overlay = style([ ...@@ -16,6 +16,6 @@ export const overlay = style([
{ {
opacity: 0.72, opacity: 0.72,
overflow: 'hidden', overflow: 'hidden',
zIndex: Z_INDEX.modalBackdrop - 1, zIndex: Z_INDEX.modalBackdrop + 1,
}, },
]) ])
...@@ -143,7 +143,7 @@ const findNFTsPurchased = ( ...@@ -143,7 +143,7 @@ const findNFTsPurchased = (
) )
}) })
return combineBuyItemsWithTxRoute(transferredItems, txRoute) return combineBuyItemsWithTxRoute(transferredItems, txRoute).updatedAssets
} }
const findNFTsNotPurchased = (toBuy: GenieAsset[], nftsPurchased: UpdatedGenieAsset[]) => { const findNFTsNotPurchased = (toBuy: GenieAsset[], nftsPurchased: UpdatedGenieAsset[]) => {
......
...@@ -96,4 +96,5 @@ export enum BagStatus { ...@@ -96,4 +96,5 @@ export enum BagStatus {
FETCHING_FINAL_ROUTE = 'Fetching final route', FETCHING_FINAL_ROUTE = 'Fetching final route',
CONFIRMING_IN_WALLET = 'Confirming in wallet', CONFIRMING_IN_WALLET = 'Confirming in wallet',
PROCESSING_TRANSACTION = 'Processing', PROCESSING_TRANSACTION = 'Processing',
CONFIRM_QUOTE = 'Confirm quote',
} }
...@@ -19,8 +19,11 @@ const isTheSame = (item: GenieAsset, routeAsset: BuyItem | PriceInfo) => { ...@@ -19,8 +19,11 @@ const isTheSame = (item: GenieAsset, routeAsset: BuyItem | PriceInfo) => {
} }
} }
const isPriceDiff = (oldPrice: string, newPrice: string) => { const getPriceDiff = (oldPrice: string, newPrice: string): { hasPriceDiff: boolean; hasVisiblePriceDiff: boolean } => {
return formatWeiToDecimal(oldPrice) !== formatWeiToDecimal(newPrice) const hasPriceDiff = oldPrice !== newPrice
const hasVisiblePriceDiff = formatWeiToDecimal(oldPrice) !== formatWeiToDecimal(newPrice)
return { hasPriceDiff, hasVisiblePriceDiff }
} }
const isAveragePriceOfPooledAssets = ( const isAveragePriceOfPooledAssets = (
...@@ -28,7 +31,7 @@ const isAveragePriceOfPooledAssets = ( ...@@ -28,7 +31,7 @@ const isAveragePriceOfPooledAssets = (
numberOfAssetsInPool: number, numberOfAssetsInPool: number,
expectedPrice: string expectedPrice: string
): boolean => { ): boolean => {
return !isPriceDiff(calcAvgGroupPoolPrice(asset, numberOfAssetsInPool), expectedPrice) return !getPriceDiff(calcAvgGroupPoolPrice(asset, numberOfAssetsInPool), expectedPrice).hasVisiblePriceDiff
} }
const isAveragedPrice = ( const isAveragedPrice = (
...@@ -74,11 +77,11 @@ const itemInRouteAndSamePool = ( ...@@ -74,11 +77,11 @@ const itemInRouteAndSamePool = (
export const combineBuyItemsWithTxRoute = ( export const combineBuyItemsWithTxRoute = (
items: UpdatedGenieAsset[], items: UpdatedGenieAsset[],
txRoute?: RoutingItem[] txRoute?: RoutingItem[]
): UpdatedGenieAsset[] => { ): { hasPriceAdjustment: boolean; updatedAssets: UpdatedGenieAsset[] } => {
return items.map((item) => { let hasPriceAdjustment = false
const updatedAssets = items.map((item) => {
const route = getRouteForItem(item, txRoute) const route = getRouteForItem(item, txRoute)
// if the item is not found in txRoute, it means it's no longer for sale
if (txRoute && !route) { if (txRoute && !route) {
return { return {
...item, ...item,
...@@ -86,15 +89,21 @@ export const combineBuyItemsWithTxRoute = ( ...@@ -86,15 +89,21 @@ export const combineBuyItemsWithTxRoute = (
} }
} }
const newPriceInfo = item.updatedPriceInfo ? item.updatedPriceInfo : item.priceInfo let newPriceInfo = item.updatedPriceInfo ? item.updatedPriceInfo : item.priceInfo
// if the price changed
if (route && 'priceInfo' in route.assetOut) { if (route && 'priceInfo' in route.assetOut) {
if (isPriceDiff(newPriceInfo.basePrice, route.assetOut.priceInfo.basePrice)) { const { hasPriceDiff, hasVisiblePriceDiff } = getPriceDiff(
newPriceInfo.basePrice,
route.assetOut.priceInfo.basePrice
)
newPriceInfo = route.assetOut.priceInfo
hasPriceAdjustment = hasPriceDiff
if (hasVisiblePriceDiff) {
if (!isAveragedPrice(item, items, route, txRoute)) { if (!isAveragedPrice(item, items, route, txRoute)) {
return { return {
...item, ...item,
updatedPriceInfo: route.assetOut.priceInfo, updatedPriceInfo: newPriceInfo,
} }
} }
} }
...@@ -107,4 +116,6 @@ export const combineBuyItemsWithTxRoute = ( ...@@ -107,4 +116,6 @@ export const combineBuyItemsWithTxRoute = (
orderSource: route && 'orderSource' in route.assetOut ? route.assetOut.orderSource : undefined, orderSource: route && 'orderSource' in route.assetOut ? route.assetOut.orderSource : undefined,
} }
}) })
return { hasPriceAdjustment, updatedAssets }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment