Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
Added swap vCow for Cow hook (#2577)
Browse files Browse the repository at this point in the history
* Added swap vCow for Cow hook

* Display 0 as vested while the swap transaction is mined

* Small PR suggested change

* Fix for failed convert vCOW tx

* Updated the convert confirmation modal
  • Loading branch information
nenadV91 authored Mar 23, 2022
1 parent fde86c4 commit 5c9a914
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 10 deletions.
5 changes: 5 additions & 0 deletions src/custom/components/TransactionConfirmationModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ export enum OperationType {
REVOKE_APPROVE_TOKEN,
ORDER_SIGN,
ORDER_CANCEL,
CONVERT_VCOW,
}

function getWalletNameLabel(walletType: WalletType): string {
Expand Down Expand Up @@ -385,6 +386,8 @@ function getOperationMessage(operationType: OperationType, chainId: number): str
return 'Soft canceling your order'
case OperationType.REVOKE_APPROVE_TOKEN:
return 'Revoking token approval'
case OperationType.CONVERT_VCOW:
return 'Converting vCOW to COW'

default:
return 'Almost there!'
Expand All @@ -405,6 +408,8 @@ function getOperationLabel(operationType: OperationType): string {
return t`order`
case OperationType.ORDER_CANCEL:
return t`cancellation`
case OperationType.CONVERT_VCOW:
return t`vCOW conversion`
}
}

Expand Down
69 changes: 63 additions & 6 deletions src/custom/pages/Profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useCallback } from 'react'
import { Txt } from 'assets/styles/styled'
import {
FlexCol,
Expand Down Expand Up @@ -44,6 +45,12 @@ import { useTokenBalance } from 'state/wallet/hooks'
import { useVCowData } from 'state/claim/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'

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

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

// Cow balance
const cow = useTokenBalance(account || undefined, chainId ? COW[chainId] : undefined)

Expand All @@ -64,8 +74,46 @@ export default function Profile() {
const vCowBalanceUnvested = formatSmartLocaleAware(unvested, AMOUNT_PRECISION) || '0'
const vCowBalance = formatSmartLocaleAware(total, AMOUNT_PRECISION) || '0'

const hasCowBalance = cow && !cow.equalTo(0)
const hasVCowBalance = total && !total.equalTo(0)
const hasCowBalance = cow && !cow?.equalTo(0)
const hasVCowBalance = total && !total?.equalTo(0)
const hasVestedBalance = vested && !vested?.equalTo(0)

// Init modal hooks
const { handleSetError, handleCloseError, ErrorModal } = useErrorModal()
const { TransactionConfirmationModal, openModal, closeModal } = useTransactionConfirmationModal(
OperationType.CONVERT_VCOW
)

// Boolean flags
const isSwapPending = swapVCowStatus === SwapVCowStatus.SUBMITTED
const isSwapInitial = swapVCowStatus === SwapVCowStatus.INITIAL
const isSwapDisabled = Boolean(!hasVestedBalance || !isSwapInitial || isSwapPending)

// Handle swaping
const { swapCallback } = useSwapVCowCallback({
openModal,
closeModal,
})

const handleVCowSwap = useCallback(async () => {
handleCloseError()

if (!swapCallback) {
return
}

setSwapVCowStatus(SwapVCowStatus.ATTEMPTING)

swapCallback()
.then(() => {
setSwapVCowStatus(SwapVCowStatus.SUBMITTED)
})
.catch((error) => {
console.error('[Profile::index::swapVCowCallback]::error', error)
setSwapVCowStatus(SwapVCowStatus.INITIAL)
handleSetError(error?.message)
})
}, [handleCloseError, handleSetError, setSwapVCowStatus, swapCallback])

const tooltipText = {
balanceBreakdown: (
Expand All @@ -74,7 +122,7 @@ export default function Profile() {
<i>Unvested</i> <p>{vCowBalanceUnvested} vCOW</p>
</span>
<span>
<i>Vested</i> <p>{vCowBalanceVested} vCOW</p>
<i>Vested</i> <p>{!isSwapPending ? vCowBalanceVested : '0'} vCOW</p>
</span>
</VestingBreakdown>
),
Expand Down Expand Up @@ -109,6 +157,9 @@ export default function Profile() {

return (
<Container>
<TransactionConfirmationModal />
<ErrorModal />

{chainId && chainId === ChainId.MAINNET && <AffiliateStatusCheck />}
<Title>Profile</Title>

Expand All @@ -135,10 +186,16 @@ export default function Profile() {
<HelpCircle size={14} />
</MouseoverTooltipContent>
</i>
<b>{vCowBalanceVested}</b>
<b>{!isSwapPending ? vCowBalanceVested : '0'}</b>
</BalanceDisplay>
<ButtonPrimary>
Convert to COW <SVG src={ArrowIcon} />
<ButtonPrimary onClick={handleVCowSwap} disabled={isSwapDisabled}>
{isSwapPending ? (
'Converting vCOW...'
) : (
<>
Convert to COW <SVG src={ArrowIcon} />
</>
)}
</ButtonPrimary>
</ConvertWrapper>
</Card>
Expand Down
11 changes: 11 additions & 0 deletions src/custom/state/claim/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export enum ClaimStatus {
FAILED = 'FAILED',
}

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

export type ClaimActions = {
// account
setInputAddress: (payload: string) => void
Expand All @@ -35,6 +41,9 @@ export type ClaimActions = {
// claim row selection
setSelected: (payload: number[]) => void
setSelectedAll: (payload: boolean) => void

// swap vCow for Cow
setSwapVCowStatus: (payload: SwapVCowStatus) => void
}

// accounts
Expand Down Expand Up @@ -77,3 +86,5 @@ export const setClaimsCount = createAction<{
claimInfo: Partial<ClaimInfo>
account: string
}>('claims/setClaimsCount')
// swap vCow
export const setSwapVCowStatus = createAction<SwapVCowStatus>('claim/setSwapVCowStatus')
67 changes: 67 additions & 0 deletions src/custom/state/claim/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@ import {
setEstimatedGas,
setIsTouched,
setClaimsCount,
setSwapVCowStatus,
SwapVCowStatus,
} from '../actions'
import { EnhancedUserClaimData } from 'pages/Claim/types'
import { supportedChainId } from 'utils/supportedChainId'
import { AMOUNT_PRECISION } from 'constants/index'
import useIsMounted from 'hooks/useIsMounted'
import { ChainId } from '@uniswap/sdk'
import { ClaimInfo } from 'state/claim/reducer'
import { OperationType } from '@src/custom/components/TransactionConfirmationModal'
import { APPROVE_GAS_LIMIT_DEFAULT } from 'hooks/useApproveCallback/useApproveCallbackMod'

// TODO: Change back this to commented line before it goes to production
// const CLAIMS_REPO_BRANCH = 'gip-13'
Expand Down Expand Up @@ -873,6 +877,7 @@ export function useClaimDispatchers() {
// has claims on other chains
setClaimsCount: (payload: { chain: SupportedChainId; claimInfo: ClaimInfo; account: string }) =>
dispatch(setClaimsCount(payload)),
setSwapVCowStatus: (payload: SwapVCowStatus) => dispatch(setSwapVCowStatus(payload)),
}),
[dispatch]
)
Expand Down Expand Up @@ -1061,3 +1066,65 @@ export function useVCowData(): VCowData {

return { isLoading, vested, unvested, total }
}

/**
* Hook used to swap vCow to Cow token
*/

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

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 = `Converting 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,
}
}
11 changes: 10 additions & 1 deletion src/custom/state/claim/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isAnyOf, Middleware } from '@reduxjs/toolkit'
import { getCowSoundSend, getCowSoundSuccessClaim } from 'utils/sound'
import { AppState } from 'state'
import { addTransaction, finalizeTransaction } from '../enhancedTransactions/actions'
import { ClaimStatus, setClaimStatus } from './actions'
import { ClaimStatus, setClaimStatus, setSwapVCowStatus, SwapVCowStatus } from './actions'

const isFinalizeTransaction = isAnyOf(finalizeTransaction)
const isAddTransaction = isAnyOf(addTransaction)
Expand Down Expand Up @@ -39,6 +39,15 @@ export const claimMinedMiddleware: Middleware<Record<string, unknown>, AppState>
// not success...
store.dispatch(setClaimStatus(ClaimStatus.FAILED))
}
} else if (transaction.swapVCow) {
const status = transaction.receipt?.status

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

store.dispatch(setSwapVCowStatus(SwapVCowStatus.INITIAL))
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/custom/state/claim/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
setEstimatedGas,
setIsTouched,
setClaimsCount,
SwapVCowStatus,
setSwapVCowStatus,
} from './actions'

export type ClaimInfo = {
Expand Down Expand Up @@ -64,6 +66,8 @@ export const initialState: ClaimState = {
selectedAll: false,
// claims on other networks
claimInfoPerAccount: { ...DEFAULT_CLAIM_INFO_PER_ACCOUNT },
// swap VCow status
swapVCowStatus: SwapVCowStatus.INITIAL,
}

export type InvestClaim = {
Expand Down Expand Up @@ -94,6 +98,8 @@ export type ClaimState = {
selectedAll: boolean
// claims on other chains
claimInfoPerAccount: ClaimInfoPerAccount
// swap VCow status
swapVCowStatus: SwapVCowStatus
}

export default createReducer(initialState, (builder) =>
Expand Down Expand Up @@ -180,4 +186,7 @@ export default createReducer(initialState, (builder) =>
.addCase(setIsTouched, (state, { payload: { index, isTouched } }) => {
state.investFlowData[index].isTouched = isTouched
})
.addCase(setSwapVCowStatus, (state, { payload }) => {
state.swapVCowStatus = payload
})
)
2 changes: 1 addition & 1 deletion src/custom/state/enhancedTransactions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type AddTransactionParams = WithChainId &
WithData &
Pick<
EnhancedTransactionDetails,
'hash' | 'hashType' | 'from' | 'approval' | 'presign' | 'claim' | 'summary' | 'safeTransaction'
'hash' | 'hashType' | 'from' | 'approval' | 'presign' | 'claim' | 'summary' | 'safeTransaction' | 'swapVCow'
>

export const addTransaction = createAction<AddTransactionParams>('enhancedTransactions/addTransaction')
Expand Down
3 changes: 2 additions & 1 deletion src/custom/state/enhancedTransactions/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function useTransactionAdder(): TransactionAdder {
(addTransactionParams: AddTransactionHookParams) => {
if (!account || !chainId) return

const { hash, summary, data, claim, approval, presign, safeTransaction } = addTransactionParams
const { hash, summary, data, claim, approval, presign, safeTransaction, swapVCow } = addTransactionParams
const hashType = isGnosisSafeWallet ? HashType.GNOSIS_SAFE_TX : HashType.ETHEREUM_TX
if (!hash) {
throw Error('No transaction hash found')
Expand All @@ -41,6 +41,7 @@ export function useTransactionAdder(): TransactionAdder {
data,
presign,
safeTransaction,
swapVCow,
})
)
},
Expand Down
18 changes: 17 additions & 1 deletion src/custom/state/enhancedTransactions/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface EnhancedTransactionDetails {
approval?: { tokenAddress: string; spender: string }
presign?: { orderId: string }
claim?: { recipient: string; cowAmountRaw?: string; indices: number[] }
swapVCow?: boolean

// Wallet specific
safeTransaction?: SafeMultisigTransactionResponse // Gnosis Safe transaction info
Expand Down Expand Up @@ -69,7 +70,21 @@ export default createReducer(initialState, (builder) =>
addTransaction,
(
transactions,
{ payload: { chainId, from, hash, hashType, approval, summary, presign, safeTransaction, claim, data } }
{
payload: {
chainId,
from,
hash,
hashType,
approval,
summary,
presign,
safeTransaction,
claim,
data,
swapVCow,
},
}
) => {
if (transactions[chainId]?.[hash]) {
console.warn('[state::enhancedTransactions] Attempted to add existing transaction', hash)
Expand All @@ -91,6 +106,7 @@ export default createReducer(initialState, (builder) =>
presign,
safeTransaction,
claim,
swapVCow,
}
transactions[chainId] = txs
}
Expand Down

0 comments on commit 5c9a914

Please sign in to comment.