Skip to content

Commit

Permalink
feat: add logic to use /price to avoid reaching API limits
Browse files Browse the repository at this point in the history
  • Loading branch information
Disti4ct committed Feb 29, 2024
1 parent 273403e commit a4fad07
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 104 deletions.
59 changes: 54 additions & 5 deletions src/front/shared/pages/Exchange/QuickSwap/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { FormattedMessage } from 'react-intl'
import { useState } from 'react'
import CSSModules from 'react-css-modules'
import styles from './index.scss'
import utils from 'common/utils'
import ADDRESSES from 'common/helpers/constants/ADDRESSES'
import actions from 'redux/actions'
import { feedback, externalConfig, constants, transactions, routing } from 'helpers'
import { feedback, externalConfig, constants, transactions, routing, apiLooper } from 'helpers'
import { ComponentState, BlockReasons, Actions, Direction } from './types'
import { SWAP_API, GWEI_DECIMALS, COIN_DECIMALS, LIQUIDITY_SOURCE_DATA, SEC_PER_MINUTE } from './constants'
import { buildApiSwapParams, estimateApiSwapData } from './swapApi'
import {
SWAP_API,
GWEI_DECIMALS,
COIN_DECIMALS,
LIQUIDITY_SOURCE_DATA,
SEC_PER_MINUTE,
} from './constants'
import Button from 'components/controls/Button/Button'
import ReviewSwapModal from './ReviewSwapModal'

type FooterProps = {
parentState: ComponentState
Expand Down Expand Up @@ -60,8 +69,40 @@ function Footer(props: FooterProps) {
error,
slippage,
currentLiquidityPair,
serviceFee,
zeroxApiKey,
} = parentState

const [finalizeSwap, setFinalizeSwap] = useState<boolean>(false)
const [quote, setQuote] = useState<Record<string, string | number> | undefined>(undefined)

const startSwapReview = async () => {
setFinalizeSwap(true)

const { headers, endpoint } = buildApiSwapParams({
route: '/quote',
slippage,
spendedAmount,
fromWallet,
toWallet,
serviceFee,
zeroxApiKey,
})
const rawQuote: any = await apiLooper.get(SWAP_API[network.networkVersion].name, endpoint, {
headers,
sourceError: true,
reportErrors: (error: IError) => {},
})
const data = await estimateApiSwapData({
data: rawQuote,
baseChainWallet,
toWallet,
gasLimit,
gasPrice,
})
setQuote(data.swapData)
}

const approve = async (direction) => {
const spender: `0x${number}` = isSourceMode
? LIQUIDITY_SOURCE_DATA[network.networkVersion]?.router
Expand Down Expand Up @@ -227,7 +268,11 @@ function Footer(props: FooterProps) {

const doNotMakeApiRequest = isApiRequestBlocking()

const commonBlockReasons = isPending || (blockReason !== BlockReasons.NotApproved && !!error && (!error.message?.match('transfer amount exceeds allowance')))
const commonBlockReasons =
isPending ||
(blockReason !== BlockReasons.NotApproved &&
!!error &&
!error.message?.match('transfer amount exceeds allowance'))
const formFilled = !!spendedAmount && !!receivedAmount

const approvingDoesNotMakeSense =
Expand All @@ -252,6 +297,9 @@ function Footer(props: FooterProps) {

return (
<div styleName="footer">
{finalizeSwap && (
<ReviewSwapModal data={quote} onSwap={apiSwap} onClose={() => setFinalizeSwap(false)} />
)}
{needApproveA ? (
<Button
pending={isPending}
Expand Down Expand Up @@ -281,8 +329,9 @@ function Footer(props: FooterProps) {
/>
</Button>
) : !isSourceMode ? (
<Button pending={isPending} disabled={!apiSwapIsAvailable} onClick={apiSwap} brand>
<FormattedMessage id="swap" defaultMessage="Swap" />
<Button pending={isPending} disabled={!apiSwapIsAvailable} onClick={startSwapReview} brand>
Review swap
<FormattedMessage id="reviewSwap" defaultMessage="Review swap" />
</Button>
) : sourceAction === Actions.Swap ? (
<Button pending={isPending} disabled={!directSwapIsAvailable} onClick={directSwap} brand>
Expand Down
3 changes: 3 additions & 0 deletions src/front/shared/pages/Exchange/QuickSwap/ReviewSwapModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ReviewSwapModal() {
return <div className="swapPreviewModal">Review swap here</div>
}
4 changes: 4 additions & 0 deletions src/front/shared/pages/Exchange/QuickSwap/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ $maxWidth: 38em;
margin-bottom: 0.5rem;
}

.swapPreviewModal {
color: green;
}

@media all and (max-width: 500px) {
.newTokenInstruction {
font-size: 0.95rem;
Expand Down
143 changes: 44 additions & 99 deletions src/front/shared/pages/Exchange/QuickSwap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { localisedUrl } from 'helpers/locale'
import actions from 'redux/actions'
import Link from 'local_modules/sw-valuelink'
import { buildApiSwapParams, estimateApiSwapData } from './swapApi'
import {
ComponentState,
Direction,
Expand Down Expand Up @@ -503,56 +504,6 @@ class QuickSwap extends PureComponent<IUniversalObj, ComponentState> {
}))
}

buildSwapParams = (route: '/price' | '/quote', skipValidation = false) => {
const { slippage, spendedAmount, fromWallet, toWallet, serviceFee, zeroxApiKey } = this.state

const sellToken = fromWallet?.contractAddress || EVM_COIN_ADDRESS
const buyToken = toWallet?.contractAddress || EVM_COIN_ADDRESS

const sellAmount = utils.amount.formatWithDecimals(
spendedAmount,
fromWallet.decimals || COIN_DECIMALS,
)

const enoughBalanceForSwap = new BigNumber(fromWallet.balance).isGreaterThan(new BigNumber(spendedAmount))

const request = [
`/swap/v1${route}?`,
`buyToken=${buyToken}&`,
`sellToken=${sellToken}&`,
`sellAmount=${sellAmount}`,
]
if (enoughBalanceForSwap) {
request.push(`&takerAddress=${fromWallet.address}`)
}

if (window?.STATISTICS_ENABLED) {
request.push(`&affiliateAddress=${externalConfig.swapContract.affiliateAddress}`)
}

if (serviceFee) {
const { address, percent } = serviceFee
request.push(`&feeRecipient=${address}`)
request.push(`&buyTokenPercentageFee=${percent}`)
}

if (skipValidation) {
request.push(`&skipValidation=true`)
}

if (slippage) {
// allow users to enter an amount up to 100, because it's more easy then enter the amount from 0 to 1
// and now convert it into the api format
const correctValue = new BigNumber(slippage).dividedBy(MAX_PERCENT)
request.push(`&slippagePercentage=${correctValue}`)
}

return {
headers: { '0x-api-key': zeroxApiKey },
endpoint: request.join(''),
}
}

onInputDataChange = async () => {
const { activeSection, sourceAction, currentLiquidityPair } = this.state

Expand All @@ -572,7 +523,7 @@ class QuickSwap extends PureComponent<IUniversalObj, ComponentState> {
}))

if (activeSection === Sections.Aggregator) {
await this.fetchSwapAPIData()
await this.fetchApiSwapPrice()
} else if (activeSection === Sections.Source) {
await this.processingSourceActions()
// start approve check only after the received amount request in processingSourceActions()
Expand Down Expand Up @@ -603,46 +554,21 @@ class QuickSwap extends PureComponent<IUniversalObj, ComponentState> {
return false
}

calculateDataFromSwap = async (params) => {
const { baseChainWallet, toWallet, gasLimit, gasPrice } = this.state
const { swap, withoutValidation } = params

// we've had a special error in the previous request. It means there is
// some problem and we add a "skip validation" parameter to bypass it.
// Usually the swap tx with this parameter fails in the blockchain,
// because it's not enough gas limit. Estimate it by yourself
if (withoutValidation) {
const estimatedGas = await actions[baseChainWallet.currency.toLowerCase()]?.estimateGas(swap)

if (typeof estimatedGas === 'number') {
swap.gas = estimatedGas
} else if (estimatedGas instanceof Error) {
this.reportError(estimatedGas)
}
}

const customGasLimit = gasLimit && gasLimit > swap.gas ? gasLimit : swap.gas
const customGasPrice = gasPrice
? utils.amount.formatWithDecimals(gasPrice, GWEI_DECIMALS)
: swap.gasPrice

const weiFee = new BigNumber(customGasLimit).times(customGasPrice)
const swapFee = utils.amount.formatWithoutDecimals(weiFee, COIN_DECIMALS)
const receivedAmount = utils.amount.formatWithoutDecimals(
swap.buyAmount,
toWallet?.decimals || COIN_DECIMALS,
)

this.setState(() => ({
receivedAmount,
swapData: swap,
swapFee,
isPending: false,
}))
}

fetchSwapAPIData = async () => {
const { isSourceMode, network, spendedAmount, isPending, zeroxApiKey } = this.state
fetchApiSwapPrice = async () => {
const {
isSourceMode,
network,
spendedAmount,
isPending,
zeroxApiKey,
slippage,
fromWallet,
toWallet,
serviceFee,
baseChainWallet,
gasLimit,
gasPrice,
} = this.state

if (!isSourceMode && !zeroxApiKey) {
return console.log('%c0x API key is not set', 'color:red')
Expand All @@ -662,8 +588,15 @@ class QuickSwap extends PureComponent<IUniversalObj, ComponentState> {
}))

let repeatRequest = true
// TODO Send basic requests to /price to avoid exceeding API limits
const params = this.buildSwapParams('/quote')
const params = buildApiSwapParams({
route: '/price',
slippage,
spendedAmount,
fromWallet,
toWallet,
serviceFee,
zeroxApiKey,
})
let { headers, endpoint } = params

while (repeatRequest) {
Expand All @@ -677,18 +610,30 @@ class QuickSwap extends PureComponent<IUniversalObj, ComponentState> {
},
})

console.table(swap)

if (!(swap instanceof Error)) {
repeatRequest = false

await this.calculateDataFromSwap({
swap,
const data = await estimateApiSwapData({
data: swap,
withoutValidation: endpoint.match(/skipValidation/),
baseChainWallet,
toWallet,
gasLimit,
gasPrice,
onError: this.reportError,
})
this.setState(() => ({ ...data }))
} else if (this.tryToSkipValidation(swap)) {
// it's a special error. Will be a new request
const p = this.buildSwapParams('/quote', true)
const p = buildApiSwapParams({
route: '/price',
skipValidation: true,
slippage,
spendedAmount,
fromWallet,
toWallet,
serviceFee,
zeroxApiKey,
})
headers = p.headers
endpoint = p.endpoint
} else {
Expand Down
Loading

0 comments on commit a4fad07

Please sign in to comment.