Skip to content

Commit

Permalink
Merge pull request #5090 from cowprotocol/release/2024-11-14
Browse files Browse the repository at this point in the history
Release/2024 11 14
  • Loading branch information
anxolin authored Nov 14, 2024
2 parents c64f6e3 + a0bc92c commit 7fa342b
Show file tree
Hide file tree
Showing 49 changed files with 761 additions and 199 deletions.
3 changes: 2 additions & 1 deletion apps/cowswap-frontend/src/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const Routes = {
HOME: '/',
SWAP: `/:chainId?${TRADE_WIDGET_PREFIX}/swap/:inputCurrencyId?/:outputCurrencyId?`,
HOOKS: `/:chainId?${TRADE_WIDGET_PREFIX}/swap/hooks/:inputCurrencyId?/:outputCurrencyId?`,
COW_SHED: `/:chainId?${TRADE_WIDGET_PREFIX}/cowShed`,
LIMIT_ORDER: `/:chainId?${TRADE_WIDGET_PREFIX}/limit/:inputCurrencyId?/:outputCurrencyId?`,
YIELD: `/:chainId?${TRADE_WIDGET_PREFIX}/yield/:inputCurrencyId?/:outputCurrencyId?`,
ADVANCED_ORDERS: `/:chainId?${TRADE_WIDGET_PREFIX}/advanced/:inputCurrencyId?/:outputCurrencyId?`,
Expand Down Expand Up @@ -54,7 +55,7 @@ export const HOOKS_STORE_MENU_ITEM = {
route: Routes.HOOKS,
label: 'Hooks',
description: 'Powerful tool to generate pre/post interaction for CoW Protocol',
badge: 'New',
badge: '🧪',
}

export const YIELD_MENU_ITEM = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { useState } from 'react'

import { HookToDappMatch } from '@cowprotocol/hook-dapp-lib'
import { CowHookDetails, HookToDappMatch } from '@cowprotocol/hook-dapp-lib'

import { ChevronDown, ChevronUp } from 'react-feather'

import { clickOnHooks } from 'modules/analytics'
import { useSimulationData } from 'modules/tenderly/hooks/useSimulationData'

import * as styledEl from './styled'

export function HookItem({ item, index }: { item: HookToDappMatch; index: number }) {
export function HookItem({ details, item, index }: { details?: CowHookDetails; item: HookToDappMatch; index: number }) {
const [isOpen, setIsOpen] = useState(false)

const simulationData = useSimulationData(details?.uuid)

const handleLinkClick = () => {
clickOnHooks(item.dapp?.name || 'Unknown hook dapp')
}
Expand All @@ -37,6 +40,16 @@ export function HookItem({ item, index }: { item: HookToDappMatch; index: number
<styledEl.HookItemContent>
{item.dapp && (
<>
{simulationData && (
<p>
<b>Simulation:</b>
<styledEl.SimulationLink status={simulationData.status}>
<a href={simulationData.link} target="_blank" rel="noopener noreferrer">
{simulationData.status ? 'Simulation successful' : 'Simulation failed'}
</a>
</styledEl.SimulationLink>
</p>
)}
<p>
<b>Description:</b> {item.dapp.descriptionShort}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,12 @@ export const ToggleIcon = styled.div<{ isOpen: boolean }>`
}
}
`

export const SimulationLink = styled.span<{ status: boolean }>`
color: var(${({ status }) => (status ? UI.COLOR_SUCCESS : UI.COLOR_DANGER)});
border-radius: 8px;
&:hover {
color: var(${({ status }) => (status ? UI.COLOR_SUCCESS_TEXT : UI.COLOR_DANGER_TEXT)});
}
`
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { ReactElement, useMemo, useState } from 'react'
import { ReactElement, useEffect, useMemo, useState } from 'react'

import { latest } from '@cowprotocol/app-data'
import { HookToDappMatch, matchHooksToDappsRegistry } from '@cowprotocol/hook-dapp-lib'
import { CowHookDetails, HookToDappMatch, matchHooksToDappsRegistry } from '@cowprotocol/hook-dapp-lib'
import { InfoTooltip } from '@cowprotocol/ui'

import { ChevronDown, ChevronUp } from 'react-feather'

import { AppDataInfo, decodeAppData } from 'modules/appData'
import { useCustomHookDapps } from 'modules/hooksStore'
import { useCustomHookDapps, useHooksStateWithSimulatedGas } from 'modules/hooksStore'
import { useTenderlyBundleSimulation } from 'modules/tenderly/hooks/useTenderlyBundleSimulation'

import { HookItem } from './HookItem'
import * as styledEl from './styled'
Expand All @@ -27,10 +28,20 @@ export function OrderHooksDetails({ appData, children, margin }: OrderHooksDetai
const preCustomHookDapps = useCustomHookDapps(true)
const postCustomHookDapps = useCustomHookDapps(false)

const hooks = useHooksStateWithSimulatedGas()

const { mutate, isValidating, data } = useTenderlyBundleSimulation()

useEffect(() => {
mutate()
}, []) // eslint-disable-line react-hooks/exhaustive-deps

if (!appDataDoc) return null

const metadata = appDataDoc.metadata as latest.Metadata

const hasSomeFailedSimulation = Object.values(data || {}).some((hook) => !hook.status)

const preHooksToDapp = matchHooksToDappsRegistry(metadata.hooks?.pre || [], preCustomHookDapps)
const postHooksToDapp = matchHooksToDappsRegistry(metadata.hooks?.post || [], postCustomHookDapps)

Expand All @@ -42,6 +53,8 @@ export function OrderHooksDetails({ appData, children, margin }: OrderHooksDetai
<styledEl.Label>
Hooks
<InfoTooltip content="Hooks are interactions before/after order execution." />
{hasSomeFailedSimulation && <styledEl.ErrorLabel>Simulation failed</styledEl.ErrorLabel>}
{isValidating && <styledEl.Spinner />}
</styledEl.Label>
<styledEl.Content onClick={() => setOpen(!isOpen)}>
{preHooksToDapp.length > 0 && (
Expand All @@ -63,8 +76,8 @@ export function OrderHooksDetails({ appData, children, margin }: OrderHooksDetai
</styledEl.Summary>
{isOpen && (
<styledEl.Details>
<HooksInfo data={preHooksToDapp} title="Pre Hooks" />
<HooksInfo data={postHooksToDapp} title="Post Hooks" />
<HooksInfo data={preHooksToDapp} hooks={hooks.preHooks} title="Pre Hooks" />
<HooksInfo data={postHooksToDapp} hooks={hooks.postHooks} title="Post Hooks" />
</styledEl.Details>
)}
</styledEl.Wrapper>,
Expand All @@ -74,9 +87,10 @@ export function OrderHooksDetails({ appData, children, margin }: OrderHooksDetai
interface HooksInfoProps {
data: HookToDappMatch[]
title: string
hooks: CowHookDetails[]
}

function HooksInfo({ data, title }: HooksInfoProps) {
function HooksInfo({ data, title, hooks }: HooksInfoProps) {
return (
<>
{data.length ? (
Expand All @@ -85,9 +99,11 @@ function HooksInfo({ data, title }: HooksInfoProps) {
{title} <CircleCount>{data.length}</CircleCount>
</h3>
<styledEl.HooksList>
{data.map((item, index) => (
<HookItem key={item.hook.callData + item.hook.target + item.hook.gasLimit} item={item} index={index} />
))}
{data.map((item, index) => {
const key = item.hook.callData + item.hook.target + item.hook.gasLimit
const details = hooks.find(({ hook }) => key === hook.callData + hook.target + hook.gasLimit)
return <HookItem key={key} item={item} index={index} details={details} />
})}
</styledEl.HooksList>
</styledEl.InfoWrapper>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ export const Label = styled.span`
gap: 4px;
`

export const ErrorLabel = styled.span`
display: flex;
align-items: center;
gap: 4px;
color: var(${UI.COLOR_DANGER_TEXT});
background-color: var(${UI.COLOR_DANGER_BG});
border-radius: 8px;
margin-left: 4px;
padding: 2px 6px;
`

export const Content = styled.div`
display: flex;
width: max-content;
Expand Down Expand Up @@ -164,3 +175,21 @@ export const CircleCount = styled.span`
font-weight: var(${UI.FONT_WEIGHT_BOLD});
margin: 0;
`

export const Spinner = styled.div`
border: 5px solid transparent;
border-top-color: ${`var(${UI.COLOR_PRIMARY_LIGHTER})`};
border-radius: 50%;
width: 12px;
height: 12px;
animation: spin 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`
8 changes: 5 additions & 3 deletions apps/cowswap-frontend/src/common/hooks/useMenuItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import { useMemo } from 'react'
import { useFeatureFlags } from '@cowprotocol/common-hooks'
import { isLocal } from '@cowprotocol/common-utils'

import { useHooksEnabled } from 'legacy/state/user/hooks'

import { HOOKS_STORE_MENU_ITEM, MENU_ITEMS, YIELD_MENU_ITEM } from '../constants/routes'

export function useMenuItems() {
const { isHooksStoreEnabled } = useFeatureFlags()
const isHooksEnabled = useHooksEnabled()
const { isYieldEnabled } = useFeatureFlags()

return useMemo(() => {
const items = [...MENU_ITEMS]

if (isHooksStoreEnabled || isLocal) {
if (isHooksEnabled) {
items.push(HOOKS_STORE_MENU_ITEM)
}

Expand All @@ -21,5 +23,5 @@ export function useMenuItems() {
}

return items
}, [isHooksStoreEnabled, isYieldEnabled])
}, [isHooksEnabled, isYieldEnabled])
}
10 changes: 9 additions & 1 deletion apps/cowswap-frontend/src/common/hooks/useNavigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export function useNavigate(): NavigateFunction {
...options,
})
},
[navigate, isWidget]
[navigate, isWidget],
)
}

export function useNavigateBack(): () => void {
const navigate = useNavigateOriginal()

return useCallback(() => {
navigate(-1)
}, [navigate])
}
23 changes: 22 additions & 1 deletion apps/cowswap-frontend/src/legacy/state/user/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import { Currency } from '@uniswap/sdk-core'

import { shallowEqual } from 'react-redux'

import { updateRecipientToggleVisible, updateUserDarkMode, updateUserDeadline, updateUserLocale } from './reducer'
import {
updateHooksEnabled,
updateRecipientToggleVisible,
updateUserDarkMode,
updateUserDeadline,
updateUserLocale,
} from './reducer'
import { SerializedToken } from './types'

import { useAppDispatch, useAppSelector } from '../hooks'
Expand Down Expand Up @@ -83,6 +89,21 @@ export function useRecipientToggleManager(): [boolean, (value: boolean) => void]
return [isVisible, toggleVisibility]
}

export function useHooksEnabled(): boolean {
return useAppSelector((state) => state.user.hooksEnabled)
}

export function useHooksEnabledManager(): [boolean, Command] {
const dispatch = useAppDispatch()
const hooksEnabled = useHooksEnabled()

const toggleHooksEnabled = useCallback(() => {
dispatch(updateHooksEnabled({ hooksEnabled: !hooksEnabled }))
}, [hooksEnabled, dispatch])

return [hooksEnabled, toggleHooksEnabled]
}

export function useUserTransactionTTL(): [number, (slippage: number) => void] {
const dispatch = useAppDispatch()
const deadline = useAppSelector((state) => state.user.userDeadline)
Expand Down
6 changes: 6 additions & 0 deletions apps/cowswap-frontend/src/legacy/state/user/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface UserState {

// TODO: mod, shouldn't be here
recipientToggleVisible: boolean
hooksEnabled: boolean

// deadline set by user in minutes, used in all txns
userDeadline: number
Expand All @@ -28,6 +29,7 @@ export const initialState: UserState = {
userDarkMode: null,
// TODO: mod, shouldn't be here
recipientToggleVisible: false,
hooksEnabled: false,
userLocale: null,
userDeadline: DEFAULT_DEADLINE_FROM_NOW,
}
Expand All @@ -42,6 +44,9 @@ const userSlice = createSlice({
updateUserDarkMode(state, action) {
state.userDarkMode = action.payload.userDarkMode
},
updateHooksEnabled(state, action) {
state.hooksEnabled = action.payload.hooksEnabled
},
updateMatchesDarkMode(state, action) {
state.matchesDarkMode = action.payload.matchesDarkMode
},
Expand All @@ -61,6 +66,7 @@ export const {
updateSelectedWallet,
updateMatchesDarkMode,
updateUserDarkMode,
updateHooksEnabled,
updateUserDeadline,
updateUserLocale,
updateRecipientToggleVisible,
Expand Down
8 changes: 8 additions & 0 deletions apps/cowswap-frontend/src/modules/analytics/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ export function toggleRecipientAddressAnalytics(enable: boolean) {
})
}

export function toggleHooksEnabledAnalytics(enable: boolean) {
cowAnalytics.sendEvent({
category: Category.HOOKS,
action: 'Toggle Hooks Enabled',
label: enable ? 'Enabled' : 'Disabled',
})
}

export function searchByAddressAnalytics(isAddressSearch: string) {
cowAnalytics.sendEvent({
category: Category.CURRENCY_SELECT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useIsSmartContractWallet } from '@cowprotocol/wallet'

import { Nullish } from 'types'

import { useHooks } from 'modules/hooksStore'
import { useHooksStateWithSimulatedGas } from 'modules/hooksStore'
import { useAccountAgnosticPermitHookData } from 'modules/permit'
import { useDerivedTradeState, useHasTradeEnoughAllowance, useIsHooksTradeType, useIsSellNative } from 'modules/trade'

Expand All @@ -33,7 +33,7 @@ function useAgnosticPermitDataIfUserHasNoAllowance(): Nullish<PermitHookData> {
export function AppDataHooksUpdater(): null {
const tradeState = useDerivedTradeState()
const isHooksTradeType = useIsHooksTradeType()
const hooksStoreState = useHooks()
const hooksStoreState = useHooksStateWithSimulatedGas()
const preHooks = isHooksTradeType ? hooksStoreState.preHooks : null
const postHooks = isHooksTradeType ? hooksStoreState.postHooks : null
const updateAppDataHooks = useUpdateAppDataHooks()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Account, { AccountOverview } from 'pages/Account'
import AdvancedOrdersPage from 'pages/AdvancedOrders'
import AnySwapAffectedUsers from 'pages/error/AnySwapAffectedUsers'
import { HooksPage } from 'pages/Hooks'
import { CowShed } from 'pages/Hooks/cowShed'
import LimitOrderPage from 'pages/LimitOrders'
import { SwapPage } from 'pages/Swap'
import YieldPage from 'pages/Yield'
Expand Down Expand Up @@ -87,6 +88,7 @@ export function RoutesApp() {
{/*Swap*/}
<Route path={RoutesEnum.SWAP} element={<SwapPage />} />
<Route path={RoutesEnum.HOOKS} element={<HooksPage />} />
<Route path={RoutesEnum.COW_SHED} element={<CowShed />} />
<Route path={RoutesEnum.SEND} element={<RedirectPathToSwapOnly />} />

{lazyRoutes.map((item, key) => LazyRoute({ ...item, key }))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GasPriceStrategyUpdater } from 'legacy/state/gas/gas-price-strategy-upd

import { addListAnalytics, removeListAnalytics } from 'modules/analytics'
import { UploadToIpfsUpdater } from 'modules/appData/updater/UploadToIpfsUpdater'
import { BalancesCombinedUpdater } from 'modules/combinedBalances/updater/BalancesCombinedUpdater'
import { CowEventsUpdater, InjectedWidgetUpdater, useInjectedWidgetParams } from 'modules/injectedWidget'
import { FinalizeTxUpdater } from 'modules/onchainTransactions'
import { OrdersNotificationsUpdater } from 'modules/orders'
Expand Down Expand Up @@ -90,6 +91,7 @@ export function Updaters() {
<PoolsInfoUpdater />
<LpTokensWithBalancesUpdater />
<VampireAttackUpdater />
<BalancesCombinedUpdater />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useMemo } from 'react'

import { TokenWithLogo } from '@cowprotocol/common-const'
import { CurrencyAmount } from '@uniswap/sdk-core'

import { useTokensBalancesCombined } from './useTokensBalancesCombined'

export function useCurrencyAmountBalanceCombined(
token: TokenWithLogo | undefined | null,
): CurrencyAmount<TokenWithLogo> | undefined {
const { values: balances } = useTokensBalancesCombined()

return useMemo(() => {
if (!token) return undefined

const balance = balances[token.address.toLowerCase()]

if (!balance) return undefined

return CurrencyAmount.fromRawAmount(token, balance.toHexString())
}, [token, balances])
}
Loading

0 comments on commit 7fa342b

Please sign in to comment.