Skip to content

Commit

Permalink
feat(swap): display order hooks details (#4925)
Browse files Browse the repository at this point in the history
  • Loading branch information
shoom3301 authored Oct 4, 2024
1 parent 302fa35 commit 1e776fc
Show file tree
Hide file tree
Showing 27 changed files with 619 additions and 274 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { HookToDappMatch } from '@cowprotocol/hook-dapp-lib'

import { Item, Wrapper } from './styled'

export function HookItem({ item }: { item: HookToDappMatch }) {
return (
<Item>
{item.dapp ? (
<Wrapper href={item.dapp.website} target="_blank">
<img src={item.dapp.image} alt={item.dapp.name} />
<p>
<strong>{item.dapp.name}</strong> <span>({item.dapp.version})</span>
</p>
</Wrapper>
) : (
<div>Unknown hook dapp</div>
)}
</Item>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import styled from 'styled-components/macro'

export const Item = styled.li`
list-style: none;
margin: 0;
padding: 0;
`

export const Wrapper = styled.a`
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
> img {
width: 30px;
height: 30px;
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ReactElement, useMemo, useState } from 'react'

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

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

import { AppDataInfo, decodeAppData } from 'modules/appData'

import { HookItem } from './HookItem'
import { HooksList, InfoWrapper, ToggleButton, Wrapper } from './styled'

interface OrderHooksDetailsProps {
appData: string | AppDataInfo
children: (content: ReactElement) => ReactElement
}

export function OrderHooksDetails({ appData, children }: OrderHooksDetailsProps) {
const [isOpen, setOpen] = useState(false)
const appDataDoc = useMemo(() => {
return typeof appData === 'string' ? decodeAppData(appData) : appData.doc
}, [appData])

if (!appDataDoc) return null

const metadata = appDataDoc.metadata as latest.Metadata

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

if (!preHooksToDapp.length && !postHooksToDapp.length) return null

return children(
isOpen ? (
<Wrapper>
<ToggleButton onClick={() => setOpen(false)}>
<ChevronUp />
</ToggleButton>
<HooksInfo data={preHooksToDapp} title="Pre Hooks" />
<HooksInfo data={postHooksToDapp} title="Post Hooks" />
</Wrapper>
) : (
<Wrapper>
<span>
{preHooksToDapp.length ? `Pre: ${preHooksToDapp.length}` : ''}
{preHooksToDapp.length && postHooksToDapp.length ? ' | ' : ''}
{postHooksToDapp.length ? `Post: ${postHooksToDapp.length}` : ''}
</span>
<ToggleButton onClick={() => setOpen(true)}>
<ChevronDown />
</ToggleButton>
</Wrapper>
),
)
}

interface HooksInfoProps {
data: HookToDappMatch[]
title: string
}

function HooksInfo({ data, title }: HooksInfoProps) {
return (
<>
{data.length ? (
<InfoWrapper>
<h3>{title}</h3>
<HooksList>
{data.map((item) => {
return <HookItem key={item.hook.callData + item.hook.target + item.hook.gasLimit} item={item} />
})}
</HooksList>
</InfoWrapper>
) : null}
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import styled from 'styled-components/macro'

export const Wrapper = styled.div`
position: relative;
padding-right: 30px;
`

export const HooksList = styled.ul`
margin: 0;
padding: 0;
padding-left: 10px;
`

export const ToggleButton = styled.button`
cursor: pointer;
background: none;
border: 0;
outline: 0;
padding: 0;
margin: 0;
position: absolute;
right: 0;
top: -4px;
&:hover {
opacity: 0.7;
}
`
export const InfoWrapper = styled.div`
h3 {
margin: 0;
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useToggleAccountModal } from 'modules/account'
import { useInjectedWidgetParams } from 'modules/injectedWidget'
import { EthFlowStepper } from 'modules/swap/containers/EthFlowStepper'

import { OrderHooksDetails } from 'common/containers/OrderHooksDetails'
import { useCancelOrder } from 'common/hooks/useCancelOrder'
import { isPending } from 'common/hooks/useCategorizeRecentActivity'
import { useGetSurplusData } from 'common/hooks/useGetSurplusFiatValue'
Expand Down Expand Up @@ -193,6 +194,7 @@ export function ActivityDetails(props: {
const getShowCancellationModal = useCancelOrder()

const isSwap = order && getUiOrderType(order) === UiOrderType.SWAP
const appData = !!order && order.fullAppData

const { disableProgressBar } = useInjectedWidgetParams()

Expand Down Expand Up @@ -390,9 +392,20 @@ export function ActivityDetails(props: {
</i>
</SummaryInnerRow>
)}

{appData && (
<OrderHooksDetails appData={appData}>
{(children) => (
<SummaryInnerRow>
<b>Hooks</b>
<i>{children}</i>
</SummaryInnerRow>
)}
</OrderHooksDetails>
)}
</>
) : (
summary ?? id
(summary ?? id)
)}

{activityLinkUrl && enhancedTransaction?.replacementType !== 'replaced' && (
Expand Down
1 change: 1 addition & 0 deletions apps/cowswap-frontend/src/modules/appData/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { getAppData } from './utils/fullAppData'
export * from './updater/AppDataUpdater'
export { useAppData, useAppDataHooks, useUploadAppData } from './hooks'
export { filterPermitSignerPermit } from './utils/appDataFilter'
export { decodeAppData } from './utils/decodeAppData'
export { replaceHooksOnAppData, buildAppData, removePermitHookFromAppData } from './utils/buildAppData'
export { buildAppDataHooks } from './utils/buildAppDataHooks'
export * from './utils/getAppDataHooks'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function HookDappContainer({ dapp, isPreHook, onDismiss, hookToEdit }: Ho
tradeNavigate,
inputCurrencyId,
outputCurrencyId,
isDarkMode,
])

const dappProps = useMemo(() => ({ context, dapp, isPreHook }), [context, dapp, isPreHook])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useIsSellNative } from 'modules/trade'

import { useSetRecipientOverride } from '../../hooks/useSetRecipientOverride'
import { useSetupHooksStoreOrderParams } from '../../hooks/useSetupHooksStoreOrderParams'
import { IframeDappsManifestUpdater } from '../../updaters/iframeDappsManifestUpdater'
import { HookRegistryList } from '../HookRegistryList'
import { PostHookButton } from '../PostHookButton'
import { PreHookButton } from '../PreHookButton'
Expand Down Expand Up @@ -81,6 +82,7 @@ export function HooksStoreWidget() {
<TradeWidgetWrapper visible$={!isHookSelectionOpen}>
<SwapWidget topContent={TopContent} bottomContent={BottomContent} />
</TradeWidgetWrapper>
<IframeDappsManifestUpdater />
{isHookSelectionOpen && (
<HookRegistryList onDismiss={onDismiss} hookToEdit={hookToEdit} isPreHook={selectedHookPosition === 'pre'} />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useSetAtom } from 'jotai'
import { useCallback } from 'react'

import { addCustomHookDappAtom } from '../state/customHookDappsAtom'
import { upsertCustomHookDappAtom } from '../state/customHookDappsAtom'
import { HookDappIframe } from '../types/hooks'

export function useAddCustomHookDapp(isPreHook: boolean) {
const setState = useSetAtom(addCustomHookDappAtom)
const setState = useSetAtom(upsertCustomHookDappAtom)

return useCallback(
(dapp: HookDappIframe) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import { Dispatch, SetStateAction, useEffect } from 'react'

import {
HOOK_DAPP_ID_LENGTH,
HookDappBase,
HookDappType,
HookDappWalletCompatibility,
} from '@cowprotocol/hook-dapp-lib'
import { HookDappBase, HookDappType } from '@cowprotocol/hook-dapp-lib'
import { useWalletInfo } from '@cowprotocol/wallet'

import { HookDappIframe } from '../../../types/hooks'

type HookDappBaseInfo = Omit<HookDappBase, 'type' | 'conditions'>

const MANDATORY_DAPP_FIELDS: (keyof HookDappBaseInfo)[] = ['id', 'name', 'image', 'version', 'website']

const isHex = (val: string) => Boolean(val.match(/^[0-9a-f]+$/i))
import { validateHookDappManifest } from '../../../validateHookDappManifest'

interface ExternalDappLoaderProps {
input: string
Expand Down Expand Up @@ -45,47 +35,24 @@ export function ExternalDappLoader({
.then((data) => {
if (!isRequestRelevant) return

const { conditions = {}, ...dapp } = data.cow_hook_dapp as HookDappBase
const dapp = data.cow_hook_dapp as HookDappBase

if (dapp) {
const emptyFields = MANDATORY_DAPP_FIELDS.filter((field) => typeof dapp[field] === 'undefined')
const validationError = validateHookDappManifest(
data.cow_hook_dapp as HookDappBase,
chainId,
isPreHook,
isSmartContractWallet,
)

if (emptyFields.length > 0) {
setManifestError(`${emptyFields.join(',')} fields are no set.`)
} else {
if (
isSmartContractWallet === true &&
conditions.walletCompatibility &&
!conditions.walletCompatibility.includes(HookDappWalletCompatibility.SMART_CONTRACT)
) {
setManifestError('The app does not support smart-contract wallets.')
} else if (!isHex(dapp.id) || dapp.id.length !== HOOK_DAPP_ID_LENGTH) {
setManifestError(<p>Hook dapp id must be a hex with length 64.</p>)
} else if (conditions.supportedNetworks && !conditions.supportedNetworks.includes(chainId)) {
setManifestError(<p>This app/hook doesn't support current network (chainId={chainId}).</p>)
} else if (conditions.position === 'post' && isPreHook) {
setManifestError(
<p>
This app/hook can only be used as a <strong>post-hook</strong> and cannot be added as a pre-hook.
</p>,
)
} else if (conditions.position === 'pre' && !isPreHook) {
setManifestError(
<p>
This app/hook can only be used as a <strong>pre-hook</strong> and cannot be added as a post-hook.
</p>,
)
} else {
setManifestError(null)
setDappInfo({
...dapp,
type: HookDappType.IFRAME,
url: input,
})
}
}
if (validationError) {
setManifestError(validationError)
} else {
setManifestError('Manifest does not contain "cow_hook_dapp" property.')
setManifestError(null)
setDappInfo({
...dapp,
type: HookDappType.IFRAME,
url: input,
})
}
})
.catch((error) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const customPostHookDappsAtom = atom((get) => {
return Object.values(get(customHookDappsAtom).post) as HookDappIframe[]
})

export const addCustomHookDappAtom = atom(null, (get, set, isPreHook: boolean, dapp: HookDappIframe) => {
export const upsertCustomHookDappAtom = atom(null, (get, set, isPreHook: boolean, dapp: HookDappIframe) => {
const { chainId } = get(walletInfoAtom)
const state = get(customHookDappsInner)

Expand Down
Loading

0 comments on commit 1e776fc

Please sign in to comment.