From 2ddf734e2a0b3db47cec659489b22011ad4b22f9 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Fri, 3 May 2024 13:10:55 +0200 Subject: [PATCH 1/5] Fix: cow swap disclaimer communication (#3648) * fix: set iframe ref after consenting * fix: query iframe after loading the Safe --- src/features/swap/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/swap/index.tsx b/src/features/swap/index.tsx index f80ca60ab2..4189747fae 100644 --- a/src/features/swap/index.tsx +++ b/src/features/swap/index.tsx @@ -57,7 +57,7 @@ const SwapWidget = ({ sell }: Params) => { const swapParams = useAppSelector(selectSwapParams) const { tradeType } = swapParams - const { safeAddress } = useSafeInfo() + const { safeAddress, safeLoading } = useSafeInfo() const wallet = useWallet() const { isConsentAccepted, onAccept } = useSwapConsent() @@ -194,7 +194,7 @@ const SwapWidget = ({ sell }: Params) => { if (iframeElement) { iframeRef.current = iframeElement as HTMLIFrameElement } - }, [params]) + }, [params, isConsentAccepted, safeLoading]) useCustomAppCommunicator(iframeRef, appData, chain) From 450496bc2f63fc9df12962e13e445b780566a0fa Mon Sep 17 00:00:00 2001 From: James Mealy Date: Fri, 3 May 2024 14:49:16 +0200 Subject: [PATCH 2/5] fix: order surplus calculation (#3653) --- .../swap/helpers/__tests__/utils.test.ts | 33 ++++++++++++++++ src/features/swap/helpers/utils.ts | 38 +++++++++++++------ 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/features/swap/helpers/__tests__/utils.test.ts b/src/features/swap/helpers/__tests__/utils.test.ts index 0b1d5b106c..fd1b610eea 100644 --- a/src/features/swap/helpers/__tests__/utils.test.ts +++ b/src/features/swap/helpers/__tests__/utils.test.ts @@ -141,5 +141,38 @@ describe('Swap helpers', () => { expect(result).toEqual('0') }) + + // eslint-disable-next-line no-only-tests/no-only-tests + it('returns the surplus amount for buy orders', () => { + const mockOrder = { + executedSellAmount: '10000000000000000000', //10 + executedBuyAmount: '50', + buyToken: { decimals: 8 }, + sellToken: { decimals: 18 }, + sellAmount: '15000000000000000000', //15 + buyAmount: '5000000000', + kind: 'buy', + } as unknown as SwapOrder + + const result = getSurplusPrice(mockOrder) + + expect(result).toEqual(5) + }) + + it('returns the surplus amount for sell orders', () => { + const mockOrder = { + executedSellAmount: '100000000000000000000', + executedBuyAmount: '10000000000', //100 + buyToken: { decimals: 8 }, + sellToken: { decimals: 18 }, + sellAmount: '100000000000000000000', + buyAmount: '5000000000', //50 + kind: 'sell', + } as unknown as SwapOrder + + const result = getSurplusPrice(mockOrder) + + expect(result).toEqual(50) + }) }) }) diff --git a/src/features/swap/helpers/utils.ts b/src/features/swap/helpers/utils.ts index 3c32b2af40..eef75ce9ee 100644 --- a/src/features/swap/helpers/utils.ts +++ b/src/features/swap/helpers/utils.ts @@ -7,6 +7,15 @@ type Quantity = { decimals: number } +enum OrderKind { + SELL = 'sell', + BUY = 'buy', +} + +function calculateDifference(amountA: string, amountB: string, decimals: number): number { + return asDecimal(BigInt(amountA), decimals) - asDecimal(BigInt(amountB), decimals) +} + function asDecimal(amount: number | bigint, decimals: number): number { return Number(formatUnits(amount, decimals)) } @@ -47,13 +56,20 @@ const calculateRatio = (a: Quantity, b: Quantity) => { return asDecimal(BigInt(a.amount), a.decimals) / asDecimal(BigInt(b.amount), b.decimals) } -export const getSurplusPrice = (order: Pick): number => { - const { executedBuyAmount, buyAmount, buyToken } = order - - const surplus = - asDecimal(BigInt(executedBuyAmount), buyToken.decimals) - asDecimal(BigInt(buyAmount), buyToken.decimals) - - return surplus +export const getSurplusPrice = ( + order: Pick< + SwapOrder, + 'executedBuyAmount' | 'buyAmount' | 'buyToken' | 'executedSellAmount' | 'sellAmount' | 'sellToken' | 'kind' + >, +): number => { + const { kind, executedSellAmount, sellAmount, sellToken, executedBuyAmount, buyAmount, buyToken } = order + if (kind === OrderKind.BUY) { + return calculateDifference(sellAmount, executedSellAmount, sellToken.decimals) + } else if (kind === OrderKind.SELL) { + return calculateDifference(executedBuyAmount, buyAmount, buyToken.decimals) + } else { + return 0 + } } export const getFilledPercentage = ( @@ -62,10 +78,10 @@ export const getFilledPercentage = ( let executed: number let total: number - if (order.kind === 'buy') { + if (order.kind === OrderKind.BUY) { executed = Number(order.executedBuyAmount) total = Number(order.buyAmount) - } else if (order.kind === 'sell') { + } else if (order.kind === OrderKind.SELL) { executed = Number(order.executedSellAmount) total = Number(order.sellAmount) } else { @@ -78,9 +94,9 @@ export const getFilledPercentage = ( export const getFilledAmount = ( order: Pick, ): string => { - if (order.kind === 'buy') { + if (order.kind === OrderKind.BUY) { return formatUnits(order.executedBuyAmount, order.buyToken.decimals) - } else if (order.kind === 'sell') { + } else if (order.kind === OrderKind.SELL) { return formatUnits(order.executedSellAmount, order.sellToken.decimals) } else { return '0' From 7c506e9e442a119640ad9ac4c5e6a142b0629233 Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Fri, 3 May 2024 15:07:07 +0200 Subject: [PATCH 3/5] fix: Hide slippage for swap limit orders (#3643) * fix: Hide slippage for swap limit orders * fix: Use helper function to get order class --- .../SwapOrderConfirmationView/index.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/features/swap/components/SwapOrderConfirmationView/index.tsx b/src/features/swap/components/SwapOrderConfirmationView/index.tsx index d5da0767e6..ba42fd120c 100644 --- a/src/features/swap/components/SwapOrderConfirmationView/index.tsx +++ b/src/features/swap/components/SwapOrderConfirmationView/index.tsx @@ -7,7 +7,13 @@ import { compareAsc } from 'date-fns' import { Alert, Typography } from '@mui/material' import { formatAmount } from '@/utils/formatNumber' import { formatVisualAmount } from '@/utils/formatters' -import { getExecutionPrice, getLimitPrice, getSlippageInPercent, getSurplusPrice } from '@/features/swap/helpers/utils' +import { + getExecutionPrice, + getLimitPrice, + getOrderClass, + getSlippageInPercent, + getSurplusPrice, +} from '@/features/swap/helpers/utils' import type { CowSwapConfirmationView } from '@safe-global/safe-gateway-typescript-sdk' import SwapTokens from '@/features/swap/components/SwapTokens' import AlertIcon from '@/public/images/common/alert.svg' @@ -25,9 +31,11 @@ export const SwapOrderConfirmationView = ({ order, settlementContract }: SwapOrd const { uid, owner, kind, validUntil, status, sellToken, buyToken, sellAmount, buyAmount, explorerUrl, receiver } = order + const executionPrice = getExecutionPrice(order) const limitPrice = getLimitPrice(order) const surplusPrice = getSurplusPrice(order) + const orderClass = getOrderClass(order) const expires = new Date(validUntil * 1000) const now = new Date() @@ -90,9 +98,13 @@ export const SwapOrderConfirmationView = ({ order, settlementContract }: SwapOrd ) : ( <> ), - - {slippage}% - , + orderClass !== 'limit' ? ( + + {slippage}% + + ) : ( + <> + ), , From a1b6bb267f0fb75c7ed90b81a2f3e2ba9d4a5bf2 Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Fri, 3 May 2024 17:18:54 +0200 Subject: [PATCH 4/5] fix: Disable execute button for expired swaps (#3650) * fix: Disable execute button for expired swaps * fix: Only show expired state if the order info status is expired --- src/components/transactions/TxDetails/index.tsx | 4 ++-- src/features/swap/hooks/useIsExpiredSwap.ts | 4 ++-- src/hooks/__tests__/useInterval.test.ts | 15 ++++++++++++--- src/hooks/useInterval.ts | 3 +++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/components/transactions/TxDetails/index.tsx b/src/components/transactions/TxDetails/index.tsx index e66b6ecfd6..f8cd5d510e 100644 --- a/src/components/transactions/TxDetails/index.tsx +++ b/src/components/transactions/TxDetails/index.tsx @@ -1,4 +1,5 @@ import { POLLING_INTERVAL } from '@/config/constants' +import useIsExpiredSwap from '@/features/swap/hooks/useIsExpiredSwap' import useIntervalCounter from '@/hooks/useIntervalCounter' import React, { type ReactElement } from 'react' import type { TransactionDetails, TransactionSummary } from '@safe-global/safe-gateway-typescript-sdk' @@ -12,7 +13,6 @@ import useChainId from '@/hooks/useChainId' import useAsync from '@/hooks/useAsync' import { isAwaitingExecution, - isExpiredSwap, isModuleExecutionInfo, isMultiSendTxInfo, isMultisigDetailedExecutionInfo, @@ -67,7 +67,7 @@ const TxDetailsBlock = ({ txSummary, txDetails }: TxDetailsProps): ReactElement safeTxHash = txDetails.detailedExecutionInfo.safeTxHash } - const expiredSwap = isExpiredSwap(txSummary.txInfo) + const expiredSwap = useIsExpiredSwap(txSummary.txInfo) return ( <> diff --git a/src/features/swap/hooks/useIsExpiredSwap.ts b/src/features/swap/hooks/useIsExpiredSwap.ts index f686525d5f..ba2f675e01 100644 --- a/src/features/swap/hooks/useIsExpiredSwap.ts +++ b/src/features/swap/hooks/useIsExpiredSwap.ts @@ -1,7 +1,7 @@ import { useState } from 'react' import type { TransactionInfo } from '@safe-global/safe-gateway-typescript-sdk' import useInterval from '@/hooks/useInterval' -import { isSwapTxInfo } from '@/utils/transaction-guards' +import { isExpiredSwap as isSwapInfoExpired, isSwapTxInfo } from '@/utils/transaction-guards' const INTERVAL_IN_MS = 10_000 @@ -16,7 +16,7 @@ const useIsExpiredSwap = (txInfo: TransactionInfo) => { const isExpiredSwap = () => { if (!isSwapTxInfo(txInfo)) return - setIsExpired(Date.now() > txInfo.validUntil * 1000) + setIsExpired(Date.now() > txInfo.validUntil * 1000 && isSwapInfoExpired(txInfo)) } useInterval(isExpiredSwap, INTERVAL_IN_MS) diff --git a/src/hooks/__tests__/useInterval.test.ts b/src/hooks/__tests__/useInterval.test.ts index f649fc13a8..a4175c9d17 100644 --- a/src/hooks/__tests__/useInterval.test.ts +++ b/src/hooks/__tests__/useInterval.test.ts @@ -6,22 +6,31 @@ describe('useInterval', () => { jest.useFakeTimers() }) + it('should run the callback function once immediately', () => { + let mockValue = 0 + const mockCallback = jest.fn(() => mockValue++) + + renderHook(() => useInterval(mockCallback, 100)) + + expect(mockValue).toEqual(1) + }) + it('should run the callback function with every interval', async () => { let mockValue = 0 const mockCallback = jest.fn(() => mockValue++) renderHook(() => useInterval(mockCallback, 100)) - expect(mockValue).toEqual(0) + expect(mockValue).toEqual(1) act(() => { jest.advanceTimersByTime(100) }) - expect(mockValue).toEqual(1) + expect(mockValue).toEqual(2) act(() => { jest.advanceTimersByTime(100) }) - expect(mockValue).toEqual(2) + expect(mockValue).toEqual(3) }) }) diff --git a/src/hooks/useInterval.ts b/src/hooks/useInterval.ts index 21c2141e1f..4a684b3f86 100644 --- a/src/hooks/useInterval.ts +++ b/src/hooks/useInterval.ts @@ -15,6 +15,9 @@ const useInterval = (callback: () => void, time: number) => { useEffect(() => { const interval = setInterval(() => callbackRef.current?.(), time) + // Call the function once initially + callbackRef.current?.() + return () => clearInterval(interval) }, [time]) } From 179be31db2553e6e8a19860d98d61aa892702d3d Mon Sep 17 00:00:00 2001 From: James Mealy Date: Fri, 3 May 2024 20:29:33 +0200 Subject: [PATCH 5/5] use new swap icon in first transaction flow (#3655) --- public/images/common/swap.svg | 16 +++++++++------- public/images/sidebar/swap.svg | 10 ---------- src/components/balances/AssetsTable/index.tsx | 2 +- src/components/dashboard/Overview/Overview.tsx | 2 +- .../sidebar/SidebarNavigation/config.tsx | 2 +- 5 files changed, 12 insertions(+), 20 deletions(-) delete mode 100644 public/images/sidebar/swap.svg diff --git a/public/images/common/swap.svg b/public/images/common/swap.svg index 8efec22bdd..2bd9d2ac4d 100644 --- a/public/images/common/swap.svg +++ b/public/images/common/swap.svg @@ -1,8 +1,10 @@ - - - - - - - + + + + + + + + + diff --git a/public/images/sidebar/swap.svg b/public/images/sidebar/swap.svg deleted file mode 100644 index 2bd9d2ac4d..0000000000 --- a/public/images/sidebar/swap.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/components/balances/AssetsTable/index.tsx b/src/components/balances/AssetsTable/index.tsx index 34c58d0e1c..3c92947340 100644 --- a/src/components/balances/AssetsTable/index.tsx +++ b/src/components/balances/AssetsTable/index.tsx @@ -29,7 +29,7 @@ import AddFundsCTA from '@/components/common/AddFunds' import { SWAP_EVENTS } from '@/services/analytics/events/swaps' import { useRouter } from 'next/router' import { AppRoutes } from '@/config/routes' -import SwapIcon from '@/public/images/sidebar/swap.svg' +import SwapIcon from '@/public/images/common/swap.svg' const skeletonCells: EnhancedTableProps['rows'][0]['cells'] = { asset: { diff --git a/src/components/dashboard/Overview/Overview.tsx b/src/components/dashboard/Overview/Overview.tsx index 9a4c414580..1a2e46dc0a 100644 --- a/src/components/dashboard/Overview/Overview.tsx +++ b/src/components/dashboard/Overview/Overview.tsx @@ -10,7 +10,7 @@ import useSafeInfo from '@/hooks/useSafeInfo' import { useVisibleBalances } from '@/hooks/useVisibleBalances' import ArrowIconSE from '@/public/images/common/arrow-se.svg' import ArrowIconNW from '@/public/images/common/arrow-top-right.svg' -import SwapIcon from '@/public/images/sidebar/swap.svg' +import SwapIcon from '@/public/images/common/swap.svg' import { OVERVIEW_EVENTS, trackEvent } from '@/services/analytics' import { useAppSelector } from '@/store' import { selectCurrency } from '@/store/settingsSlice' diff --git a/src/components/sidebar/SidebarNavigation/config.tsx b/src/components/sidebar/SidebarNavigation/config.tsx index 775e86ad37..27cc371aeb 100644 --- a/src/components/sidebar/SidebarNavigation/config.tsx +++ b/src/components/sidebar/SidebarNavigation/config.tsx @@ -7,7 +7,7 @@ import TransactionIcon from '@/public/images/sidebar/transactions.svg' import ABIcon from '@/public/images/sidebar/address-book.svg' import AppsIcon from '@/public/images/apps/apps-icon.svg' import SettingsIcon from '@/public/images/sidebar/settings.svg' -import SwapIcon from '@/public/images/sidebar/swap.svg' +import SwapIcon from '@/public/images/common/swap.svg' import { SvgIcon } from '@mui/material' export type NavItem = {