diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 00000000..e93af444 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,86 @@ +Version 0.2.0 (2020-11-10) +========================== + +- [Confirmation message to delete outcomes is not visible when there too many outcomes in the partition section.][575] +- [UI - CONDITION\'S SPLIT POSITIONS section has a double bottom border when it is empty.][576] +- [Cannot merge 3 or more positions of a deeper level.][484] +- [\[Prod/Dev\] 404 error appears when open condition details from a position detail (when there are \>1 conditions).][580] +- [\[FE\] Impossible to merge positions of a \'deeper\' level (collateral USDC).][362] +- [\[FE\] Balance of a position is not cleared out after a merge.][365] +- [\[FE\] Redeem positions: Incorrect field\'s values for the RESOLVED CONDITION ID/ REDEEMED POSITION PREVIEW.][313] +- [\[UI/UX\] Mobile version: UI issues to fix and discuss.][330] +- [Select positions list is blinking when search for a position.][396] +- [\[FE\] Select position list blinking / flashing.][391] +- [UI: Warning pop-up with an error \"expands\" Select position pop-up.][388] +- [\[FE\] Crash when splitting from a condition with many outcomes.][490] +- [Given 2 positions A and B\|C their merge preview is wrong.][487] +- [\[FE\] Crash when splitting from a condition with more than 31 outcomes.][261] +- [When disconnected, the app use default Rinkeby for the Network: it causes errors with filtering.][555] +- [Nothing happens when click on the Ok button on the \'Are you sure you want to leave this page?\' pop-up when navigate to the same page.][552] +- [Empty Condition Id Preview section appears when prepare an omen condition as a not logged in user.][559] +- [Impossible to open a duplicated conditions by the left click on the Duplicated Condition warning.][522] +- [Prepare condition: \'working..\' pop-up hangs when connect a wallet after pressing on the \'Prepare\' button.][561] +- [Redeem Positions heading is missing.][545] +- [Disable sorting by Reporter/Oracle in the Conditions list.][562] +- [\[FE\] Improve pagination in the condition list section.][123] +- [Display oracle = realit.io for a position when it belongs to an omen condition.][538] +- [Link to oracle if it is known.][540] +- [Omen Condition: Prepare button remains disabled until open and reselect a category.][486] +- [Collateral symbol search: Add a possibility to search by all the words that start with a symbol.][524] +- [Positions list: cDAI icon is blinking when open Collateral filter in mainnet.][525] +- [Add a link to the docs.][548] +- [Render question text, rather than ID, for Omen/Reality.eth conditions and positions.][541] +- [\[FE\] Implement a warning pop-up to prevent data loss when leaving a page.][211] +- [UI/UX: issues with search/filters on the Positions and Conditions pages.][440] +- [UI issues with Positions/Conditions grids.][499] +- [\[FE\] Split Position Result Modal window appears when transaction is failed.][276] +- [Add a possibility to search by a custom collateral symbol.][504] + +Version 0.1.0 (2020-10-28) +========================== + +Position list, search, and filter features +------------------------------------------ +- [\[FE\] Position List Search Items, improvements for searching in the subgraph.][507] +- [Positions list: Search query hags eternally when switch search options.][506] +- [Positions/Conditions list: Pagination is not reset to the 1st page after filters applying.][505] +- [Add a possibility to search by a custom collateral symbol.][504] +- [Creation date: validation message is displayed when clear out any filter field.][503] +- [\[FE\] Move \'Custom Token\' option to the bottom in the Collateral filter.][501] +- [UI issues with Positions/Conditions grids.][499] +- [UI: Custom collateral icons are loaded with a delay on Positions list.][496] +- [Creation date filter: No validation message when From date to date (1 day difference).][481] +- [\[FE\] Positions List Filters.][411] +- [\[FE\] Positions List Search Items.][413] + +Condition list, search, and filter features +------------------------------------------- +- [\[FE\] Remove Kleros from the filters][475] +- [\[FE\] Conditions list: Search returns no results when a creation date filter is filled with To=From date.][469] +- [\[FE\] Search results are not refreshed when clear out values in the > \'Number of outcomes\' and \'Created date\' filters.][468] +- [\[FE\] Search returns an odd GraphQL error when search by numbers.][467] +- [\[FE\] Filters are not applied when the user connects.][460] +- [\[UI / UX\] Column names overlap each other when filter is opened.][459] +- [\[FE\] Disable report payout option when a condition is resolved.][458] +- [\[FE\] Condition List Search Items, improvements for searching in the subgraph.][447] +- [UI/UX: issues with search/filters on the Positions and Conditions pages.][440] +- [\[FE - UI/UX\] New Search Feature.][403] +- [\[FE - UI/UX\] New Filters Feature.][404] + +Split positions +--------------- +- [Increase a click area in the Split positions pop-up for navigation icon.][482] +- [Change column title from in the Select Condition pop-up.][480] + +Prepare Condition +----------------- +- [\[FE\] Make Kleros the default arbitrator for Omen Conditions.][474] +- [Resolved in Realit.io condition is displayed like an Open one in CTEAF app.][466] +- [\[FE\] Edit / Remove outcomes is not working.][451] +- [Prepare Omen condition: issues with the Resolution date field.][434] + +Redeem Positions +---------------- + +- [Redeem positions: select position pop-up hangs (eternally) when click on the Position field \>3 times.][398] + diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..341cf11f --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 \ No newline at end of file diff --git a/package.json b/package.json index c688cdbc..7cdfc390 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "conditional-tokens-factory", - "version": "0.1.0", + "version": "0.2.0", "private": true, "dependencies": { "@apollo/react-hooks": "^3.1.5", diff --git a/public/index.html b/public/index.html index 64a1b5ce..48b5be8b 100644 --- a/public/index.html +++ b/public/index.html @@ -95,6 +95,7 @@ background-repeat: no-repeat; background-size: cover; content: ''; + display: block; flex-grow: 0; flex-shrink: 0; height: 50px; diff --git a/src/components/filters/CollateralFilterDropdown/index.tsx b/src/components/filters/CollateralFilterDropdown/index.tsx index d108268b..606b8f61 100644 --- a/src/components/filters/CollateralFilterDropdown/index.tsx +++ b/src/components/filters/CollateralFilterDropdown/index.tsx @@ -1,4 +1,5 @@ import React from 'react' +import styled from 'styled-components' import { ButtonSelectLight } from 'components/buttons/ButtonSelectLight' import { DropdownItem, DropdownPosition } from 'components/common/Dropdown' @@ -8,12 +9,14 @@ import { FilterTitle } from 'components/pureStyledComponents/FilterTitle' import { useWeb3ConnectedOrInfura } from 'contexts/Web3Context' import { CollateralFilterOptions, Token } from 'util/types' +const Wrapper = styled.div`` + interface Props { onClick: (symbol: string, address: Maybe) => void value: string } -export const CollateralFilterDropdown = ({ onClick, value }: Props) => { +export const CollateralFilterDropdown = ({ onClick, value, ...restProps }: Props) => { const { networkConfig } = useWeb3ConnectedOrInfura() const tokensList = networkConfig ? [ @@ -51,7 +54,7 @@ export const CollateralFilterDropdown = ({ onClick, value }: Props) => { ] return ( - <> + Collateral tokenItem.value === value)} @@ -67,6 +70,6 @@ export const CollateralFilterDropdown = ({ onClick, value }: Props) => { ))} /> - + ) } diff --git a/src/components/filters/DateFilter/index.tsx b/src/components/filters/DateFilter/index.tsx index 9a093767..9f2ad0d2 100644 --- a/src/components/filters/DateFilter/index.tsx +++ b/src/components/filters/DateFilter/index.tsx @@ -11,6 +11,8 @@ import { getLogger } from 'util/logger' const Wrapper = styled.div`` +const Rows = styled.div`` + const Row = styled.div` align-items: flex-end; column-gap: 8px; @@ -41,6 +43,7 @@ const Date = styled(Textfield)` height: 32px; padding-left: 6px; padding-right: 6px; + position: relative; ` interface Props { @@ -53,7 +56,7 @@ interface Props { const logger = getLogger('DateFilter') export const DateFilter: React.FC = (props) => { - const { onChangeFrom, onChangeTo, onSubmit, title } = props + const { onChangeFrom, onChangeTo, onSubmit, title, ...restProps } = props const toDate = useRef(null) const fromDate = useRef(null) @@ -107,13 +110,20 @@ export const DateFilter: React.FC = (props) => { const fromGreaterThanToError = React.useMemo( () => - to && from && to < from ? To should be greater than From : null, + to && from && to < from ? ( + + To must be greater than From + + ) : null, [from, to] ) + const datesValidityError = React.useMemo( () => !validToDate || !validFromDate ? ( - {`Invalid date or out of range. Valid dates are from ${MIN_DATE} to ${MAX_DATE}`} + {`Date must be between ${moment(MIN_DATE).format('L')} and ${moment( + MAX_DATE + ).format('L')}`} ) : null, [validToDate, validFromDate] ) @@ -141,37 +151,39 @@ export const DateFilter: React.FC = (props) => { }, [from, to, onSubmit]) return ( - + {title} - - - - - - - - - - - - - + + + + + + + + + + + + + + + {(!!datesValidityError || !!fromGreaterThanToError) && ( {datesValidityError} diff --git a/src/components/filters/WrappedCollateralFilterDropdown/index.tsx b/src/components/filters/WrappedCollateralFilterDropdown/index.tsx index 8d8145b4..a695c4b3 100644 --- a/src/components/filters/WrappedCollateralFilterDropdown/index.tsx +++ b/src/components/filters/WrappedCollateralFilterDropdown/index.tsx @@ -1,4 +1,5 @@ import React from 'react' +import styled from 'styled-components' import { ButtonSelectLight } from 'components/buttons/ButtonSelectLight' import { DropdownItem, DropdownPosition } from 'components/common/Dropdown' @@ -6,13 +7,15 @@ import { FilterDropdown } from 'components/pureStyledComponents/FilterDropdown' import { FilterTitle } from 'components/pureStyledComponents/FilterTitle' import { WrappedCollateralOptions } from 'util/types' +const Wrapper = styled.div`` + interface Props { onClick: (value: WrappedCollateralOptions) => void value: string } export const WrappedCollateralFilterDropdown: React.FC = (props) => { - const { onClick, value } = props + const { onClick, value, ...restProps } = props const dropdownItems = [ { @@ -39,7 +42,7 @@ export const WrappedCollateralFilterDropdown: React.FC = (props) => { ] return ( - <> + Wrapped Collateral item.value === value)} @@ -55,6 +58,6 @@ export const WrappedCollateralFilterDropdown: React.FC = (props) => { ))} /> - + ) } diff --git a/src/components/form/ConditionsDropdown/index.tsx b/src/components/form/ConditionsDropdown/index.tsx new file mode 100644 index 00000000..5666a386 --- /dev/null +++ b/src/components/form/ConditionsDropdown/index.tsx @@ -0,0 +1,86 @@ +import React from 'react' +import styled from 'styled-components' + +import { ButtonSelect } from 'components/buttons/ButtonSelect' +import { Dropdown, DropdownItem, DropdownPosition } from 'components/common/Dropdown' +import { TitleValue } from 'components/text/TitleValue' + +const Button = styled(ButtonSelect)`` + +const ButtonText = styled.span` + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +` + +const IdsDropdown = styled(Dropdown)` + .dropdownItems { + width: 100%; + } +` + +interface Props { + conditions: Maybe> + isLoading?: boolean + onClick: (conditionId: string) => void + title?: string + value: Maybe +} + +export const ConditionsDropdown: React.FC = (props) => { + const { conditions, isLoading, onClick, title = 'Condition ID', value, ...restProps } = props + + const dropdownItems = + conditions && conditions.length + ? [ + ...conditions.map((item) => { + return { + onClick: () => onClick(item), + value: item, + } + }), + ] + : undefined + + const currentIndex = + value && dropdownItems && dropdownItems.length + ? dropdownItems.findIndex((item) => item.value === value) + : 0 + + return ( + {dropdownItems[currentIndex].value} + ) : isLoading ? ( + 'Loading...' + ) : ( + 'No conditions available for positions...' + ) + } + /> + } + dropdownPosition={DropdownPosition.center} + items={ + dropdownItems + ? dropdownItems.map((item, index) => ( + + {item.value} + + )) + : [] + } + {...restProps} + /> + } + /> + ) +} diff --git a/src/components/form/DisplayTableHashes/index.tsx b/src/components/form/DisplayTableHashes/index.tsx index b4f812d3..8756ed30 100644 --- a/src/components/form/DisplayTableHashes/index.tsx +++ b/src/components/form/DisplayTableHashes/index.tsx @@ -39,7 +39,7 @@ export const DisplayTableHashes = (props: Props) => { return ( { return ( = (props) => { {isFetching && ( Fetching… - + )} diff --git a/src/components/mergePositions/MergePreview/index.tsx b/src/components/mergePositions/MergePreview/index.tsx index dac7dc9f..372f7bb3 100644 --- a/src/components/mergePositions/MergePreview/index.tsx +++ b/src/components/mergePositions/MergePreview/index.tsx @@ -1,64 +1,39 @@ import { BigNumber } from 'ethers/utils' -import React, { useEffect, useMemo, useState } from 'react' +import React, { useMemo } from 'react' import { StripedList, StripedListEmpty, - StripedListItem, + StripedListItemPreview, } from 'components/pureStyledComponents/StripedList' import { TitleValue } from 'components/text/TitleValue' -import { useConditionContext } from 'contexts/ConditionContext' -import { useMultiPositionsContext } from 'contexts/MultiPositionsContext' -import { useWeb3ConnectedOrInfura } from 'contexts/Web3Context' -import { getLogger } from 'util/logger' -import { arePositionMergeablesByCondition, getMergePreview, getTokenSummary } from 'util/tools' +import { PositionWithUserBalanceWithDecimals } from 'hooks/usePositionsList' +import { GetCondition_condition } from 'types/generatedGQLForCTE' +import { getMergePreview } from 'util/tools' +import { Token } from 'util/types' interface Props { amount: BigNumber + token: Maybe + positions: Array + condition: Maybe } -const logger = getLogger('MergePreview') - -export const MergePreview = ({ amount }: Props) => { - const { networkConfig, provider } = useWeb3ConnectedOrInfura() - - const { positions } = useMultiPositionsContext() - const { condition } = useConditionContext() - const [mergedPosition, setMergedPosition] = useState('') - - const canMergePositions = useMemo(() => { - return condition && arePositionMergeablesByCondition(positions, condition) - }, [positions, condition]) - - useEffect(() => { - let cancelled = false - if (canMergePositions && condition && positions.length > 0) { - getTokenSummary(networkConfig, provider, positions[0].collateralToken.id) - .then((token) => { - if (!cancelled) { - setMergedPosition(getMergePreview(positions, condition, amount, token)) - } - }) - .catch((err) => { - logger.error(err) - }) - } else { - setMergedPosition('') - } - return () => { - cancelled = true +export const MergePreview = ({ amount, condition, positions, token }: Props) => { + const preview = useMemo(() => { + if (condition && token) { + return getMergePreview(positions, condition.id, amount, token, condition.outcomeSlotCount) } - }, [canMergePositions, condition, positions, amount, provider, networkConfig]) + return null + }, [positions, condition, amount, token]) return ( - {mergedPosition ? ( - - {mergedPosition} - + + {preview ? ( + {preview} ) : ( No merged positions yet. )} diff --git a/src/components/mergePositions/MergeWith/index.tsx b/src/components/mergePositions/MergeWith/index.tsx new file mode 100644 index 00000000..037d802e --- /dev/null +++ b/src/components/mergePositions/MergeWith/index.tsx @@ -0,0 +1,89 @@ +import React, { useState } from 'react' +import styled from 'styled-components' + +import { Checkbox } from 'components/pureStyledComponents/Checkbox' +import { + StripedList, + StripedListEmpty, + StripedListItem, +} from 'components/pureStyledComponents/StripedList' +import { InlineLoading } from 'components/statusInfo/InlineLoading' +import { SpinnerSize } from 'components/statusInfo/common' +import { TitleValue } from 'components/text/TitleValue' +import { MergeablePosition } from 'util/types' + +const Wrapper = styled.div`` + +const MergeableStripedListItem = styled(StripedListItem)` + cursor: pointer; + justify-content: flex-start; + padding-left: 15px; + padding-right: 15px; + + &:hover { + background-color: ${(props) => props.theme.colors.whitesmoke2}; + filter: brightness(94%); + } +` + +const MergeableStripedListItemText = styled.span` + margin-left: 8px; +` + +const MergeableItem: React.FC<{ + index: number + item: MergeablePosition + onClick: (item: MergeablePosition, index: number) => void +}> = (props) => { + const { index, item, onClick, ...restProps } = props + const [selected, setSelected] = useState(false) + + const itemOnClick = () => { + setSelected(!selected) + onClick(item, index) + } + + return ( + + + {item.positionPreview} + + ) +} + +interface Props { + isLoading?: boolean + mergeablePositions: Array | undefined + onClick: (item: MergeablePosition, index: number) => void + // eslint-disable-next-line @typescript-eslint/no-explicit-any + errorFetching: any +} + +export const MergeWith: React.FC = (props) => { + const { errorFetching, isLoading, mergeablePositions, onClick, ...restProps } = props + + return ( + + + {mergeablePositions && mergeablePositions.length && !errorFetching ? ( + mergeablePositions.map((item, index) => ( + + )) + ) : ( + + {isLoading && !errorFetching && } + {!isLoading && + errorFetching && + 'There was an error fetching the positions. Try again.'} + {!isLoading && !errorFetching && 'No mergeable positions.'} + + )} + + } + /> + + ) +} diff --git a/src/components/modals/EditPartitionModal/index.tsx b/src/components/modals/EditPartitionModal/index.tsx index fca10eb8..f5c0c7a7 100644 --- a/src/components/modals/EditPartitionModal/index.tsx +++ b/src/components/modals/EditPartitionModal/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import lodashClonedeep from 'lodash.clonedeep' import React, { useCallback, useEffect, useState } from 'react' -import styled, { withTheme } from 'styled-components' +import styled, { css, withTheme } from 'styled-components' import { Button } from 'components/buttons/Button' import { ButtonAdd } from 'components/buttons/ButtonAdd' @@ -25,7 +25,19 @@ import { TitleControlButton } from 'components/pureStyledComponents/TitleControl import { TitleValue } from 'components/text/TitleValue' import { OutcomeProps } from 'util/types' +const CommonCSS = css` + max-height: 300px; + min-height: 200px; +` + const Collections = styled(StripedList)` + ${CommonCSS} + height: 100%; + position: relative; +` + +const CollectionsOuter = styled.div` + ${CommonCSS} position: relative; ` @@ -110,6 +122,14 @@ const ButtonReset = styled(Button)` margin-right: auto; ` +const ConfirmOverlayFull = styled(ConfirmOverlay)` + border-radius: 4px; + bottom: 1px; + left: 1px; + right: 1px; + top: 1px; +` + interface DraggedOutcomeProps extends OutcomeProps { collectionFromIndex: number outcomeIndex: number @@ -432,8 +452,8 @@ const PartitionModal: React.FC = (props) => { You can drag outcomes across collections. Click on an outcome to remove it. - - <> + + {allCollections.length > 0 ? ( allCollections.map( (outcomesList: Array, collectionIndex: number) => { @@ -457,20 +477,20 @@ const PartitionModal: React.FC = (props) => { No collections. )} - {confirmDeleteAllCollections && ( - { - removeAllCollections() - setConfirmDeleteAllCollections(false) - }} - deny={() => { - setConfirmDeleteAllCollections(false) - }} - text="Remove all collections?" - /> - )} - - + + {confirmDeleteAllCollections && ( + { + removeAllCollections() + setConfirmDeleteAllCollections(false) + }} + deny={() => { + setConfirmDeleteAllCollections(false) + }} + text="Remove all collections?" + /> + )} + A valid partition needs at least two collections. No outcomes can be orphaned. diff --git a/src/components/modals/SelectConditionModal/index.tsx b/src/components/modals/SelectConditionModal/index.tsx index b52446e5..cf042970 100644 --- a/src/components/modals/SelectConditionModal/index.tsx +++ b/src/components/modals/SelectConditionModal/index.tsx @@ -170,7 +170,7 @@ export const SelectConditionModal: React.FC = (props) => { {error && !isLoading && } {!error && ( = (props) => { {error && !isLoading && } {!error && ( = (props) => { title={singlePosition ? 'Selected Position' : 'Selected Positions'} value={ props.theme.colors.primary}; + content: ''; + height: 8px; + left: 1px; + position: absolute; + top: 1px; + width: 8px; + } +` + +export const Checkbox = styled.div<{ checked?: boolean }>` + background-color: #fff; + border: solid 1px ${(props) => props.theme.colors.primary}; + flex-grow: 0; + flex-shrink: 0; + height: 12px; + position: relative; + width: 12px; + + ${(props) => props.checked && CheckboxSelectedCSS} +` diff --git a/src/components/pureStyledComponents/CompactFilterCell.tsx b/src/components/pureStyledComponents/CompactFilterCell.tsx new file mode 100644 index 00000000..c5334444 --- /dev/null +++ b/src/components/pureStyledComponents/CompactFilterCell.tsx @@ -0,0 +1,6 @@ +import styled from 'styled-components' + +export const CompactFilterCell = styled.div` + display: flex; + flex-direction: column; +` diff --git a/src/components/pureStyledComponents/CompactFiltersLayout.tsx b/src/components/pureStyledComponents/CompactFiltersLayout.tsx new file mode 100644 index 00000000..812108f4 --- /dev/null +++ b/src/components/pureStyledComponents/CompactFiltersLayout.tsx @@ -0,0 +1,39 @@ +import styled from 'styled-components' + +export const CompactFiltersLayout = styled.div` + column-gap: 20px; + display: grid; + grid-template-columns: 1fr 1fr; + margin-bottom: 36px; + row-gap: 20px; + + .dateFilterRows { + column-gap: 10px; + display: grid; + grid-template-columns: 110px 1fr; + + .dateFilterRow { + margin: 0; + + &:first-child { + display: block; + } + + input[type='date'] { + &::-webkit-inner-spin-button, + &::-webkit-calendar-picker-indicator { + background: transparent; + color: transparent; + height: 30px; + left: 0; + margin: 0; + padding: 0; + position: absolute; + top: 0; + width: 110px; + z-index: 5; + } + } + } + } +` diff --git a/src/components/pureStyledComponents/EmptyContentText.tsx b/src/components/pureStyledComponents/EmptyContentText.tsx index 193c708a..5f8954dc 100644 --- a/src/components/pureStyledComponents/EmptyContentText.tsx +++ b/src/components/pureStyledComponents/EmptyContentText.tsx @@ -5,6 +5,7 @@ export const EmptyContentText = styled.div` color: ${(props) => props.theme.colors.textColor}; display: flex; font-size: 15px; + font-weight: 600; height: 100%; justify-content: center; line-height: 1.5; diff --git a/src/components/pureStyledComponents/Error.tsx b/src/components/pureStyledComponents/Error.tsx index 0602fb23..7b912f5e 100644 --- a/src/components/pureStyledComponents/Error.tsx +++ b/src/components/pureStyledComponents/Error.tsx @@ -6,7 +6,7 @@ export const ErrorContainer = styled.div` export const Error = styled.p` color: ${(props) => props.theme.colors.error}; - font-size: 14px; + font-size: 12px; font-weight: 600; line-height: 1.4; margin: 0 0 5px 0; diff --git a/src/components/pureStyledComponents/RadioButton.tsx b/src/components/pureStyledComponents/RadioButton.tsx new file mode 100644 index 00000000..5be85e61 --- /dev/null +++ b/src/components/pureStyledComponents/RadioButton.tsx @@ -0,0 +1,27 @@ +import styled, { css } from 'styled-components' + +const RadioButtonSelectedCSS = css` + &:before { + background-color: ${(props) => props.theme.colors.primary}; + border-radius: 50%; + content: ''; + height: 8px; + left: 1px; + position: absolute; + top: 1px; + width: 8px; + } +` + +export const RadioButton = styled.div<{ checked?: boolean }>` + background-color: #fff; + border-radius: 50%; + border: solid 1px ${(props) => props.theme.colors.primary}; + flex-grow: 0; + flex-shrink: 0; + height: 12px; + position: relative; + width: 12px; + + ${(props) => props.checked && RadioButtonSelectedCSS} +` diff --git a/src/components/pureStyledComponents/StripedList.tsx b/src/components/pureStyledComponents/StripedList.tsx index 2de593fd..6a44c27a 100644 --- a/src/components/pureStyledComponents/StripedList.tsx +++ b/src/components/pureStyledComponents/StripedList.tsx @@ -25,9 +25,10 @@ export const StripedListItem = styled.div<{ justifyContent?: string }>` font-size: 15px; font-weight: 400; justify-content: ${(props) => props.justifyContent}; - line-height: 1; + line-height: 1.3; padding: 12px 20px; text-align: left; + word-break: break-all; &:nth-child(even) { background-color: ${(props) => props.theme.colors.whitesmoke2}; @@ -48,6 +49,10 @@ export const StripedListItemLessPadding = styled(StripedListItem)` padding: 8px 12px; ` +export const StripedListItemPreview = styled(StripedListItem)` + font-weight: 600; +` + StripedListItem.defaultProps = { justifyContent: 'space-between', } @@ -58,6 +63,7 @@ export const StripedListEmpty = styled.div` display: flex; flex-grow: 1; font-size: 15px; + font-weight: 600; height: 100%; justify-content: center; line-height: 1.5; diff --git a/src/components/redeemPosition/PositionPreview/index.tsx b/src/components/redeemPosition/PositionPreview/index.tsx index e9fc24c0..f06a0a99 100644 --- a/src/components/redeemPosition/PositionPreview/index.tsx +++ b/src/components/redeemPosition/PositionPreview/index.tsx @@ -1,4 +1,3 @@ -import { BigNumber } from 'ethers/utils' import React, { useEffect, useMemo } from 'react' import { @@ -11,7 +10,6 @@ import { NetworkConfig } from 'config/networkConfig' import { useBalanceForPosition } from 'hooks/useBalanceForPosition' import { useCollateral } from 'hooks/useCollateral' import { GetCondition_condition, GetPosition_position } from 'types/generatedGQLForCTE' -import { getRedeemedBalance, getRedeemedPreview } from 'util/tools' interface Props { position: Maybe @@ -20,7 +18,7 @@ interface Props { } export const PositionPreview = ({ condition, position }: Props) => { - const { balanceERC1155, refetch } = useBalanceForPosition(position?.id || '') + const { refetch } = useBalanceForPosition(position?.id || '') const { collateral: token } = useCollateral(position ? position.collateralToken.id : '') useEffect(() => { @@ -29,20 +27,23 @@ export const PositionPreview = ({ condition, position }: Props) => { } }, [position, refetch]) - const redeemedBalance = useMemo( - () => - position && condition - ? getRedeemedBalance(position, condition, balanceERC1155) - : new BigNumber(0), - [position, condition, balanceERC1155] - ) + // TODO: until we refactor this section with the new designs + // const redeemedBalance = useMemo( + // () => new BigNumber(0), + // position && condition + // ? getRedeemedBalance(position, condition, balanceERC1155) + // : new BigNumber(0), + // [] + // ) const redeemedPreview = useMemo(() => { if (position && condition && token) { - return getRedeemedPreview(position, condition, redeemedBalance, token) + // TODO: until we refactor this section with the new designs + // return getRedeemedPreview(position, condition, redeemedBalance, token) + return '' } return '' - }, [position, condition, redeemedBalance, token]) + }, [position, condition, token]) return ( {splitPositionPreview.length > 0 ? ( splitPositionPreview.map((preview, i) => ( - {preview} + {preview} )) ) : ( No Split Positions. diff --git a/src/components/statusInfo/Spinner/img/SpinnerSVG.tsx b/src/components/statusInfo/Spinner/img/SpinnerSVG.tsx index 2a0db8ee..30bfb8c6 100644 --- a/src/components/statusInfo/Spinner/img/SpinnerSVG.tsx +++ b/src/components/statusInfo/Spinner/img/SpinnerSVG.tsx @@ -1,7 +1,9 @@ import React from 'react' import styled from 'styled-components' -const Wrapper = styled.svg`` +const Wrapper = styled.svg` + display: block; +` export const SpinnerSVG: React.FC = (props) => { return ( diff --git a/src/components/table/SelectablePositionTable/index.tsx b/src/components/table/SelectablePositionTable/index.tsx new file mode 100644 index 00000000..116bccbc --- /dev/null +++ b/src/components/table/SelectablePositionTable/index.tsx @@ -0,0 +1,342 @@ +import { useDebounceCallback } from '@react-hook/debounce' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import DataTable from 'react-data-table-component' +import styled from 'styled-components' + +import { TokenIcon } from 'components/common/TokenIcon' +import { CollateralFilterDropdown } from 'components/filters/CollateralFilterDropdown' +import { DateFilter } from 'components/filters/DateFilter' +import { WrappedCollateralFilterDropdown } from 'components/filters/WrappedCollateralFilterDropdown' +import { SearchField } from 'components/form/SearchField' +import { Switch } from 'components/form/Switch' +import { CompactFiltersLayout } from 'components/pureStyledComponents/CompactFiltersLayout' +import { EmptyContentText } from 'components/pureStyledComponents/EmptyContentText' +import { RadioButton } from 'components/pureStyledComponents/RadioButton' +import { InlineLoading } from 'components/statusInfo/InlineLoading' +import { SpinnerSize } from 'components/statusInfo/common' +import { TableControls } from 'components/table/TableControls' +import { FormatHash } from 'components/text/FormatHash' +import { TitleValue } from 'components/text/TitleValue' +import { useWeb3ConnectedOrInfura } from 'contexts/Web3Context' +import { PositionWithUserBalanceWithDecimals, usePositionsList } from 'hooks/usePositionsList' +import { usePositionsSearchOptions } from 'hooks/usePositionsSearchOptions' +import { customStyles } from 'theme/tableCustomStyles' +import { truncateStringInTheMiddle } from 'util/tools' +import { + AdvancedFilterPosition, + CollateralFilterOptions, + PositionSearchOptions, + WrappedCollateralOptions, +} from 'util/types' + +const Search = styled(SearchField)` + min-width: 0; + width: 400px; +` + +const TableControlsStyled = styled(TableControls)` + padding-top: 13px; +` + +const TitleValueExtended = styled(TitleValue)<{ hideTitle?: boolean }>` + ${(props) => props.hideTitle && 'h2 { display: none;}'} +` + +interface Props { + hideTitle?: boolean + onRowClicked: (position: PositionWithUserBalanceWithDecimals) => void + onClearCallback: () => void + selectedPosition: Maybe + title?: string + clearFilters: boolean +} + +export const SelectablePositionTable: React.FC = (props) => { + const { + clearFilters, + hideTitle, + onClearCallback, + onRowClicked, + selectedPosition, + title = 'Positions', + ...restProps + } = props + + const { networkConfig } = useWeb3ConnectedOrInfura() + + const [positionList, setPositionList] = useState([]) + + const [searchBy, setSearchBy] = useState(PositionSearchOptions.PositionId) + const [textToShow, setTextToShow] = useState('') + const [textToSearch, setTextToSearch] = useState('') + const [resetPagination, setResetPagination] = useState(false) + const [showFilters, setShowFilters] = useState(false) + + const [selectedCollateralFilter, setSelectedCollateralFilter] = useState>(null) + const [selectedCollateralValue, setSelectedCollateralValue] = useState( + CollateralFilterOptions.All + ) + + const [selectedFromCreationDate, setSelectedFromCreationDate] = useState>(null) + const [selectedToCreationDate, setSelectedToCreationDate] = useState>(null) + const [wrappedCollateral, setWrappedCollateral] = useState( + WrappedCollateralOptions.All + ) + + const debouncedHandlerTextToSearch = useDebounceCallback((textToSearch) => { + setTextToSearch(textToSearch) + }, 500) + + const onChangeSearch = useCallback( + (event: React.ChangeEvent) => { + const { value } = event.currentTarget + setTextToShow(value) + debouncedHandlerTextToSearch(value) + }, + [debouncedHandlerTextToSearch] + ) + + const onClearSearch = useCallback(() => { + setTextToShow('') + debouncedHandlerTextToSearch('') + }, [debouncedHandlerTextToSearch]) + + const advancedFilters: AdvancedFilterPosition = useMemo(() => { + return { + CollateralValue: { + type: selectedCollateralValue, + value: selectedCollateralFilter, + }, + ToCreationDate: selectedToCreationDate, + FromCreationDate: selectedFromCreationDate, + TextToSearch: { + type: searchBy, + value: textToSearch, + }, + WrappedCollateral: wrappedCollateral, + } + }, [ + wrappedCollateral, + selectedCollateralValue, + selectedCollateralFilter, + selectedToCreationDate, + selectedFromCreationDate, + searchBy, + textToSearch, + ]) + + const { data, error, loading } = usePositionsList(advancedFilters) + + // #1 Filter, only positions with balance + const positionsWithBalance = useMemo( + () => + data && + data.length && + data.filter( + (position: PositionWithUserBalanceWithDecimals) => !position.userBalanceERC1155.isZero() + ), + [data] + ) + + const resetFilters = useCallback(() => { + setResetPagination(!resetPagination) + setSelectedToCreationDate(null) + setSelectedFromCreationDate(null) + setSearchBy(PositionSearchOptions.PositionId) + setTextToSearch('') + setSelectedCollateralFilter(null) + setSelectedCollateralValue(CollateralFilterOptions.All) + setWrappedCollateral(WrappedCollateralOptions.All) + }, [resetPagination]) + + // Clear the filters on network change + useEffect(() => { + setShowFilters(false) + resetFilters() + onClearCallback() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [networkConfig, clearFilters]) + + // Filter selected positions from original list. And positions without balance as indicated by props. + useEffect(() => { + if (positionsWithBalance) { + setPositionList(positionsWithBalance) + } else { + setPositionList([]) + } + }, [setPositionList, positionsWithBalance]) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const defaultColumns: Array = useMemo( + () => [ + { + // eslint-disable-next-line react/display-name + cell: (position: PositionWithUserBalanceWithDecimals) => ( + onRowClicked(position)} + /> + ), + maxWidth: '12px', + minWidth: '12px', + }, + { + // eslint-disable-next-line react/display-name + cell: (row: PositionWithUserBalanceWithDecimals) => ( + onRowClicked(row)} + /> + ), + maxWidth: '170px', + name: 'Position Id', + selector: 'createTimestamp', + sortable: true, + }, + { + // eslint-disable-next-line react/display-name + cell: (row: PositionWithUserBalanceWithDecimals) => { + return row.token ? ( + onRowClicked(row)} token={row.token} /> + ) : ( + row.collateralToken + ) + }, + maxWidth: '140px', + minWidth: '140px', + name: 'Collateral', + selector: 'collateralToken', + sortable: true, + }, + { + // eslint-disable-next-line react/display-name + cell: (row: PositionWithUserBalanceWithDecimals) => ( + onRowClicked(row)}>{row.userBalanceERC1155WithDecimals} + ), + name: 'ERC1155', + right: true, + selector: 'userBalanceERC1155Numbered', + sortable: true, + }, + ], + [onRowClicked, selectedPosition] + ) + + const toggleShowFilters = useCallback(() => { + setShowFilters(!showFilters) + }, [showFilters]) + + // Clear the filters + useEffect(() => { + if (!showFilters) { + resetFilters() + onClearCallback() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showFilters]) + + const isLoading = useMemo(() => !textToSearch && loading, [textToSearch, loading]) + const isSearching = useMemo(() => textToSearch && loading, [textToSearch, loading]) + + const showSpinner = useMemo(() => (isLoading || isSearching) && !error, [ + isLoading, + isSearching, + error, + ]) + + const dropdownItems = usePositionsSearchOptions(setSearchBy) + + useEffect(() => { + if ( + textToSearch !== '' || + wrappedCollateral !== WrappedCollateralOptions.All || + selectedCollateralValue !== CollateralFilterOptions.All || + selectedCollateralFilter || + selectedToCreationDate || + selectedFromCreationDate + ) { + setResetPagination(!resetPagination) + } + onClearCallback() + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + textToSearch, + wrappedCollateral, + selectedCollateralValue, + selectedCollateralFilter, + selectedToCreationDate, + selectedFromCreationDate, + ]) + + return ( + + + } + start={} + /> + {showFilters && ( + + ) => { + setSelectedCollateralFilter(address) + setSelectedCollateralValue(symbol) + }} + value={selectedCollateralValue} + /> + { + setWrappedCollateral(value) + }} + value={wrappedCollateral} + /> + { + setSelectedFromCreationDate(from) + setSelectedToCreationDate(to) + }} + title="Creation Date" + /> + + )} + + ) : error ? ( + Error: {error.message} + ) : ( + No positions found. + ) + } + noHeader + onRowClicked={onRowClicked} + pagination + paginationPerPage={5} + paginationResetDefaultPage={resetPagination} + paginationRowsPerPageOptions={[5, 10, 15]} + pointerOnHover + responsive + /> + + } + {...restProps} + /> + ) +} diff --git a/src/components/text/TitleValue/index.tsx b/src/components/text/TitleValue/index.tsx index 48b6e2a5..eed3f07e 100644 --- a/src/components/text/TitleValue/index.tsx +++ b/src/components/text/TitleValue/index.tsx @@ -5,6 +5,7 @@ const Wrapper = styled.div<{ flexDirection?: string }>` display: flex; flex-direction: ${(props) => props.flexDirection}; margin: 0; + min-width: 0; ` const Title = styled.h2<{ flexDirection?: string }>` diff --git a/src/config/constants.ts b/src/config/constants.ts index 5545d802..13574e14 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -130,5 +130,5 @@ export const MIN_OUTCOMES_ALLOWED = Number(process.env.REACT_APP_MIN_OUTCOMES_AL export const MAX_OUTCOMES_ALLOWED = Number(process.env.REACT_APP_MAX_OUTCOMES_ALLOWED || 256) -export const MAX_DATE = '2030-01-01' export const MIN_DATE = '2015-07-30' +export const MAX_DATE = '2030-01-01' diff --git a/src/contexts/PositionContext.tsx b/src/contexts/PositionContext.tsx index 9cdab647..404908be 100644 --- a/src/contexts/PositionContext.tsx +++ b/src/contexts/PositionContext.tsx @@ -73,14 +73,12 @@ export const PositionProvider = (props: Props) => { } ) - if (positionId && fetchedPosition?.position) { - const { position: positionFromTheGraph } = fetchedPosition ?? { position: null } + if (positionId && fetchedPosition) { + position = fetchedPosition?.position + } - if (positionFromTheGraph) { - position = positionFromTheGraph - } else { - errors.push(PositionErrors.NOT_FOUND_ERROR) - } + if (positionId && !position && !loading) { + errors.push(PositionErrors.NOT_FOUND_ERROR) } const { balanceERC20, balanceERC1155, refetch: refetchBalances } = useBalanceForPosition( @@ -90,6 +88,7 @@ export const PositionProvider = (props: Props) => { if (position && balanceERC1155 && balanceERC1155.isZero() && checkForEmptyBalanceERC1155) { errors.push(PositionErrors.EMPTY_BALANCE_ERC1155_ERROR) } + if (position && balanceERC20 && balanceERC20.isZero() && checkForEmptyBalanceERC20) { errors.push(PositionErrors.EMPTY_BALANCE_ERC20_ERROR) } diff --git a/src/hooks/useCondition.tsx b/src/hooks/useCondition.tsx new file mode 100644 index 00000000..396ad5e6 --- /dev/null +++ b/src/hooks/useCondition.tsx @@ -0,0 +1,14 @@ +import { useQuery } from '@apollo/react-hooks' + +import { GetConditionQuery } from 'queries/CTEConditions' + +export const useCondition = (conditionId: Maybe) => { + const { data } = useQuery(GetConditionQuery, { + variables: { + id: conditionId, + }, + fetchPolicy: 'no-cache', + }) + + return data?.condition +} diff --git a/src/hooks/useIsConditionResolved.tsx b/src/hooks/useIsConditionResolved.tsx new file mode 100644 index 00000000..a98a43f4 --- /dev/null +++ b/src/hooks/useIsConditionResolved.tsx @@ -0,0 +1,19 @@ +import { useQuery } from '@apollo/react-hooks' +import { useMemo } from 'react' + +import { GetConditionQuery } from 'queries/CTEConditions' + +export const useIsConditionResolved = (conditionId: Maybe) => { + const { data } = useQuery(GetConditionQuery, { + variables: { + id: conditionId, + }, + fetchPolicy: 'no-cache', + }) + + return useMemo( + () => (data && conditionId ? !!data.condition.resolved : false), + // eslint-disable-next-line react-hooks/exhaustive-deps + [data, conditionId] + ) +} diff --git a/src/hooks/utils.test.ts b/src/hooks/utils.test.ts index 9130cc13..a697a840 100644 --- a/src/hooks/utils.test.ts +++ b/src/hooks/utils.test.ts @@ -88,6 +88,8 @@ test('marshalPositionListData should return the Positions without balances', asy conditions: [], wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', createTimestamp: '1571930105', + conditionIds: [], + indexSets: [], }, { id: 'Position2', @@ -97,6 +99,8 @@ test('marshalPositionListData should return the Positions without balances', asy conditions: [], wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', createTimestamp: '1571930105', + conditionIds: [], + indexSets: [], }, ] @@ -113,6 +117,8 @@ test('marshalPositionListData should return the Positions with some balances', a conditions: [], wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', createTimestamp: '1571930105', + conditionIds: [], + indexSets: [], }, { id: 'Position2', @@ -122,6 +128,8 @@ test('marshalPositionListData should return the Positions with some balances', a conditions: [], wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', createTimestamp: '1571930105', + conditionIds: [], + indexSets: [], }, ] diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index a65bd48c..0da580b7 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -7,10 +7,13 @@ export interface ConditionInformation { oracle: string questionId: string conditionId: string + outcomeSlotCount: number } export interface Position { id: string + indexSets: string[] + conditionIds: string[] createTimestamp: number collateralToken: string wrappedToken: Maybe @@ -29,6 +32,8 @@ export const marshalPositionListData = ( : 0 return { id: position.id, + indexSets: position.indexSets, + conditionIds: position.conditionIds, collateralToken: position.collateralToken.id, wrappedToken: position?.wrappedToken?.id, createTimestamp: position.createTimestamp, @@ -40,6 +45,7 @@ export const marshalPositionListData = ( oracle: condition.oracle, questionId: condition.questionId, conditionId: condition.id, + outcomeSlotCount: condition.outcomeSlotCount, } }) ?? [], } as Position diff --git a/src/pages/ConditionDetails/Wrapper.tsx b/src/pages/ConditionDetails/Wrapper.tsx index f6ab5184..2b178c75 100644 --- a/src/pages/ConditionDetails/Wrapper.tsx +++ b/src/pages/ConditionDetails/Wrapper.tsx @@ -30,7 +30,7 @@ export const Wrapper = (props: WrapperProps) => { setConditionId(conditionId) }, [conditionId, setConditionId]) - const DisplayErrors = (): JSX.Element => { + const DisplayErrors = React.useCallback(() => { const isNotLoadingAndThereIsNoCondition: boolean = !loading && !condition if (isNotLoadingAndThereIsNoCondition && isConditionErrorNotIndexed(errors)) { return ( @@ -53,9 +53,9 @@ export const Wrapper = (props: WrapperProps) => { } else if (isNotLoadingAndThereIsNoCondition && isConditionErrorNotFound(errors)) { return } else { - return <> + return null } - } + }, [condition, errors, history, loading]) return ( <> diff --git a/src/pages/ConditionsList/index.tsx b/src/pages/ConditionsList/index.tsx index 56ec9e87..3bf8e9e4 100644 --- a/src/pages/ConditionsList/index.tsx +++ b/src/pages/ConditionsList/index.tsx @@ -126,7 +126,6 @@ export const ConditionsList: React.FC = () => { setTextToSearch('') }, [resetPagination]) - // Clear the filters useEffect(() => { if (!showFilters) { diff --git a/src/pages/MergePositions/Contents.tsx b/src/pages/MergePositions/Contents.tsx index 1580a80c..832f22a5 100644 --- a/src/pages/MergePositions/Contents.tsx +++ b/src/pages/MergePositions/Contents.tsx @@ -7,122 +7,152 @@ import { Button } from 'components/buttons/Button' import { ButtonType } from 'components/buttons/buttonStylingTypes' import { CenteredCard } from 'components/common/CenteredCard' import { Amount } from 'components/form/Amount' -import { SelectCondition } from 'components/form/SelectCondition' -import { SelectPositions } from 'components/form/SelectPositions' +import { ConditionsDropdown } from 'components/form/ConditionsDropdown' import { MergePreview } from 'components/mergePositions/MergePreview' import { MergeResultModal } from 'components/mergePositions/MergeResultModal' +import { MergeWith } from 'components/mergePositions/MergeWith' import { ButtonContainer } from 'components/pureStyledComponents/ButtonContainer' import { Row } from 'components/pureStyledComponents/Row' import { FullLoading } from 'components/statusInfo/FullLoading' import { StatusInfoInline, StatusInfoType } from 'components/statusInfo/StatusInfoInline' import { IconTypes } from 'components/statusInfo/common' +import { SelectablePositionTable } from 'components/table/SelectablePositionTable' import { NULL_PARENT_ID, ZERO_BN } from 'config/constants' -import { useBatchBalanceContext } from 'contexts/BatchBalanceContext' -import { useConditionContext } from 'contexts/ConditionContext' -import { useMultiPositionsContext } from 'contexts/MultiPositionsContext' import { Web3ContextStatus, useWeb3ConnectedOrInfura } from 'contexts/Web3Context' +import { useCondition } from 'hooks/useCondition' +import { useIsConditionResolved } from 'hooks/useIsConditionResolved' +import { PositionWithUserBalanceWithDecimals, usePositionsList } from 'hooks/usePositionsList' +import { ConditionInformation } from 'hooks/utils' import { ConditionalTokensService } from 'services/conditionalTokens' +import { ERC20Service } from 'services/erc20' import { getLogger } from 'util/logger' +import { Remote } from 'util/remoteData' import { - arePositionMergeables, - arePositionMergeablesByCondition, getFreeIndexSet, getFullIndexSet, getTokenSummary, + isDisjointPartition, isPartitionFullIndexSet, minBigNumber, + positionString, xorIndexSets, } from 'util/tools' -import { Status, Token } from 'util/types' +import { + AdvancedFilterPosition, + MergeablePosition, + PositionSearchOptions, + Token, + WrappedCollateralOptions, +} from 'util/types' const logger = getLogger('MergePosition') export const Contents = () => { - const { - _type: statusContext, - CTService, - connect, - networkConfig, - provider, - } = useWeb3ConnectedOrInfura() + const { _type: status, CTService, connect, networkConfig, provider } = useWeb3ConnectedOrInfura() - const { clearPositions, errors: positionsErrors, positions } = useMultiPositionsContext() + const [transactionStatus, setTransactionStatus] = useState>>( + Remote.notAsked>() + ) - const { balances, errors: balancesErrors, updateBalances } = useBatchBalanceContext() + const [conditionId, setConditionId] = useState>(null) + const [position, setPosition] = useState>(null) - const { clearCondition, condition, errors: conditionErrors } = useConditionContext() - const [status, setStatus] = useState>(null) - const [error, setError] = useState>(null) - const [collateralToken, setCollateralToken] = useState>(null) - const [mergeResult, setMergeResult] = useState('') + const [conditionIds, setConditionIds] = useState>([]) + const [isLoadingConditions, setIsLoadingConditions] = useState(false) - const canMergePositions = useMemo(() => { - return condition && arePositionMergeablesByCondition(positions, condition) - }, [positions, condition]) + const [mergeablePositions, setMergeablePositions] = useState>([]) + const [isLoadingMergeablePositions, setIsLoadingMergeablePositions] = useState(false) - const mergeablePositions = useMemo(() => { - return arePositionMergeables(positions) - }, [positions]) + const [selectedPositions, setSelectedPositions] = useState< + Array + >([]) + const [selectedBalancePositions, setSelectedBalancePositions] = useState>([]) - useEffect(() => { - let cancelled = false - if (positions.length && mergeablePositions) { - getTokenSummary(networkConfig, provider, positions[0].collateralToken.id) - .then((token) => { - if (!cancelled) { - setCollateralToken(token) - } - }) - .catch((err) => { - logger.error(err) - }) - } - return () => { - cancelled = true - } - }, [positions, networkConfig, provider, mergeablePositions]) - - const maxBalance = useMemo( - () => (mergeablePositions && balances.length ? minBigNumber(balances) : ZERO_BN), - [balances, mergeablePositions] - ) + const [mergeResult, setMergeResult] = useState('') const [amount, setAmount] = useState(ZERO_BN) - const amountChangeHandler = useCallback((value: BigNumber) => { - setAmount(value) - }, []) + const [collateralToken, setCollateralToken] = useState>(null) + const [clearFilters, setClearFilters] = useState(false) - const useWalletHandler = useCallback(() => { - if (mergeablePositions && maxBalance.gt(ZERO_BN)) { - setAmount(maxBalance) + const isConditionResolved = useIsConditionResolved(conditionId) + + useEffect(() => { + const getCollateral = async () => { + try { + if (position) { + const token = await getTokenSummary(networkConfig, provider, position.collateralToken) + setCollateralToken(token) + } + } catch (err) { + logger.error(err) + } + return null } - }, [maxBalance, mergeablePositions]) + + getCollateral() + }, [networkConfig, provider, position]) const decimals = useMemo(() => (collateralToken ? collateralToken.decimals : 0), [ collateralToken, ]) - const disabled = useMemo( - () => - status === Status.Loading || - positionsErrors.length > 0 || - conditionErrors.length > 0 || - balancesErrors.length > 0 || - !canMergePositions || - amount.isZero(), - [canMergePositions, amount, status, positionsErrors, conditionErrors, balancesErrors] + const clearComponent = useCallback(() => { + setConditionId(null) + setPosition(null) + setConditionIds([]) + setIsLoadingConditions(false) + setMergeablePositions([]) + setIsLoadingMergeablePositions(false) + setSelectedPositions([]) + setSelectedBalancePositions([]) + setMergeResult('') + setAmount(ZERO_BN) + setMergeResult('') + setTransactionStatus(Remote.notAsked>()) + }, []) + + const onConditionSelect = useCallback((conditionId: string) => { + setConditionId(conditionId) + logger.log(conditionId) + }, []) + + const onMergeableItemClick = useCallback( + (item: MergeablePosition, index: number) => { + logger.log(item, index) + const { position } = item + const existPosition = selectedPositions.find( + (selectedPosition) => selectedPosition.id === position.id + ) + if (!existPosition) { + setSelectedPositions([...selectedPositions, position]) + setConditionId(position.conditions[0].conditionId) + } + }, + [selectedPositions] ) + const onAmountChange = useCallback((value: BigNumber) => { + setAmount(value) + }, []) + + const condition = useCondition(conditionId) + const onMerge = useCallback(async () => { try { - if (positions && condition && statusContext === Web3ContextStatus.Connected) { - setStatus(Status.Loading) + if ( + position && + conditionId && + selectedPositions.length > 0 && + condition && + status === Web3ContextStatus.Connected + ) { + setTransactionStatus(Remote.loading()) - const { collateralToken, conditionIds, indexSets } = positions[0] + const { collateralToken, conditionIds, indexSets } = selectedPositions[0] const newCollectionsSet = conditionIds.reduce( - (acc, conditionId, i) => - conditionId !== condition.id - ? [...acc, { conditionId, indexSet: new BigNumber(indexSets[i]) }] + (acc, id, i) => + id !== conditionId + ? [...acc, { conditionId: id, indexSet: new BigNumber(indexSets[i]) }] : acc, new Array<{ conditionId: string; indexSet: BigNumber }>() ) @@ -131,15 +161,15 @@ export const Contents = () => { : ethers.constants.HashZero // It shouldn't be able to call onMerge if positions were not mergeables, so no -1 for findIndex. - const partition = positions.map( + const partition = selectedPositions.map( ({ conditionIds, indexSets }) => - indexSets[conditionIds.findIndex((conditionId) => conditionId === condition.id)] + indexSets[conditionIds.findIndex((id) => conditionId === id)] ) await CTService.mergePositions( - collateralToken.id, + collateralToken, parentCollectionId, - condition.id, + conditionId, partition, amount ) @@ -148,11 +178,11 @@ export const Contents = () => { if (isPartitionFullIndexSet(condition.outcomeSlotCount, partition)) { if (parentCollectionId === NULL_PARENT_ID) { // original collateral, - setMergeResult(collateralToken.id) + setMergeResult(collateralToken) } else { // or a position setMergeResult( - ConditionalTokensService.getPositionId(collateralToken.id, parentCollectionId) + ConditionalTokensService.getPositionId(collateralToken, parentCollectionId) ) } } else { @@ -164,74 +194,234 @@ export const Contents = () => { ) setMergeResult( ConditionalTokensService.getPositionId( - collateralToken.id, + collateralToken, ConditionalTokensService.getCollectionId( parentCollectionId, - condition.id, + conditionId, indexSetOfMergedPosition ) ) ) } - setStatus(Status.Ready) + setTransactionStatus(Remote.success(true)) } else { connect() } } catch (err) { - setStatus(Status.Error) - setError(err) + setTransactionStatus(Remote.failure(err)) logger.error(err) } - }, [positions, condition, statusContext, CTService, amount, connect]) + }, [selectedPositions, position, conditionId, condition, status, CTService, amount, connect]) - const clearComponent = useCallback(() => { - setAmount(ZERO_BN) - clearPositions() - clearCondition() - updateBalances([]) - setMergeResult('') - setStatus(null) - }, [clearPositions, clearCondition, updateBalances]) - - const fullLoadingActionButton = - status === Status.Error - ? { - buttonType: ButtonType.danger, - onClick: () => setStatus(null), - text: 'Close', + const advancedFilters: AdvancedFilterPosition = useMemo(() => { + return { + CollateralValue: { + type: null, + value: null, + }, + ToCreationDate: null, + FromCreationDate: null, + TextToSearch: { + type: PositionSearchOptions.All, + value: null, + }, + WrappedCollateral: WrappedCollateralOptions.All, + } + }, []) + + const { data: positions, error: errorPositions } = usePositionsList(advancedFilters) + + const onRowClicked = useCallback( + async (position: PositionWithUserBalanceWithDecimals) => { + setPosition(position) + + setMergeablePositions([]) + setConditionIds([]) + + if (positions && positions.length > 0 && !!position) { + setIsLoadingConditions(true) + setIsLoadingMergeablePositions(true) + + const positionsFiltered = positions.filter( + (positionWithBalance: PositionWithUserBalanceWithDecimals) => + //#1 Filter, only positions with balance + !positionWithBalance.userBalanceERC1155.isZero() && + //#2 Filter, remove original selected position + positionWithBalance.id.toLowerCase() !== position.id.toLowerCase() && + //#3 Filter, positions with the same condition set + positionWithBalance.conditionIds.sort().join('') === + position.conditionIds.sort().join('') && + //#4 Filter, positions with the same collateral + positionWithBalance.collateralToken.toLowerCase() === + position.collateralToken.toLowerCase() && + //#5 Filter, allow only disjoint positions + positionWithBalance.conditions.filter((condition: ConditionInformation) => + isDisjointPartition(positionWithBalance.indexSets, condition.outcomeSlotCount) + ).length > 0 + ) + + const positionsPromises = positionsFiltered.map(async (positionFiltered) => { + const { collateralToken, conditionIds, indexSets } = positionFiltered + const balanceOfPositionId = await CTService.balanceOf(positionFiltered.id) + const erc20Service = new ERC20Service(provider, collateralToken) + const tokenPosition = await erc20Service.getProfileSummary() + + return { + position: positionFiltered, + positionPreview: positionString( + conditionIds, + indexSets, + balanceOfPositionId, + tokenPosition + ), + } + }) + + const possibleMergeablePositions: MergeablePosition[] = await Promise.all(positionsPromises) + + // Calculate condition Ids to fulfill the dropdown + const conditionIds: string[] = possibleMergeablePositions.reduce( + (acc: string[], cur: MergeablePosition) => acc.concat([...cur.position.conditionIds]), + [] + ) + + // Remove duplicates + const possibleConditions = conditionIds.filter( + (item, pos) => conditionIds.indexOf(item) === pos + ) + + setConditionIds(possibleConditions) + setMergeablePositions(possibleMergeablePositions) + setIsLoadingConditions(false) + setIsLoadingMergeablePositions(false) + } + }, + [positions, CTService, provider] + ) + + const maxBalance = useMemo( + () => (selectedBalancePositions.length > 0 ? minBigNumber(selectedBalancePositions) : ZERO_BN), + [selectedBalancePositions] + ) + + const onUsePositionBalance = useCallback(() => { + if (selectedPositions && maxBalance.gt(ZERO_BN)) { + setAmount(maxBalance) + } + }, [maxBalance, selectedPositions]) + + useEffect(() => { + const getBalance = async (positionIds: Array) => { + try { + if (positionIds.length > 0 && position) { + const balancePositions = await CTService.balanceOfBatch(positionIds) + setSelectedBalancePositions(balancePositions) + } else { + setSelectedBalancePositions([]) } - : undefined + } catch (err) { + logger.error(err) + setSelectedBalancePositions([]) + } + } - const fullLoadingIcon = - status === Status.Error - ? IconTypes.error - : status === Status.Loading - ? IconTypes.spinner - : undefined + const selectedPositionsIds = selectedPositions.map( + (selectedPosition: PositionWithUserBalanceWithDecimals) => selectedPosition.id + ) + if (position) { + // We add the first selected position that we search + selectedPositionsIds.push(position.id) + } + getBalance(selectedPositionsIds) + }, [CTService, selectedPositions, position]) - const fullLoadingMessage = - status === Status.Error ? error?.message : status === Status.Loading ? 'Working...' : undefined + const fullLoadingActionButton = useMemo( + () => + transactionStatus.isFailure() + ? { + buttonType: ButtonType.danger, + onClick: () => setTransactionStatus(Remote.notAsked>()), + text: 'Close', + } + : transactionStatus.isSuccess() + ? { + buttonType: ButtonType.primary, + onClick: () => setTransactionStatus(Remote.notAsked>()), + text: 'OK', + } + : undefined, + [transactionStatus] + ) + + const fullLoadingMessage = useMemo( + () => + transactionStatus.isFailure() + ? transactionStatus.getFailure() + : transactionStatus.isLoading() + ? 'Working...' + : 'Merge positions finished!', + [transactionStatus] + ) + + const fullLoadingTitle = useMemo( + () => (transactionStatus.isFailure() ? 'Error' : 'Merge Positions'), + [transactionStatus] + ) + + const fullLoadingIcon = useMemo( + () => + transactionStatus.isFailure() + ? IconTypes.error + : transactionStatus.isLoading() + ? IconTypes.spinner + : IconTypes.ok, + [transactionStatus] + ) + + const isWorking = useMemo( + () => + transactionStatus.isLoading() || + transactionStatus.isFailure() || + transactionStatus.isSuccess(), + [transactionStatus] + ) - const fullLoadingTitle = status === Status.Error ? 'Error' : 'Merge Positions' - const isWorking = status === Status.Loading || status === Status.Error - const isFinished = status === Status.Ready + const disabled = useMemo( + () => + transactionStatus.isLoading() || + !selectedPositions.length || + !conditionId || + !position || + amount.isZero(), + [transactionStatus, amount, selectedPositions, conditionId, position] + ) return ( + - { - setAmount(ZERO_BN) - }} - showOnlyPositionsWithBalance - title="Positions" + - + - {condition && condition.resolved && ( + {isConditionResolved && ( This condition is already resolved. @@ -243,14 +433,19 @@ export const Contents = () => { amount={amount} balance={maxBalance} decimals={decimals} - disabled={!mergeablePositions} + isFromAPosition={true} max={maxBalance.toString()} - onAmountChange={amountChangeHandler} - onUseWalletBalance={useWalletHandler} + onAmountChange={onAmountChange} + onUseWalletBalance={onUsePositionBalance} /> - + {isWorking && ( { title={fullLoadingTitle} /> )} - {isFinished && collateralToken && ( + {transactionStatus.isSuccess() && collateralToken && ( { + clearComponent() + setClearFilters(!clearFilters) + }} collateralToken={collateralToken} - isOpen={status === Status.Ready} + isOpen={transactionStatus.isSuccess()} mergeResult={mergeResult} > )} @@ -280,7 +478,7 @@ export const Contents = () => { ? true : 'Are you sure you want to leave this page? The changes you made will be lost?' } - when={positions.length > 0 || !!condition || !amount.isZero()} + when={true} /> ) diff --git a/src/pages/MergePositions/index.tsx b/src/pages/MergePositions/index.tsx index 1cd65be6..35ecf5cf 100644 --- a/src/pages/MergePositions/index.tsx +++ b/src/pages/MergePositions/index.tsx @@ -1,20 +1,13 @@ import React from 'react' import { PageTitle } from 'components/pureStyledComponents/PageTitle' -import { BatchBalanceProvider } from 'contexts/BatchBalanceContext' -import { ConditionProvider } from 'contexts/ConditionContext' -import { MultiPositionsProvider } from 'contexts/MultiPositionsContext' import { Contents } from 'pages/MergePositions/Contents' export const MergePositions = () => { return ( - - - - Merge Positions - - - - + <> + Merge Positions + + ) } diff --git a/src/pages/PositionDetails/Contents.tsx b/src/pages/PositionDetails/Contents.tsx index 10667493..d255f704 100644 --- a/src/pages/PositionDetails/Contents.tsx +++ b/src/pages/PositionDetails/Contents.tsx @@ -34,6 +34,7 @@ import { StripedListEmpty, StripedListItem, StripedListItemLessPadding, + StripedListItemPreview, } from 'components/pureStyledComponents/StripedList' import { FullLoading } from 'components/statusInfo/FullLoading' import { IconTypes } from 'components/statusInfo/common' @@ -637,12 +638,14 @@ export const Contents = (props: Props) => { } /> - - {positionPreview || ''} } - /> - + {positionPreview && ( + + {positionPreview}} + /> + + )} {isWrapModalOpen && ( { onRequestClose={() => setOpenDisplayConditionsTableModal(false)} title="Conditions" titleTable="Condition Id" - url="condition" + url="conditions" /> )} {openDisplayOraclesIdsTableModal && areOracleIdsMoreThanOne && ( diff --git a/src/pages/PositionDetails/Wrapper.tsx b/src/pages/PositionDetails/Wrapper.tsx index bdf9f507..9d7f1fa2 100644 --- a/src/pages/PositionDetails/Wrapper.tsx +++ b/src/pages/PositionDetails/Wrapper.tsx @@ -41,18 +41,18 @@ export const Wrapper = (props: WrapperProps) => { } }) - const DisplayErrors = (): JSX.Element => { + const DisplayErrors = React.useCallback(() => { const isNotLoadingAndThereIsNoPosition: boolean = !loading && !position - if (isNotLoadingAndThereIsNoPosition && isPositionErrorNotFound(errors)) { - return - } else if (isNotLoadingAndThereIsNoPosition && isPositionErrorInvalid(errors)) { + if (isNotLoadingAndThereIsNoPosition && isPositionErrorInvalid(errors)) { return } else if (isNotLoadingAndThereIsNoPosition && isPositionErrorFetching(errors)) { return + } else if (isNotLoadingAndThereIsNoPosition && isPositionErrorNotFound(errors)) { + return } else { - return <> + return null } - } + }, [errors, loading, position]) return ( <> diff --git a/src/pages/PositionsList/index.test.tsx b/src/pages/PositionsList/index.test.tsx index 5eed2bab..0174f13e 100644 --- a/src/pages/PositionsList/index.test.tsx +++ b/src/pages/PositionsList/index.test.tsx @@ -9,9 +9,10 @@ import { ThemeProvider } from 'styled-components' import { NetworkConfig } from 'config/networkConfig' import { Connected, Infura, Web3Context, Web3ContextStatus } from 'contexts/Web3Context' import { PositionsList } from 'pages/PositionsList/index' -import { PositionsListType, buildQueryPositions } from 'queries/CTEPositions' +import { buildQueryPositionsList } from 'queries/CTEPositions' import { UserWithPositionsQuery } from 'queries/CTEUsers' import theme from 'theme' +import { AdvancedFilterPosition, PositionSearchOptions, WrappedCollateralOptions } from 'util/types' const connect = jest.fn() const disconnect = jest.fn() @@ -29,11 +30,21 @@ const infuraStatus = { networkConfig, } as Infura -const buildQueryOptions: PositionsListType = { - positionId: '', +const advancedFilters: AdvancedFilterPosition = { + CollateralValue: { + type: null, + value: null, + }, + ToCreationDate: null, + FromCreationDate: null, + TextToSearch: { + type: PositionSearchOptions.All, + value: null, + }, + WrappedCollateral: WrappedCollateralOptions.All, } -const query = buildQueryPositions(buildQueryOptions) +const query = buildQueryPositionsList(advancedFilters) // eslint-disable-next-line @typescript-eslint/no-explicit-any const renderWithConnectedProvider = (component: any, query: any) => { diff --git a/src/pages/PositionsList/index.tsx b/src/pages/PositionsList/index.tsx index e51aa5b0..811b3032 100644 --- a/src/pages/PositionsList/index.tsx +++ b/src/pages/PositionsList/index.tsx @@ -115,17 +115,17 @@ export const PositionsList = () => { const [urlTableModal, setUrlTableModal] = useState('') const [titleModal, setTitleModal] = useState('') - const debouncedHandlerPositionIdToSearch = useDebounceCallback((positionIdToSearch) => { - setTextToSearch(positionIdToSearch) + const debouncedHandlerTextToSearch = useDebounceCallback((textToSearch) => { + setTextToSearch(textToSearch) }, 500) const onChangeSearch = useCallback( (event: React.ChangeEvent) => { const { value } = event.currentTarget setTextToShow(value) - debouncedHandlerPositionIdToSearch(value) + debouncedHandlerTextToSearch(value) }, - [debouncedHandlerPositionIdToSearch] + [debouncedHandlerTextToSearch] ) // Clear the filters on network change @@ -178,8 +178,8 @@ export const PositionsList = () => { const onClearSearch = useCallback(() => { setTextToShow('') - debouncedHandlerPositionIdToSearch('') - }, [debouncedHandlerPositionIdToSearch]) + debouncedHandlerTextToSearch('') + }, [debouncedHandlerTextToSearch]) const { data, error, loading, refetchPositions, refetchUserPositions } = usePositionsList( advancedFilters diff --git a/src/pages/PrepareCondition/index.tsx b/src/pages/PrepareCondition/index.tsx index e916b35a..f1d92e3b 100644 --- a/src/pages/PrepareCondition/index.tsx +++ b/src/pages/PrepareCondition/index.tsx @@ -1,6 +1,6 @@ import lodashClonedeep from 'lodash.clonedeep' import moment from 'moment' -import React, { KeyboardEvent, useState, useCallback, useEffect, useMemo, ChangeEvent } from 'react' +import React, { ChangeEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react' import { Controller, useForm } from 'react-hook-form' import { Prompt } from 'react-router' import { useHistory } from 'react-router-dom' @@ -292,7 +292,6 @@ export const PrepareCondition = () => { ) const prepareCondition = useCallback(async () => { - try { if (status === Web3ContextStatus.Connected && address) { setPrepareConditionStatus(Remote.loading()) @@ -628,6 +627,8 @@ export const PrepareCondition = () => { <> { if (e.target.checkValidity()) { @@ -636,7 +637,6 @@ export const PrepareCondition = () => { setErrorOmenCondition('resolutionDate', 'validity') } }} - placeholder="MM/DD/YYYY" ref={registerOmenCondition({ required: true, min: today, @@ -652,7 +652,9 @@ export const PrepareCondition = () => { {['min', 'max', 'validity'].includes( errorsOmenCondition.resolutionDate.type ) && ( - {`Invalid date or out of range. Valid dates are from today (${today}) to ${MAX_DATE}`} + {`Date must between ${moment(today).format( + 'L' + )} and ${moment(MAX_DATE).format('L')}`} )} )} diff --git a/src/pages/RedeemPosition/index.tsx b/src/pages/RedeemPosition/index.tsx index 4f8b18a8..a8a001b1 100644 --- a/src/pages/RedeemPosition/index.tsx +++ b/src/pages/RedeemPosition/index.tsx @@ -1,10 +1,10 @@ import React from 'react' +import { PageTitle } from 'components/pureStyledComponents/PageTitle' import { BatchBalanceProvider } from 'contexts/BatchBalanceContext' import { ConditionProvider } from 'contexts/ConditionContext' import { MultiPositionsProvider } from 'contexts/MultiPositionsContext' import { Contents } from 'pages/RedeemPosition/Contents' -import { PageTitle } from 'components/pureStyledComponents/PageTitle' export const RedeemPosition = () => { return ( diff --git a/src/queries/CTEPositions.tsx b/src/queries/CTEPositions.tsx index 93f66971..ea2f3e3a 100644 --- a/src/queries/CTEPositions.tsx +++ b/src/queries/CTEPositions.tsx @@ -174,7 +174,7 @@ export const buildQueryPositions = (options: PositionsListType = DEFAULT_OPTIONS const variablesClause = variablesClauseInternal ? `(${variablesClauseInternal})` : '' const query = gql` - query Positions ${variablesClause} { + query PositionsList ${variablesClause} { positions(first: 1000 ${whereClause} , orderBy: createTimestamp, orderDirection: desc) { ...PositionData } diff --git a/src/services/conditionalTokens.ts b/src/services/conditionalTokens.ts index f05175b2..63731cd3 100644 --- a/src/services/conditionalTokens.ts +++ b/src/services/conditionalTokens.ts @@ -204,15 +204,31 @@ export class ConditionalTokensService { conditionId: string, partition: string[], amount: BigNumber - ): Promise { - const tx = await this.contract.mergePositions( + ): Promise { + const tx: TransactionResponse = await this.contract.mergePositions( collateralToken, parentCollectionId, conditionId, partition, - amount + amount, + { + value: '0x0', + gasLimit: 2750000, // TODO - should we try to precalculate this? + } ) - return this.provider.waitForTransaction(tx.hash, CONFIRMATIONS_TO_WAIT) + + logger.log(`Transaction hash: ${tx.hash}`) + + return tx + .wait(CONFIRMATIONS_TO_WAIT) + .then((receipt: TransactionReceipt) => { + logger.log(`Transaction was mined in block ${receipt}`) + return receipt + }) + .catch((error) => { + logger.error(error) + throw improveErrorMessage(error) + }) } async getOutcomeSlotCount(conditionId: string): Promise { diff --git a/src/theme/dataTableCSS.tsx b/src/theme/dataTableCSS.tsx index d6347fca..c7dbd777 100644 --- a/src/theme/dataTableCSS.tsx +++ b/src/theme/dataTableCSS.tsx @@ -17,11 +17,12 @@ export const dataTableCSS = css` margin-bottom: 0 !important; } - &.inlineTable { + &.condensedTable { border: 1px solid ${(props) => props.theme.colors.lightGrey}; + border-radius: 4px; box-shadow: none; - margin-bottom: 32px; - min-height: 170px; + margin-bottom: 24px; + min-height: 280px; .rdt_TableHeadRow { min-height: 38px; @@ -32,6 +33,10 @@ export const dataTableCSS = css` min-height: 39px; } + .rdt_TableRow { + min-height: 40px; + } + .rdt_TableCol, .rdt_TableCell { padding-left: 15px; @@ -47,6 +52,12 @@ export const dataTableCSS = css` div { white-space: nowrap; } + + .rdt_TableCol_Sortable { + > span { + padding: 0 8px; + } + } } .rdt_TableRow { diff --git a/src/theme/tableCustomStyles.tsx b/src/theme/tableCustomStyles.tsx index 3fa74395..8c1a86af 100644 --- a/src/theme/tableCustomStyles.tsx +++ b/src/theme/tableCustomStyles.tsx @@ -11,6 +11,7 @@ export const customStyles: IDataTableStyles = { borderBottomStyle: 'solid', borderBottomWidth: '1px', flexGrow: 1, + marginBottom: '-1px', }, }, tableWrapper: { diff --git a/src/types/generatedGQLForCTE.ts b/src/types/generatedGQLForCTE.ts index f8d1e176..23eb3cdd 100644 --- a/src/types/generatedGQLForCTE.ts +++ b/src/types/generatedGQLForCTE.ts @@ -201,6 +201,77 @@ export interface Positions { // @generated // This file was automatically generated and should not be edited. +// ==================================================== +// GraphQL query operation: PositionsList +// ==================================================== + +export interface PositionsList_positions_collateralToken { + __typename: "CollateralToken"; + id: string; +} + +export interface PositionsList_positions_wrappedToken { + __typename: "WrappedToken"; + id: string; +} + +export interface PositionsList_positions_collection_conditions { + __typename: "Condition"; + id: string; + oracle: string; + questionId: string; + outcomeSlotCount: number; + resolved: boolean; + creator: string; + payouts: any[] | null; + payoutNumerators: any[] | null; + payoutDenominator: any | null; +} + +export interface PositionsList_positions_collection_positions { + __typename: "Position"; + id: string; +} + +export interface PositionsList_positions_collection { + __typename: "Collection"; + id: string; + conditions: PositionsList_positions_collection_conditions[]; + conditionIds: string[]; + indexSets: any[]; + positions: PositionsList_positions_collection_positions[] | null; +} + +export interface PositionsList_positions_conditions { + __typename: "Condition"; + id: string; + oracle: string; + questionId: string; + outcomeSlotCount: number; +} + +export interface PositionsList_positions { + __typename: "Position"; + id: string; + indexSets: any[]; + activeValue: any; + createTimestamp: any; + collateralToken: PositionsList_positions_collateralToken; + wrappedToken: PositionsList_positions_wrappedToken | null; + collection: PositionsList_positions_collection; + conditionIds: string[]; + conditions: PositionsList_positions_conditions[]; +} + +export interface PositionsList { + positions: PositionsList_positions[]; +} + +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + // ==================================================== // GraphQL query operation: GetPosition // ==================================================== diff --git a/src/util/tools.test.ts b/src/util/tools.test.ts index ae760840..a7ddc664 100644 --- a/src/util/tools.test.ts +++ b/src/util/tools.test.ts @@ -1,6 +1,7 @@ import { BigNumber } from 'ethers/utils' -import { GetCondition_condition, GetPosition_position } from 'types/generatedGQLForCTE' +import { PositionWithUserBalanceWithDecimals } from 'hooks/usePositionsList' +import { GetCondition_condition } from 'types/generatedGQLForCTE' import { arePositionMergeables, arePositionMergeablesByCondition, @@ -8,7 +9,6 @@ import { getRedeemedBalance, getRedeemedPreview, indexSetFromOutcomes, - indexSetsByCondition, isConditionFullIndexSet, isDisjointPartition, isFullIndexSetPartition, @@ -27,533 +27,387 @@ test('positionString should return the rigth Positions string', async () => { ).toStrictEqual('[DAI C:0x123 O:0 & C:0x345 O:0|2] x10.00') }) -const positions: GetPosition_position[] = [ +const positions: PositionWithUserBalanceWithDecimals[] = [ { - activeValue: '1000000000000000000', - collateralToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'CollateralToken', - }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', - }, - collection: { - conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x0a62065a29201b69b9ad309abe4e2a39ecfea5bc6aeb37df6fe01f8d0afea5fd', - indexSets: ['2'], - positions: [ - { - id: '0x0b12077261024ae2cfa078cc329234dabae53e38bb68d14005027e66105b4332', - __typename: 'Position', - }, - ], - __typename: 'Collection', - }, + id: '0x0b12077261024ae2cfa078cc329234dabae53e38bb68d14005027e66105b4332', + indexSets: ['2'], conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], + createTimestamp: 2, + collateralToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0x0b12077261024ae2cfa078cc329234dabae53e38bb68d14005027e66105b4332', - indexSets: ['2'], - __typename: 'Position', - }, - { - activeValue: '1000000000000000000', - collateralToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'CollateralToken', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - collection: { - conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x1339c2c62050329af1f9c07fff3069387006d853dbfdb302a9c0adbf4dbe391c', - indexSets: ['5'], - positions: [ - { - id: '0x1673bab498c5019f2a9eae23b5506eb9a5043b4b910c04c0cd529f27797a34dc', - __typename: 'Position', - }, - ], - __typename: 'Collection', + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, + }, + { + id: '0x1673bab498c5019f2a9eae23b5506eb9a5043b4b910c04c0cd529f27797a34dc', + indexSets: ['5'], conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], + createTimestamp: 2, + collateralToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0x1673bab498c5019f2a9eae23b5506eb9a5043b4b910c04c0cd529f27797a34dc', - indexSets: ['5'], - __typename: 'Position', - }, - { - activeValue: '0', - collateralToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'CollateralToken', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - collection: { - conditionIds: [ - '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - ], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 2, - payoutDenominator: '100', - payoutNumerators: ['75', '25'], - payouts: ['0.75', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c7', - resolved: true, - __typename: 'Condition', - }, - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x04e783afdabafaad65a05f1b9c8ef4ea5b8226a116a65289c4cb79fb8331ade6', - indexSets: ['1', '5'], - positions: [ - { - id: '0xae08dcc0c88f95ac5938445a2c3589229be7e928aa4cc7709c61535c45c4cdeb', - __typename: 'Position', - }, - ], - __typename: 'Collection', + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, + }, + { + id: '0xae08dcc0c88f95ac5938445a2c3589229be7e928aa4cc7709c61535c45c4cdeb', + indexSets: ['1', '5'], conditionIds: [ '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', ], + createTimestamp: 2, + collateralToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', + conditionId: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', outcomeSlotCount: 2, - __typename: 'Condition', + oracle: '', + questionId: '', }, { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0xae08dcc0c88f95ac5938445a2c3589229be7e928aa4cc7709c61535c45c4cdeb', - indexSets: ['1', '5'], - __typename: 'Position', - }, - { - activeValue: '0', - collateralToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'CollateralToken', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - collection: { - conditionIds: [ - '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - ], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 2, - payoutDenominator: '100', - payoutNumerators: ['75', '25'], - payouts: ['0.75', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c7', - resolved: true, - __typename: 'Condition', - }, - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x512cbec0414bb6acd477f4469167c2e5a29287e9ccc5ff2a1b491a8436cc7e47', - indexSets: ['2', '5'], - positions: [ - { - id: '0xfb66d52cfff63f2a23b4456c3636383888cc2ec313513cf736c8acae67c53e29', - __typename: 'Position', - }, - ], - __typename: 'Collection', + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, + }, + { + id: '0xfb66d52cfff63f2a23b4456c3636383888cc2ec313513cf736c8acae67c53e29', + indexSets: ['2', '5'], conditionIds: [ '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', ], + createTimestamp: 2, + collateralToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', + conditionId: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', outcomeSlotCount: 2, - __typename: 'Condition', + oracle: '', + questionId: '', }, { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0xfb66d52cfff63f2a23b4456c3636383888cc2ec313513cf736c8acae67c53e29', - indexSets: ['2', '5'], - __typename: 'Position', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, + }, + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, + }, + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, + }, }, ] -const positionsUSDC: GetPosition_position[] = [ +const positionsUSDC: PositionWithUserBalanceWithDecimals[] = [ { - activeValue: '1000000000000000000', - collateralToken: { - id: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', - __typename: 'CollateralToken', - }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', - }, - collection: { - conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x0a62065a29201b69b9ad309abe4e2a39ecfea5bc6aeb37df6fe01f8d0afea5fd', - indexSets: ['2'], - positions: [ - { - id: '0x0b12077261024ae2cfa078cc329234dabae53e38bb68d14005027e66105b4332', - __typename: 'Position', - }, - ], - __typename: 'Collection', - }, + id: '0x0b12077261024ae2cfa078cc329234dabae53e38bb68d14005027e66105b4332', + indexSets: ['2'], conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], + createTimestamp: 2, + collateralToken: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0x0b12077261024ae2cfa078cc329234dabae53e38bb68d14005027e66105b4332', - indexSets: ['2'], - __typename: 'Position', - }, - { - activeValue: '1000000000000000000', - collateralToken: { - id: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', - __typename: 'CollateralToken', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - collection: { - conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x1339c2c62050329af1f9c07fff3069387006d853dbfdb302a9c0adbf4dbe391c', - indexSets: ['4'], - positions: [ - { - id: '0x1673bab498c5019f2a9eae23b5506eb9a5043b4b910c04c0cd529f27797a34dc', - __typename: 'Position', - }, - ], - __typename: 'Collection', + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, + }, + { + id: '0x1673bab498c5019f2a9eae23b5506eb9a5043b4b910c04c0cd529f27797a34dc', + indexSets: ['4'], conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], + createTimestamp: 2, + collateralToken: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0x1673bab498c5019f2a9eae23b5506eb9a5043b4b910c04c0cd529f27797a34dc', - indexSets: ['4'], - __typename: 'Position', - }, - { - activeValue: '1000000000000000000', - collateralToken: { - id: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', - __typename: 'CollateralToken', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - collection: { - conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x1339c2c62050329af1f9c07fff3069387006d853dbfdb302a9c0adbf4dbe391c', - indexSets: ['1'], - positions: [ - { - id: '0xed780c42f5d8d7d7cb9a4d81f63af01cf0c1a9c81e5c05dc12f846b077e75001', - __typename: 'Position', - }, - ], - __typename: 'Collection', + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, + }, + { + id: '0xed780c42f5d8d7d7cb9a4d81f63af01cf0c1a9c81e5c05dc12f846b077e75001', + indexSets: ['1'], conditionIds: ['0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f'], + createTimestamp: 2, + collateralToken: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0xed780c42f5d8d7d7cb9a4d81f63af01cf0c1a9c81e5c05dc12f846b077e75001', - indexSets: ['1'], - __typename: 'Position', - }, - { - activeValue: '0', - collateralToken: { - id: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', - __typename: 'CollateralToken', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - collection: { - conditionIds: [ - '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - ], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 2, - payoutDenominator: '100', - payoutNumerators: ['75', '25'], - payouts: ['0.75', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c7', - resolved: true, - __typename: 'Condition', - }, - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x04e783afdabafaad65a05f1b9c8ef4ea5b8226a116a65289c4cb79fb8331ade6', - indexSets: ['1', '5'], - positions: [ - { - id: '0xae08dcc0c88f95ac5938445a2c3589229be7e928aa4cc7709c61535c45c4cdeb', - __typename: 'Position', - }, - ], - __typename: 'Collection', + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, + }, + { + id: '0xae08dcc0c88f95ac5938445a2c3589229be7e928aa4cc7709c61535c45c4cdeb', + indexSets: ['1', '5'], conditionIds: [ '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', ], + createTimestamp: 2, + collateralToken: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', + conditionId: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', outcomeSlotCount: 2, - __typename: 'Condition', + oracle: '', + questionId: '', }, { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0xae08dcc0c88f95ac5938445a2c3589229be7e928aa4cc7709c61535c45c4cdeb', - indexSets: ['1', '5'], - __typename: 'Position', - }, - { - activeValue: '0', - collateralToken: { - id: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', - __typename: 'CollateralToken', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - wrappedToken: { - id: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - __typename: 'WrappedToken', + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, - collection: { - conditionIds: [ - '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - ], - conditions: [ - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 2, - payoutDenominator: '100', - payoutNumerators: ['75', '25'], - payouts: ['0.75', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c7', - resolved: true, - __typename: 'Condition', - }, - { - creator: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', - oracle: '0xc759678ef908eab0e9d94599da7b8848c0af35c2', - outcomeSlotCount: 3, - payoutDenominator: '100', - payoutNumerators: ['25', '50', '25'], - payouts: ['0.25', '0.50', '0.25'], - questionId: '0xf9ff13c514572a600f9ea2795eeded39002e0ae5d2d055664d0e7def481e62c3', - resolved: true, - __typename: 'Condition', - }, - ], - id: '0x512cbec0414bb6acd477f4469167c2e5a29287e9ccc5ff2a1b491a8436cc7e47', - indexSets: ['2', '5'], - positions: [ - { - id: '0xfb66d52cfff63f2a23b4456c3636383888cc2ec313513cf736c8acae67c53e29', - __typename: 'Position', - }, - ], - __typename: 'Collection', + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, }, + }, + { + id: '0xfb66d52cfff63f2a23b4456c3636383888cc2ec313513cf736c8acae67c53e29', + indexSets: ['2', '5'], conditionIds: [ '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', ], + createTimestamp: 2, + collateralToken: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + wrappedToken: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + userBalanceERC1155: new BigNumber(1), + userBalanceERC20: new BigNumber(1), conditions: [ { - id: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', + conditionId: '0xc1157d244e86a352e3a56c8f17c96bf58e6150a0c700d67e0906ec392bd812e3', outcomeSlotCount: 2, - __typename: 'Condition', + oracle: '', + questionId: '', }, { - id: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', + conditionId: '0xf583ac873d83f9db78f7a8fe18a2b8e3d050d8a283c41a014a5b8df45c20856f', outcomeSlotCount: 3, - __typename: 'Condition', + oracle: '', + questionId: '', }, ], - id: '0xfb66d52cfff63f2a23b4456c3636383888cc2ec313513cf736c8acae67c53e29', - indexSets: ['2', '5'], - __typename: 'Position', + userBalanceERC1155WithDecimals: '', + userBalanceERC20WithDecimals: '', + userBalanceERC1155Numbered: 0, + userBalanceERC20Numbered: 0, + collateralTokenERC1155: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, + }, + collateralTokenSymbol: 'USDC', + collateralTokenERC20: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, + }, + token: { + symbol: '', + address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', + decimals: 18, + }, }, ] @@ -664,7 +518,7 @@ test('getRedeemedBalance should return the balance for multi outcome position', test('getRedeemedPreview should return new position to redeem', async () => { expect( - getRedeemedPreview(positions[2], resolvedConditions[0], new BigNumber(`${1e19}`), { + getRedeemedPreview(positions[2], resolvedConditions[0].id, new BigNumber(`${1e19}`), { symbol: 'DAI', address: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', decimals: 18, @@ -674,7 +528,7 @@ test('getRedeemedPreview should return new position to redeem', async () => { test('getRedeemedPreview should return new collateral to redeem', async () => { expect( - getRedeemedPreview(positions[1], resolvedConditions[1], new BigNumber(`${1e19}`), { + getRedeemedPreview(positions[1], resolvedConditions[1].id, new BigNumber(`${1e19}`), { symbol: 'DAI', address: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', decimals: 18, @@ -709,36 +563,72 @@ test('#arePositionMergeables - positions should be mergeable', async () => { test('#arePositionMergeablesByCondition - positions should be mergeable by condition when fillIndexSet partition', async () => { expect( - arePositionMergeablesByCondition([positions[0], positions[1]], resolvedConditions[1]) + arePositionMergeablesByCondition( + [positions[0], positions[1]], + resolvedConditions[1].id, + resolvedConditions[1].outcomeSlotCount + ) ).toEqual(true) expect( - arePositionMergeablesByCondition([positions[2], positions[3]], resolvedConditions[0]) + arePositionMergeablesByCondition( + [positions[2], positions[3]], + resolvedConditions[0].id, + resolvedConditions[0].outcomeSlotCount + ) ).toEqual(true) }) test('#arePositionMergeablesByCondition - positions should be mergeable by condition when disjoint not fillIndexSet partition', async () => { expect( - arePositionMergeablesByCondition([positionsUSDC[0], positionsUSDC[1]], resolvedConditions[1]) + arePositionMergeablesByCondition( + [positionsUSDC[0], positionsUSDC[1]], + resolvedConditions[1].id, + resolvedConditions[1].outcomeSlotCount + ) ).toEqual(true) }) test('#isConditionFullIndexSet - should be false when positions are not mergeable', async () => { - expect(isConditionFullIndexSet([positions[0], positionsUSDC[1]], resolvedConditions[0])).toEqual( - false - ) - expect(isConditionFullIndexSet(positions, resolvedConditions[0])).toEqual(false) + expect( + isConditionFullIndexSet( + [positions[0], positionsUSDC[1]], + resolvedConditions[0].id, + resolvedConditions[0].outcomeSlotCount + ) + ).toEqual(false) + expect( + isConditionFullIndexSet( + positions, + resolvedConditions[0].id, + resolvedConditions[0].outcomeSlotCount + ) + ).toEqual(false) }) test('#isConditionFullIndexSet - should be false when positions are not mergeable', async () => { - expect(isConditionFullIndexSet([positions[0], positionsUSDC[1]], resolvedConditions[0])).toEqual( - false - ) - expect(isConditionFullIndexSet(positions, resolvedConditions[0])).toEqual(false) + expect( + isConditionFullIndexSet( + [positions[0], positionsUSDC[1]], + resolvedConditions[0].id, + resolvedConditions[0].outcomeSlotCount + ) + ).toEqual(false) + expect( + isConditionFullIndexSet( + positions, + resolvedConditions[0].id, + resolvedConditions[0].outcomeSlotCount + ) + ).toEqual(false) }) test('#isConditionFullIndexSet - should be false when positions are mergeable but not fullIndexSet', async () => { expect( - isConditionFullIndexSet([positionsUSDC[0], positionsUSDC[1]], resolvedConditions[1]) + isConditionFullIndexSet( + [positionsUSDC[0], positionsUSDC[1]], + resolvedConditions[1].id, + resolvedConditions[1].outcomeSlotCount + ) ).toEqual(false) }) @@ -746,7 +636,8 @@ test('#isConditionFullIndexSet - should be true', async () => { expect( isConditionFullIndexSet( [positionsUSDC[0], positionsUSDC[1], positionsUSDC[2]], - resolvedConditions[1] + resolvedConditions[1].id, + resolvedConditions[1].outcomeSlotCount ) ).toEqual(true) }) @@ -785,21 +676,33 @@ test('isFullIndexSetPartition should work', async () => { test('getMergePreview should return new position when merging fullIndexSet partition', async () => { expect( - getMergePreview([positions[2], positions[3]], resolvedConditions[0], new BigNumber(`${1e19}`), { - symbol: 'DAI', - address: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - decimals: 18, - } as Token) + getMergePreview( + [positions[2], positions[3]], + resolvedConditions[0].id, + new BigNumber(`${1e19}`), + { + symbol: 'DAI', + address: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + decimals: 18, + } as Token, + resolvedConditions[0].outcomeSlotCount + ) ).toStrictEqual('[DAI C:0xf583ac...20856f O:0|2] x10.00') }) test('getMergePreview should return collateral when merging fullIndexSet partition', async () => { expect( - getMergePreview([positions[0], positions[2]], resolvedConditions[1], new BigNumber(`${1e19}`), { - symbol: 'DAI', - address: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', - decimals: 18, - } as Token) + getMergePreview( + [positions[0], positions[2]], + resolvedConditions[1].id, + new BigNumber(`${1e19}`), + { + symbol: 'DAI', + address: '0x5592ec0cfb4dbc12d3ab100b257153436a1f0fea', + decimals: 18, + } as Token, + resolvedConditions[1].outcomeSlotCount + ) ).toStrictEqual('10.00 DAI') }) @@ -807,13 +710,14 @@ test('getMergePreview should return new position when merging non fullIndexSet p expect( getMergePreview( [positionsUSDC[0], positionsUSDC[1]], - resolvedConditions[1], + resolvedConditions[1].id, new BigNumber(`${1e7}`), { symbol: 'USDC', address: '0x4dbcdf9b62e891a7cec5a2568c3f4faf9e8abe2b', decimals: 6, - } as Token + } as Token, + resolvedConditions[1].outcomeSlotCount ) ).toStrictEqual('[USDC C:0xf583ac...20856f O:1|2] x10.00') }) diff --git a/src/util/tools.ts b/src/util/tools.ts index 275db1c0..c9aa2a77 100644 --- a/src/util/tools.ts +++ b/src/util/tools.ts @@ -5,13 +5,11 @@ import moment from 'moment-timezone' import BN from 'bn.js' import { BYTES_REGEX } from 'config/constants' import { NetworkConfig } from 'config/networkConfig' +import { PositionWithUserBalanceWithDecimals } from 'hooks/usePositionsList' +import { ConditionInformation } from 'hooks/utils' import zipObject from 'lodash.zipobject' import { ERC20Service } from 'services/erc20' -import { - GetCondition_condition, - GetMultiPositions_positions, - GetPosition_position, -} from 'types/generatedGQLForCTE' +import { GetCondition_condition } from 'types/generatedGQLForCTE' import { CollateralErrors, ConditionErrors, NetworkIds, PositionErrors, Token } from 'util/types' const ZERO_BN = new BN(0) @@ -130,7 +128,6 @@ export const mulBN = (a: BigNumber, b: number, scale = 10000): BigNumber => { export const positionString = ( conditionIds: string[], - // eslint-disable-next-line @typescript-eslint/no-explicit-any indexSets: string[], balance: BigNumber, token: Token @@ -153,11 +150,13 @@ export const indexSetToBase2 = (indexSet: string) => { return new BN(indexSet).toString(2) } export const getRedeemedBalance = ( - position: GetPosition_position, + position: PositionWithUserBalanceWithDecimals, resolvedCondition: GetCondition_condition, balance: BigNumber ) => { - const conditionIndex = position.conditions.findIndex(({ id }) => id === resolvedCondition.id) + const conditionIndex = position.conditions.findIndex( + ({ conditionId }) => conditionId === resolvedCondition.id + ) const indexSet = position.indexSets[conditionIndex] const { payouts } = resolvedCondition @@ -174,13 +173,15 @@ export const getRedeemedBalance = ( } export const getRedeemedPreview = ( - position: GetPosition_position, - resolvedCondition: GetCondition_condition, + position: PositionWithUserBalanceWithDecimals, + conditionId: string, redeemedBalance: BigNumber, token: Token ) => { if (position.conditions.length > 1) { - const conditionIndex = position.conditions.findIndex(({ id }) => id === resolvedCondition.id) + const conditionIndex = position.conditions.findIndex( + (condition: ConditionInformation) => condition.conditionId === conditionId + ) const filteredConditionIds = position.conditionIds.filter((_, i) => i !== conditionIndex) const filteredIndexSets = position.indexSets.filter((_, i) => i !== conditionIndex) @@ -190,7 +191,7 @@ export const getRedeemedPreview = ( return `${formatBigNumber(redeemedBalance, token.decimals)} ${token.symbol}` } -export const positionsSameConditionsSet = (positions: GetMultiPositions_positions[]) => { +export const positionsSameConditionsSet = (positions: PositionWithUserBalanceWithDecimals[]) => { // all postions include same conditions set and collateral token const conditionIdsSet = positions.map((position) => [...position.conditionIds].sort().join('')) return conditionIdsSet.every((set) => set === conditionIdsSet[0]) @@ -199,47 +200,51 @@ export const positionsSameConditionsSet = (positions: GetMultiPositions_position // more than 1 position // same collateral // same conditions set -export const arePositionMergeables = (positions: GetMultiPositions_positions[]) => { +export const arePositionMergeables = (positions: PositionWithUserBalanceWithDecimals[]) => { return ( positions.length > 1 && - positions.every( - (position) => position.collateralToken.id === positions[0].collateralToken.id - ) && + positions.every((position) => position.collateralToken === positions[0].collateralToken) && positionsSameConditionsSet(positions) ) } // disjoint partition export const arePositionMergeablesByCondition = ( - positions: GetMultiPositions_positions[], - condition: GetCondition_condition + positions: PositionWithUserBalanceWithDecimals[], + conditionId: string, + outcomeSlotCount: number ) => { - return arePositionMergeables(positions) && isConditionDisjoint(positions, condition) + return ( + arePositionMergeables(positions) && + isConditionDisjoint(positions, conditionId, outcomeSlotCount) + ) } // TODO This is used with the assumption that our desired condition is common to that position. // But we only check that every position has a condition in common // An option is to check if this dictionary is undefined for some conditionId. -export const indexSetsByCondition = (position: GetMultiPositions_positions) => { +export const indexSetsByCondition = (position: PositionWithUserBalanceWithDecimals) => { return zipObject(position.conditionIds, position.indexSets as string[]) } export const isConditionFullIndexSet = ( - positions: GetMultiPositions_positions[], - condition: GetCondition_condition + positions: PositionWithUserBalanceWithDecimals[], + conditionId: string, + outcomeSlotCount: number ) => { - const partition = positions.map((position) => indexSetsByCondition(position)[condition.id]) + const partition = positions.map((position) => indexSetsByCondition(position)[conditionId]) - return isFullIndexSetPartition(partition, condition.outcomeSlotCount) + return isFullIndexSetPartition(partition, outcomeSlotCount) } export const isConditionDisjoint = ( - positions: GetMultiPositions_positions[], - condition: GetCondition_condition + positions: PositionWithUserBalanceWithDecimals[], + conditionId: string, + outcomeSlotCount: number ) => { - const partition = positions.map((position) => indexSetsByCondition(position)[condition.id]) + const partition = positions.map((position) => indexSetsByCondition(position)[conditionId]) - return isDisjointPartition(partition, condition.outcomeSlotCount) + return isDisjointPartition(partition, outcomeSlotCount) } export const isDisjointPartition = (partition: string[], outcomeSlotCount: number) => { @@ -327,20 +332,21 @@ const validIndexSet = (fullIndexSet: string, indexSetA: string) => { } export const getMergePreview = ( - positions: GetPosition_position[], - condition: GetCondition_condition, + positions: PositionWithUserBalanceWithDecimals[], + conditionId: string, amount: BigNumber, - token: Token + token: Token, + outcomeSlotCount: number ) => { - if (isConditionFullIndexSet(positions, condition)) { - return getRedeemedPreview(positions[0], condition, amount, token) + if (isConditionFullIndexSet(positions, conditionId, outcomeSlotCount)) { + return getRedeemedPreview(positions[0], conditionId, amount, token) } else { // this assumes all positions have same conditions set order - const conditionIndex = positions[0].conditionIds.findIndex((id) => id === condition.id) + const conditionIndex = positions[0].conditionIds.findIndex((id) => id === conditionId) // For all positions sum values on their indexSets corresponding to the given condition const newIndexSet = positions - .map((position) => indexSetsByCondition(position)[condition.id]) + .map((position) => indexSetsByCondition(position)[conditionId]) .reduce((acc, val) => orIndexSets(acc, val)) // Maintain all indexSet the same except for the ones changed diff --git a/src/util/types.ts b/src/util/types.ts index 6a3104f7..5688d24f 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -2,6 +2,7 @@ import { BigNumber } from 'ethers/utils' import { Moment } from 'moment' import { NetworkConfig } from 'config/networkConfig' +import { PositionWithUserBalanceWithDecimals } from 'hooks/usePositionsList' export interface Question { arbitratorAddress: string @@ -247,3 +248,8 @@ export interface AdvancedFilterPosition { } WrappedCollateral: WrappedCollateralOptions } + +export interface MergeablePosition { + position: PositionWithUserBalanceWithDecimals + positionPreview: string +}