Skip to content

Commit 353fb75

Browse files
fix(twap): display a warning when fbh was changed after twap order (#5202)
* fix(twap): display a warning when fbh was changed after twap order * chore: add loading state * chore: fix pendingOrders * feat: style TWAP warning banner (#5204) * feat: style TWAP warning banner * feat: fix typo and change orientation of banner * fix: persist pendingTxHash in atom --------- Co-authored-by: fairlight <[email protected]>
1 parent 362c9a5 commit 353fb75

File tree

5 files changed

+176
-8
lines changed

5 files changed

+176
-8
lines changed

apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useAtomValue, useSetAtom } from 'jotai'
2-
import { useCallback, useEffect, useMemo } from 'react'
2+
import { ReactNode, useCallback, useEffect, useMemo } from 'react'
33

44
import { useTokensAllowances, useTokensBalances } from '@cowprotocol/balances-and-allowances'
55
import { useIsSafeViaWc, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet'
@@ -60,12 +60,14 @@ interface OrdersTableWidgetProps {
6060
displayOrdersOnlyForSafeApp: boolean
6161
orders: Order[]
6262
orderType: TabOrderTypes
63+
children?: ReactNode
6364
}
6465

6566
export function OrdersTableWidget({
6667
orders: allOrders,
6768
orderType,
6869
displayOrdersOnlyForSafeApp,
70+
children,
6971
}: OrdersTableWidgetProps) {
7072
const { chainId, account } = useWalletInfo()
7173
const location = useLocation()
@@ -122,14 +124,14 @@ export function OrdersTableWidget({
122124
(orders: ParsedOrder[]) => {
123125
updateOrdersToCancel(orders)
124126
},
125-
[updateOrdersToCancel]
127+
[updateOrdersToCancel],
126128
)
127129

128130
const toggleOrderForCancellation = useCallback(
129131
(order: ParsedOrder) => {
130132
updateOrdersToCancel(toggleOrderInCancellationList(ordersToCancel, order))
131133
},
132-
[ordersToCancel, updateOrdersToCancel]
134+
[ordersToCancel, updateOrdersToCancel],
133135
)
134136

135137
const getShowCancellationModal = useCallback(
@@ -138,7 +140,7 @@ export function OrdersTableWidget({
138140

139141
return rawOrder ? cancelOrder(rawOrder) : null
140142
},
141-
[allOrders, cancelOrder]
143+
[allOrders, cancelOrder],
142144
)
143145

144146
const getAlternativeOrderModalContext = useGetAlternativeOrderModalContextCallback()
@@ -167,6 +169,7 @@ export function OrdersTableWidget({
167169
<>
168170
<PendingPermitUpdater orders={ordersToCheckPendingPermit} />
169171
<ContentWrapper>
172+
{children}
170173
<OrdersTableContainer
171174
chainId={chainId}
172175
tabs={tabs}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { atom, useAtom } from 'jotai'
2+
import { useSetAtom } from 'jotai/index'
3+
import { useEffect } from 'react'
4+
5+
import { usePrevious } from '@cowprotocol/common-hooks'
6+
import { ButtonPrimary, InlineBanner, Loader, BannerOrientation, UI } from '@cowprotocol/ui'
7+
import { useWalletInfo } from '@cowprotocol/wallet'
8+
9+
import styled from 'styled-components/macro'
10+
11+
import { useAllTransactions } from 'legacy/state/enhancedTransactions/hooks'
12+
13+
import { useExtensibleFallbackContext } from '../../hooks/useExtensibleFallbackContext'
14+
import { useSetupFallbackHandler } from '../../hooks/useSetupFallbackHandler'
15+
import { verifyExtensibleFallback } from '../../services/verifyExtensibleFallback'
16+
import { updateFallbackHandlerVerificationAtom } from '../../state/fallbackHandlerVerificationAtom'
17+
18+
const Banner = styled(InlineBanner)`
19+
/* TODO: Make all these part of the InlineBanner props */
20+
position: relative;
21+
font-size: 15px;
22+
23+
> span {
24+
gap: 20px;
25+
}
26+
27+
> span > span {
28+
gap: 20px;
29+
}
30+
31+
&::before {
32+
content: '';
33+
position: absolute;
34+
top: 0;
35+
left: 0;
36+
width: 100%;
37+
height: 100%;
38+
background: var(${UI.COLOR_PAPER});
39+
z-index: -1;
40+
border-radius: inherit;
41+
}
42+
43+
&::after {
44+
content: '';
45+
position: absolute;
46+
top: 0;
47+
left: 0;
48+
width: 100%;
49+
height: 100%;
50+
background: inherit;
51+
z-index: -1;
52+
border-radius: inherit;
53+
}
54+
`
55+
56+
const ActionButton = styled(ButtonPrimary)`
57+
display: inline-block;
58+
width: 100%;
59+
font-size: 16px;
60+
padding: 16px 24px;
61+
min-height: auto;
62+
`
63+
64+
const pendingTxHashAtom = atom<string | null>(null)
65+
66+
export function SetupFallbackHandlerWarning() {
67+
const [pendingTxHash, setPendingTxHash] = useAtom(pendingTxHashAtom)
68+
69+
const { account } = useWalletInfo()
70+
const setupFallbackHandler = useSetupFallbackHandler()
71+
const isTransactionPending = useIsTransactionPending(pendingTxHash)
72+
const prevIsTransactionPending = usePrevious(isTransactionPending)
73+
const txWasMined = prevIsTransactionPending === true && isTransactionPending === false
74+
75+
const updateFallbackHandlerVerification = useSetAtom(updateFallbackHandlerVerificationAtom)
76+
77+
const extensibleFallbackContext = useExtensibleFallbackContext()
78+
79+
const handleUpdateClick = async () => {
80+
const txHash = await setupFallbackHandler()
81+
82+
if (txHash) {
83+
setPendingTxHash(txHash)
84+
}
85+
}
86+
87+
useEffect(() => {
88+
if (!txWasMined) return
89+
90+
setPendingTxHash(null)
91+
92+
if (!extensibleFallbackContext || !account) return
93+
94+
verifyExtensibleFallback(extensibleFallbackContext).then((result) => {
95+
updateFallbackHandlerVerification({ [account]: result })
96+
})
97+
}, [txWasMined, account, extensibleFallbackContext, updateFallbackHandlerVerification, setPendingTxHash])
98+
99+
return (
100+
<Banner
101+
bannerType="danger"
102+
backDropBlur
103+
orientation={BannerOrientation.Vertical}
104+
iconSize={46}
105+
noWrapContent
106+
padding="20px"
107+
>
108+
<span>
109+
<p>
110+
Your Safe fallback handler was changed after TWAP orders were placed. All open TWAP orders are not getting
111+
created because of that. Please, update the fallback handler in order to make the orders work again.
112+
</p>
113+
<ActionButton disabled={isTransactionPending} onClick={handleUpdateClick}>
114+
{isTransactionPending ? <Loader /> : 'Update fallback handler'}
115+
</ActionButton>
116+
</span>
117+
</Banner>
118+
)
119+
}
120+
121+
function useIsTransactionPending(txHash: string | null): boolean {
122+
const allTransactions = useAllTransactions()
123+
124+
if (!txHash) return false
125+
126+
return Object.keys(allTransactions).some((hash) => {
127+
const tx = allTransactions[hash]
128+
129+
if (!tx || tx.receipt || tx.replacementType || tx.errorMessage) return false
130+
131+
return tx.hash === txHash
132+
})
133+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useSafeAppsSdk } from '@cowprotocol/wallet'
2+
3+
import { useExtensibleFallbackContext } from './useExtensibleFallbackContext'
4+
5+
import { useTransactionAdder } from '../../../legacy/state/enhancedTransactions/hooks'
6+
import { extensibleFallbackSetupTxs } from '../services/extensibleFallbackSetupTxs'
7+
8+
export function useSetupFallbackHandler() {
9+
const safeAppsSdk = useSafeAppsSdk()
10+
const extensibleFallbackContext = useExtensibleFallbackContext()
11+
const addTransaction = useTransactionAdder()
12+
13+
return async () => {
14+
if (!safeAppsSdk || !extensibleFallbackContext) return
15+
16+
const fallbackSetupTxs = await extensibleFallbackSetupTxs(extensibleFallbackContext)
17+
const { safeTxHash } = await safeAppsSdk.txs.send({ txs: fallbackSetupTxs })
18+
19+
addTransaction({
20+
hash: safeTxHash,
21+
summary: 'Setup TWAP fallback handler',
22+
})
23+
24+
return safeTxHash
25+
}
26+
}

apps/cowswap-frontend/src/modules/twap/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ export * from './hooks/useCancelTwapOrder'
1010
export * from './hooks/useMapTwapCurrencyInfo'
1111
export * from './hooks/useTwapFormState'
1212
export * from './hooks/useTwapSlippage'
13+
export { useIsFallbackHandlerRequired } from './hooks/useFallbackHandlerVerification'
14+
export { SetupFallbackHandlerWarning } from './containers/SetupFallbackHandlerWarning'
1315
export * from './updaters/index'
1416
export * from './types'

apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useAtomValue } from 'jotai'
22

3+
import { PENDING_STATES } from 'legacy/state/orders/actions'
4+
35
import {
46
advancedOrdersAtom,
57
AdvancedOrdersWidget,
@@ -10,38 +12,40 @@ import { useInjectedWidgetParams } from 'modules/injectedWidget'
1012
import { OrdersTableWidget, TabOrderTypes } from 'modules/ordersTable'
1113
import * as styledEl from 'modules/trade/pure/TradePageLayout'
1214
import {
15+
SetupFallbackHandlerWarning,
1316
TwapConfirmModal,
1417
TwapFormWidget,
1518
TwapUpdaters,
1619
useAllEmulatedOrders,
20+
useIsFallbackHandlerRequired,
1721
useMapTwapCurrencyInfo,
1822
useTwapFormState,
1923
useTwapSlippage,
2024
} from 'modules/twap'
2125
import { TwapFormState } from 'modules/twap/pure/PrimaryActionButton/getTwapFormState'
2226

23-
2427
export default function AdvancedOrdersPage() {
2528
const { isUnlocked } = useAtomValue(advancedOrdersAtom)
2629

2730
const allEmulatedOrders = useAllEmulatedOrders()
31+
const isFallbackHandlerRequired = useIsFallbackHandlerRequired()
2832

2933
const twapFormValidation = useTwapFormState()
3034
const twapSlippage = useTwapSlippage()
3135
const mapTwapCurrencyInfo = useMapTwapCurrencyInfo()
36+
const { hideOrdersTable } = useInjectedWidgetParams()
3237

3338
const disablePriceImpact = twapFormValidation === TwapFormState.SELL_AMOUNT_TOO_SMALL
34-
3539
const advancedWidgetParams = { disablePriceImpact }
36-
37-
const { hideOrdersTable } = useInjectedWidgetParams()
40+
const pendingOrders = allEmulatedOrders.filter((order) => PENDING_STATES.includes(order.status))
3841

3942
return (
4043
<>
4144
<FillAdvancedOrdersDerivedStateUpdater slippage={twapSlippage} />
4245
<SetupAdvancedOrderAmountsFromUrlUpdater />
4346
<styledEl.PageWrapper isUnlocked={isUnlocked}>
4447
<styledEl.PrimaryWrapper>
48+
{isFallbackHandlerRequired && pendingOrders.length > 0 && <SetupFallbackHandlerWarning />}
4549
<AdvancedOrdersWidget
4650
updaters={<TwapUpdaters />}
4751
confirmContent={<TwapConfirmModal />}

0 commit comments

Comments
 (0)