Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/lib/modules/hooks/getHooksMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type HooksMetadata = {
id: string
name: string
description: string
learnMore?: string
addresses: Record<string, string[]> // chainId -> addresses[]
}

Expand Down
11 changes: 8 additions & 3 deletions packages/lib/modules/pool/PoolDetail/PoolHeader/PoolSwapFees.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
HStack,
PopoverContent,
Text,
VStack,
Box,
PopoverHeader,
PopoverArrow,
PopoverBody,
} from '@chakra-ui/react'
import { fNum } from '@repo/lib/shared/utils/numbers'
import { Repeat } from 'react-feather'
Expand Down Expand Up @@ -52,10 +54,13 @@ export function PoolSwapFees({ pool }: { pool: Pool }) {
</Badge>
</PopoverTrigger>
<PopoverContent maxW="300px" p="sm" w="auto">
<VStack alignItems="flex-start">
<PopoverArrow bg="background.level3" />
<PopoverHeader>
<Text color="font.secondary" fontWeight="bold" size="md">
Dynamic fee percentage
</Text>
</PopoverHeader>
<PopoverBody>
{isDynamicSwapFee ? (
<Text fontSize="sm" variant="secondary">
This pool has a dynamic fee rate that may change per swap based on custom logic.
Expand All @@ -65,7 +70,7 @@ export function PoolSwapFees({ pool }: { pool: Pool }) {
{`This pool has a dynamic fee rate that may be updated through ${PROJECT_CONFIG.projectName} governance.`}
</Text>
)}
</VStack>
</PopoverBody>
</PopoverContent>
</>
)}
Expand Down
82 changes: 42 additions & 40 deletions packages/lib/modules/pool/PoolDetail/PoolHookBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,53 @@
import { Badge, Card, Center, Heading, HStack, Stack, Text, VStack } from '@chakra-ui/react'
import { Badge, Card, Center, Heading, HStack, Stack, Text } from '@chakra-ui/react'
import { useHook } from '../../hooks/useHook'
import { usePool } from '../PoolProvider'
import { HookIcon } from '@repo/lib/shared/components/icons/HookIcon'
import { StableSurgePromoBanner } from '@repo/lib/shared/components/promos/StableSurgePromoBanner'

export function PoolHookBanner() {
const { pool } = usePool()
const { hooks, hasHookData } = useHook(pool)

if (!hasHookData) return null

return (
<Card>
<VStack alignItems="flex-start" w="full">
{hooks.map(
hook =>
hook && (
<HStack alignItems="center" direction="row" gap="4" key={hook.name} px={4} py={4}>
<Badge
alignItems="center"
background="background.level0"
border="1px solid"
borderColor="border.base"
color="font.primary"
display="flex"
fontSize="xs"
fontWeight="normal"
h={14}
rounded="full"
shadow="sm"
w={14}
>
<Center h="full" w="full">
<HookIcon size={45} />
</Center>
</Badge>
<Stack>
<Heading fontSize="1.25rem" variant="h4">
{hook.name}
</Heading>
<Text fontSize="sm" fontWeight="medium" variant="secondary">
{hook.description}
</Text>
</Stack>
</HStack>
)
)}
</VStack>
</Card>
)
return hooks.map(hook => {
if (!hook) return null

if (hook.id === 'hooks_stablesurge') {
return <StableSurgePromoBanner key={hook.id} />
} else {
return (
<Card key={hook.id}>
<HStack alignItems="center" direction="row" gap="4" px={4} py={4}>
<Badge
alignItems="center"
background="background.level0"
border="1px solid"
borderColor="border.base"
color="font.primary"
display="flex"
fontSize="xs"
fontWeight="normal"
h={14}
rounded="full"
shadow="sm"
w={14}
>
<Center h="full" w="full">
<HookIcon size={45} />
</Center>
</Badge>
<Stack>
<Heading fontSize="1.25rem" variant="h4">
{hook.name}
</Heading>
<Text fontSize="sm" fontWeight="medium" variant="secondary">
{hook.description}
</Text>
</Stack>
</HStack>
</Card>
)
}
})
}
105 changes: 69 additions & 36 deletions packages/lib/modules/pool/PoolDetail/PoolHookTag.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import {
Badge,
Box,
Center,
HStack,
Link,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
Portal,
Text,
Expand All @@ -12,54 +18,81 @@ import { useHook } from '../../hooks/useHook'

import { PoolListItem } from '../pool.types'
import { Pool } from '../pool.types'
import { BalBadge } from '@repo/lib/shared/components/badges/BalBadge'

type Props = {
pool: Pool | PoolListItem
size?: 'sm' | 'md'
onlyShowIcon?: boolean
}

const badgeSize = {
sm: { h: 7, w: 7, iconSize: 25 },
md: { h: 8, w: 8, iconSize: 32 },
}

export function PoolHookTag({ pool, size = 'md' }: Props) {
export function PoolHookTag({ pool, onlyShowIcon = false }: Props) {
const { hooks } = useHook(pool)

// TODO: add nested hook support when needed
const hook = hooks[0]

if (!hook) return null

return (
return onlyShowIcon ? (
<BalBadge color="font.secondary" fontSize="xs" h={7} p="0" textTransform="lowercase" w={7}>
<Center color="font.secondary" h="full" w="full">
<HookIcon size={20} />
</Center>
</BalBadge>
) : (
<Popover trigger="hover">
<PopoverTrigger>
<Badge
alignItems="center"
background="background.level2"
border="1px solid"
borderColor="border.base"
color="font.primary"
display="flex"
fontSize="xs"
fontWeight="normal"
h={badgeSize[size].h}
rounded="full"
shadow="sm"
w={badgeSize[size].w}
>
<Center h="full" w="full">
<HookIcon size={badgeSize[size].iconSize} />
</Center>
</Badge>
</PopoverTrigger>
<Portal>
<PopoverContent px="sm" py="sm" width="fit-content">
<Text fontSize="sm" variant="secondary">
{hook.name}
</Text>
</PopoverContent>
</Portal>
{({ isOpen }) => (
<>
<PopoverTrigger>
<Box
alignItems="center"
background="background.level2"
border="1px solid"
borderColor={isOpen ? 'font.primary' : 'border.base'}
display="flex"
fontWeight="normal"
h={{ base: '28px' }}
px="sm"
py="xs"
rounded="full"
shadow="sm"
>
<HStack color={isOpen ? 'font.primary' : 'font.secondary'} gap="xs">
<HookIcon size={18} />
<Text
color={isOpen ? 'font.primary' : 'font.secondary'}
fontSize="sm"
variant="secondary"
>
{hook.name}
</Text>
</HStack>
</Box>
</PopoverTrigger>
<Portal>
<PopoverContent px="sm" py="sm">
<PopoverArrow bg="background.level3" />
<PopoverHeader>
<Text color="font.secondary" fontWeight="bold" size="md">
{hook.name} Hook
</Text>
</PopoverHeader>
<PopoverBody>
<Text fontSize="sm" variant="secondary">
{hook.description}
</Text>
</PopoverBody>
{hook.learnMore && (
<PopoverFooter>
<Link href={hook.learnMore} target="_blank">
Learn more
</Link>
</PopoverFooter>
)}
</PopoverContent>
</Portal>
</>
)}
</Popover>
)
}
19 changes: 18 additions & 1 deletion packages/lib/modules/pool/PoolDetail/PoolInfo/PoolContracts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
GqlPriceRateProviderData,
GqlHookReviewData,
Erc4626ReviewData,
HookFragment,
} from '@repo/lib/shared/services/api/generated/graphql'
import { Address, zeroAddress } from 'viem'
import { useTokens } from '@repo/lib/modules/tokens/TokensProvider'
Expand All @@ -37,6 +38,10 @@ import { HookInfoPopOver } from './HookInfo'
import { Erc4626InfoPopOver } from './Erc4626Info'
import { ApiToken } from '@repo/lib/modules/tokens/token.types'
import { getBlockExplorerAddressUrl } from '@repo/lib/shared/utils/blockExplorer'
import { useHook } from '@repo/lib/modules/hooks/useHook'
import { getChainId } from '@repo/lib/config/app.config'
import { HooksMetadata } from '@repo/lib/modules/hooks/getHooksMetadata'
import { Pool } from '../../pool.types'

type RateProvider = {
tokenAddress: Address
Expand Down Expand Up @@ -115,10 +120,19 @@ function getHookIcon(data: GqlHookReviewData | undefined | null) {
)
}

function getHookName(hook: HookFragment, pool: Pool, hooksMetadata: (HooksMetadata | undefined)[]) {
if (!hooksMetadata) return hook.name

const chainId = getChainId(pool.chain)

return hooksMetadata.find(metadata => metadata?.addresses[chainId]?.includes(hook.address))?.name
}

export function PoolContracts({ ...props }: CardProps) {
const { pool, chain, poolExplorerLink, hasGaugeAddress, gaugeAddress, gaugeExplorerLink } =
usePool()

const { hooks: hooksMetadata } = useHook(pool)
const { getToken } = useTokens()

const contracts = useMemo(() => {
Expand Down Expand Up @@ -233,7 +247,10 @@ export function PoolContracts({ ...props }: CardProps) {
variant="link"
>
<HStack gap="xxs">
<Text color="link">{abbreviateAddress(hook.address)}</Text>
<Text color="link">
{abbreviateAddress(hook.address)} (
{getHookName(hook, pool, hooksMetadata)})
</Text>
<ArrowUpRight size={12} />
</HStack>
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
isBoosted,
hasNestedPools,
hasHooks,
hasStableSurgeHook,
} from '../../../pool.helpers'
import { zeroAddress } from 'viem'

Expand Down Expand Up @@ -43,6 +44,7 @@ export enum RiskKey {
RateProviderBridge = 'rate-provider-bridges',
NestedPool = 'nested-pools',
Hook = 'hooks-risk',
StableSurgeHook = 'stablesurge-hook',
}

export const RISK_TITLES: Partial<Record<RiskKey, string>> = {
Expand All @@ -66,6 +68,7 @@ export const RISK_TITLES: Partial<Record<RiskKey, string>> = {
[RiskKey.RateProviderBridge]: 'Rate provider cross-chain bridge risks: Layer Zero',
[RiskKey.NestedPool]: 'Nested pool risks',
[RiskKey.Hook]: 'Hook risks',
[RiskKey.StableSurgeHook]: 'StableSurge hook risks',
}

export type Risk = {
Expand Down Expand Up @@ -101,6 +104,7 @@ const avalancheRisks = getLink(RiskKey.Avalanche)
const mutableRisks = getLink(RiskKey.Mutable)
const nestedPoolRisks = getLink(RiskKey.NestedPool)
const hookRisks = getLink(RiskKey.Hook)
const stableSurgeHookRisks = getLink(RiskKey.StableSurgeHook)

export function getPoolRisks(pool: GqlPoolElement): Risk[] {
const result: Risk[] = []
Expand All @@ -120,6 +124,7 @@ export function getPoolRisks(pool: GqlPoolElement): Risk[] {
if (pool.chain === GqlChain.Avalanche) result.push(avalancheRisks)
if (hasNestedPools(pool)) result.push(nestedPoolRisks)
if (hasHooks(pool)) result.push(hookRisks)
if (hasStableSurgeHook(pool)) result.push(stableSurgeHookRisks)
if (hasOwner(pool)) result.push(mutableRisks)

result.push(getLink(RiskKey.General))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function PoolListTableDetailsCell({ pool }: Props) {
width={20}
/>
))}
<PoolHookTag pool={pool} size="sm" />
<PoolHookTag onlyShowIcon pool={pool} />
</HStack>
)
}
9 changes: 9 additions & 0 deletions packages/lib/modules/pool/pool.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ export function hasHooks(pool: Pool): boolean {
return !![pool.hook, ...nestedHooks].filter(Boolean).length
}

export function hasStableSurgeHook(pool: Pool): boolean {
const nestedHooks = pool.poolTokens.flatMap(token =>
token.nestedPool ? token.nestedPool.hook : []
)
const hooks = [...(pool.hook ? [pool.hook] : []), ...nestedHooks]

return hooks.some(hook => hook && hook.name === 'StableSurgeHook')
}

export function hasReviewedErc4626(token: GqlPoolTokenDetail): boolean {
return token.isErc4626 && !!token.erc4626ReviewData
}
Expand Down
Loading