Skip to content

Commit

Permalink
Separate swapVCow code from claim (#365)
Browse files Browse the repository at this point in the history
* Separate swapVCow code from claim

* Updated names
  • Loading branch information
nenadV91 authored Apr 5, 2022
1 parent 3681563 commit c2271be
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/custom/hooks/useCowBalanceAndSubsidy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { JSBI } from '@uniswap/sdk'
import { CurrencyAmount } from '@uniswap/sdk-core'

import { getDiscountFromBalance } from 'components/CowSubsidyModal/utils'
import { useVCowData } from 'state/claim/hooks'
import { useVCowData } from 'state/cowToken/hooks'
import { useTokenBalance } from 'state/wallet/hooks'
import { useActiveWeb3React } from '.'

Expand Down
5 changes: 2 additions & 3 deletions src/custom/pages/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import AppMod from './AppMod'
import styled from 'styled-components/macro'
import { RedirectPathToSwapOnly, RedirectToSwap } from 'pages/Swap/redirects'
import { Suspense, lazy } from 'react'
import { Route, Switch } from 'react-router-dom'
import { Redirect, Route, Switch } from 'react-router-dom'

import AnySwapAffectedUsers from 'pages/error/AnySwapAffectedUsers'
import * as Sentry from '@sentry/react'
Expand All @@ -17,7 +17,6 @@ const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN
const SENTRY_TRACES_SAMPLE_RATE = process.env.REACT_APP_SENTRY_TRACES_SAMPLE_RATE

const Swap = lazy(() => import(/* webpackPrefetch: true, webpackChunkName: "swap" */ 'pages/Swap'))
const Claim = lazy(() => import(/* webpackChunkName: "claim" */ 'pages/Claim'))
const PrivacyPolicy = lazy(() => import(/* webpackChunkName: "privacy_policy" */ 'pages/PrivacyPolicy'))
const CookiePolicy = lazy(() => import(/* webpackChunkName: "cookie_policy" */ 'pages/CookiePolicy'))
const TermsAndConditions = lazy(() => import(/* webpackChunkName: "terms" */ 'pages/TermsAndConditions'))
Expand Down Expand Up @@ -99,10 +98,10 @@ export default function App() {
<Wrapper>
<Suspense fallback={Loading}>
<Switch>
<Redirect from="/claim" to="/profile" />
<Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/claim" component={Claim} />
<Route exact strict path="/about" component={About} />
<Route exact strict path="/profile" component={Profile} />

Expand Down
10 changes: 4 additions & 6 deletions src/custom/pages/Profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,13 @@ import ArrowIcon from 'assets/cow-swap/arrow.svg'
import CowImage from 'assets/cow-swap/cow_v2.svg'
import CowProtocolImage from 'assets/cow-swap/cowprotocol.svg'
import { useTokenBalance } from 'state/wallet/hooks'
import { useVCowData } from 'state/claim/hooks'
import { useVCowData, useSwapVCowCallback, useSetSwapVCowStatus, useSwapVCowStatus } from 'state/cowToken/hooks'
import { AMOUNT_PRECISION } from 'constants/index'
import { COW } from 'constants/tokens'
import { useErrorModal } from 'hooks/useErrorMessageAndModal'
import { OperationType } from 'components/TransactionConfirmationModal'
import useTransactionConfirmationModal from 'hooks/useTransactionConfirmationModal'
import { useClaimDispatchers, useClaimState } from 'state/claim/hooks'
import { SwapVCowStatus } from 'state/claim/actions'
import { useSwapVCowCallback } from 'state/claim/hooks'
import { SwapVCowStatus } from 'state/cowToken/actions'

const COW_DECIMALS = COW[ChainId.MAINNET].decimals

Expand All @@ -62,8 +60,8 @@ export default function Profile() {
const isTradesTooltipVisible = account && chainId == 1 && !!profileData?.totalTrades
const hasOrders = useHasOrders(account)

const { setSwapVCowStatus } = useClaimDispatchers()
const { swapVCowStatus } = useClaimState()
const setSwapVCowStatus = useSetSwapVCowStatus()
const swapVCowStatus = useSwapVCowStatus()

// Cow balance
const cow = useTokenBalance(account || undefined, chainId ? COW[chainId] : undefined)
Expand Down
13 changes: 13 additions & 0 deletions src/custom/state/cowToken/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createAction } from '@reduxjs/toolkit'

export enum SwapVCowStatus {
INITIAL = 'INITIAL',
ATTEMPTING = 'ATTEMPTING',
SUBMITTED = 'SUBMITTED',
}

export type CowTokenActions = {
setSwapVCowStatus: (payload: SwapVCowStatus) => void
}

export const setSwapVCowStatus = createAction<SwapVCowStatus>('cowToken/setSwapVCowStatus')
152 changes: 152 additions & 0 deletions src/custom/state/cowToken/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { useCallback, useMemo } from 'react'

import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { TransactionResponse } from '@ethersproject/providers'

import { useVCowContract } from 'hooks/useContract'
import { useActiveWeb3React } from 'hooks/web3'
import { useSingleCallResult, Result } from 'state/multicall/hooks'
import { useTransactionAdder } from 'state/enhancedTransactions/hooks'
import { V_COW } from 'constants/tokens'
import { AppState } from 'state'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { setSwapVCowStatus, SwapVCowStatus } from './actions'
import { OperationType } from 'components/TransactionConfirmationModal'
import { APPROVE_GAS_LIMIT_DEFAULT } from 'hooks/useApproveCallback/useApproveCallbackMod'

export type SetSwapVCowStatusCallback = (payload: SwapVCowStatus) => void

type VCowData = {
isLoading: boolean
total: CurrencyAmount<Currency> | undefined | null
unvested: CurrencyAmount<Currency> | undefined | null
vested: CurrencyAmount<Currency> | undefined | null
}

interface SwapVCowCallbackParams {
openModal: (message: string, operationType: OperationType) => void
closeModal: () => void
}

/**
* Hook that parses the result input with BigNumber value to CurrencyAmount
*/
function useParseVCowResult(result: Result | undefined) {
const { chainId } = useActiveWeb3React()

const vCowToken = chainId ? V_COW[chainId] : undefined

return useMemo(() => {
if (!chainId || !vCowToken || !result) {
return
}

return CurrencyAmount.fromRawAmount(vCowToken, result[0].toString())
}, [chainId, result, vCowToken])
}

/**
* Hook that fetches the needed vCow data and returns it in VCowData type
*/
export function useVCowData(): VCowData {
const vCowContract = useVCowContract()
const { account } = useActiveWeb3React()

const { loading: isVestedLoading, result: vestedResult } = useSingleCallResult(vCowContract, 'swappableBalanceOf', [
account ?? undefined,
])
const { loading: isTotalLoading, result: totalResult } = useSingleCallResult(vCowContract, 'balanceOf', [
account ?? undefined,
])

const vested = useParseVCowResult(vestedResult)
const total = useParseVCowResult(totalResult)

const unvested = useMemo(() => {
if (!total || !vested) {
return null
}

// Check if total < vested, if it is something is probably wrong and we return null
if (total.lessThan(vested)) {
return null
}

return total.subtract(vested)
}, [total, vested])

const isLoading = isVestedLoading || isTotalLoading

return { isLoading, vested, unvested, total }
}

/**
* Hook used to swap vCow to Cow token
*/
export function useSwapVCowCallback({ openModal, closeModal }: SwapVCowCallbackParams) {
const { chainId, account } = useActiveWeb3React()
const vCowContract = useVCowContract()

const addTransaction = useTransactionAdder()
const vCowToken = chainId ? V_COW[chainId] : undefined

const swapCallback = useCallback(async () => {
if (!account) {
throw new Error('Not connected')
}
if (!chainId) {
throw new Error('No chainId')
}
if (!vCowContract) {
throw new Error('vCOW contract not present')
}
if (!vCowToken) {
throw new Error('vCOW token not present')
}

const estimatedGas = await vCowContract.estimateGas.swapAll({ from: account }).catch(() => {
// general fallback for tokens who restrict approval amounts
return vCowContract.estimateGas.swapAll().catch((error) => {
console.log(
'[useSwapVCowCallback] Error estimating gas for swapAll. Using default gas limit ' +
APPROVE_GAS_LIMIT_DEFAULT.toString(),
error
)
return APPROVE_GAS_LIMIT_DEFAULT
})
})

const summary = `Convert vCOW to COW`
openModal(summary, OperationType.CONVERT_VCOW)

return vCowContract
.swapAll({ from: account, gasLimit: estimatedGas })
.then((tx: TransactionResponse) => {
addTransaction({
swapVCow: true,
hash: tx.hash,
summary,
})
})
.finally(closeModal)
}, [account, addTransaction, chainId, closeModal, openModal, vCowContract, vCowToken])

return {
swapCallback,
}
}

/**
* Hook that sets the swap vCow->Cow status
*/
export function useSetSwapVCowStatus(): SetSwapVCowStatusCallback {
const dispatch = useAppDispatch()
return useCallback((payload: SwapVCowStatus) => dispatch(setSwapVCowStatus(payload)), [dispatch])
}

/**
* Hook that gets swap vCow->Cow status
*/
export function useSwapVCowStatus() {
return useAppSelector((state: AppState) => state.cowToken.swapVCowStatus)
}
39 changes: 39 additions & 0 deletions src/custom/state/cowToken/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isAnyOf, Middleware } from '@reduxjs/toolkit'
import { AppState } from 'state'
import { finalizeTransaction } from '../enhancedTransactions/actions'
import { setSwapVCowStatus, SwapVCowStatus } from './actions'
import { getCowSoundSuccess } from 'utils/sound'

const isFinalizeTransaction = isAnyOf(finalizeTransaction)

// Watch for swapVCow tx being finalized and triggers a change of status
export const cowTokenMiddleware: Middleware<Record<string, unknown>, AppState> = (store) => (next) => (action) => {
const result = next(action)

let cowSound

if (isFinalizeTransaction(action)) {
const { chainId, hash } = action.payload
const transaction = store.getState().transactions[chainId][hash]

if (transaction.swapVCow) {
const status = transaction.receipt?.status

console.debug(
`[stat:swapVCow:middleware] Convert vCOW to COW transaction finalized with status ${status}`,
transaction.hash
)

store.dispatch(setSwapVCowStatus(SwapVCowStatus.INITIAL))
cowSound = getCowSoundSuccess()
}
}

if (cowSound) {
cowSound.play().catch((e) => {
console.error('🐮 [middleware::swapVCow] Moooooo cannot be played', e)
})
}

return result
}
16 changes: 16 additions & 0 deletions src/custom/state/cowToken/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createReducer } from '@reduxjs/toolkit'
import { SwapVCowStatus, setSwapVCowStatus } from './actions'

export type CowTokenState = {
swapVCowStatus: SwapVCowStatus
}

export const initialState: CowTokenState = {
swapVCowStatus: SwapVCowStatus.INITIAL,
}

export default createReducer(initialState, (builder) =>
builder.addCase(setSwapVCowStatus, (state, { payload }) => {
state.swapVCowStatus = payload
})
)
6 changes: 4 additions & 2 deletions src/custom/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ import { updateVersion } from 'state/global/actions'
import affiliate from 'state/affiliate/reducer'
import enhancedTransactions from 'state/enhancedTransactions/reducer'
import claim from 'state/claim/reducer'
import cowToken from 'state/cowToken/reducer'

import { popupMiddleware, soundMiddleware } from './orders/middleware'
import { claimMinedMiddleware } from './claim/middleware'
import { cowTokenMiddleware } from 'state/cowToken/middleware'
import { DEFAULT_NETWORK_FOR_LISTS } from 'constants/lists'

const UNISWAP_REDUCERS = {
Expand Down Expand Up @@ -54,6 +55,7 @@ const reducers = {
affiliate,
profile,
claim,
cowToken,
}

const PERSISTED_KEYS: string[] = ['user', 'transactions', 'orders', 'lists', 'gas', 'affiliate', 'profile']
Expand All @@ -66,7 +68,7 @@ const store = configureStore({
// .concat(routingApi.middleware)
.concat(save({ states: PERSISTED_KEYS, debounce: 1000 }))
.concat(popupMiddleware)
.concat(claimMinedMiddleware)
.concat(cowTokenMiddleware)
.concat(soundMiddleware),
preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: process.env.NODE_ENV === 'test' }),
})
Expand Down

0 comments on commit c2271be

Please sign in to comment.