Skip to content

Commit

Permalink
Add approve
Browse files Browse the repository at this point in the history
  • Loading branch information
leonthuongto committed Jan 12, 2025
1 parent 178feb8 commit 27d679b
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 9 deletions.
17 changes: 12 additions & 5 deletions src/pages/DexV2/Swap/components/SwapCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import chainIcon from 'assets/images/dex-v2/chain.svg'
import SwapPair from './SwapPair'
import { ButtonPrimary } from '../../common'
import SwapSettingsModal from './SwapSettingsModal'
import SwapDetail from './SwapDetail'
import SwapDetails from './SwapDetails'
import useSwapping from 'state/dexV2/swap/useSwapping'
import { useSwapState } from 'state/dexV2/swap/useSwapState'
import { useSwapAssets } from 'state/dexV2/swap/useSwapAssets'
Expand All @@ -19,6 +19,7 @@ import useWeb3 from 'hooks/dex-v2/useWeb3'
import useValidation from 'state/dexV2/swap/useValidation'
import { WrapType } from 'lib/utils/balancer/wrapper'
import { SubgraphPoolBase } from '@ixswap1/dex-v2-sdk'
import SwapPreviewModal from './SwapPreviewModal'

const SwapCard: React.FC = () => {
const { inputAsset, outputAsset } = useSwapAssets()
Expand All @@ -27,6 +28,7 @@ const SwapCard: React.FC = () => {
const { appNetworkConfig, isMismatchedNetwork } = useWeb3()

const [isOpenSwapSettings, setOpenSwapSettings] = useState(false)
const [isOpenSwapPreview, setOpenSwapPreview] = useState(false)
const [exactIn, setExactIn] = useState(true)
const [dismissedErrors, setDismissedErrors] = useState({
highPriceImpact: false,
Expand Down Expand Up @@ -190,8 +192,6 @@ const SwapCard: React.FC = () => {
<img src={settingIcon} alt="Settings" />
</Flex>
</Flex>

{isOpenSwapSettings ? <SwapSettingsModal onClose={() => setOpenSwapSettings(false)} /> : null}
</Flex>

<SwapPair
Expand All @@ -202,11 +202,18 @@ const SwapCard: React.FC = () => {
setExactIn={setExactIn}
/>

<SwapDetail swapping={swapping} />
<SwapDetails swapping={swapping} />

<div>
{account ? <ButtonPrimary onClick={swap}>Next</ButtonPrimary> : <ButtonPrimary>Connect Wallet</ButtonPrimary>}
{account ? (
<ButtonPrimary onClick={() => setOpenSwapPreview(true)}>Preview</ButtonPrimary>
) : (
<ButtonPrimary>Connect Wallet</ButtonPrimary>
)}
</div>

{isOpenSwapSettings ? <SwapSettingsModal onClose={() => setOpenSwapSettings(false)} /> : null}
{isOpenSwapPreview ? <SwapPreviewModal swapping={swapping} onClose={() => setOpenSwapPreview(false)} /> : null}
</Container>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import React from 'react'
import { Flex } from 'rebass'
import styled from 'styled-components'
import _get from 'lodash/get'

import ChevronDown from 'assets/images/dex-v2/chev-down.svg'
import InfoIcon from 'assets/images/dex-v2/info.svg'
import { UseSwapping } from 'state/dexV2/swap/useSwapping'
import useNumbers, { FNumFormats } from 'hooks/dex-v2/useNumbers'
import { bn } from 'lib/balancer/utils/numbers'
import useUserSettings from 'state/dexV2/userSettings/useUserSettings'
import { WrapType } from 'lib/utils/balancer/wrapper'

type Props = {
swapping: UseSwapping
}

const SwapDetail: React.FC<Props> = () => {
const SwapDetails: React.FC<Props> = ({ swapping }) => {
const { fNum, toFiat } = useNumbers()
const { slippage, slippageDecimal } = useUserSettings()

const wrapType = _get(swapping, 'wrapType', '');
const isNativeWrapOrUnwrap = wrapType === WrapType.Wrap || wrapType === WrapType.Unwrap;
const priceImpact = _get(swapping, 'sor.priceImpact', 0)
const priceImpactDisplay = fNum(priceImpact, FNumFormats.percent)
// const _slippage = isNativeWrapOrUnwrap ? 0 : slippage
// const _slippageDecimal = isNativeWrapOrUnwrap ? 0 : slippageDecimal
// const maxSlippageUsd = bn(slippage).div(100).times(returnAmountUsd)

console.log('swapping', swapping)
console.log('priceImpactDisplay', priceImpactDisplay)

return (
<Container>
<Flex justifyContent="space-between" alignItems="center" width="100%">
Expand All @@ -25,8 +45,8 @@ const SwapDetail: React.FC<Props> = () => {
<DetailContainer>
<Flex justifyContent="space-between" width="100%" alignItems="center">
<SummaryKey>Price impact</SummaryKey>
<SummaryValue isRed>
-2.68% <img src={InfoIcon} alt="icon" />
<SummaryValue isRed={priceImpact < 0}>
{priceImpactDisplay} <img src={InfoIcon} alt="icon" />
</SummaryValue>
</Flex>
<Flex justifyContent="space-between" width="100%" alignItems="center">
Expand All @@ -52,7 +72,7 @@ const SwapDetail: React.FC<Props> = () => {
)
}

export default SwapDetail
export default SwapDetails

const Container = styled.div`
display: flex;
Expand Down
227 changes: 227 additions & 0 deletions src/pages/DexV2/Swap/components/SwapPreviewModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import React, { useEffect, useMemo, useState } from 'react'
import Portal from '@reach/portal'
import styled from 'styled-components'

import { CenteredFixed } from 'components/LaunchpadMisc/styled'
import { ReactComponent as CloseIcon } from 'assets/images/dex-v2/close.svg'
import useTokenApprovalActions, { ApprovalAction } from 'state/dexV2/tokens/hooks/useTokenApprovalActions'
import { UseSwapping } from 'state/dexV2/swap/useSwapping'
import { configService } from 'services/config/config.service'
import { TransactionActionInfo, TransactionActionState } from 'pages/DexV2/types/transactions'
import Loader from 'components/Loader'
import { useErrorMsg } from 'lib/utils/errors'
import { toast } from 'react-toastify'

interface SwapSettingsModalProps {
swapping: UseSwapping
onClose: () => void
}

const SwapPreviewModal: React.FC<SwapSettingsModalProps> = (props) => {
const { onClose } = props
const { getTokenApprovalActions } = useTokenApprovalActions()
const { formatErrorMsg } = useErrorMsg()

const [loadingApprovals, setLoadingApprovals] = useState(true)
const [currentActionIndex, setCurrentActionIndex] = useState(0)
const [loading, setLoading] = useState(false)

const labels = useMemo(() => {
if (props.swapping.isWrap) {
return {
modalTitle: `Preview ${props.swapping.tokenIn.symbol} Wrap`,
confirmSwap: `Confirm ${props.swapping.tokenIn.symbol} wrap`,
}
} else if (props.swapping.isUnwrap) {
return {
modalTitle: `Preview ${props.swapping.tokenOut.symbol} Unwrap`,
confirmSwap: `Preview ${props.swapping.tokenOut.symbol} Unwrap`,
}
} else if (props.swapping.exactIn) {
return {
modalTitle: 'Preview swap',
confirmSwap: 'Confirm swap',
}
}
// exact out
return {
modalTitle: 'Preview swap',
confirmSwap: 'Confirm swap',
}
}, [props.swapping])

async function swap() {
return props.swapping.swap(() => {
props.swapping.resetAmounts()
onClose()
})
}

const initActions: any[] = [
{
label: labels.confirmSwap,
loadingLabel: 'Confirm swap in wallet',
confirmingLabel: 'Confirming...',
action: swap as () => Promise<any>,
},
]
const [actions, setActions] = useState<any[]>(initActions)
const currentAction: any = actions[currentActionIndex]

async function submit(actionInfo: TransactionActionInfo): Promise<void> {
const { action, postActionValidation } = actionInfo
try {
setLoading(true)
await action()
await postActionValidation?.()
setCurrentActionIndex(currentActionIndex + 1)
} catch (error: any) {
console.error('Error submitting action', error?.message)
const errorMsg = formatErrorMsg(error?.message)
if (errorMsg) {
toast.error(errorMsg.title)
}
} finally {
setLoading(false)
}
}

useEffect(() => {
const amountsToApprove: any = [
{
address: props.swapping.tokenIn.address,
amount: props.swapping.tokenInAmountInput,
},
]

const getActions = async () => {
const approvalActions = await getTokenApprovalActions({
amountsToApprove,
spender: configService.network.addresses.vault,
actionType: ApprovalAction.Swapping,
})

setActions([...approvalActions, ...initActions])
setLoadingApprovals(false)
}

getActions()
}, [])

console.log('actions', actions)

return (
<Portal>
<CenteredFixed width="100vw" height="100vh">
<ModalContent>
<HeaderModal>
<TitleWrapper>
<Title>Transaction settings</Title>
<CloseButton onClick={onClose}>
<CloseIcon />
</CloseButton>
</TitleWrapper>
</HeaderModal>

<BodyModal>
<Button onClick={() => submit(currentAction)} disabled={loading}>
{loading ? <Loader /> : null}
{currentAction?.label}
</Button>
</BodyModal>
</ModalContent>
</CenteredFixed>
</Portal>
)
}

export default SwapPreviewModal

const ModalContent = styled.div`
background: white;
border-radius: 16px;
width: 480px;
`

const HeaderModal = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
align-self: stretch;
padding-top: 32px;
padding-left: 32px;
padding-right: 32px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
`

const BodyModal = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
padding-top: 16px;
padding-left: 32px;
padding-right: 32px;
padding-bottom: 32px;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
`

const CloseButton = styled.div`
cursor: pointer;
color: rgba(41, 41, 51, 0.5);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: normal;
letter-spacing: -0.42px;
`

const TitleWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
`

const Title = styled.div`
color: rgba(41, 41, 51, 0.9);
font-family: Inter;
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: normal;
letter-spacing: -0.6px;
`

export const Button = styled.button`
display: flex;
height: 48px;
padding: 12px 16px;
justify-content: center;
align-items: center;
gap: 10px;
flex: 1 0 0;
border-radius: 8px;
background: #66f;
font-family: Inter;
color: #fff;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: normal;
letter-spacing: -0.28px;
cursor: pointer;
border: none;
&:hover {
transform: scale(0.99);
}
&:disabled {
background: #ececfb;
}
`
31 changes: 31 additions & 0 deletions src/state/dexV2/userSettings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createSlice } from '@reduxjs/toolkit'

export type YesNo = 'yes' | 'no'

const DEFAULT_SLIPPAGE = '0.5' // 0.5%
const DEFAULT_ENABLE_SIGNATURES: YesNo = 'yes'

export interface UserSettingsState {
slippage: string
enableSignatures: YesNo
}

const initialState: UserSettingsState = {
slippage: DEFAULT_SLIPPAGE,
enableSignatures: DEFAULT_ENABLE_SIGNATURES,
}

const userSettingsSlice = createSlice({
name: 'userSettings',
initialState,
reducers: {
setUserSettingsState(state, action) {
const newState = { ...state, ...action.payload }

return newState
},
},
})

export const { setUserSettingsState } = userSettingsSlice.actions
export default userSettingsSlice.reducer
20 changes: 20 additions & 0 deletions src/state/dexV2/userSettings/useUserSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { bn } from 'lib/balancer/utils/numbers'
import { useDispatch, useSelector } from 'react-redux'
import { AppState } from 'state'
import { setUserSettingsState } from '.'

const useUserSettings = () => {
const { slippage } = useSelector((state: AppState) => state.userSettings)
const dispatch = useDispatch()

const slippageDecimal = bn(slippage).div(100).toString()
const slippageBps = bn(slippage).times(100).toString()

const setSlippage = (newSlippage: string) => {
dispatch(setUserSettingsState({ slippage: newSlippage }))
}

return { slippage, slippageDecimal, slippageBps, setSlippage }
}

export default useUserSettings
Loading

0 comments on commit 27d679b

Please sign in to comment.