Skip to content

Commit

Permalink
Merge pull request #4787 from cowprotocol/release/2024-08-14
Browse files Browse the repository at this point in the history
Release/2024 08 14
  • Loading branch information
anxolin authored Aug 14, 2024
2 parents 760ec23 + 63c0f55 commit 50b800e
Show file tree
Hide file tree
Showing 29 changed files with 196 additions and 405 deletions.
11 changes: 10 additions & 1 deletion apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,16 @@ export function CoWAmmBanner() {
return ClosableBanner('cow_amm_banner', (close) => (
<BannerWrapper>
<i></i>
<CloseButton size={24} onClick={close} />
<CloseButton
size={24}
onClick={() => {
cowAnalytics.sendEvent({
category: 'CoW Swap',
action: 'CoW AMM Banner CTA Closed',
})
close()
}}
/>
<div>
<Title>Now live: the first MEV-capturing AMM</Title>
<Description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import { useWalletInfo } from '@cowprotocol/wallet'

import ReactDOM from 'react-dom'



import { upToLarge, useMediaQuery } from 'legacy/hooks/useMediaQuery'

import { useToggleAccountModal } from 'modules/account'
import { clickNotifications } from 'modules/analytics'
import { NotificationBell, NotificationSidebar } from 'modules/notifications'
import { useUnreadNotifications } from 'modules/notifications/hooks/useUnreadNotifications'
import { Web3Status } from 'modules/wallet/containers/Web3Status'

import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported'
Expand All @@ -33,6 +37,9 @@ export function AccountElement({ className, standaloneMode, pendingActivities }:
const isUpToLarge = useMediaQuery(upToLarge)
const { isNotificationsFeedEnabled } = useFeatureFlags()

const unreadNotifications = useUnreadNotifications()
const unreadNotificationsCount = Object.keys(unreadNotifications).length

const [isSidebarOpen, setSidebarOpen] = useState(false)

return (
Expand All @@ -44,7 +51,17 @@ export function AccountElement({ className, standaloneMode, pendingActivities }:
</BalanceText>
)}
<Web3Status pendingActivities={pendingActivities} onClick={() => account && toggleAccountModal()} />
{account && isNotificationsFeedEnabled && <NotificationBell onClick={() => setSidebarOpen(true)} />}
{account && isNotificationsFeedEnabled && (
<NotificationBell
unreadCount={unreadNotificationsCount}
onClick={() => {
clickNotifications(
unreadNotificationsCount === 0 ? 'click-bell' : 'click-bell-with-pending-notifications'
)
setSidebarOpen(true)
}}
/>
)}
</Wrapper>

{ReactDOM.createPortal(
Expand Down
57 changes: 4 additions & 53 deletions apps/cowswap-frontend/src/legacy/utils/priceLegacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import {
} from 'api/1inch'
import { getQuote } from 'api/cowProtocol'
import QuoteApiError, { QuoteApiErrorCodes } from 'api/cowProtocol/errors/QuoteError'
import {
getPriceQuote as getPriceQuoteMatcha,
MatchaPriceQuote,
toPriceInformation as toPriceInformationMatcha,
} from 'api/matcha-0x'

import {
LegacyPriceInformationWithSource,
Expand Down Expand Up @@ -74,29 +69,19 @@ function _filterWinningPrice(params: FilterWinningPriceParams) {

export type QuoteResult = [PromiseSettledResult<PriceInformation>, PromiseSettledResult<OrderQuoteResponse>]
export type AllPricesResult = {
gpPriceResult: PromiseSettledResult<PriceInformation | null>
paraSwapPriceResult: PromiseSettledResult<null>
matcha0xPriceResult: PromiseSettledResult<MatchaPriceQuote | null>
oneInchPriceResult: PromiseSettledResult<PriceQuote1inch | null>
}

/**
* Return all price estimations from all price sources
*/
async function getAllPrices(params: LegacyPriceQuoteParams): Promise<AllPricesResult> {
const matchaPricePromise = withTimeout(getPriceQuoteMatcha(params), PRICE_API_TIMEOUT_MS, 'Matcha(0x): Get Price API')

const oneInchPricePromise = withTimeout(getPriceQuote1inch(params), PRICE_API_TIMEOUT_MS, '1inch: Get Price API')

// Get results from API queries
const [matchaPrice, oneInchPrice] = await Promise.allSettled([matchaPricePromise, oneInchPricePromise])
const [oneInchPrice] = await Promise.allSettled([oneInchPricePromise])

return {
// Warning!
// /markets endpoint was deleted, so we just skip it
gpPriceResult: { status: 'fulfilled', value: null },
paraSwapPriceResult: { status: 'fulfilled', value: null },
matcha0xPriceResult: matchaPrice,
oneInchPriceResult: oneInchPrice,
}
}
Expand All @@ -106,35 +91,12 @@ async function getAllPrices(params: LegacyPriceQuoteParams): Promise<AllPricesRe
* successful price quotes and errors price quotes. For each price, it also give the context (the name of the price feed)
*/
function _extractPriceAndErrorPromiseValues(
// we pass the kind of trade here as matcha doesn't have an easy way to differentiate
kind: OrderKind,
gpPriceResult: PromiseSettledResult<PriceInformation | null>,
paraSwapPriceResult: PromiseSettledResult<null>,
matchaPriceResult: PromiseSettledResult<MatchaPriceQuote | null>,
oneInchPriceResult: PromiseSettledResult<PriceQuote1inch | null>
): [Array<LegacyPriceInformationWithSource>, Array<LegacyPromiseRejectedResultWithSource>] {
// Prepare an array with all successful estimations
const priceQuotes: Array<LegacyPriceInformationWithSource> = []
const errorsGetPrice: Array<LegacyPromiseRejectedResultWithSource> = []

if (isPromiseFulfilled(gpPriceResult)) {
const gpPrice = gpPriceResult.value
if (gpPrice) {
priceQuotes.push({ ...gpPrice, source: 'gnosis-protocol' })
}
} else {
errorsGetPrice.push({ ...gpPriceResult, source: 'gnosis-protocol' })
}

if (isPromiseFulfilled(matchaPriceResult)) {
const matchaPrice = toPriceInformationMatcha(matchaPriceResult.value, kind)
if (matchaPrice) {
priceQuotes.push({ ...matchaPrice, source: 'matcha-0x', data: matchaPriceResult.value })
}
} else {
errorsGetPrice.push({ ...matchaPriceResult, source: 'matcha-0x' })
}

if (isPromiseFulfilled(oneInchPriceResult)) {
const oneInchPrice = toPriceInformation1inch(oneInchPriceResult.value)
if (oneInchPrice) {
Expand Down Expand Up @@ -172,17 +134,10 @@ export async function getBestPrice(
options?: GetBestPriceOptions
): Promise<PriceInformation> {
// Get all prices
const { gpPriceResult, paraSwapPriceResult, matcha0xPriceResult, oneInchPriceResult } = await getAllPrices(params)
const { oneInchPriceResult } = await getAllPrices(params)

// Aggregate successful and error prices
const [priceQuotes, errorsGetPrice] = _extractPriceAndErrorPromiseValues(
// we pass the kind of trade here as matcha doesn't have an easy way to differentiate
params.kind,
gpPriceResult,
paraSwapPriceResult,
matcha0xPriceResult,
oneInchPriceResult
)
const [priceQuotes, errorsGetPrice] = _extractPriceAndErrorPromiseValues(oneInchPriceResult)

// Print prices who failed to be fetched
if (errorsGetPrice.length > 0) {
Expand All @@ -199,11 +154,7 @@ export async function getBestPrice(
return _filterWinningPrice({ ...options, kind: params.kind, amounts, priceQuotes })
} else {
// It was not possible to get a price estimation
const priceQuoteError = new LegacyPriceQuoteError('Error querying price from APIs', params, [
gpPriceResult,
paraSwapPriceResult,
matcha0xPriceResult,
])
const priceQuoteError = new LegacyPriceQuoteError('Error querying price from APIs', params, [oneInchPriceResult])

const sentryError = new Error()
Object.assign(sentryError, priceQuoteError, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const SideBar = styled.div`
height: 100%;
max-width: 100%;
border-radius: ${({ theme }) => (theme.isInjectedWidgetMode ? '24px' : '0')};
z-index: 10000;
z-index: 10;
}
`

Expand Down
10 changes: 10 additions & 0 deletions apps/cowswap-frontend/src/modules/analytics/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum Category {
TWAP = 'TWAP',
COW_FORTUNE = 'CoWFortune',
SURPLUS_MODAL = 'Surplus Modal',
NOTIFICATIONS = 'Notifications',
}

export function shareFortuneTwitterAnalytics() {
Expand Down Expand Up @@ -321,3 +322,12 @@ export function shareSurplusOnTwitter() {
action: `Share on Twitter`,
})
}

export function clickNotifications(event: string, notificationId?: number, title?: string) {
cowAnalytics.sendEvent({
category: Category.NOTIFICATIONS,
action: event,
value: notificationId,
label: title,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import styled from 'styled-components/macro'
import { HookDappContext } from '../../context'
import buildImg from '../../images/build.png'

const TITLE = 'Build your own Pre-hook'
const DESCRIPTION = 'Add an arbitrary calldata to be executed before your hook'

export const PRE_BUILD: HookDappInternal = {
name: TITLE,
description: DESCRIPTION,
const getAppDetails = (isPreHook: boolean): HookDappInternal => ({
name: `Build your own ${isPreHook ? 'Pre' : 'Post'}-hook`,
description: `Add an arbitrary calldata to be executed ${isPreHook ? 'before' : 'after'} your hook`,
type: HookDappType.INTERNAL,
path: '/hooks-dapps/pre/build',
image: buildImg,
component: <ClaimGnoHookApp />,
component: <BuildHookApp isPreHook={isPreHook} />,
version: 'v0.1.0',
}
})

export const PRE_BUILD = getAppDetails(true)
export const POST_BUILD = getAppDetails(false)

const Wrapper = styled.div`
display: flex;
Expand Down Expand Up @@ -80,14 +80,20 @@ const Row = styled.div`
}
`

export function ClaimGnoHookApp() {
export interface BuildHookAppProps {
isPreHook: boolean
}

export function BuildHookApp({ isPreHook }: BuildHookAppProps) {
const hookDappContext = useContext(HookDappContext)
const [hook, setHook] = useState<CowHook>({
target: '',
callData: '',
gasLimit: '',
})

const dapp = isPreHook ? PRE_BUILD : POST_BUILD

const clickOnAddHook = useCallback(() => {
const { callData, gasLimit, target } = hook
if (!hookDappContext || !callData || !gasLimit || !target) {
Expand All @@ -97,10 +103,10 @@ export function ClaimGnoHookApp() {
hookDappContext.addHook(
{
hook: hook,
dapp: PRE_BUILD,
dapp,
outputTokens: undefined, // TODO: Simulate and extract the output tokens
},
true
isPreHook
)
}, [hook, hookDappContext])

Expand All @@ -111,8 +117,8 @@ export function ClaimGnoHookApp() {
return (
<Wrapper>
<Header>
<img src={buildImg} alt={TITLE} width="60" />
<p>{DESCRIPTION}</p>
<img src={buildImg} alt={dapp.name} width="60" />
<p>{dapp.description}</p>
</Header>
<ContentWrapper>
<Row>
Expand Down Expand Up @@ -141,7 +147,7 @@ export function ClaimGnoHookApp() {
/>
</Row>
</ContentWrapper>
<ButtonPrimary onClick={clickOnAddHook}>+Add Pre-hook</ButtonPrimary>
<ButtonPrimary onClick={clickOnAddHook}>+Add {isPreHook ? 'Pre' : 'Post'}-hook</ButtonPrimary>
<Link
onClick={(e) => {
e.preventDefault()
Expand Down
16 changes: 3 additions & 13 deletions apps/cowswap-frontend/src/modules/hooksStore/hookRegistry.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { HookDapp, HookDappIframe, HookDappType } from '@cowprotocol/types'

import { PRE_BUILD } from './dapps/BuildHookApp'
import { PRE_BUILD, POST_BUILD } from './dapps/BuildHookApp'
import { PRE_CLAIM_GNO } from './dapps/ClaimGnoHookApp'
import bridgeImg from './images/bridge.svg'
import buildImg from './images/build.png'
import cowAMM from './images/cowAMM.png'
import curveImg from './images/curve.svg'
import daiImg from './images/dai.svg'
Expand Down Expand Up @@ -74,15 +73,6 @@ const POST_MAKER: HookDappIframe = {
version: FAKE_VERSION,
}

const POST_BUILD: HookDappIframe = {
name: 'Build your own Post-hook',
description: 'Add an arbitrary calldata to be executed after your hook',
type: HookDappType.IFRAME,
url: FAKE_URL,
image: buildImg,
version: FAKE_VERSION,
}

const POST_HOOK_DAPPS_ALL = [POST_BRIDGE, POST_MAKER, POST_BUILD]

export const PRE_HOOK_REGISTRY: Record<SupportedChainId, HookDapp[]> = {
Expand All @@ -95,6 +85,6 @@ export const PRE_HOOK_REGISTRY: Record<SupportedChainId, HookDapp[]> = {
export const POST_HOOK_REGISTRY: Record<SupportedChainId, HookDapp[]> = {
[SupportedChainId.MAINNET]: POST_HOOK_DAPPS_ALL,
[SupportedChainId.GNOSIS_CHAIN]: POST_HOOK_DAPPS_ALL,
[SupportedChainId.SEPOLIA]: [],
[SupportedChainId.ARBITRUM_ONE]: [],
[SupportedChainId.SEPOLIA]: [POST_BUILD],
[SupportedChainId.ARBITRUM_ONE]: [POST_BUILD],
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { UI } from '@cowprotocol/ui'
import SVG from 'react-inlinesvg'
import styled from 'styled-components/macro'

import { useUnreadNotifications } from '../hooks/useUnreadNotifications'

const Icon = styled.div<{ hasNotification?: boolean }>`
--size: 18px;
Expand Down Expand Up @@ -59,14 +58,12 @@ const Icon = styled.div<{ hasNotification?: boolean }>`

interface NotificationBellProps {
onClick: Command
unreadCount: number
}

export function NotificationBell({ onClick }: NotificationBellProps) {
const unreadNotifications = useUnreadNotifications()
const unreadNotificationsCount = Object.keys(unreadNotifications).length

export function NotificationBell({ onClick, unreadCount }: NotificationBellProps) {
return (
<Icon hasNotification={unreadNotificationsCount > 0} onClick={onClick}>
<Icon hasNotification={unreadCount > 0} onClick={onClick}>
<SVG src={ICON_NOTIFICATION} />
</Icon>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useSetAtom } from 'jotai/index'
import React, { ReactNode, useEffect, useMemo } from 'react'

import { clickNotifications } from 'modules/analytics'

import { ListWrapper, NoNotifications, NotificationCard, NotificationsListWrapper, NotificationThumb } from './styled'

import { useAccountNotifications } from '../../hooks/useAccountNotifications'
Expand Down Expand Up @@ -50,6 +52,7 @@ export function NotificationsList({ children }: { children: ReactNode }) {
target={target}
noImage={!thumbnail}
rel={target === '_blank' ? 'noopener noreferrer' : ''}
onClick={() => clickNotifications('click-notification-card', id, title)}
>
{thumbnail && (
<NotificationThumb>
Expand Down
Loading

0 comments on commit 50b800e

Please sign in to comment.