Skip to content

Commit

Permalink
feat: swap from pool
Browse files Browse the repository at this point in the history
  • Loading branch information
agualis committed Oct 16, 2024
1 parent 3cf52f9 commit 7904d83
Show file tree
Hide file tree
Showing 15 changed files with 387 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'

import { PoolActionsLayout } from '@repo/lib/modules/pool/actions/PoolActionsLayout'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client'

import { PoolActionsLayout } from '@repo/lib/modules/pool/actions/PoolActionsLayout'
import { getPoolTokens } from '@repo/lib/modules/pool/pool.helpers'
import { usePoolRedirect } from '@repo/lib/modules/pool/pool.hooks'
import { chainToSlugMap } from '@repo/lib/modules/pool/pool.utils'
import { usePool } from '@repo/lib/modules/pool/PoolProvider'
import { SwapForm } from '@repo/lib/modules/swap/SwapForm'
import SwapLayout from '@repo/lib/modules/swap/SwapLayout'
import { PathParams } from '@repo/lib/modules/swap/SwapProvider'
import { useTokens } from '@repo/lib/modules/tokens/TokensProvider'
import { Hash } from 'viem'

type Props = {
params: { txHash?: string[] }
}
// Page for swapping from a pool page
export default function PoolSwapPage({ params: { txHash } }: Props) {
const { getToken } = useTokens()
const { pool, isLoading } = usePool()
const { redirectToPoolPage } = usePoolRedirect(pool)

const poolTokens = getPoolTokens(pool, getToken)

const maybeTxHash = (txHash?.[0] as Hash) || undefined

const pathParams: PathParams = {
chain: chainToSlugMap[pool.chain],
tokenIn: poolTokens[0].address,
tokenOut: poolTokens[1].address,
poolTokens,
urlTxHash: maybeTxHash,
}

return (
<PoolActionsLayout>
{isLoading ? null : (
<SwapLayout pathParams={pathParams}>
<SwapForm redirectToPoolPage={redirectToPoolPage} />
</SwapLayout>
)}
</PoolActionsLayout>
)
}
32 changes: 4 additions & 28 deletions apps/frontend-v3/app/(app)/swap/[[...slug]]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,20 @@
'use client'

import { ChainSlug, slugToChainMap } from '@repo/lib/modules/pool/pool.utils'
import { SwapProvider } from '@repo/lib/modules/swap/SwapProvider'
import { TokenBalancesProvider } from '@repo/lib/modules/tokens/TokenBalancesProvider'
import { TokenInputsValidationProvider } from '@repo/lib/modules/tokens/TokenInputsValidationProvider'
import { useTokens } from '@repo/lib/modules/tokens/TokensProvider'
import { TransactionStateProvider } from '@repo/lib/modules/transactions/transaction-steps/TransactionStateProvider'
import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql'
import { PropsWithChildren } from 'react'
import { PriceImpactProvider } from '@repo/lib/modules/price-impact/PriceImpactProvider'
import { DefaultPageContainer } from '@repo/lib/shared/components/containers/DefaultPageContainer'
import { getSwapPathParams } from '@repo/lib/modules/swap/getSwapPathParams'
import { RelayerSignatureProvider } from '@repo/lib/modules/relayer/RelayerSignatureProvider'
import SwapLayout from '../../../../../../packages/lib/modules/swap/SwapLayout'
import { DefaultPageContainer } from '@repo/lib/shared/components/containers/DefaultPageContainer'

type Props = PropsWithChildren<{
params: { slug?: string[] }
}>

export default function SwapLayout({ params: { slug }, children }: Props) {
export default function Layout({ params: { slug }, children }: Props) {
const pathParams = getSwapPathParams(slug)

const { getTokensByChain } = useTokens()
const initChain = pathParams.chain
? slugToChainMap[pathParams.chain as ChainSlug]
: GqlChain.Mainnet
const initTokens = getTokensByChain(initChain)

return (
<DefaultPageContainer minH="100vh">
<TransactionStateProvider>
<RelayerSignatureProvider>
<TokenInputsValidationProvider>
<TokenBalancesProvider initTokens={initTokens}>
<PriceImpactProvider>
<SwapProvider pathParams={{ ...pathParams }}>{children}</SwapProvider>
</PriceImpactProvider>
</TokenBalancesProvider>
</TokenInputsValidationProvider>
</RelayerSignatureProvider>
</TransactionStateProvider>
<SwapLayout pathParams={pathParams}> {children} </SwapLayout>
</DefaultPageContainer>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client'

import {
Box,
Button,
HStack,
Link,
Popover,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverTrigger,
VStack,
} from '@chakra-ui/react'
import { SwapIcon } from '@repo/lib/shared/components/icons/SwapIcon'
import { staggeredFadeInUp } from '@repo/lib/shared/utils/animations'
import { AnimatePresence, motion } from 'framer-motion'
import NextLink from 'next/link'
import { usePathname } from 'next/navigation'
import { useState } from 'react'
import { MoreVertical } from 'react-feather'

export function PoolAdvancedOptions() {
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
const pathname = usePathname()

return (
<Popover
isOpen={isPopoverOpen}
onOpen={() => setIsPopoverOpen(true)}
onClose={() => setIsPopoverOpen(false)}
placement="bottom-end"
>
<PopoverTrigger>
<Button variant="tertiary" size="lg" color="grayText">
{<MoreVertical size={16} />}
</Button>
</PopoverTrigger >
<Box zIndex="popover" shadow="2xl" width="max">
<PopoverContent>
<PopoverArrow bg="background.level3" />
<PopoverCloseButton top="sm" />
<PopoverBody p="lg">
<AnimatePresence>
{isPopoverOpen && (
<VStack
align="start"
spacing="xxs"
as={motion.div}
initial="hidden"
animate="show"
exit="exit"
variants={staggeredFadeInUp}
>
<HStack>
<SwapIcon size={24} />
<Link as={NextLink} href={`${pathname}/swap`} prefetch={true} variant="nav">
Swap tokens directly via this pool
</Link>
</HStack>
</VStack>
)}
</AnimatePresence>
</PopoverBody>
</PopoverContent>
</Box>
</Popover>
)
}
23 changes: 14 additions & 9 deletions packages/lib/modules/pool/PoolDetail/PoolHeader/PoolHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Stack, Button, VStack, useDisclosure } from '@chakra-ui/react'
import { Stack, Button, VStack, useDisclosure, HStack } from '@chakra-ui/react'
import { usePathname, useRouter } from 'next/navigation'
import PoolMetaBadges from './PoolMetaBadges'

Expand All @@ -13,6 +13,7 @@ import {
} from '@repo/lib/shared/components/modals/PartnerRedirectModal'
import { useState } from 'react'
import { getXavePoolLink } from '../../pool.utils'
import { PoolAdvancedOptions } from './PoolAdvancedOptions'

export function PoolHeader() {
const pathname = usePathname()
Expand Down Expand Up @@ -55,14 +56,18 @@ export function PoolHeader() {
<PoolMetaBadges />
<Stack spacing="md" direction={{ base: 'column', md: 'row' }}>
<PoolCategories />
<Button
onClick={handleClick}
variant="primary"
size="lg"
isDisabled={isAddLiquidityBlocked}
>
Add liquidity
</Button>
<HStack spacing="sm">

<Button
onClick={handleClick}
variant="primary"
size="lg"
isDisabled={isAddLiquidityBlocked}
>
Add liquidity
</Button>
<PoolAdvancedOptions />
</HStack>
<PartnerRedirectModal
partner={redirectPartner}
redirectUrl={redirectPartnerUrl}
Expand Down
29 changes: 29 additions & 0 deletions packages/lib/modules/swap/PoolSwapCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { VStack, Card, HStack, Text } from '@chakra-ui/react'
import { BalAlert } from '@repo/lib/shared/components/alerts/BalAlert'
import { BalAlertContent } from '@repo/lib/shared/components/alerts/BalAlertContent'
import { NetworkIcon } from '@repo/lib/shared/components/icons/NetworkIcon'
import { usePool } from '../pool/PoolProvider'

export function PoolSwapCard() {
const { pool } = usePool()
return (
<VStack>
<BalAlert
status="warning"
content={
<BalAlertContent
title="Direct single pool swap (expert option)"
description="This swap routes through a single pool only. Better rates are usually available through the standard swap UI."
forceColumnMode
/>
}
/>
<Card h="full">
<HStack>
<NetworkIcon chain={pool.chain} size={6} />
<Text>{pool.name} (swap route)</Text>
</HStack>
</Card>
</VStack>
)
}
64 changes: 44 additions & 20 deletions packages/lib/modules/swap/SwapForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ import { useUserAccount } from '../web3/UserAccountProvider'
import { ConnectWallet } from '../web3/ConnectWallet'
import { SafeAppAlert } from '@repo/lib/shared/components/alerts/SafeAppAlert'
import { useTokens } from '../tokens/TokensProvider'
import { useIsPoolSwap } from './useIsPoolSwap'
import { CompactTokenSelectModal } from '../tokens/TokenSelectModal/TokenSelectList/CompactTokenSelectModal'
import { PoolSwapCard } from './PoolSwapCard'

export function SwapForm() {
type Props = {
redirectToPoolPage?: () => void // Only used for pool swaps
}
export function SwapForm({ redirectToPoolPage }: Props) {
const {
tokenIn,
tokenOut,
Expand Down Expand Up @@ -72,6 +78,7 @@ export function SwapForm() {
const isMounted = useIsMounted()
const { isConnected } = useUserAccount()
const { startTokenPricePolling } = useTokens()
const isPoolSwap = useIsPoolSwap()

const isLoadingSwaps = simulationQuery.isLoading
const isLoading = isLoadingSwaps || !isMounted
Expand Down Expand Up @@ -107,8 +114,9 @@ export function SwapForm() {

if (swapTxHash) {
resetSwapAmounts()
replaceUrlPath()
transactionSteps.resetTransactionSteps()
if (isPoolSwap) return redirectToPoolPage?.()
replaceUrlPath()
}
}

Expand All @@ -124,7 +132,7 @@ export function SwapForm() {
>
<Card rounded="xl">
<CardHeader as={HStack} w="full" justify="space-between" zIndex={11}>
<span>{capitalize(swapAction)}</span>
<span>{isPoolSwap ? 'Single pool swap' : capitalize(swapAction)}</span>
<HStack>
<Tooltip label={copiedDeepLink ? 'Copied!' : 'Copy swap link'}>
<Button variant="tertiary" size="sm" color="grayText" onClick={copyDeepLink}>
Expand All @@ -137,14 +145,17 @@ export function SwapForm() {
</CardHeader>
<CardBody as={VStack} align="start">
<VStack spacing="md" w="full">
{isPoolSwap && <PoolSwapCard />}
<SafeAppAlert />
<ChainSelect
value={selectedChain}
onChange={newValue => {
setSelectedChain(newValue as GqlChain)
setTokenInAmount('')
}}
/>
{!isPoolSwap && (
<ChainSelect
value={selectedChain}
onChange={newValue => {
setSelectedChain(newValue as GqlChain)
setTokenInAmount('')
}}
/>
)}
<VStack w="full">
<TokenInput
ref={finalRefTokenIn}
Expand Down Expand Up @@ -235,16 +246,29 @@ export function SwapForm() {
</CardFooter>
</Card>
</Center>
<TokenSelectModal
finalFocusRef={tokenSelectKey === 'tokenIn' ? finalRefTokenIn : finalRefTokenOut}
chain={selectedChain}
tokens={tokens}
currentToken={tokenSelectKey === 'tokenIn' ? tokenIn.address : tokenOut.address}
isOpen={tokenSelectDisclosure.isOpen}
onOpen={tokenSelectDisclosure.onOpen}
onClose={tokenSelectDisclosure.onClose}
onTokenSelect={handleTokenSelect}
/>
{isPoolSwap ? (
<CompactTokenSelectModal
finalFocusRef={tokenSelectKey === 'tokenIn' ? finalRefTokenIn : finalRefTokenOut}
chain={selectedChain}
tokens={tokens}
isOpen={tokenSelectDisclosure.isOpen}
onOpen={tokenSelectDisclosure.onOpen}
onClose={tokenSelectDisclosure.onClose}
onTokenSelect={handleTokenSelect}
/>
) : (
<TokenSelectModal
finalFocusRef={tokenSelectKey === 'tokenIn' ? finalRefTokenIn : finalRefTokenOut}
chain={selectedChain}
tokens={tokens}
currentToken={tokenSelectKey === 'tokenIn' ? tokenIn.address : tokenOut.address}
isOpen={tokenSelectDisclosure.isOpen}
onOpen={tokenSelectDisclosure.onOpen}
onClose={tokenSelectDisclosure.onClose}
onTokenSelect={handleTokenSelect}
/>
)}

<SwapPreviewModal
finalFocusRef={nextBtn}
isOpen={previewModalDisclosure.isOpen}
Expand Down
39 changes: 39 additions & 0 deletions packages/lib/modules/swap/SwapLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client'

import { ChainSlug, slugToChainMap } from '@repo/lib/modules/pool/pool.utils'
import { PriceImpactProvider } from '@repo/lib/modules/price-impact/PriceImpactProvider'
import { RelayerSignatureProvider } from '@repo/lib/modules/relayer/RelayerSignatureProvider'
import { PathParams, SwapProvider } from '@repo/lib/modules/swap/SwapProvider'
import { TokenBalancesProvider } from '@repo/lib/modules/tokens/TokenBalancesProvider'
import { TokenInputsValidationProvider } from '@repo/lib/modules/tokens/TokenInputsValidationProvider'
import { useTokens } from '@repo/lib/modules/tokens/TokensProvider'
import { TransactionStateProvider } from '@repo/lib/modules/transactions/transaction-steps/TransactionStateProvider'
import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql'
import { PropsWithChildren } from 'react'

type Props = PropsWithChildren<{
pathParams: PathParams
}>

// Layout shared by standard swap page (/swap) and pool swap page (/poolid/swap)
export default function SwapLayout({ pathParams, children }: Props) {
const { getTokensByChain } = useTokens()
const initChain = pathParams.chain
? slugToChainMap[pathParams.chain as ChainSlug]
: GqlChain.Mainnet
const initTokens = pathParams.poolTokens || getTokensByChain(initChain)

return (
<TransactionStateProvider>
<RelayerSignatureProvider>
<TokenInputsValidationProvider>
<TokenBalancesProvider initTokens={initTokens}>
<PriceImpactProvider>
<SwapProvider pathParams={{ ...pathParams }}>{children}</SwapProvider>
</PriceImpactProvider>
</TokenBalancesProvider>
</TokenInputsValidationProvider>
</RelayerSignatureProvider>
</TransactionStateProvider>
)
}
Loading

0 comments on commit 7904d83

Please sign in to comment.