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

Separate swapVCow code from claim #365

Merged
merged 2 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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