From 39e186759ba660fbd3c31d0d276ef66d2646bc6b Mon Sep 17 00:00:00 2001 From: Nenad Vracar <34926005+nenadV91@users.noreply.github.com> Date: Fri, 29 Apr 2022 19:28:53 +0200 Subject: [PATCH] Locked gno claim 2 (#405) * locked GNO vesting contract addresses and merkle proof data strip down ABI * fetch balances * some bugfixes * claim flow * cleanup * some cosmetics * chunk up the merkle proof data * improve profile page fluid section layout * remove accidentally committed script * Fix code style issues with Prettier * work around a bug in the transpiler * fix layout issue on small screens * move claim data into extra repo * use path aliases * fix duplicate use of transaction confirmation modal, which is not supported * some minor fixes * Added vCow locked GNO balance to header button * Add MOO sounds to the claim from locked GNO transaction * Add contract link buttons to locked GNO card * Updated locked GNO contract link * use new rinkeby contracts (#407) * Reset confirmed status after some time * Removed some unnecessary code from the merge * Fixed some formatting issues * Add confirmed state for convert vCow to cow * Fix for tooltip styles * Fix for failed transactions * Locked gno claim 2 fixes (#453) * use new rinkeby contracts * fix merkle drop contract address on GC * more specific names * localize vesting start date * improve copy * fix contract address link * Add cow from locked GNO to combined balance (#456) * Add cow from locked GNO to combined balance * Add shimmer effect and handle disconnect * Locked gno claim fixes (#460) * Added some fixes * Update combined balance calculation * Added comments for locked GNO timestamps * Move locked GNO start date to const file * Moved some other locked GNO dates to const file * Added some PR updates * Small update * After rebase fixes * Temporary fix for missing cards on profile page * Fix for governance card link (#489) * Fix for convert button * Fix for locked GNO claim button on wallet change Co-authored-by: Jan-Felix Co-authored-by: Lint Action --- src/custom/abis/TokenDistro.json | 2 +- .../Transaction/ActivityDetails.tsx | 2 +- .../components/CowBalanceButton/index.tsx | 41 +++- .../TransactionConfirmationModal/index.tsx | 6 +- src/custom/constants/index.ts | 12 +- src/custom/constants/tokens/index.ts | 16 ++ src/custom/hooks/useCowBalanceAndSubsidy.ts | 28 +-- .../claimData/gnosisChain.json | 34 ++++ .../LockedGnoVesting/claimData/index.test.ts | 43 +++++ .../LockedGnoVesting/claimData/index.ts | 60 ++++++ .../LockedGnoVesting/claimData/mainnet.json | 18 ++ .../LockedGnoVesting/claimData/rinkeby.json | 159 +++++++++++++++ .../pages/Profile/LockedGnoVesting/hooks.ts | 120 ++++++++++++ .../pages/Profile/LockedGnoVesting/index.tsx | 182 ++++++++++++++++++ src/custom/pages/Profile/VCOWDropdown.tsx | 150 --------------- src/custom/pages/Profile/index.tsx | 100 +++++++--- src/custom/pages/Profile/styled.tsx | 22 ++- src/custom/state/claim/actions.ts | 11 -- src/custom/state/claim/hooks/index.ts | 133 +------------ src/custom/state/claim/middleware.ts | 11 +- src/custom/state/claim/reducer.ts | 9 - src/custom/state/cowToken/actions.ts | 1 + src/custom/state/cowToken/hooks.ts | 34 +++- src/custom/state/cowToken/middleware.ts | 12 +- .../state/enhancedTransactions/actions.ts | 11 +- .../state/enhancedTransactions/hooks/index.ts | 4 +- .../state/enhancedTransactions/reducer.ts | 3 + 27 files changed, 844 insertions(+), 380 deletions(-) create mode 100644 src/custom/pages/Profile/LockedGnoVesting/claimData/gnosisChain.json create mode 100644 src/custom/pages/Profile/LockedGnoVesting/claimData/index.test.ts create mode 100644 src/custom/pages/Profile/LockedGnoVesting/claimData/index.ts create mode 100644 src/custom/pages/Profile/LockedGnoVesting/claimData/mainnet.json create mode 100644 src/custom/pages/Profile/LockedGnoVesting/claimData/rinkeby.json create mode 100644 src/custom/pages/Profile/LockedGnoVesting/hooks.ts create mode 100644 src/custom/pages/Profile/LockedGnoVesting/index.tsx delete mode 100644 src/custom/pages/Profile/VCOWDropdown.tsx diff --git a/src/custom/abis/TokenDistro.json b/src/custom/abis/TokenDistro.json index cfdff2c797..29cd52fd28 100644 --- a/src/custom/abis/TokenDistro.json +++ b/src/custom/abis/TokenDistro.json @@ -30,4 +30,4 @@ "stateMutability": "nonpayable", "type": "function" } -] \ No newline at end of file +] diff --git a/src/custom/components/AccountDetails/Transaction/ActivityDetails.tsx b/src/custom/components/AccountDetails/Transaction/ActivityDetails.tsx index 93c8e03618..9d53c0b277 100644 --- a/src/custom/components/AccountDetails/Transaction/ActivityDetails.tsx +++ b/src/custom/components/AccountDetails/Transaction/ActivityDetails.tsx @@ -232,7 +232,7 @@ export function ActivityDetails(props: { let inputToken = activityDerivedState?.order?.inputToken || null let outputToken = activityDerivedState?.order?.outputToken || null - if (enhancedTransaction?.swapVCow) { + if (enhancedTransaction?.swapVCow || enhancedTransaction?.swapLockedGNOvCow) { inputToken = V_COW[chainId] outputToken = COW[chainId] } diff --git a/src/custom/components/CowBalanceButton/index.tsx b/src/custom/components/CowBalanceButton/index.tsx index d90af3b36f..4265972b37 100644 --- a/src/custom/components/CowBalanceButton/index.tsx +++ b/src/custom/components/CowBalanceButton/index.tsx @@ -1,5 +1,5 @@ import { Trans } from '@lingui/macro' -import styled from 'styled-components/macro' +import styled, { css } from 'styled-components/macro' import CowProtocolLogo from 'components/CowProtocolLogo' import { useCombinedBalance } from 'state/cowToken/hooks' import { ChainId } from 'state/lists/actions/actionsMod' @@ -7,7 +7,7 @@ import { formatMax, formatSmartLocaleAware } from 'utils/format' import { AMOUNT_PRECISION } from 'constants/index' import { COW } from 'constants/tokens' -export const Wrapper = styled.div` +export const Wrapper = styled.div<{ isLoading: boolean }>` ${({ theme }) => theme.card.boxShadow}; color: ${({ theme }) => theme.text1}; padding: 0 12px; @@ -20,6 +20,35 @@ export const Wrapper = styled.div` border-radius: 12px; pointer-events: auto; + ${({ theme, isLoading }) => + isLoading && + css` + overflow: hidden; + &::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + transform: translateX(-100%); + background-image: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0, + ${theme.shimmer1} 20%, + ${theme.shimmer2} 60%, + rgba(255, 255, 255, 0) + ); + animation: shimmer 2s infinite; + content: ''; + } + + @keyframes shimmer { + 100% { + transform: translateX(100%); + } + } + `} + > b { margin: 0 0 0 5px; color: inherit; @@ -48,13 +77,13 @@ interface CowBalanceButtonProps { const COW_DECIMALS = COW[ChainId.MAINNET].decimals export default function CowBalanceButton({ onClick }: CowBalanceButtonProps) { - const combinedBalance = useCombinedBalance() + const { balance, isLoading } = useCombinedBalance() - const formattedBalance = formatSmartLocaleAware(combinedBalance, AMOUNT_PRECISION) - const formattedMaxBalance = formatMax(combinedBalance, COW_DECIMALS) + const formattedBalance = formatSmartLocaleAware(balance, AMOUNT_PRECISION) + const formattedMaxBalance = formatMax(balance, COW_DECIMALS) return ( - + {formattedBalance || 0} diff --git a/src/custom/components/TransactionConfirmationModal/index.tsx b/src/custom/components/TransactionConfirmationModal/index.tsx index 06969c6289..9749487aaa 100644 --- a/src/custom/components/TransactionConfirmationModal/index.tsx +++ b/src/custom/components/TransactionConfirmationModal/index.tsx @@ -355,6 +355,7 @@ export enum OperationType { ORDER_SIGN, ORDER_CANCEL, CONVERT_VCOW, + CLAIM_VESTED_COW, } function getWalletNameLabel(walletType: WalletType): string { @@ -384,7 +385,8 @@ function getOperationMessage(operationType: OperationType, chainId: number): str return 'Revoking token approval' case OperationType.CONVERT_VCOW: return 'Converting vCOW to COW' - + case OperationType.CLAIM_VESTED_COW: + return 'Claiming vested COW' default: return 'Almost there!' } @@ -406,6 +408,8 @@ function getOperationLabel(operationType: OperationType): string { return t`cancellation` case OperationType.CONVERT_VCOW: return t`vCOW conversion` + case OperationType.CLAIM_VESTED_COW: + return t`vested COW claim` } } diff --git a/src/custom/constants/index.ts b/src/custom/constants/index.ts index ae9c2ff492..29c0710a65 100644 --- a/src/custom/constants/index.ts +++ b/src/custom/constants/index.ts @@ -60,13 +60,13 @@ export const GP_VAULT_RELAYER: Partial> = { export const V_COW_CONTRACT_ADDRESS: Record = { [ChainId.MAINNET]: '0xd057b63f5e69cf1b929b356b579cba08d7688048', [ChainId.XDAI]: '0xc20C9C13E853fc64d054b73fF21d3636B2d97eaB', - [ChainId.RINKEBY]: '0x9386177e95A853070076Df2403b9D547D653126D', // <- TODO: change these at some point after testing is done + [ChainId.RINKEBY]: '0x9386177e95A853070076Df2403b9D547D653126D', } export const COW_CONTRACT_ADDRESS: Record = { [ChainId.MAINNET]: '0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB', [ChainId.XDAI]: '0x177127622c4A00F3d409B75571e12cB3c8973d3c', - [ChainId.RINKEBY]: '0xbdf1e19f8c78A77fb741b44EbA5e4c0C8DBAeF91', // <- TODO: change these at some point after testing is done + [ChainId.RINKEBY]: '0xbdf1e19f8c78A77fb741b44EbA5e4c0C8DBAeF91', } // See https://github.com/gnosis/gp-v2-contracts/commit/821b5a8da213297b0f7f1d8b17c893c5627020af#diff-12bbbe13cd5cf42d639e34a39d8795021ba40d3ee1e1a8282df652eb161a11d6R13 @@ -141,3 +141,11 @@ export const WAITING_TIME_RECONNECT_LAST_PROVIDER = 15000 // 15s // COWSWAP = new quote endpoint // LEGACY = price racing logic (checking 0x, gp, paraswap, etc) export const DEFAULT_GP_PRICE_STRATEGY = 'COWSWAP' + +// Start date of COW vesting for locked GNO +export const LOCKED_GNO_VESTING_START_DATE = new Date('02-11-2022 13:05:15 GMT') + +// These values match the vesting contract configuration (see contract's `cliffTime` and `duration` fields). +// They are fixed and will never change. +export const LOCKED_GNO_VESTING_START_TIME = 1644584715000 +export const LOCKED_GNO_VESTING_DURATION = 126144000000 // 4 years diff --git a/src/custom/constants/tokens/index.ts b/src/custom/constants/tokens/index.ts index a50b4820dc..fa74c4644f 100644 --- a/src/custom/constants/tokens/index.ts +++ b/src/custom/constants/tokens/index.ts @@ -137,3 +137,19 @@ export const ADDRESS_IMAGE_OVERRIDE = { [V_COW_TOKEN_MAINNET.address]: vCowLogo, [COW_TOKEN_MAINNET.address]: cowLogo, } + +/** + * Addresses related to COW vesting for Locked GNO + * These are used in src/custom/pages/Profile/LockedGnoVesting hooks and index files + */ +export const MERKLE_DROP_CONTRACT_ADDRESSES: Record = { + [SupportedChainId.MAINNET]: '0x64646f112FfD6F1B7533359CFaAF7998F23C8c40', + [SupportedChainId.RINKEBY]: '0x5444c4AFb2ec7f7367C10F7732b8558650c5899F', + [SupportedChainId.XDAI]: '0x3d610e917130f9D036e85A030596807f57e11093', +} + +export const TOKEN_DISTRO_CONTRACT_ADDRESSES: Record = { + [SupportedChainId.MAINNET]: '0x68FFAaC7A431f276fe73604C127Bd78E49070c92', + [SupportedChainId.RINKEBY]: '0xeBA8CE5b23c054f1511F8fF5114d848329B8258d', + [SupportedChainId.XDAI]: '0x3d610e917130f9D036e85A030596807f57e11093', +} diff --git a/src/custom/hooks/useCowBalanceAndSubsidy.ts b/src/custom/hooks/useCowBalanceAndSubsidy.ts index c2e8da086f..1cf835d428 100644 --- a/src/custom/hooks/useCowBalanceAndSubsidy.ts +++ b/src/custom/hooks/useCowBalanceAndSubsidy.ts @@ -1,37 +1,13 @@ import { useMemo } from 'react' import { BigNumber } from 'bignumber.js' -import { JSBI } from '@uniswap/sdk' -import { CurrencyAmount } from '@uniswap/sdk-core' - import { getDiscountFromBalance } from 'components/CowSubsidyModal/utils' -import { useVCowData } from 'state/cowToken/hooks' -import { useTokenBalance } from 'state/wallet/hooks' -import { useActiveWeb3React } from 'hooks/web3' - -import { COW } from 'constants/tokens' -import { SupportedChainId } from 'constants/chains' +import { useCombinedBalance } from 'state/cowToken/hooks' import { COW_SUBSIDY_DATA } from 'components/CowSubsidyModal/constants' const ZERO_BALANCE_SUBSIDY = { subsidy: { tier: 0, discount: COW_SUBSIDY_DATA[0][1] }, balance: undefined } export default function useCowBalanceAndSubsidy() { - const { account, chainId } = useActiveWeb3React() - // vcow balance - const { total: vCowBalance } = useVCowData() - // Cow balanc - const cowBalance = useTokenBalance(account || undefined, chainId ? COW[chainId] : undefined) - - const balance = useMemo(() => { - if (vCowBalance && cowBalance) { - const totalBalance = JSBI.add(vCowBalance.quotient, cowBalance.quotient) - - // COW and vCOW safely have the same identifying properties: decimals - // so we make JSBI maths and create a new currency as adding vCow and Cow throws and exception - return CurrencyAmount.fromRawAmount(COW[chainId || SupportedChainId.MAINNET], totalBalance) - } else { - return cowBalance || vCowBalance - } - }, [chainId, cowBalance, vCowBalance]) + const { balance } = useCombinedBalance() return useMemo(() => { if (!balance || balance?.equalTo('0')) return ZERO_BALANCE_SUBSIDY diff --git a/src/custom/pages/Profile/LockedGnoVesting/claimData/gnosisChain.json b/src/custom/pages/Profile/LockedGnoVesting/claimData/gnosisChain.json new file mode 100644 index 0000000000..19eec9d38c --- /dev/null +++ b/src/custom/pages/Profile/LockedGnoVesting/claimData/gnosisChain.json @@ -0,0 +1,34 @@ +[ + "0x00000000cc0b822819f03424dacf9077fdaa58a3", + "0x06d4b89475a53111e8939b1ae8af7e14804a5186", + "0x0f1c44076d4cf58e1458a418393e6be57f3519f9", + "0x1702f2d0df7c99011a690461c34e51bd81cbab48", + "0x1ec30dec8378e6df6988da0e8a2b49b17055f0aa", + "0x26404746cd2228018f86c98f580e5030b06ff3c8", + "0x3047fa36aa687014f3ead30e4873adc3f58467bf", + "0x3812364136fe59a5db130666925be092050bd8ba", + "0x4193e4ca2241d4d16356a94da537e3c3a600678c", + "0x499487f6be895b71cf57881c22d5f6d855fcb8a2", + "0x52077db357420b6999b6b78c46c3ab2fc596fba8", + "0x5aa6a1d420bf8fa45d141af73e9230e1e8c3dc16", + "0x61fde66b0a208be7096ffa314d7cd92f519b2352", + "0x6b504204a85e0231b1a1b1926b9264939f29c65e", + "0x7246a274656e797fab4b02eb1e0581d46f0358e2", + "0x79a074122be96e1fc9bdd32dba04759421d12f90", + "0x817a33e007afb85ec23da7de231c1902cf4686c1", + "0x882289186d7b1b9cd35191780761ee80975f0fd1", + "0x90fa3f3c3a290ee19bbc94dc539dede8e21ce28f", + "0x99c72eb5c22c38137541ef4b9a2fd0316c42b510", + "0xa1e63c0f203df1314153ad6648bd38fd99774d85", + "0xabe8430e3f0beca32915da84e530f81a01379953", + "0xb6271e5a916f3764c5d3387dff14922249d9d70f", + "0xbf8ab1e63a9b883a6b5a396cb5a36138af6e020a", + "0xc789026a0f0b15c532c77405491331997f2b2bbc", + "0xce57ebed9ac38402dcaa44f65a1c9b04e26b8283", + "0xd64c69277d2c842c9ab17683479ac063aa35d4f5", + "0xdfeb9c25186aadf1487979be7da312f95fd55275", + "0xe6f44434c052c00d280bc236c8f58d8a9e42eec8", + "0xee9ec3273c52ea783b86cfefab32b50b1b26fce7", + "0xf6330ad6f2d488f6f29cf45e8fa5bf798e3b8df3", + "0xff36b9cb75c9178841d8b75baf9776bfa59fbe9d" +] diff --git a/src/custom/pages/Profile/LockedGnoVesting/claimData/index.test.ts b/src/custom/pages/Profile/LockedGnoVesting/claimData/index.test.ts new file mode 100644 index 0000000000..1fe48bc278 --- /dev/null +++ b/src/custom/pages/Profile/LockedGnoVesting/claimData/index.test.ts @@ -0,0 +1,43 @@ +import { fetchClaim } from '.' + +describe('fetchClaim', () => { + it('should fetch the correct amount and merkle proof for 0x01eda16f6a6c3b051ecc63b0d93c8c3a27491dc2 on mainnet', async () => { + const claim = await fetchClaim('0x01eda16f6a6c3b051ecc63b0d93c8c3a27491dc2', 1) + expect(claim?.amount).toEqual('0x03e7eed24f376724a0') + expect(claim?.proof).toEqual([ + '0x60d453c5e23c77b881dd9ff7529fcf464edebd491d5ea8bf5e11e23cc6f47480', + '0x0877e91858fac51a42947fbd26999874691a9e888cf34436fa66c0028ab7bbd3', + '0xfe29bdb74e8f410a109fde3f458c927507e618808b8413ed1c5fd323c5c9a628', + '0x6c650faf73c5b63f83db90a590bd33ba56643ebdc302511b31d826d19396816b', + '0x9144ff27437147862213a49a1b7fe83f3a4f7d63a2077f282d4e950fb1fca985', + '0x3c34f4ba3f7f0377e3cf9cf529dab73fd7ff4fe6dfbac59b69f04260b8b33cd9', + '0x91b09c40dbce8d252add3b512793d4b3cfcbe3b46c5c9d6d56ba993ecf770470', + '0xe97c601df9a399f368546575516b533f0438e87866e46e4694687e94d0241218', + '0x1d6a881baea3b1685912b1f308eb9373e808fb3cfff389ffa0cec82e3eb0ea1a', + '0xd84e4549adcf49890f091546b3f873d102368cb115fac46655102eff21a1291b', + ]) + }) + + it('should fetch the correct amount and merkle proof for 0x00645dd21310882cc32399abcb54e0a05b3b5d1d on Gnosis Chain', async () => { + const claim = await fetchClaim('0x00645dd21310882cc32399abcb54e0a05b3b5d1d', 100) + expect(claim?.amount).toEqual('0x58b0138dc7e4ccd980') + expect(claim?.proof).toEqual([ + '0x65675d39883f109dec07e3d7b4613c412c81ee3311fd093cff00359e52d739c1', + '0x9638aa10de5885b599958cddae3f3ab119f11b61d32e03741198d15c9208e7fb', + '0xf2b9715498991ab98a9e819889268409a351b9754fca9a1b05946f45829699d6', + '0x00c71fda7f57a05619bdc2e6260970a7b4dd726ad0b87717818c33ec28d28e6c', + '0x7631f21c6d503b95f7eb594f0ae219b5743268dd9cbabb71956aeff8ca333083', + '0xb54a34f057a323d8de571f623f8390c02f8d075e1613e8dc8d8c267d8cddb957', + '0xcee373a777b7f6eb5c9b6855ce2fc88aedc4accc4bbb27371f253c23efe3fdf4', + '0xdd4dc16f1f24d8f6f4fe323caeaa275b457b0d9340510eeb80c679b08bc94304', + '0x20a84c8d8e7d15409c6ea4591310bdc57bebe4489b53036d10f9e0467f339600', + '0xd0c880019076ffcd06d15b98b049e82d349323a6da4871484964e2c5b4e446a2', + '0x4b71fa3ace335c8703d193770bcef717053532750939e342c416afef044a3eba', + ]) + }) + + it('should return null for ineligible addresses', async () => { + const claim = await fetchClaim('0x0000000000000000000000000000000000000000', 4) + expect(claim).toBe(null) + }) +}) diff --git a/src/custom/pages/Profile/LockedGnoVesting/claimData/index.ts b/src/custom/pages/Profile/LockedGnoVesting/claimData/index.ts new file mode 100644 index 0000000000..26569078f1 --- /dev/null +++ b/src/custom/pages/Profile/LockedGnoVesting/claimData/index.ts @@ -0,0 +1,60 @@ +import { SupportedChainId } from 'constants/chains' +import mainnetIndex from './mainnet.json' +import rinkebyIndex from './rinkeby.json' +import gnosisChainIndex from './gnosisChain.json' + +interface Claim { + index: number + amount: string + proof: string[] +} + +const indexFiles = { + [SupportedChainId.MAINNET]: mainnetIndex, + [SupportedChainId.RINKEBY]: rinkebyIndex, + [SupportedChainId.XDAI]: gnosisChainIndex, +} + +const chainNames = { + [SupportedChainId.MAINNET]: 'mainnet', + [SupportedChainId.RINKEBY]: 'rinkeby', + [SupportedChainId.XDAI]: 'gnosisChain', +} + +const DISTRO_REPO_BRANCH_NAME = 'main' + +export const fetchClaim = async (address: string, chainId: SupportedChainId): Promise => { + const lowerCaseAddress = address.toLowerCase() + + const indexFile = indexFiles[chainId] + const chainName = chainNames[chainId] + const chunkIndex = lookupChunkIndex(indexFile, lowerCaseAddress) + if (chunkIndex === -1) return null // address is lower than the lowest address in the index, which means it's ineligible + + const chunk = await fetchChunk(`${chainName}/chunk_${chunkIndex}.json`) + return chunk[lowerCaseAddress] || null +} + +// The merkle proof data has been sorted by address in ascending order and then chunked up. +// see: https://github.com/gnosis/locked-gno-cow-merkle-distro/blob/main/chunkClaimData.js +// Our index json gives the first address of each chunk. +// This function returns the chunk index for the given address, or -1 if the address is lower than the lowest address in the index. +const lookupChunkIndex = ( + chunkIndexJson: typeof mainnetIndex | typeof rinkebyIndex | typeof gnosisChainIndex, + address: string +) => { + let nextChunkIndex = chunkIndexJson.findIndex((a) => address < a) + if (nextChunkIndex === -1) nextChunkIndex = chunkIndexJson.length + return nextChunkIndex - 1 +} + +const chunkCache = new Map>>() +const fetchChunk = (path: string) => { + const promise = + chunkCache.get(path) ?? + (fetch( + `https://raw.githubusercontent.com/gnosis/locked-gno-cow-merkle-distro/${DISTRO_REPO_BRANCH_NAME}/${path}` + ).then((res) => res.json()) as Promise>) + chunkCache.set(path, promise) + return promise +} diff --git a/src/custom/pages/Profile/LockedGnoVesting/claimData/mainnet.json b/src/custom/pages/Profile/LockedGnoVesting/claimData/mainnet.json new file mode 100644 index 0000000000..7b1992516e --- /dev/null +++ b/src/custom/pages/Profile/LockedGnoVesting/claimData/mainnet.json @@ -0,0 +1,18 @@ +[ + "0x00000081c22fe36e0b47a4f1c67041301f945014", + "0x0d78a4b2657dd319ae1f47ce5e529d03e984cef5", + "0x2059a96525c364560a806ca035a40c9f379ebca9", + "0x32ae635f5136adb181a442cc890be39263bc13c8", + "0x44dde9695027ca6acb7cdf3b361c37056122e4af", + "0x51ffd343d6fecab9e9c5640f0e6a67dec31bc76c", + "0x63829da9dc103b63d984391f7580eddb220ba6d9", + "0x773d161310d07cafc6f767ca24f43e52163b9be6", + "0x8642214d3cb4eb38ee618be37f78dd74a3093869", + "0x98b7a46a33d60f71522115ab7e2ec8f0fc294038", + "0xaa942b60823be690a17576207b819807891d71f6", + "0xbbf7bfc4d9acce27082613e2c14d9fe8d1a54a29", + "0xcb11673592cc6c4b9584f33bbba6c6cf07dde3f7", + "0xdb0b39341290a30510e46e7692195fe16097e0df", + "0xe91f64ca1da165ca8437686b69a022156550837b", + "0xf64c2e6679e089cd1c3e303ca1245d4941747700" +] diff --git a/src/custom/pages/Profile/LockedGnoVesting/claimData/rinkeby.json b/src/custom/pages/Profile/LockedGnoVesting/claimData/rinkeby.json new file mode 100644 index 0000000000..6818a8186a --- /dev/null +++ b/src/custom/pages/Profile/LockedGnoVesting/claimData/rinkeby.json @@ -0,0 +1,159 @@ +[ + "0x00059034c8759f5c89af6792eb3cfa2ccd12f6f3", + "0x0184d440421238834410842c4d69f0e654d66acd", + "0x03074cc70ad58a2d5fac3b949fb3415b3c056397", + "0x0475850b204fae6238821e380bb311560c64630e", + "0x06328edeecc339ac22e47be452a84eb415d523b1", + "0x07c788a928a4c4dba9518ef9f86cc60f4d38d37e", + "0x097b1bc9fe6f8c28adc338a899974929d54216d7", + "0x0c508dc180dd9faed35bf41cbf684cc084fb6905", + "0x0a197d9b2f2888b52aa74ea5bfb3ad077c943030", + "0x0c6d8d86d9402f58a268827dc471fc3473843444", + "0x0fa5280a086c776135271d983d0fd6a0066710f6", + "0x10f82b6252fd12e367c048eb2ec178f1d00db210", + "0x1290c7340ffff6471cdc45f4c5768ec49625e6a0", + "0x144c539c51706856fd52a8dd01c7868399ff1a41", + "0x162cbb6e5e2eb077b51ecaa86ee9969f26432e42", + "0x179581d20fd3c9f20044ca822ae4dea0bc9a3f37", + "0x193040bedb3d8cbf9c74e10f5f79f0a49f526c84", + "0x1b4cd2dc47c5da4e46bfb034558221c7033ff052", + "0x1a0224874b2729cd00cc3cc344a518b7cbf7f1c4", + "0x1b760b2c2f72911f9f30a90bffd404c1794ddcbd", + "0x1ea39e098ad3b7b7c9dd3f1db708e4bacc490c32", + "0x2130a0bbb5ead8aca8a1abe5f3162e269c5baaf8", + "0x228a1e08e126d3fca3b21972c3a0a7a8b1b9bc4a", + "0x23a03fa897b25a4dff8db1cdcd6b08c153dcf36d", + "0x257c5a7161bed08e7ff4077ae2f1514415375583", + "0x26aa1eb7a5f6d5c630b4b7cc2190f566d656d4e9", + "0x27f34b649fec407c75ed918b28ed0e7026861cca", + "0x29a15c23b9a8d921f05898d869e9a61ebaa75f99", + "0x2ca28e930137b3f33d05e1112de55d46a9011d6d", + "0x2a027f808663a128f18b0fc4dddcca90b3717000", + "0x2c172a786a54684c9e1ff80a00959a026e7f508a", + "0x2f3f3b3769467d10b9440deb34de3da0b483cc28", + "0x316debafa5bc27c6e3a8c76a99d02bf5c9f7b953", + "0x3326b5b1bcd0ad0823558cb8358e875e05375ac1", + "0x34be1c1c77aac31fccc9e1eae3b79d07ad39a7ac", + "0x364cb4be791157e5da2bb729cdd18ca175db384d", + "0x3828e189c90607bf5c1429881a11b23a8cc38c06", + "0x39dfa06c8da6d293a9e7d85975576e20ef3e1c7a", + "0x3a34f90f54e1e1901f1cceda798ee85e790107e1", + "0x3aa4b732931b64cc0fca3f8d899aa4bca5f7612f", + "0x3eb7fcce1d85df6261f415132ed5874907963852", + "0x40c8a0a691c25ad3a7999303c4c05d44cc88087a", + "0x42a2c6fcbf66d961bea60e637fa2e3c0a101e986", + "0x445579b34ffbbee3b9b3308124396753ce4702c1", + "0x45efc6305f9b71929b9f9bb79a111492a6416e8f", + "0x47cc0c6e2886b737ed6216081bb73cbc2aca6cbe", + "0x49b16c4e15a7a23f75ef5e44e3e315b27714c125", + "0x4a083817ce8139a189786383b844d57c3059ac61", + "0x4b45054a781cbdac2fa579f5914eae1c834fc002", + "0x4e97b169cf1d0968b8cae6504e3b575c9af230d8", + "0x50eceed71a25542d3169ee7cd38ddf24851018d1", + "0x525aa961cb6a422f325445f4529d58a5df3d19cc", + "0x54411314b68bf220b0d0486f4248f36971cce554", + "0x55c1feb182f427697c02df97f1d2de11f41ad071", + "0x576358d7bf08d814a1051fc3b266860be5fe3570", + "0x58b752f7c2c635e39fa5709d6e56ba06510966af", + "0x5c06fa63757cbd45f1e5fc2fa8da571c3feb367b", + "0x5a001759b80248d263fe632cc431c654348a230d", + "0x5c3665cdbf36a9df4178a9f4d6cb62c301c785b3", + "0x5ec84015ab245c589842833b21debc23c8a4d9d1", + "0x615587711c931525370b7154ea917c93bc317989", + "0x62d4d69ea0953d35f27c9c8e3fecf7f835b8a295", + "0x6465efc973df6a7859e5c44eb1bcac14ff2d979e", + "0x6610446c100422d7cbb28b8dd0791213681bdaa1", + "0x67a7cdaea8b478101a0f58001e30527b89924be3", + "0x69a0128e60b2ec968899837ccc1f21422c3c12c4", + "0x6a0d6465b524a8aa79f7f3a152c9f87b69cec5b7", + "0x6aa37d290dee00d86549cf8ca5062bdb87a44ea0", + "0x6d7c9d2b5bc8f9b783e1b54fc24b174b2d8d52bb", + "0x70b530ab416d13c0923637c78665ea533297eb3b", + "0x72197a2476421ba2ca60455a805e6f94a42e0e77", + "0x73a0b8e1f052c1a1eaf3d85b01931257e537ca68", + "0x74fd7681c2d1b5a0a93e22d7aafbb3bc4300519f", + "0x769d604d5e072ceb97deae66fe82169028696ed0", + "0x78abf0022545abcbe4921df5de15be859f77b956", + "0x7ab27209466bfcf86ab81e6655ebc2135cfce78c", + "0x7a032e163c77731ad5288197577119b0d86394db", + "0x7baba1c8c1667d214bf5513a1aa70939a2980319", + "0x7f00e8171e750382d666238787d804fcec83ef5e", + "0x8143169bb0e216aec221ddf14218ec748e0ec8b8", + "0x82fb6a1e102e9378c646dbf275b7bf43eb6b9ae8", + "0x84ac4c50ac68570b837eaaae7eb61a6f8fab1c04", + "0x85fed552e130f9ce445d3f88e714495203a0d216", + "0x87abf9ed650cb91f033b7491990a0f2064142cad", + "0x899af182307fe58bee26d2b692a6faaadf249a47", + "0x8a09284d0cab525d4c240c814766111ef21f5995", + "0x8abe320d57b5bb6bf66a8779afb85f6309df7064", + "0x8dd1b3ecac3f3abf809c8452466f074c0287576d", + "0x905fd2473e4d6d95257cfe2d6c8c63d4cc05ec9f", + "0x91d91ceca4aadc28338005334806557992b8ab79", + "0x935f1e210662fefded39835aed9881f63d0ca093", + "0x94fca7bbf19082eace66d20c94b6fa3f0436d604", + "0x969c3a502ce1a717136a28996b109d2a00d13730", + "0x980811cf7c361405e3dba9273140e926f1f4388d", + "0x9958a4e14c7db35d788b34cb0aa5c1a47382cb03", + "0x9c427b35edde0b741f3f0106e3251234756c5e35", + "0x9a1d89633462bd50c092c8336a9ffe95bc3b6f1f", + "0x9c7ab0c5f88fa5bcaab71ac210b3525d470af7e8", + "0xa05048aac1a7d20febcec3c02d3fc7d287581b4d", + "0xa3319cc21e72b41ee1cacbf3da5d080ecac3d8e3", + "0xa6c29b30f7d763e586669d62e130d2e4558bdfec", + "0xaa3345f445b1ae496b52db500b666a86cb957880", + "0xab3ceb901c3c46b18239c4a66cb90151894b743d", + "0xb15bfbf5e4a111f1dc54546d35221065f12cc285", + "0xb49f66e7112797eea1c7a570eb46209748c32c03", + "0xb7903cf5ad35681f837ff7f0454ee16ca3ea997a", + "0xba0055430cae59e394e436297c1684a6db0be7a2", + "0xbc5e71e399845f0ede4e9ae23216e696855d39cc", + "0xc1dfb536e8e8f3dbdafaeac8f04777599c76cdc6", + "0xc4d3e4f4c0e541130d353c70021aaaecdacd5704", + "0xc81a900d07d598e27be852c8e072a366b8c90518", + "0xca097381b52159a5e5e4ae83b0e438e494256477", + "0xccdcf7dbd1649c6707fe79e4b436fc1ef29c1f35", + "0xd2200457ee0dfeed200cb773fca3fbf6cc78c2f2", + "0xd58fccb65794c86ca5b5110d6af3205361d3f54a", + "0xd91ee4e4da18d08584b160d0edb9e033caa8de23", + "0xda4196394028c7d0b3428ef703a6702af6c85727", + "0xdf97c11d8b9191d9ac7ca1dde7dc7cae9ce2d225", + "0xe2ba5523f1b19bb184ecc0b34a50c49f6088345d", + "0xe65bc6fed5a46a85aeb2220da167b8d735be2403", + "0xe9a1b8a11c8502097d0ee62bc2cbd3494929d3b4", + "0xea16d8f5d6b97647e992df0dc9854df87fb19e15", + "0xf070989c80bb0be93a673c95948e844b749149c4", + "0xf3ad9dd3af050f8bce826b286c95867745d44c9e", + "0xf66b60ad4a787ba999d2ccb2db2392a92daca694", + "0xfa267b99d6297e8d85395e0ae59fe1fd205b2df2", + "0xa012a3205191a6a02e7898eebc67660979ed472a", + "0xa1b621409294e58b07d4fa21c4f8d08376b29eb3", + "0xa5258f5642346424f11e12b42f5eb939287b21dd", + "0xa86f3af216f93e7b6fb53c1b853c1f655089fefa", + "0xaa0502a2a4d0256ec0660b2a3675a58151f52dfa", + "0xac895daec94bb9ba6ffca66bb5c711b6f07b42cc", + "0xb185880e77be3c2d82415419bb2c4712c7932d65", + "0xb45653679e55695381d5b82c3e29678bb0c80fde", + "0xb830c17e105d7a1ed29867d747ffc078a78b28ef", + "0xba0e4ff7750f0853d65ea5e39a9cc4b2ed78dfcf", + "0xbb473e2aa3ccd6fab7fc93d475eb8147c11021ac", + "0xc0a3dfa619de93b6e6101c7e5b7bd66514d1236b", + "0xc4241db3f2ca670c526486536707fbdc34c4bc59", + "0xc7516c1bec68ecc13b28a4e174e242a886ce4456", + "0xca11075f1132cb0aef7cfbbfdd29b85f23908d12", + "0xce043dcf412face6c552b297f71fb884fab37917", + "0xd20c1c9b91773e5c0e8ca075c20bd232abc001b4", + "0xd5af622490011bbfb5687b671469ae252aa72f67", + "0xd93498af122fc13ff14fec5afa3294a4bf76db68", + "0xda0a262923cba710ae5db9bd2b0828f479ea92d1", + "0xdea552fe29f400c5e9d703c18c276479d0cf8940", + "0xe21166fc05be349b05c5b6391edc059b00263d73", + "0xe5ad2f84af4e636aa871aff482bf30382399a319", + "0xe8f2659451e465883d800be3cef21aa8bb13a978", + "0xea0277e79d70deb7a7ca6487ff73a8e206817325", + "0xee3d82666c4b82b9123b8f0f64550fd0f3f3fdf3", + "0xf217f0e968705979cc4b70cfe3d013cbccac0995", + "0xf59deb55b53c9a602e7a834cd35f63dd62c608bc", + "0xf910d6a0dd2cd02d036c72d67032011e35879928", + "0xfa0964a117a36dec6bf108176bd9a6f8e3517520", + "0xfeef05f905fb74b49a3096ba4de05d2ac8d800fc" +] diff --git a/src/custom/pages/Profile/LockedGnoVesting/hooks.ts b/src/custom/pages/Profile/LockedGnoVesting/hooks.ts new file mode 100644 index 0000000000..601b2b776c --- /dev/null +++ b/src/custom/pages/Profile/LockedGnoVesting/hooks.ts @@ -0,0 +1,120 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { ContractTransaction } from '@ethersproject/contracts' +import { CurrencyAmount, Token } from '@uniswap/sdk-core' +import { useActiveWeb3React } from 'hooks/web3' +import MERKLE_DROP_ABI from 'abis/MerkleDrop.json' +import TOKEN_DISTRO_ABI from 'abis/TokenDistro.json' +import { MerkleDrop, TokenDistro } from 'abis/types' +import { useSingleCallResult } from 'lib/hooks/multicall' +import { useTransactionAdder } from 'state/enhancedTransactions/hooks' +import { useContract } from 'hooks/useContract' +import { COW as COW_TOKENS } from 'constants/tokens' +import { SupportedChainId } from 'constants/chains' +import { OperationType } from 'components/TransactionConfirmationModal' +import { fetchClaim } from './claimData' +import { MERKLE_DROP_CONTRACT_ADDRESSES, TOKEN_DISTRO_CONTRACT_ADDRESSES } from 'constants/tokens' +import { LOCKED_GNO_VESTING_START_TIME, LOCKED_GNO_VESTING_DURATION } from 'constants/index' + +// We just generally use the mainnet version. We don't read from the contract anyways so the address doesn't matter +const COW = COW_TOKENS[SupportedChainId.MAINNET] + +const useMerkleDropContract = () => useContract(MERKLE_DROP_CONTRACT_ADDRESSES, MERKLE_DROP_ABI, true) +const useTokenDistroContract = () => useContract(TOKEN_DISTRO_CONTRACT_ADDRESSES, TOKEN_DISTRO_ABI, true) + +export const useAllocation = (): CurrencyAmount => { + const { chainId, account } = useActiveWeb3React() + const initialAllocation = useRef(CurrencyAmount.fromRawAmount(COW, 0)) + const [allocation, setAllocation] = useState(initialAllocation.current) + + useEffect(() => { + let canceled = false + if (account && chainId) { + fetchClaim(account, chainId).then((claim) => { + if (!canceled) { + setAllocation(CurrencyAmount.fromRawAmount(COW, claim?.amount ?? 0)) + } + }) + } else { + setAllocation(initialAllocation.current) + } + return () => { + canceled = true + } + }, [chainId, account, initialAllocation]) + + return allocation +} + +export const useCowFromLockedGnoBalances = () => { + const { account } = useActiveWeb3React() + const allocated = useAllocation() + const vested = allocated + .multiply(Math.min(Date.now() - LOCKED_GNO_VESTING_START_TIME, LOCKED_GNO_VESTING_DURATION)) + .divide(LOCKED_GNO_VESTING_DURATION) + + const tokenDistro = useTokenDistroContract() + const { result, loading } = useSingleCallResult(allocated.greaterThan(0) ? tokenDistro : null, 'balances', [ + account ?? undefined, + ]) + const claimed = useMemo(() => CurrencyAmount.fromRawAmount(COW, result ? result.claimed.toString() : 0), [result]) + + return { + allocated, + vested, + claimed, + loading, + } +} + +interface ClaimCallbackParams { + openModal: (message: string, operationType: OperationType) => void + closeModal: () => void + isFirstClaim: boolean +} +export function useClaimCowFromLockedGnoCallback({ + openModal, + closeModal, + isFirstClaim, +}: ClaimCallbackParams): () => Promise { + const { chainId, account } = useActiveWeb3React() + const merkleDrop = useMerkleDropContract() + const tokenDistro = useTokenDistroContract() + + const addTransaction = useTransactionAdder() + + const claimCallback = useCallback(async () => { + if (!account) { + throw new Error('Not connected') + } + if (!chainId) { + throw new Error('No chainId') + } + if (!merkleDrop || !tokenDistro) { + throw new Error('Contract not present') + } + + const claim = await fetchClaim(account, chainId) + if (!claim) throw new Error('Trying to claim without claim data') + + const { index, proof, amount } = claim + + // On the very first claim we need to provide the merkle proof. + // Afterwards the allocation will be already in the tokenDistro contract and we can just claim it there. + const claimPromise = isFirstClaim ? merkleDrop.claim(index, amount, proof) : tokenDistro.claim() + const summary = 'Claim vested COW' + openModal(summary, OperationType.CLAIM_VESTED_COW) + + return claimPromise + .then((tx) => { + addTransaction({ + swapLockedGNOvCow: true, + hash: tx.hash, + summary, + }) + return tx + }) + .finally(closeModal) + }, [account, addTransaction, chainId, closeModal, openModal, isFirstClaim, merkleDrop, tokenDistro]) + + return claimCallback +} diff --git a/src/custom/pages/Profile/LockedGnoVesting/index.tsx b/src/custom/pages/Profile/LockedGnoVesting/index.tsx new file mode 100644 index 0000000000..799e2983f0 --- /dev/null +++ b/src/custom/pages/Profile/LockedGnoVesting/index.tsx @@ -0,0 +1,182 @@ +import { Trans } from '@lingui/macro' +import { useCallback, useState, useEffect } from 'react' +import SVG from 'react-inlinesvg' +import { Card, BalanceDisplay, ConvertWrapper, VestingBreakdown, CardActions, ExtLink } from 'pages/Profile/styled' +import { ButtonPrimary } from 'custom/components/Button' +import { HelpCircle } from 'components/Page' +import { MouseoverTooltipContent } from 'components/Tooltip' +import cowImage from 'assets/cow-swap/cow_v2.svg' +import ArrowIcon from 'assets/cow-swap/arrow.svg' +import { AMOUNT_PRECISION } from 'constants/index' +import { formatSmartLocaleAware } from 'utils/format' +import { OperationType } from 'components/TransactionConfirmationModal' +import { useErrorModal } from 'hooks/useErrorMessageAndModal' +import CopyHelper from 'components/Copy' +import { getBlockExplorerUrl } from 'utils' +import { formatDateWithTimezone } from 'utils/time' +import { SupportedChainId as ChainId } from 'constants/chains' +import { useActiveWeb3React } from 'hooks/web3' +import { MERKLE_DROP_CONTRACT_ADDRESSES, TOKEN_DISTRO_CONTRACT_ADDRESSES } from 'constants/tokens' +import { LOCKED_GNO_VESTING_START_DATE } from 'constants/index' +import { useCowFromLockedGnoBalances, useClaimCowFromLockedGnoCallback } from './hooks' +import usePrevious from 'hooks/usePrevious' + +enum ClaimStatus { + INITIAL, + ATTEMPTING, + SUBMITTED, + CONFIRMED, +} + +interface Props { + openModal: (message: string, operationType: OperationType) => void + closeModal: () => void +} + +const LockedGnoVesting: React.FC = ({ openModal, closeModal }: Props) => { + const { chainId = ChainId.MAINNET, account } = useActiveWeb3React() + const [status, setStatus] = useState(ClaimStatus.INITIAL) + const { allocated, vested, claimed, loading: loadingBalances } = useCowFromLockedGnoBalances() + const unvested = allocated.subtract(vested) + const allocatedFormatted = formatSmartLocaleAware(allocated, AMOUNT_PRECISION) || '0' + const vestedFormatted = formatSmartLocaleAware(vested, AMOUNT_PRECISION) || '0' + const unvestedFormatted = formatSmartLocaleAware(unvested, AMOUNT_PRECISION) || '0' + const claimableFormatted = formatSmartLocaleAware(vested.subtract(claimed), AMOUNT_PRECISION) || '0' + const previousAccount = usePrevious(account) + + const canClaim = !loadingBalances && unvested.greaterThan(0) && status === ClaimStatus.INITIAL + const isClaimPending = status === ClaimStatus.SUBMITTED + + const { handleSetError, handleCloseError, ErrorModal } = useErrorModal() + + const isFirstClaim = claimed.equalTo(0) + + const claimCallback = useClaimCowFromLockedGnoCallback({ + openModal, + closeModal, + isFirstClaim, + }) + + const contractAddress = isFirstClaim + ? MERKLE_DROP_CONTRACT_ADDRESSES[chainId] + : TOKEN_DISTRO_CONTRACT_ADDRESSES[chainId] + + const handleClaim = useCallback(async () => { + handleCloseError() + if (!claimCallback) { + return + } + + setStatus(ClaimStatus.ATTEMPTING) + + claimCallback() + .then((tx) => { + setStatus(ClaimStatus.SUBMITTED) + return tx.wait() + }) + .then((tx) => { + const success = tx.status === 1 + setStatus(success ? ClaimStatus.CONFIRMED : ClaimStatus.INITIAL) + + setTimeout(() => { + setStatus(ClaimStatus.INITIAL) + }, 5000) + }) + .catch((error) => { + console.error('[Profile::LockedGnoVesting::index::claimCallback]::error', error) + setStatus(ClaimStatus.INITIAL) + handleSetError(error?.message) + }) + }, [handleCloseError, handleSetError, claimCallback]) + + // Fix for enabling claim button after user changes account + useEffect(() => { + if (account && previousAccount && account !== previousAccount && status !== ClaimStatus.INITIAL) { + setStatus(ClaimStatus.INITIAL) + } + }, [account, previousAccount, status]) + + if (allocated.equalTo(0)) { + // don't render anything until we know that the user is actually eligible to claim + return null + } + + return ( + <> + + + COW token + + COW vesting from locked GNO + + {allocatedFormatted} COW{' '} + + + Unvested

{unvestedFormatted} COW

+
+ + Vested

{vestedFormatted} COW

+
+ + } + > + +
+
+
+
+ + + + Claimable{' '} + +

+ COW vesting from the GNO lock is vested linearly over four years, starting on{' '} + {formatDateWithTimezone(LOCKED_GNO_VESTING_START_DATE)}. +

+

Each time you claim, you will receive the entire claimable amount.

+ + } + > + +
+
+ {claimableFormatted} +
+ {status === ClaimStatus.CONFIRMED ? ( + + Successfully claimed + + ) : ( + + {isClaimPending ? ( + 'Claiming COW...' + ) : ( + <> + Claim COW + + )} + + )} +
+ + + View contract ↗ + +
Copy contract
+
+
+
+ + + + ) +} + +export default LockedGnoVesting diff --git a/src/custom/pages/Profile/VCOWDropdown.tsx b/src/custom/pages/Profile/VCOWDropdown.tsx deleted file mode 100644 index 075c75990b..0000000000 --- a/src/custom/pages/Profile/VCOWDropdown.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useCallback, /* useMemo, */ useRef, useState } from 'react' -import styled from 'styled-components/macro' -import { useOnClickOutside } from 'hooks/useOnClickOutside' -import { ChevronDown } from 'react-feather' -import { CurrencyAmount, Token } from '@uniswap/sdk-core' -import { Txt } from 'assets/styles/styled' -import CowProtocolLogo from 'components/CowProtocolLogo' -import { formatMax, formatSmartLocaleAware } from 'utils/format' -import { AMOUNT_PRECISION } from '@src/custom/constants' - -type VCOWDropdownProps = { - balance?: CurrencyAmount -} - -export default function VCOWDropdown({ balance }: VCOWDropdownProps) { - const [open, setOpen] = useState(false) - const toggle = useCallback(() => setOpen((open) => !open), []) - const node = useRef(null) - useOnClickOutside(node, open ? toggle : undefined) - - // Disabled dropdown for now - // const hasBalance = useMemo(() => balance?.greaterThan(0), [balance]) - const hasBalance = false - - return ( - - - - - - - Balance - - {formatSmartLocaleAware(balance, AMOUNT_PRECISION) || '0'} vCOW - - - - - {hasBalance && } - - - {open && hasBalance && ( - - - - Voting Power - Vesting - - Total - - - - 000 - 000 - - 000 - - - - - )} - - ) -} - -const Wrapper = styled.div` - position: relative; - display: inline; - ${({ theme }) => theme.mediaWidth.upToMedium` - justify-self: end; - `}; - - ${({ theme }) => theme.mediaWidth.upToVerySmall` - margin: 0 0.5rem 0 0; - width: initial; - text-overflow: ellipsis; - flex-shrink: 1; - justify-self: stretch; - `}; -` - -const MenuFlyout = styled.span` - background-color: ${({ theme }) => theme.bg4}; - border: 1px solid ${({ theme }) => theme.bg0}; - box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), - 0px 24px 32px rgba(0, 0, 0, 0.01); - border-radius: 12px; - padding: 1rem; - display: flex; - flex-direction: column; - font-size: 1rem; - position: absolute; - right: 0; - top: 4.5rem; - z-index: 200; - min-width: 100%; -` - -export const DropdownWrapper = styled.button<{ hasBalance?: boolean }>` - align-items: center; - background-color: ${({ theme }) => theme.bg4}; - border-radius: 12px; - border: 1px solid ${({ theme }) => theme.bg0}; - color: ${({ theme }) => theme.text1}; - display: inline-flex; - flex-direction: row; - font-weight: 700; - font-size: 12px; - height: 100%; - padding: 0.2rem 0.4rem; - - :hover, - :focus { - cursor: ${({ hasBalance }) => (hasBalance ? 'pointer' : 'inherit')}; - outline: none; - border: ${({ hasBalance }) => (hasBalance ? `1px solid $\{({ theme }) => theme.bg3}` : '1px solid transparent')}; - } - ${({ theme }) => theme.mediaWidth.upToVerySmall` - min-width: 100%; - justify-content: space-between; - `}; -` - -export const VCOWBalance = styled.div` - display: flex; - flex-direction: row; - align-items: center; - flex-grow: 1; - height: 56px; - justify-content: center; - border-radius: 12px; - padding: 8px; - background-color: ${({ theme }) => theme.bg4}; -` - -export const ProfileFlexCol = styled.div` - display: flex; - align-items: flex-start; - flex-direction: column; - - span { - padding: 0 8px; - } -` -export const ProfileFlexWrap = styled.div` - display: flex; - align-items: center; - flex-direction: row; - justify-content: space-between; -` diff --git a/src/custom/pages/Profile/index.tsx b/src/custom/pages/Profile/index.tsx index 8858e896ec..702c2ccfa5 100644 --- a/src/custom/pages/Profile/index.tsx +++ b/src/custom/pages/Profile/index.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Txt } from 'assets/styles/styled' import { FlexCol, @@ -20,6 +20,8 @@ import { BalanceDisplay, ConvertWrapper, VestingBreakdown, + BannerCardContent, + BannerCardSvg, } from 'pages/Profile/styled' import { useActiveWeb3React } from 'hooks/web3' import Copy from 'components/Copy/CopyMod' @@ -52,13 +54,19 @@ import { COW } from 'constants/tokens' import { useErrorModal } from 'hooks/useErrorMessageAndModal' import { OperationType } from 'components/TransactionConfirmationModal' import useTransactionConfirmationModal from 'hooks/useTransactionConfirmationModal' -import { SwapVCowStatus } from 'state/cowToken/actions' import AddToMetamask from 'components/AddToMetamask' import { Link } from 'react-router-dom' import CopyHelper from 'components/Copy' +import { SwapVCowStatus } from 'state/cowToken/actions' +import LockedGnoVesting from './LockedGnoVesting' +import useBlockNumber from 'lib/hooks/useBlockNumber' +import usePrevious from 'hooks/usePrevious' const COW_DECIMALS = COW[ChainId.MAINNET].decimals +// Number of blocks to wait before we re-enable the swap COW -> vCOW button after confirmation +const BLOCKS_TO_WAIT = 2 + export default function Profile() { const referralLink = useReferralLink() const { account, chainId = ChainId.MAINNET, library } = useActiveWeb3React() @@ -67,6 +75,11 @@ export default function Profile() { const isTradesTooltipVisible = account && chainId === SupportedChainId.MAINNET && !!profileData?.totalTrades const hasOrders = useHasOrders(account) const selectedAddress = useAddress() + const previousAccount = usePrevious(account) + + const blockNumber = useBlockNumber() + const [confirmationBlock, setConfirmationBlock] = useState(undefined) + const [shouldUpdate, setShouldUpdate] = useState(false) const setSwapVCowStatus = useSetSwapVCowStatus() const swapVCowStatus = useSwapVCowStatus() @@ -77,28 +90,31 @@ export default function Profile() { // vCow balance values const { unvested, vested, total, isLoading: isVCowLoading } = useVCowData() + // Boolean flags + const hasVestedBalance = vested && !vested.equalTo(0) + const hasVCowBalance = total && !total.equalTo(0) + + const isSwapPending = swapVCowStatus === SwapVCowStatus.SUBMITTED + const isSwapInitial = swapVCowStatus === SwapVCowStatus.INITIAL + const isSwapConfirmed = swapVCowStatus === SwapVCowStatus.CONFIRMED + const isSwapDisabled = Boolean( + !hasVestedBalance || !isSwapInitial || isSwapPending || isSwapConfirmed || shouldUpdate + ) + const cowBalance = formatSmartLocaleAware(cow, AMOUNT_PRECISION) || '0' const cowBalanceMax = formatMax(cow, COW_DECIMALS) || '0' - const vCowBalanceVested = formatSmartLocaleAware(vested, AMOUNT_PRECISION) || '0' - const vCowBalanceVestedMax = vested ? formatMax(vested, COW_DECIMALS) : '0' + const vCowBalanceVested = formatSmartLocaleAware(shouldUpdate ? undefined : vested, AMOUNT_PRECISION) || '0' + const vCowBalanceVestedMax = vested ? formatMax(shouldUpdate ? undefined : vested, COW_DECIMALS) : '0' const vCowBalanceUnvested = formatSmartLocaleAware(unvested, AMOUNT_PRECISION) || '0' const vCowBalance = formatSmartLocaleAware(total, AMOUNT_PRECISION) || '0' const vCowBalanceMax = total ? formatMax(total, COW_DECIMALS) : '0' - const hasVestedBalance = vested && !vested.equalTo(0) - const hasVCowBalance = total && !total.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, @@ -165,6 +181,50 @@ export default function Profile() { ) + const renderConvertToCowContent = useCallback(() => { + let content = null + + if (isSwapPending) { + content = Converting vCOW... + } else if (isSwapConfirmed) { + content = Successfully converted! + } else { + content = ( + <> + Convert to COW + + ) + } + + return content + }, [isSwapConfirmed, isSwapPending]) + + // Fixes the issue with change in status after swap confirmation + // Makes sure to wait 2 blocks after confirmation to enable the swap button again + useEffect(() => { + if (isSwapConfirmed && !confirmationBlock) { + setConfirmationBlock(blockNumber) + setShouldUpdate(true) + } + + if (!confirmationBlock || !blockNumber) { + return + } + + if (isSwapConfirmed && blockNumber - confirmationBlock > BLOCKS_TO_WAIT && hasVestedBalance) { + setSwapVCowStatus(SwapVCowStatus.INITIAL) + setConfirmationBlock(undefined) + setShouldUpdate(false) + } + }, [blockNumber, confirmationBlock, hasVestedBalance, isSwapConfirmed, setSwapVCowStatus, shouldUpdate]) + + // Reset swap button status on account change + useEffect(() => { + if (account && previousAccount && account !== previousAccount && !isSwapInitial) { + setSwapVCowStatus(SwapVCowStatus.INITIAL) + } + }, [account, isSwapInitial, previousAccount, setSwapVCowStatus]) + const currencyCOW = COW[chainId] return ( @@ -201,13 +261,7 @@ export default function Profile() { {vCowBalanceVested} - {isSwapPending ? ( - 'Converting vCOW...' - ) : ( - <> - Convert to COW - - )} + {renderConvertToCowContent()} @@ -247,8 +301,10 @@ export default function Profile() { + + - + CoW DAO Governance Use your (v)COW balance to vote on important proposals or participate in forum discussions. @@ -256,8 +312,8 @@ export default function Profile() { View proposals ↗ CoW forum ↗ - - + + diff --git a/src/custom/pages/Profile/styled.tsx b/src/custom/pages/Profile/styled.tsx index 43b41966ab..20a74397c2 100644 --- a/src/custom/pages/Profile/styled.tsx +++ b/src/custom/pages/Profile/styled.tsx @@ -7,10 +7,12 @@ import { transparentize } from 'polished' import { ExternalLink } from 'theme' import { ButtonCustom as AddToMetaMask } from 'components/AddToMetamask' import { CopyIcon as ClickToCopy } from 'components/Copy' +import SVG from 'react-inlinesvg' export const Container = styled.div` max-width: 910px; width: 100%; + z-index: 1; ` export const Wrapper = styled(Page)` @@ -243,13 +245,23 @@ export const CardsWrapper = styled.div` padding: 0; z-index: 2; - > div:nth-of-type(3n) { + > div { + flex: 1 1 300px; + } + > div:last-child:nth-child(odd) { flex: 1 1 100%; } ${({ theme }) => theme.mediaWidth.upToSmall` display: flex; flex-flow: column wrap; + + > div { + flex: 1 1 100%; + } + > div:last-child:nth-child(odd) { + flex: 1 1 100%; + } `}; ` @@ -626,3 +638,11 @@ export const VestingBreakdown = styled.div` color: ${({ theme }) => theme.primary1}; } ` + +export const BannerCardContent = styled.span` + z-index: 2; +` + +export const BannerCardSvg = styled(SVG)` + z-index: 1; +` diff --git a/src/custom/state/claim/actions.ts b/src/custom/state/claim/actions.ts index 6a38933aad..c9653a1b51 100644 --- a/src/custom/state/claim/actions.ts +++ b/src/custom/state/claim/actions.ts @@ -10,12 +10,6 @@ export enum ClaimStatus { FAILED = 'FAILED', } -export enum SwapVCowStatus { - INITIAL = 'INITIAL', - ATTEMPTING = 'ATTEMPTING', - SUBMITTED = 'SUBMITTED', -} - export type ClaimActions = { // account setInputAddress: (payload: string) => void @@ -41,9 +35,6 @@ export type ClaimActions = { // claim row selection setSelected: (payload: number[]) => void setSelectedAll: (payload: boolean) => void - - // swap vCow for Cow - setSwapVCowStatus: (payload: SwapVCowStatus) => void } // accounts @@ -86,5 +77,3 @@ export const setClaimsCount = createAction<{ claimInfo: Partial account: string }>('claims/setClaimsCount') -// swap vCow -export const setSwapVCowStatus = createAction('claim/setSwapVCowStatus') diff --git a/src/custom/state/claim/hooks/index.ts b/src/custom/state/claim/hooks/index.ts index 86632779ea..e459abb398 100644 --- a/src/custom/state/claim/hooks/index.ts +++ b/src/custom/state/claim/hooks/index.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import JSBI from 'jsbi' import ms from 'ms.macro' -import { Currency, CurrencyAmount, Price, Token } from '@uniswap/sdk-core' +import { CurrencyAmount, Price, Token } from '@uniswap/sdk-core' import { TransactionResponse } from '@ethersproject/providers' import { parseUnits } from '@ethersproject/units' import { BigNumber } from '@ethersproject/bignumber' @@ -10,7 +10,7 @@ import { VCow as VCowType } from 'abis/types' import { useVCowContract } from 'hooks/useContract' import { useActiveWeb3React } from 'hooks/web3' -import { useSingleContractMultipleData, useSingleCallResult, CallStateResult } from 'lib/hooks/multicall' +import { useSingleContractMultipleData } from 'lib/hooks/multicall' import { useTransactionAdder } from 'state/enhancedTransactions/hooks' import { GpEther, V_COW } from 'constants/tokens' @@ -52,8 +52,6 @@ import { setEstimatedGas, setIsTouched, setClaimsCount, - setSwapVCowStatus, - SwapVCowStatus, } from '../actions' import { EnhancedUserClaimData } from 'pages/Claim/types' import { supportedChainId } from 'utils/supportedChainId' @@ -61,8 +59,7 @@ 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' +import { CallState } from '@uniswap/redux-multicall' export { useUserClaimData, useUserHasAvailableClaim } from '@src/state/claim/hooks' @@ -168,7 +165,7 @@ export function useClassifiedUserClaims(account: Account, optionalChainId?: Supp let isContractCallLoading = false - results.forEach((result, index) => { + results.forEach((result: CallState, index: number) => { const claim = userClaims[index] // Use the loading state from the multicall results @@ -875,7 +872,6 @@ 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] ) @@ -1005,124 +1001,3 @@ export const useClaimLinks = () => { [chainId] ) } - -/** - * Hook that parses the result input with BigNumber value to CurrencyAmount - */ -function useParseVCowResult(result: CallStateResult | 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 - */ -type VCowData = { - isLoading: boolean - total: CurrencyAmount | undefined | null - unvested: CurrencyAmount | undefined | null - vested: CurrencyAmount | undefined | null -} - -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 - */ - -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 = `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, - } -} diff --git a/src/custom/state/claim/middleware.ts b/src/custom/state/claim/middleware.ts index 18a348d0c2..13193743d3 100644 --- a/src/custom/state/claim/middleware.ts +++ b/src/custom/state/claim/middleware.ts @@ -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, setSwapVCowStatus, SwapVCowStatus } from './actions' +import { ClaimStatus, setClaimStatus } from './actions' const isFinalizeTransaction = isAnyOf(finalizeTransaction) const isAddTransaction = isAnyOf(addTransaction) @@ -39,15 +39,6 @@ export const claimMinedMiddleware: Middleware, 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)) } } diff --git a/src/custom/state/claim/reducer.ts b/src/custom/state/claim/reducer.ts index f6da47223d..78c2a79a60 100644 --- a/src/custom/state/claim/reducer.ts +++ b/src/custom/state/claim/reducer.ts @@ -19,8 +19,6 @@ import { setEstimatedGas, setIsTouched, setClaimsCount, - SwapVCowStatus, - setSwapVCowStatus, } from './actions' export type ClaimInfo = { @@ -66,8 +64,6 @@ 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 = { @@ -98,8 +94,6 @@ export type ClaimState = { selectedAll: boolean // claims on other chains claimInfoPerAccount: ClaimInfoPerAccount - // swap VCow status - swapVCowStatus: SwapVCowStatus } export default createReducer(initialState, (builder) => @@ -186,7 +180,4 @@ export default createReducer(initialState, (builder) => .addCase(setIsTouched, (state, { payload: { index, isTouched } }) => { state.investFlowData[index].isTouched = isTouched }) - .addCase(setSwapVCowStatus, (state, { payload }) => { - state.swapVCowStatus = payload - }) ) diff --git a/src/custom/state/cowToken/actions.ts b/src/custom/state/cowToken/actions.ts index ea1f01f1d4..828b5db34c 100644 --- a/src/custom/state/cowToken/actions.ts +++ b/src/custom/state/cowToken/actions.ts @@ -4,6 +4,7 @@ export enum SwapVCowStatus { INITIAL = 'INITIAL', ATTEMPTING = 'ATTEMPTING', SUBMITTED = 'SUBMITTED', + CONFIRMED = 'CONFIRMED', } export type CowTokenActions = { diff --git a/src/custom/state/cowToken/hooks.ts b/src/custom/state/cowToken/hooks.ts index 931b32f942..65232344b1 100644 --- a/src/custom/state/cowToken/hooks.ts +++ b/src/custom/state/cowToken/hooks.ts @@ -14,6 +14,9 @@ import { setSwapVCowStatus, SwapVCowStatus } from './actions' import { OperationType } from 'components/TransactionConfirmationModal' import { APPROVE_GAS_LIMIT_DEFAULT } from 'hooks/useApproveCallback/useApproveCallbackMod' import { useTokenBalance } from 'state/wallet/hooks' +import { useCowFromLockedGnoBalances } from 'pages/Profile/LockedGnoVesting/hooks' +import { SupportedChainId } from 'constants/chains' +import JSBI from 'jsbi' export type SetSwapVCowStatusCallback = (payload: SwapVCowStatus) => void @@ -162,18 +165,37 @@ export function useCowBalance() { } /** - * Hook that returns combined vCOW + COW balance + * Hook that returns combined vCOW + COW balance + vCow from locked GNO */ export function useCombinedBalance() { + const { chainId, account } = useActiveWeb3React() const { total: vCowBalance } = useVCowData() + const { allocated, claimed } = useCowFromLockedGnoBalances() const cowBalance = useCowBalance() - return useMemo(() => { - if (!vCowBalance || !cowBalance) { + const lockedGnoBalance = useMemo(() => { + if (!allocated || !claimed) { return } - const sum = vCowBalance.asFraction.add(cowBalance.asFraction) - return CurrencyAmount.fromRawAmount(cowBalance.currency, sum.quotient) - }, [cowBalance, vCowBalance]) + return JSBI.subtract(allocated.quotient, claimed.quotient) + }, [allocated, claimed]) + + return useMemo(() => { + let tmpBalance = JSBI.BigInt(0) + + const isLoading = account && (!vCowBalance || !lockedGnoBalance || !cowBalance) ? true : false + + const cow = COW[chainId || SupportedChainId.MAINNET] + + if (account) { + if (vCowBalance) tmpBalance = JSBI.add(tmpBalance, vCowBalance.quotient) + if (lockedGnoBalance) tmpBalance = JSBI.add(tmpBalance, lockedGnoBalance) + if (cowBalance) tmpBalance = JSBI.add(tmpBalance, cowBalance.quotient) + } + + const balance = CurrencyAmount.fromRawAmount(cow, tmpBalance) + + return { balance, isLoading } + }, [vCowBalance, lockedGnoBalance, cowBalance, chainId, account]) } diff --git a/src/custom/state/cowToken/middleware.ts b/src/custom/state/cowToken/middleware.ts index 61921df89a..250b1c2e56 100644 --- a/src/custom/state/cowToken/middleware.ts +++ b/src/custom/state/cowToken/middleware.ts @@ -16,7 +16,7 @@ export const cowTokenMiddleware: Middleware, AppState> = const { chainId, hash } = action.payload const transaction = store.getState().transactions[chainId][hash] - if (transaction.swapVCow) { + if (transaction.swapVCow || transaction.swapLockedGNOvCow) { const status = transaction.receipt?.status console.debug( @@ -24,12 +24,18 @@ export const cowTokenMiddleware: Middleware, AppState> = transaction.hash ) - store.dispatch(setSwapVCowStatus(SwapVCowStatus.INITIAL)) - if (status === 1 && transaction.replacementType !== 'cancel') { cowSound = getCowSoundSuccess() + + if (transaction.swapVCow) { + store.dispatch(setSwapVCowStatus(SwapVCowStatus.CONFIRMED)) + } } else { cowSound = getCowSoundError() + + if (transaction.swapVCow) { + store.dispatch(setSwapVCowStatus(SwapVCowStatus.INITIAL)) + } } } } diff --git a/src/custom/state/enhancedTransactions/actions.ts b/src/custom/state/enhancedTransactions/actions.ts index 47b7c07ede..2480148329 100644 --- a/src/custom/state/enhancedTransactions/actions.ts +++ b/src/custom/state/enhancedTransactions/actions.ts @@ -10,7 +10,16 @@ export type AddTransactionParams = WithChainId & WithData & Pick< EnhancedTransactionDetails, - 'hash' | 'hashType' | 'from' | 'approval' | 'presign' | 'claim' | 'summary' | 'safeTransaction' | 'swapVCow' + | 'hash' + | 'hashType' + | 'from' + | 'approval' + | 'presign' + | 'claim' + | 'summary' + | 'safeTransaction' + | 'swapVCow' + | 'swapLockedGNOvCow' > export const addTransaction = createAction('enhancedTransactions/addTransaction') diff --git a/src/custom/state/enhancedTransactions/hooks/index.ts b/src/custom/state/enhancedTransactions/hooks/index.ts index 3ff7edb923..8a9567d266 100644 --- a/src/custom/state/enhancedTransactions/hooks/index.ts +++ b/src/custom/state/enhancedTransactions/hooks/index.ts @@ -24,7 +24,8 @@ export function useTransactionAdder(): TransactionAdder { (addTransactionParams: AddTransactionHookParams) => { if (!account || !chainId) return - const { hash, summary, data, claim, approval, presign, safeTransaction, swapVCow } = addTransactionParams + const { hash, summary, data, claim, approval, presign, safeTransaction, swapVCow, swapLockedGNOvCow } = + addTransactionParams const hashType = isGnosisSafeWallet ? HashType.GNOSIS_SAFE_TX : HashType.ETHEREUM_TX if (!hash) { throw Error('No transaction hash found') @@ -42,6 +43,7 @@ export function useTransactionAdder(): TransactionAdder { presign, safeTransaction, swapVCow, + swapLockedGNOvCow, }) ) }, diff --git a/src/custom/state/enhancedTransactions/reducer.ts b/src/custom/state/enhancedTransactions/reducer.ts index 18efd56144..2e1274e02c 100644 --- a/src/custom/state/enhancedTransactions/reducer.ts +++ b/src/custom/state/enhancedTransactions/reducer.ts @@ -37,6 +37,7 @@ export interface EnhancedTransactionDetails { presign?: { orderId: string } claim?: { recipient: string; cowAmountRaw?: string; indices: number[] } swapVCow?: boolean + swapLockedGNOvCow?: boolean // Wallet specific safeTransaction?: SafeMultisigTransactionResponse // Gnosis Safe transaction info @@ -83,6 +84,7 @@ export default createReducer(initialState, (builder) => claim, data, swapVCow, + swapLockedGNOvCow, }, } ) => { @@ -107,6 +109,7 @@ export default createReducer(initialState, (builder) => safeTransaction, claim, swapVCow, + swapLockedGNOvCow, } transactions[chainId] = txs }