Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SWA-46] Limit order refactoring #1839

Draft
wants to merge 37 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3114155
[SWA-46] LimitOrder base classes
Jun 6, 2023
b89e982
[SWA-46] Limit order configuration
Jun 15, 2023
d1a9edd
[SWA-46] Limit order basic Provider
Jun 15, 2023
8ce58b3
[SWA-46] Limit order initial types
Jun 15, 2023
b6c0aa4
[SWA-46] Limit Base abstract setup
Jun 15, 2023
0c0260d
[SWA-46] Limit CoW and 1Inch Base config
Jun 15, 2023
fd07d82
[SWA-46] Create Limit Order class
Jun 15, 2023
43214cc
[SWA-46] Limit order tab
Jun 16, 2023
d4205ab
[SWA-46] Set Base structure for limit order page
Jun 17, 2023
45cd5e7
[SWA-46] Update getMarketPrice call
Jun 17, 2023
c2a2abb
[SWA-46] Update unused variable to fix type checks
Jun 17, 2023
e7442f3
[SWA-46] Update swap logic
Jun 17, 2023
983922b
[SWA-46] Fix default value, token and tokenAmount
Jun 18, 2023
ff9c428
[SWA-46] Remove unwanted console logs and fix buyTokenChange
Jun 18, 2023
9885026
[SWA-46] Code cleanup
Jun 18, 2023
1509de4
[SWA-46] Fix style issues
Jun 18, 2023
f826676
[SWA-46] Optimize MarketPriceButton
Jun 19, 2023
00c7954
[SWA-46] Fix MarketPrice Styles
Jun 19, 2023
6f91adc
[SWA-46] Add LimitOder type
Jun 19, 2023
42f7424
[SWA-46] Fix quote price for buy and sell token
Jun 22, 2023
f87db38
[SWA-46] Fix quote price and fix Swap Tokens
Jun 23, 2023
ed86748
[SWA-46] Add confirmation Modal
Jun 23, 2023
3d625e5
[SWA-46] Move CoW api to Services
Jun 23, 2023
c076de5
[SWA-72] Set default limit price and toggle it
Jun 24, 2023
f714dec
[SWA-46] Input value change trigger
Jul 23, 2023
14256b4
[SWA-72] On user input override the default limit price and update th…
Jul 25, 2023
a96afe5
feat: [SWA-95] Fallback connect wallet component
Jul 26, 2023
534f7da
feat: [SWA-85] Update amounts based on limit price
Jul 26, 2023
e5afcd6
feat: [SWA-85] Fix limit price calculations
Jul 26, 2023
ba4a093
feat: [SWA-85] Cleanup commented code
Jul 26, 2023
b4d33f5
feat: [SWA-88] Enable user input limit price
Jul 27, 2023
22fb61c
[SWA-92] Market prices and update limit order every 15 seconds
Jul 28, 2023
0345d92
[SWA-92] Place limit order
Jul 28, 2023
090ded6
[SWA-94] Disable limit order and handle common errors
Jul 30, 2023
b3a35fb
[SWA-94] Code cleanup and fallbacks
Jul 30, 2023
21cc8fe
[SWA-94] Code cleanup and remove context
Jul 31, 2023
7fb1c68
[SWA-94] Bug Fix
Jul 31, 2023
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ mochawesome-report
**/generated/**/*
!src/analytics/generated/**/*
!src/pages/Swap/LimitOrderBox/generated/**/*
!src/pages/Swap/LimitOrder/generated/**/*
storybook-static

10 changes: 10 additions & 0 deletions src/pages/Swap/Components/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ export function Tabs() {
Swap
</Button>
<LimitOrderTab className={activeTab === SwapTab.LIMIT_ORDER ? 'active' : ''} setActiveTab={setActiveTab} />

<Button
onClick={() => setActiveTab(SwapTab.LIMIT_ORDER_NEW)}
title="Limit Order"
className={activeTab === SwapTab.LIMIT_ORDER_NEW ? 'active' : ''}
>
<StyledSliders height={11} />
{t('tabs.limit')}
</Button>

<Button
title="Bridge Swap"
onClick={() => {
Expand Down
33 changes: 33 additions & 0 deletions src/pages/Swap/LimitOrder/Components/ApprovalFlow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ButtonPrimary } from '../../../../components/Button'
import { Loader } from '../../../../components/Loader'
import ProgressSteps from '../../../../components/ProgressSteps'
import { ApprovalState } from '../../../../hooks/useApproveCallback'

import { AutoRow } from './AutoRow'

interface ApprovalFlowProps {
approval: ApprovalState
approveCallback: () => Promise<void>
tokenInSymbol: string
}

export const ApprovalFlow = ({ approval, approveCallback, tokenInSymbol }: ApprovalFlowProps) => (
<>
<ButtonPrimary
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED}
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader />
</AutoRow>
) : (
'Approve ' + tokenInSymbol
)}
</ButtonPrimary>
<div style={{ marginTop: '1rem' }}>
<ProgressSteps steps={[approval === ApprovalState.APPROVED]} />
</div>
</>
)
12 changes: 12 additions & 0 deletions src/pages/Swap/LimitOrder/Components/AutoRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import styled from 'styled-components'

import { AutoRow as AutoRowBase } from '../../../../components/Row'

export const AutoRow = styled(AutoRowBase)`
gap: 12px;
justify-items: space-between;
flex-wrap: nowrap;
> div {
width: 50%;
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import styled, { useTheme } from 'styled-components'

import { ButtonPrimary } from '../../../../../components/Button'
import { StyledKey, StyledValue } from '../../../Components/SwapModalFooter'

export type FooterData = {
askPrice: string
marketPriceDifference: string
isDiffPositive: boolean
expiresIn: string
market: string
onConfirm: () => void
}

export const ConfirmationFooter = ({
askPrice,
onConfirm,
expiresIn,
marketPriceDifference,
isDiffPositive,
market,
}: FooterData) => {
const theme = useTheme()
const priceDiffColor = isDiffPositive ? theme.green1 : theme.red1

return (
<Wrapper>
<SingleRow>
<StyledKey>Ask price</StyledKey>
<StyledValue>{askPrice}</StyledValue>
</SingleRow>
<SingleRow>
<StyledKey>Diff. market price</StyledKey>
<StyledValue color={priceDiffColor}>{marketPriceDifference}%</StyledValue>
</SingleRow>
<SingleRow>
<StyledKey>Expires in</StyledKey>
<StyledValue> {expiresIn}</StyledValue>
</SingleRow>
<SingleRow>
<StyledKey>Market</StyledKey>
<StyledValue>{market}</StyledValue>
</SingleRow>
<ButtonPrimary marginTop={'20px'} onClick={onConfirm}>
PLACE LIMIT ORDER
</ButtonPrimary>
</Wrapper>
)
}

const Wrapper = styled.div`
display: flex;
gap: 7px;
flex-direction: column;
`
const SingleRow = styled.div`
display: flex;
justify-content: space-between;
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { CurrencyAmount, TokenAmount } from '@swapr/sdk'

import { ArrowDown } from 'react-feather'
import styled from 'styled-components'

import { CurrencyLogo } from '../../../../../components/CurrencyLogo'
import { toFixedSix } from '../utils'

export type HeaderData = {
fiatValueInput: CurrencyAmount | null
fiatValueOutput: CurrencyAmount | null
buyToken: TokenAmount
sellToken: TokenAmount
}

export const ConfirmationHeader = ({ fiatValueInput, fiatValueOutput, buyToken, sellToken }: HeaderData) => {
const fiatInput = fiatValueInput && fiatValueInput.toFixed(2, { groupSeparator: ',' })
const fiatOutput = fiatValueOutput && fiatValueOutput.toFixed(2, { groupSeparator: ',' })

return (
<Wrapper>
<CurrencyAmountContainer>
<CurrencyLogoInfo>
<PurpleText>YOU SWAP</PurpleText>
<LogoWithText>
<CurrencySymbol>{sellToken.currency.symbol}</CurrencySymbol>{' '}
<CurrencyLogo size="20px" currency={sellToken.currency} />
</LogoWithText>
</CurrencyLogoInfo>
<AmountWithUsd>
<Amount>{toFixedSix(Number(sellToken.toExact()))}</Amount>
{fiatInput && <PurpleText>${fiatInput}</PurpleText>}
</AmountWithUsd>
</CurrencyAmountContainer>
<StyledArrow />
<CurrencyAmountContainer>
<CurrencyLogoInfo>
<PurpleText>YOU RECIEVE</PurpleText>
<LogoWithText>
<CurrencySymbol>{buyToken.currency.symbol}</CurrencySymbol>{' '}
<CurrencyLogo size="20px" currency={buyToken.currency} />
</LogoWithText>
</CurrencyLogoInfo>
<AmountWithUsd>
<Amount>{toFixedSix(Number(buyToken.toExact()))}</Amount>
{fiatOutput && <PurpleText>${fiatOutput}</PurpleText>}
</AmountWithUsd>
</CurrencyAmountContainer>
</Wrapper>
)
}

const Wrapper = styled.div`
display: flex;
flex-direction: column;
margin-top: 20px;
`
const CurrencyAmountContainer = styled.div`
display: flex;
padding: 0 19px;
align-items: center;
justify-content: space-between;
height: 82px;
border: 1px solid ${({ theme }) => theme.bg3};
border-radius: 8.72381px;
`
const CurrencySymbol = styled.div`
font-weight: 600;
font-size: 20px;
line-height: 24px;
`
const LogoWithText = styled.div`
display: flex;
align-items: center;
gap: 6px;
`
const CurrencyLogoInfo = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`

const AmountWithUsd = styled.div`
display: flex;
flex-direction: column;
align-items: end;
gap: 4px;
`
const Amount = styled.div`
font-weight: 600;
font-size: 20px;
line-height: 28px;
`
const PurpleText = styled.div`
font-weight: 600;
font-size: 10px;
line-height: 12px;
letter-spacing: 0.08em;
color: ${({ theme }) => theme.purple3};
`
const StyledArrow = styled(ArrowDown)`
width: 100%;
margin: 4px 0;
height: 16px;
`
111 changes: 111 additions & 0 deletions src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { CurrencyAmount } from '@swapr/sdk'

import { useCallback } from 'react'

import TransactionConfirmationModal, {
ConfirmationModalContent,
TransactionErrorContent,
} from '../../../../../components/TransactionConfirmationModal'
import { Kind, LimitOrderBase, MarketPrices, Providers } from '../../../../../services/LimitOrders'
import { calculateMarketPriceDiffPercentage } from '../utils'

import { ConfirmationFooter } from './ConfirmationFooter'
import { ConfirmationHeader } from './ConfirmationHeader'

interface ConfirmLimitOrderModalProps {
isOpen: boolean
attemptingTxn: boolean
errorMessage: string | undefined
onDismiss: () => void
onConfirm: () => void
marketPrices: MarketPrices
fiatValueInput: CurrencyAmount | null
fiatValueOutput: CurrencyAmount | null
protocol: LimitOrderBase
}

export default function ConfirmLimitOrderModal({
onConfirm,
onDismiss,
errorMessage,
isOpen,
attemptingTxn,
marketPrices,
fiatValueInput,
fiatValueOutput,

protocol,
}: ConfirmLimitOrderModalProps) {
const {
buyAmount,
sellAmount,
limitPrice,
expiresAt,
expiresAtUnit,
kind,
limitOrderProtocol = Providers.COW,
} = protocol

const modalHeader = useCallback(() => {
return (
<ConfirmationHeader
fiatValueInput={fiatValueInput}
fiatValueOutput={fiatValueOutput}
buyToken={buyAmount}
sellToken={sellAmount}
/>
)
}, [fiatValueInput, fiatValueOutput, buyAmount, sellAmount])

const [baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount]
const askPrice = `${kind} ${baseTokenAmount?.currency?.symbol} at ${limitPrice} ${quoteTokenAmount?.currency?.symbol}`

let { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage(
kind ?? Kind.Sell,
marketPrices,
limitPrice!
)
const expiresInFormatted = `${expiresAt} ${expiresAtUnit}`

const modalBottom = useCallback(() => {
return onConfirm ? (
<ConfirmationFooter
onConfirm={onConfirm}
askPrice={askPrice}
expiresIn={expiresInFormatted}
marketPriceDifference={marketPriceDiffPercentage.toFixed(2)}
market={`${limitOrderProtocol} Protocol`}
isDiffPositive={isDiffPositive}
/>
) : null
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [marketPriceDiffPercentage, isDiffPositive, onConfirm, askPrice, expiresInFormatted])

// text to show while loading
const pendingText = 'Confirm Signature'

const confirmationContent = useCallback(
() =>
errorMessage ? (
<TransactionErrorContent onDismiss={onDismiss} message={errorMessage} />
) : (
<ConfirmationModalContent
title="Confirm Limit Order"
onDismiss={onDismiss}
topContent={modalHeader}
bottomContent={modalBottom}
/>
),
[onDismiss, modalBottom, modalHeader, errorMessage]
)

return (
<TransactionConfirmationModal
isOpen={isOpen}
onDismiss={onDismiss}
attemptingTxn={attemptingTxn}
content={confirmationContent}
pendingText={pendingText}
/>
)
}
Loading