diff --git a/.env.sample b/.env.sample index bb1677d7..284c9b3b 100644 --- a/.env.sample +++ b/.env.sample @@ -1,10 +1,18 @@ BUILD_ENV= -COMMUNITY_API_KEY= +REALTOKEN_COMMUNITY_API_KEY= THEGRAPH_API_KEY= -REALTOKENAPI='https://api.realtoken.community/v1/token' -REALTOKENAPI_HISTORY='https://history.api.realtoken.community/' + +REALTOKEN_COMMUNITY_API_BASE='https://api.realtoken.community/' +REALTOKEN_COMMUNITY_API_VERSION='v1' +REALTOKEN_COMMUNITY_API_GET_ALLTOKENS='token' +REALTOKEN_COMMUNITY_API_HISTORY_BASE='https://history.api.realtoken.community/' +PITSBI_API_BASE='https://api.pitsbi.io/' +PITSBI_API_VERSION='api' +PITSBI_API_GET_LASTUPDATE='last_get_realTokens_communityDashboard' +PITSBI_API_GET_ALLTOKENS='realTokens_communityDashboard' + MATOMO_URL='https://data.realtoken.community/' -MATOMO_SITE_ID= +MATOMO_SITE_ID=1 -RPC_URLS_ETH_MAINNET = https://rpc.eth.gateway.fm,https://ethereum-rpc.publicnode.com,https://eth-mainnet.public.blastapi.io,https://ethereum.blockpi.network/v1/rpc/public,https://rpc.mevblocker.io/fast,https://rpc.mevblocker.io,https://0xrpc.io/eth -RPC_URLS_GNOSIS_MAINNET = https://rpc.gnosischain.com,https://rpc.gnosis.gateway.fm,https://rpc.ap-southeast-1.gateway.fm/v4/gnosis/non-archival/mainnet,https://gnosis-rpc.publicnode.com,https://gnosis.oat.farm,https://0xrpc.io/gno \ No newline at end of file +RPC_URLS_ETH_MAINNET=https://rpc.eth.gateway.fm,https://ethereum-rpc.publicnode.com,https://eth-mainnet.public.blastapi.io,https://ethereum.blockpi.network/v1/rpc/public,https://rpc.mevblocker.io/fast,https://rpc.mevblocker.io,https://0xrpc.io/eth +RPC_URLS_GNOSIS_MAINNET=https://rpc.gnosischain.com,https://rpc.gnosis.gateway.fm,https://rpc.ap-southeast-1.gateway.fm/v4/gnosis/non-archival/mainnet,https://gnosis-rpc.publicnode.com,https://gnosis.oat.farm,https://0xrpc.io/gno diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 75bd971a..1cd380dd 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -70,19 +70,55 @@ jobs: SSH_PORT: ${{ secrets.SSH_PORT }} - name: Update branch environment + env: + DOCKER_BRANCH: ${{ github.ref_name }} + DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }} + DOCKER_LOGIN: ${{ secrets.DOCKER_LOGIN }} + DOCKER_PASSWD: ${{ secrets.DOCKER_PASSWD }} + DOMAIN_URL: ${{ secrets.DOMAIN_URL }} + THEGRAPH_API_KEY: ${{ secrets.THEGRAPH_API_KEY }} + REALTOKEN_COMMUNITY_API_KEY: ${{ secrets.REALTOKEN_COMMUNITY_API_KEY }} + RPC_URLS_ETH_MAINNET: ${{ secrets.RPC_URLS_ETH_MAINNET }} + RPC_URLS_GNOSIS_MAINNET: ${{ secrets.RPC_URLS_GNOSIS_MAINNET }} + MATOMO_ID_PROD: ${{ secrets.MATOMO_ID_PROD }} + MATOMO_ID_PREPROD: ${{ secrets.MATOMO_ID_PREPROD }} + MATOMO_ID_DEVELOP: ${{ secrets.MATOMO_ID_DEVELOP }} run: | - ssh staging 'export DOCKER_BRANCH=${{ github.ref_name }} DOCKER_REGISTRY=${{ secrets.DOCKER_REGISTRY }} - cd /var/docker/dashboard-v2/${DOCKER_BRANCH} - git pull origin ${DOCKER_BRANCH} - docker compose --file docker-compose-branch.yml pull - docker login -u ${{ secrets.DOCKER_LOGIN }} -p ${{ secrets.DOCKER_PASSWD }} ${DOCKER_REGISTRY} - THEGRAPH_API_KEY=${{ secrets.THEGRAPH_API_KEY }} \ - COMMUNITY_API_KEY=${{ secrets.COMMUNITY_API_KEY }} \ - REALTOKENAPI=https://api.realtoken.community/v1/token \ - REALTOKENAPI_HISTORY=https://history.api.realtoken.community/ \ - MATOMO_URL=https://data.realtoken.community/ \ - MATOMO_SITE_ID=${{ github.ref_name == 'master' && secrets.MATOMO_ID_PROD || github.ref_name == 'preprod' && secrets.MATOMO_ID_PREPROD || secrets.MATOMO_ID_DEVELOP }} \ - RPC_URLS_ETH_MAINNET=${{ secrets.RPC_URLS_ETH_MAINNET }} \ - RPC_URLS_GNOSIS_MAINNET=${{ secrets.RPC_URLS_GNOSIS_MAINNET }} \ - HOSTNAME=${{ github.ref_name == 'master' && 'dashboard.realtoken.community' || 'dashboard.${DOCKER_BRANCH}.realtoken.community' }} \ - docker compose --project-name ${{ github.ref_name }}-dashboard --file docker-compose-branch.yml up -d' + if [[ "$DOCKER_BRANCH" == "master" ]]; then + export MATOMO_SITE_ID="$MATOMO_ID_PROD" + export HOSTNAME="dashboard.$DOMAIN_URL" + elif [[ "$DOCKER_BRANCH" == "preprod" ]]; then + export MATOMO_SITE_ID="$MATOMO_ID_PREPROD" + export HOSTNAME="dashboard.$DOCKER_BRANCH.$DOMAIN_URL" + else + export MATOMO_SITE_ID="$MATOMO_ID_DEVELOP" + export HOSTNAME="dashboard.$DOCKER_BRANCH.$DOMAIN_URL" + fi + + ssh staging < = ({ realtoken }) => { + const { t } = useTranslation('common', { keyPrefix: 'assetPage.issues' }) + const { t: tAssetIssues } = useTranslation('common', { + keyPrefix: 'assetIssues', + }) + + const statusLabel = () => { + switch (realtoken.extraData?.pitsBI?.actions?.realt_status) { + case RealTokenToBeFixedStatus.NoExhibit: + return tAssetIssues('status.noExhibit') + case RealTokenToBeFixedStatus.Scheduled: + return tAssetIssues('status.scheduled') + case RealTokenToBeFixedStatus.UpgradedAndReady: + return tAssetIssues('status.upgradedReady') + case undefined: + default: + return tAssetIssues('status.unknown') + } + } + + return ( + <> + + + ) +} + +AssetPageIssuesTab.displayName = 'AssetPageIssuesTab' diff --git a/src/components/assetsView/filters/AssetsViewFilterModal.tsx b/src/components/assetsView/filters/AssetsViewFilterModal.tsx index 5bcd75fd..4e6e6b74 100644 --- a/src/components/assetsView/filters/AssetsViewFilterModal.tsx +++ b/src/components/assetsView/filters/AssetsViewFilterModal.tsx @@ -14,10 +14,13 @@ import { assetsViewFilterAtom, } from 'src/states' import { selectUserIncludesOtherAssets } from 'src/store/features/settings/settingsSelector' +import { selectUserDisplayAdditionalData } from 'src/store/features/settings/settingsSelector' import { userIncludesOtherAssetsChanged } from 'src/store/features/settings/settingsSlice' import { AssetProductType } from '../types' import { AssetsViewProductTypeFilter } from './AssetsViewFilterProductType' +import { AssetsViewIssuePriorityFilter } from './AssetsViewIssuePriorityFilter' +import { AssetsViewIssueStatusFilter } from './AssetsViewIssuesStatusFilter' import { AssetsViewRentStatusFilter } from './AssetsViewRentStatusFilter' import { AssetsViewRmmStatusFilter } from './AssetsViewRmmStatusFilter' import { AssetsViewSort } from './AssetsViewSort' @@ -59,6 +62,7 @@ export const AssetsViewFilterModal: FC = ({ const dispatch = useDispatch() const setUserIncludesOtherAssets = (value: boolean) => dispatch(userIncludesOtherAssetsChanged(value)) + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) const onClose = useCallback(() => { context.closeModal(id) @@ -123,6 +127,22 @@ export const AssetsViewFilterModal: FC = ({ setFilterModel({ ...filterModel, ...value }) }} /> + {userDisplayAdditionalData && ( + <> + { + setFilterModel({ ...filterModel, ...value }) + }} + /> + { + setFilterModel({ ...filterModel, ...value }) + }} + /> + + )} diff --git a/src/components/assetsView/filters/AssetsViewIssuePriorityFilter.tsx b/src/components/assetsView/filters/AssetsViewIssuePriorityFilter.tsx new file mode 100644 index 00000000..6c71a78b --- /dev/null +++ b/src/components/assetsView/filters/AssetsViewIssuePriorityFilter.tsx @@ -0,0 +1,106 @@ +import { FC } from 'react' +import { useTranslation } from 'react-i18next' + +import { Select } from '@mantine/core' + +import { assetsViewDefaultFilter } from 'src/states' +import { + OtherRealtoken, + UserRealtoken, +} from 'src/store/features/wallets/walletsSelector' +import { RealTokenToBeRepairedPriority } from 'src/types/APIPitsBI' + +import { useInputStyles } from '../../inputs/useInputStyles' +import { AssetIssuePriorityType } from '../types' + +interface AssetsViewIssuePriorityFilterModel { + issuePriority: AssetIssuePriorityType +} + +interface AssetsViewIssuePriorityFilterProps { + filter: AssetsViewIssuePriorityFilterModel + onChange: (value: AssetsViewIssuePriorityFilterModel) => void +} +export const AssetsViewIssuePriorityFilter: FC< + AssetsViewIssuePriorityFilterProps +> = ({ filter, onChange }) => { + const { t } = useTranslation('common', { keyPrefix: 'assetIssuePriority' }) + const { classes: inputClasses } = useInputStyles() + + const viewOptions = [ + { + value: AssetIssuePriorityType.ALL, + label: t('options.all'), + }, + { + value: AssetIssuePriorityType.NONE, + label: t('options.none'), + }, + { + value: AssetIssuePriorityType.HIGH, + label: t('options.high'), + }, + { + value: AssetIssuePriorityType.MEDIUM, + label: t('options.medium'), + }, + { + value: AssetIssuePriorityType.LOW, + label: t('options.low'), + }, + ] + + return ( + + onChange({ + issueStatus: + (value as AssetIssueStatusType) ?? + assetsViewDefaultFilter.issueStatus, + }) + } + classNames={inputClasses} + /> + ) +} +AssetsViewIssueStatusFilter.displayName = 'AssetsViewIssueStatusFilter' + +export function useAssetsViewIssueStatusFilter( + filter: AssetsViewIssueStatusFilterModel, +) { + function assetIssueStatusFilterFunction( + asset: UserRealtoken | OtherRealtoken, + ) { + const Asset = asset as UserRealtoken + switch (filter.issueStatus) { + case AssetIssueStatusType.ALL: + return true + case AssetIssueStatusType.NOEXHIBIT: + return ( + Asset.extraData?.pitsBI?.actions?.realt_status === undefined || + Asset.extraData?.pitsBI?.actions?.realt_status === + RealTokenToBeFixedStatus.NoExhibit + ) + case AssetIssueStatusType.UPGRADEDANDREADY: + return ( + Asset.extraData?.pitsBI?.actions?.realt_status === + RealTokenToBeFixedStatus.UpgradedAndReady + ) + case AssetIssueStatusType.SCHEDULED: + return ( + Asset.extraData?.pitsBI?.actions?.realt_status === + RealTokenToBeFixedStatus.Scheduled + ) + } + } + + return { assetIssueStatusFilterFunction } +} diff --git a/src/components/assetsView/filters/useFilters.ts b/src/components/assetsView/filters/useFilters.ts index d5276a7f..3790068d 100644 --- a/src/components/assetsView/filters/useFilters.ts +++ b/src/components/assetsView/filters/useFilters.ts @@ -1,12 +1,17 @@ +import { useSelector } from 'react-redux' + import { useAtom } from 'jotai' import { assetsViewDefaultFilter, assetsViewFilterAtom } from 'src/states' +import { selectUserDisplayAdditionalData } from 'src/store/features/settings/settingsSelector' import { OtherRealtoken, UserRealtoken, } from 'src/store/features/wallets/walletsSelector' import { useAssetsViewProductTypeFilter } from './AssetsViewFilterProductType' +import { useAssetsViewIssuePriorityFilter } from './AssetsViewIssuePriorityFilter' +import { useAssetsViewIssueStatusFilter } from './AssetsViewIssuesStatusFilter' import { useAssetsViewRentStatusFilter } from './AssetsViewRentStatusFilter' import { useAssetsViewRmmStatusFilter } from './AssetsViewRmmStatusFilter' import { useAssetsViewSort } from './AssetsViewSort' @@ -17,6 +22,7 @@ import { useAssetsViewUserStatusFilter } from './AssetsViewUserStatusFilter' export function useAssetsViewFilters() { const [currentFilter] = useAtom(assetsViewFilterAtom) const activeFilter = Object.assign({}, assetsViewDefaultFilter, currentFilter) + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) const { assetSortFunction } = useAssetsViewSort(activeFilter) const { assetSubsidyFilterFunction } = @@ -31,6 +37,10 @@ export function useAssetsViewFilters() { useAssetsViewRmmStatusFilter(activeFilter) const { assetUserProtocolFilterFunction } = useAssetsViewUserProtocolFilter(activeFilter) + const { assetIssueStatusFilterFunction } = + useAssetsViewIssueStatusFilter(activeFilter) + const { assetIssuePriorityFilterFunction } = + useAssetsViewIssuePriorityFilter(activeFilter) function assetsViewFilterFunction( tokenList: (UserRealtoken | OtherRealtoken)[], @@ -42,6 +52,17 @@ export function useAssetsViewFilters() { .filter(assetRentStatusFilterFunction) .filter(assetSubsidyFilterFunction) .filter(assetRmmStatusFilterFunction) + .filter( + userDisplayAdditionalData ? assetIssueStatusFilterFunction : () => true, + ) + .filter( + userDisplayAdditionalData ? assetIssueStatusFilterFunction : () => true, + ) + .filter( + userDisplayAdditionalData + ? assetIssuePriorityFilterFunction + : () => true, + ) .sort(assetSortFunction) } diff --git a/src/components/assetsView/types/assetIssuePriority.type.ts b/src/components/assetsView/types/assetIssuePriority.type.ts new file mode 100644 index 00000000..57e2218f --- /dev/null +++ b/src/components/assetsView/types/assetIssuePriority.type.ts @@ -0,0 +1,7 @@ +export enum AssetIssuePriorityType { + ALL = 'all', + NONE = 'none', + HIGH = 'high', + MEDIUM = 'medium', + LOW = 'low', +} diff --git a/src/components/assetsView/types/assetIssueStatus.type.ts b/src/components/assetsView/types/assetIssueStatus.type.ts new file mode 100644 index 00000000..b5a9f603 --- /dev/null +++ b/src/components/assetsView/types/assetIssueStatus.type.ts @@ -0,0 +1,6 @@ +export enum AssetIssueStatusType { + ALL = 'all', + NOEXHIBIT = 'no_exhibit', + UPGRADEDANDREADY = 'upgraded_and_ready', + SCHEDULED = 'scheduled', +} diff --git a/src/components/assetsView/types/index.ts b/src/components/assetsView/types/index.ts index 74dbbcbe..23981803 100644 --- a/src/components/assetsView/types/index.ts +++ b/src/components/assetsView/types/index.ts @@ -6,3 +6,5 @@ export * from './assetRentStatus.type' export * from './assetRmmStatus.type' export * from './assetUserProtocol.type' export * from './assetProduct.type' +export * from './assetIssueStatus.type' +export * from './assetIssuePriority.type' diff --git a/src/components/assetsView/views/AssetTable.tsx b/src/components/assetsView/views/AssetTable.tsx index 546055d9..da7e25e4 100644 --- a/src/components/assetsView/views/AssetTable.tsx +++ b/src/components/assetsView/views/AssetTable.tsx @@ -10,12 +10,19 @@ import moment from 'moment' import { useCurrencyValue } from 'src/hooks/useCurrencyValue' import { useFullyRentedAPR } from 'src/hooks/useFullyRentedAPR' +import { selectUserDisplayAdditionalData } from 'src/store/features/settings/settingsSelector' import { selectTransfersIsLoaded } from 'src/store/features/transfers/transfersSelector' import { OtherRealtoken, UserRealtoken, } from 'src/store/features/wallets/walletsSelector' +import { + ExhibitStatusTag, + IssueStatusTag, + PriorityStatusTag, +} from '../../commons/assets' + export const AssetTable: FC<{ realtokens: (UserRealtoken | OtherRealtoken)[] }> = (props) => { @@ -49,7 +56,7 @@ AssetTable.displayName = 'AssetTable' const AssetTableHeader: FC = () => { const { t } = useTranslation('common', { keyPrefix: 'assetTable' }) const transfersIsLoaded = useSelector(selectTransfersIsLoaded) - + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) return ( {t('property')} @@ -74,6 +81,19 @@ const AssetTableHeader: FC = () => { {t('rentedUnits')} {t('propertyValue')} {t('lastChange')} + {userDisplayAdditionalData && ( + <> + + {t('status.header')} + + + {t('priority.header')} + + + {t('lawsuit.header')} + + + )} ) } @@ -82,6 +102,7 @@ AssetTableHeader.displayName = 'AssetTableHeader' const AssetTableRow: FC<{ value: UserRealtoken }> = (props) => { const { t } = useTranslation('common', { keyPrefix: 'numbers' }) const transfersIsLoaded = useSelector(selectTransfersIsLoaded) + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) const router = useRouter() const value = props.value.value @@ -161,6 +182,30 @@ const AssetTableRow: FC<{ value: UserRealtoken }> = (props) => { .toDate() .toLocaleDateString()} + {userDisplayAdditionalData && ( + <> + + + + + + + + + + + )} ) } @@ -168,7 +213,7 @@ const AssetTableRow: FC<{ value: UserRealtoken }> = (props) => { const OtherTableRow: FC<{ value: OtherRealtoken }> = (props) => { const { t } = useTranslation('common', { keyPrefix: 'numbers' }) const transfersIsLoaded = useSelector(selectTransfersIsLoaded) - + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) const { shortName, value, unitPriceCost, amount, totalInvestment } = props.value @@ -218,6 +263,25 @@ const OtherTableRow: FC<{ value: OtherRealtoken }> = (props) => { {'-'} + {userDisplayAdditionalData && ( + <> + +
+ {'-'} +
+
+ +
+ {'-'} +
+
+ +
+ {'-'} +
+
+ + )} ) } diff --git a/src/components/cards/AssetCard.module.sass b/src/components/cards/AssetCard.module.sass index 8f96a520..55c09342 100644 --- a/src/components/cards/AssetCard.module.sass +++ b/src/components/cards/AssetCard.module.sass @@ -36,6 +36,9 @@ .textSm font-size: 14px +.textXs + font-size: 12px + .textLocation font-size: 12px text-align: center diff --git a/src/components/cards/AssetCard.tsx b/src/components/cards/AssetCard.tsx index 24fb609c..d234dc1c 100644 --- a/src/components/cards/AssetCard.tsx +++ b/src/components/cards/AssetCard.tsx @@ -4,13 +4,16 @@ import { useSelector } from 'react-redux' import Image from 'next/image' -import { Badge, Card, Group } from '@mantine/core' +import { Badge, Card, Grid, Group } from '@mantine/core' import moment from 'moment' import { useCurrencyValue } from 'src/hooks/useCurrencyValue' import { useFullyRentedAPR } from 'src/hooks/useFullyRentedAPR' -import { selectUserRentCalculation } from 'src/store/features/settings/settingsSelector' +import { + selectUserDisplayAdditionalData, + selectUserRentCalculation, +} from 'src/store/features/settings/settingsSelector' import { OtherRealtoken, UserRealtoken, @@ -19,6 +22,9 @@ import { RentCalculationState } from 'src/types/RentCalculation' import { Divider, + ExhibitStatusTag, + IssueStatusTag, + PriorityStatusTag, RentStatusTag, RmmStatusTag, SubsidyStatusTag, @@ -39,14 +45,19 @@ interface PropertyCardProps { const PropertyCardComponent: FC = (props) => { const { t: tNumbers } = useTranslation('common', { keyPrefix: 'numbers' }) const { t } = useTranslation('common', { keyPrefix: 'assetCard' }) - const rentCalculation = useSelector(selectUserRentCalculation) + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) const realtimeDate = moment(new Date(rentCalculation.date)) - const rentStartDate = new Date(props.value.rentStartDate.date) + const rentStartDate_date = props.value.rentStartDate?.date ?? null + if (!rentStartDate_date) { + console.warn(`Rent start date is not defined for ${props.value.uuid}`) + } + const rentStartDate = new Date(rentStartDate_date) const isDisabled = - rentCalculation.state === RentCalculationState.Realtime && - rentStartDate > realtimeDate.toDate() + !rentStartDate_date || + (rentCalculation.state === RentCalculationState.Realtime && + rentStartDate > realtimeDate.toDate()) const rentNotStarted = t('rentNotStarted') const isSubsidized = @@ -174,6 +185,50 @@ const PropertyCardComponent: FC = (props) => { + {userDisplayAdditionalData && ( +
+
{t('assetIssues.title')}
+ + + + {t('assetIssues.status')} + + + + + + + + {t('assetIssues.priority')} + + + + + + + + {t('assetIssues.lawsuit')} + + {/* center element */} + + + + + +
+ )} +
diff --git a/src/components/cards/main/SummaryCard.tsx b/src/components/cards/main/SummaryCard.tsx index 6f1c97e2..9a44e90f 100644 --- a/src/components/cards/main/SummaryCard.tsx +++ b/src/components/cards/main/SummaryCard.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { Box, Card, Text, Title } from '@mantine/core' -import { IconArchive, IconBolt, IconBoltOff } from '@tabler/icons' +import { IconArchive, IconBolt, IconBoltOff } from '@tabler/icons-react' import { selectTransfersIsLoaded } from 'src/store/features/transfers/transfersSelector' import { OtherRealtoken } from 'src/store/features/wallets/walletsSelector' @@ -18,6 +18,7 @@ interface SummaryCardProps { otherAssetsData: { rwa: OtherRealtoken | null reg: OtherRealtoken | null + reUsd: OtherRealtoken | null regVotingPower: OtherRealtoken | null } } @@ -33,6 +34,7 @@ export const SummaryCard: FC = ({ otherAssetsData }) => { const rwaValue = otherAssetsData?.rwa?.value ?? 0 const regValue = otherAssetsData?.reg?.value ?? 0 + const reUsdValue = otherAssetsData?.reUsd?.value ?? 0 const regVotingPowerAmount = otherAssetsData?.regVotingPower?.amount ?? 0 // Calculate the power logo size of the voting power depending on the amount const additionnalPowerSize = Math.floor(Math.log10(regVotingPowerAmount)) @@ -43,6 +45,7 @@ export const SummaryCard: FC = ({ otherAssetsData }) => { realtokensValue.total + stableDepositValue + rwaValue + + reUsdValue + regValue - stableDebtValue @@ -66,6 +69,7 @@ export const SummaryCard: FC = ({ otherAssetsData }) => { + ((props, ref) => { + const { value, priority, ...rest } = props + const iconColor = + !value || value === 'na' + ? 'gray' + : value === RealTokenToBeFixedStatus.NoExhibit + ? 'green' + : value === RealTokenToBeFixedStatus.Scheduled + ? priority === 1 + ? 'red' + : priority === 2 + ? 'orange' + : priority === 3 + ? 'yellow' + : !priority + ? 'gray' + : 'purple' + : value === RealTokenToBeFixedStatus.UpgradedAndReady + ? 'green' + : 'purple' + const icon = + value === RealTokenToBeFixedStatus.NoExhibit || value == 'na' ? ( + + ) : value === RealTokenToBeFixedStatus.Scheduled ? ( + + ) : value === RealTokenToBeFixedStatus.UpgradedAndReady ? ( + + ) : ( + + ) + return ( +
+
+ {icon} +
+
+ ) +}) + +/* Status Component + * Displays a status icon with a tooltip + */ +export const IssueStatusTag: FC<{ + value: string | undefined + priority: number | undefined +}> = ({ value, priority }) => { + const { t } = useTranslation('common', { keyPrefix: 'assetIssues' }) + const isLoadingExtraData = useSelector(selectRealtokensIsLoadingExtraData) + const toolTipText = !value + ? t(`status.unknown`) + : value === 'na' + ? t('status.na') + : value === RealTokenToBeFixedStatus.NoExhibit + ? t('status.noExhibit') + : value === RealTokenToBeFixedStatus.Scheduled + ? t('status.scheduled') + : value === RealTokenToBeFixedStatus.UpgradedAndReady + ? t('status.upgradedReady') + : t('status.unknown') + return ( + <> + {isLoadingExtraData ? ( + + ) : ( + + + + )} + + ) +} + +IssueStatusTag.displayName = 'IssueStatusTag' + +/* Priority Icons Component + * Displays icons based on priority + * @param value: number representing the priority level + * use forwardRef to allow the component to be used as with a tooltip + */ +// eslint-disable-next-line react/display-name +const PriorityIcons = forwardRef( + (props, ref) => { + const { value, ...rest } = props + const iconColor = + !value || value === 0 || value === -1 + ? 'gray' + : value === 1 + ? 'red' + : value === 2 + ? 'orange' + : value === 3 + ? 'yellow' + : 'purple' + const piorityColor = iconColor + const prioritySize = !value ? 16 : 22 + const iconSize = 16 + const iconPriority = !value ? ( + value === 0 ? ( + + ) : ( + <> + ) + ) : value === 1 ? ( + + ) : value === 2 ? ( + + ) : ( + + ) + const icon = + value === 0 ? ( + <> + ) : value === 1 ? ( + + ) : value === 2 ? ( + + ) : ( + + ) + + return ( +
+
+ {iconPriority} + {icon} +
+
+ ) + }, +) + +/* Priority Badge Component + * Displays a badge with an icon representing the priority level, as well as a tooltip with the priority level text. + */ +export const PriorityStatusTag: FC<{ value: number | undefined }> = ({ + value, +}) => { + const { t } = useTranslation('common', { keyPrefix: 'assetIssues' }) + const isLoadingExtraData = useSelector(selectRealtokensIsLoadingExtraData) + const toolTipText = !value + ? value === 0 + ? t('priority.na') + : t('priority.unknown') + : value === -1 + ? t('priority.na') + : t(`priority.${value}`) + return isLoadingExtraData ? ( + + ) : ( + + + + ) +} + +PriorityStatusTag.displayName = 'PriorityStatusTag' + +/* Exhibit Icons Component + * Displays an icon based on the exhibit number and volume, with a tooltip showing the exhibit number + * use forwardRef to allow the component to be used as with a tooltip + */ +// eslint-disable-next-line react/display-name +const ExhibitIcons = forwardRef< + HTMLDivElement, + { + exhibitNumber: number | undefined + exhibitVolume: number | undefined + priority: number | undefined + } +>((props, ref) => { + const { exhibitNumber, exhibitVolume, priority, ...rest } = props + const noExhibit = + (!exhibitNumber && !exhibitVolume) || + (exhibitNumber === -1 && exhibitVolume === -1) + const iconColor = noExhibit + ? 'gray' + : priority === 1 + ? 'red' + : priority === 2 + ? 'orange' + : priority === 3 + ? 'yellow' + : !priority + ? 'gray' + : 'purple' + const iconSize = 16 + const icon = + !exhibitNumber && !exhibitVolume ? ( + exhibitNumber === 0 && exhibitVolume === 0 ? ( + + ) : ( + + ) + ) : ( + + ) + return ( +
+
+ {icon} +
+
+ ) +}) + +/* Exhibit Component + * Displays an exhibit icon with a tooltip + */ +export const ExhibitStatusTag: FC<{ + exhibitNumber: number | undefined + exhibitVolume: number | undefined + priority: number | undefined +}> = ({ exhibitNumber, exhibitVolume, priority }) => { + const { t } = useTranslation('common', { keyPrefix: 'assetIssues' }) + const isLoadingExtraData = useSelector(selectRealtokensIsLoadingExtraData) + const toolTipText = + exhibitNumber === 0 && exhibitVolume === 0 + ? t('lawsuit.na') + : !exhibitNumber && !exhibitVolume + ? t('lawsuit.unknown') + : exhibitNumber === -1 && exhibitVolume === -1 + ? t('lawsuit.na') + : t('lawsuit.exhibit') + ` # ${exhibitNumber} volume ${exhibitVolume}` + return ( + <> + {isLoadingExtraData ? ( + + ) : ( + + + + )} + + ) +} + +ExhibitStatusTag.displayName = 'ExhibitStatusTag' diff --git a/src/components/commons/assets/index.ts b/src/components/commons/assets/index.ts index 4369f854..2fe36d27 100644 --- a/src/components/commons/assets/index.ts +++ b/src/components/commons/assets/index.ts @@ -1,3 +1,4 @@ export * from './RentStatusTag' export * from './RmmStatusTag' export * from './SubsidyStatusTag' +export * from './IssuesStatusTags' diff --git a/src/components/layouts/Footer.tsx b/src/components/layouts/Footer.tsx index 2e904d30..e7b36e16 100644 --- a/src/components/layouts/Footer.tsx +++ b/src/components/layouts/Footer.tsx @@ -5,7 +5,7 @@ import { IconBrandDiscord, IconBrandMedium, IconBrandTelegram, -} from '@tabler/icons' +} from '@tabler/icons-react' import { Logo } from 'src/assets' diff --git a/src/components/layouts/Header.tsx b/src/components/layouts/Header.tsx index 4c1a4653..a8bd83dd 100644 --- a/src/components/layouts/Header.tsx +++ b/src/components/layouts/Header.tsx @@ -19,7 +19,7 @@ import { IconFilePencil, IconHome2, IconReceipt, -} from '@tabler/icons' +} from '@tabler/icons-react' import { Logo } from 'src/assets' import { selectTransfersIsLoaded } from 'src/store/features/transfers/transfersSelector' diff --git a/src/components/layouts/SettingsMenu.tsx b/src/components/layouts/SettingsMenu.tsx index ee12916e..abb5753e 100644 --- a/src/components/layouts/SettingsMenu.tsx +++ b/src/components/layouts/SettingsMenu.tsx @@ -31,13 +31,16 @@ import { IconMoon, IconSettings, IconSun, -} from '@tabler/icons' + IconTableMinus, + IconTablePlus, +} from '@tabler/icons-react' import { setCookie } from 'cookies-next' import { TransferDatabaseService } from 'src/repositories/transfers/TransferDatabase' import { selectUserCurrency, + selectUserDisplayAdditionalData, selectUserIncludesEth, selectUserIncludesLevinSwap, selectUserIncludesOtherAssets, @@ -47,6 +50,7 @@ import { } from 'src/store/features/settings/settingsSelector' import { userCurrencyChanged, + userDisplayAdditionalDataChanged, userIncludesEthChanged, userIncludesLevinSwapChanged, userIncludesOtherAssetsChanged, @@ -269,6 +273,7 @@ const FetchDataSettings: FC = () => { const userIncludesLevinSwap = useSelector(selectUserIncludesLevinSwap) const userIncludesRmmV2 = useSelector(selectUserIncludesRmmV2) const userIncludesOtherAssets = useSelector(selectUserIncludesOtherAssets) + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) const setUserIncludesEth = (value: boolean) => dispatch(userIncludesEthChanged(value)) @@ -278,6 +283,8 @@ const FetchDataSettings: FC = () => { dispatch(userIncludesRmmV2Changed(value)) const setUserIncludesOtherAssets = (value: boolean) => dispatch(userIncludesOtherAssetsChanged(value)) + const setUserDisplayAdditionalData = (value: boolean) => + dispatch(userDisplayAdditionalDataChanged(value)) return ( <> @@ -318,6 +325,16 @@ const FetchDataSettings: FC = () => { offLabel={} style={{ margin: '4px 8px' }} /> + + setUserDisplayAdditionalData(event.currentTarget.checked) + } + label={t('displayAdditionalData')} + onLabel={} + offLabel={} + style={{ margin: '4px 8px' }} + /> ) } diff --git a/src/components/layouts/WalletMenu.tsx b/src/components/layouts/WalletMenu.tsx index c5ee351c..05da396e 100644 --- a/src/components/layouts/WalletMenu.tsx +++ b/src/components/layouts/WalletMenu.tsx @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux' import { ActionIcon, Badge, Box, Button, Flex, Menu } from '@mantine/core' import { useDisclosure } from '@mantine/hooks' import { useModals } from '@mantine/modals' -import { IconWallet } from '@tabler/icons' +import { IconWallet } from '@tabler/icons-react' import { useWeb3React } from '@web3-react/core' import { ethers } from 'ethers' diff --git a/src/hooks/useFullyRentedAPR.ts b/src/hooks/useFullyRentedAPR.ts index d303399e..4222d290 100644 --- a/src/hooks/useFullyRentedAPR.ts +++ b/src/hooks/useFullyRentedAPR.ts @@ -100,10 +100,18 @@ const APRDisabled = ( token: UserRealtoken, ) => { const realtimeDate = moment(new Date(rentCalculation.date)) - const rentStartDate = new Date(token.rentStartDate.date) + // Set date to null when rentStartDate(.)date is not defined, + // avoid "Cannot read properties of null". + // Typically for RWA asset + const rentStartDate_date = token.rentStartDate?.date ?? null + if (!rentStartDate_date) { + console.warn(`Rent start date is not defined for ${token.uuid}`) + } + const rentStartDate = new Date(rentStartDate_date) const isDisabled = - rentCalculation.state === RentCalculationState.Realtime && - rentStartDate > realtimeDate.toDate() + !rentStartDate_date || + (rentCalculation.state === RentCalculationState.Realtime && + rentStartDate > realtimeDate.toDate()) return isDisabled } diff --git a/src/hooks/useInitStore.ts b/src/hooks/useInitStore.ts index 041019d4..d7a3130a 100644 --- a/src/hooks/useInitStore.ts +++ b/src/hooks/useInitStore.ts @@ -5,10 +5,14 @@ import { useWeb3React } from '@web3-react/core' import { fetchCurrenciesRates } from 'src/store/features/currencies/currenciesSlice' import { selectRealtokens } from 'src/store/features/realtokens/realtokensSelector' -import { fetchRealtokens } from 'src/store/features/realtokens/realtokensSlice' +import { + fetchRealtokens, + fetchRealtokensExtraData, +} from 'src/store/features/realtokens/realtokensSlice' import { selectAllUserAddressList, selectUserAddressList, + selectUserDisplayAdditionalData, } from 'src/store/features/settings/settingsSelector' import { initializeSettings, @@ -32,6 +36,7 @@ export default function useInitStore() { const addressList = useSelector(selectUserAddressList) const realtokens = useSelector(selectRealtokens) const { account } = useWeb3React() + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) useEffect(() => { const accountAddress = account?.toLowerCase() @@ -59,4 +64,10 @@ export default function useInitStore() { dispatch(resetTransfers()) } }, [realtokens, addresses, dispatch]) + + useEffect(() => { + if (realtokens.length && userDisplayAdditionalData) { + dispatch(fetchRealtokensExtraData()) + } + }, [realtokens, userDisplayAdditionalData]) } diff --git a/src/hooks/useREG.ts b/src/hooks/useREG.ts index d7816e73..7ffae25a 100644 --- a/src/hooks/useREG.ts +++ b/src/hooks/useREG.ts @@ -21,13 +21,15 @@ import { import { APIRealTokenProductType } from 'src/types/APIRealToken' import { Currency } from 'src/types/Currencies' import { ERC20ABI } from 'src/utils/blockchain/abi/ERC20ABI' +import { + HoneySwapFactory_Address, + REG_Vault_Gnosis_ContractAddress, +} from 'src/utils/blockchain/consts/misc' import { DEFAULT_REG_PRICE, DEFAULT_USDC_USD_RATE, DEFAULT_XDAI_USD_RATE, - HoneySwapFactory_Address, REG_ContractAddress, - REG_Vault_Gnosis_ContractAddress, REG_asset_ID, REGtokenDecimals, USDConXdai_ContractAddress, diff --git a/src/hooks/useREUSD.ts b/src/hooks/useREUSD.ts new file mode 100644 index 00000000..e2c049af --- /dev/null +++ b/src/hooks/useREUSD.ts @@ -0,0 +1,234 @@ +import { useEffect, useState } from 'react' +import { useSelector } from 'react-redux' + +import { Contract } from 'ethers' +import test from 'node:test' + +import { WalletType } from 'src/repositories' +import { initializeProviders } from 'src/repositories/RpcProvider' +import { + selectCurrencyRates, + selectUserCurrency, +} from 'src/store/features/currencies/currenciesSelector' +import { + selectUserAddressList, + selectUserIncludesEth, +} from 'src/store/features/settings/settingsSelector' +import { + BalanceByWalletType, + REUSDGRealtoken, + updateBalanceValues, +} from 'src/store/features/wallets/walletsSelector' +import { APIRealTokenProductType } from 'src/types/APIRealToken' +import { Currency } from 'src/types/Currencies' +import { ERC20ABI } from 'src/utils/blockchain/abi/ERC20ABI' +import { + CHAIN_ID__GNOSIS_XDAI, + HoneySwapFactory_Address, + SUSHISWAP_DEPLOYMENTS, +} from 'src/utils/blockchain/consts/misc' +import { + DEFAULT_REUSD_PRICE, + DEFAULT_USDC_USD_RATE, + DEFAULT_XDAI_USD_RATE, + REUSD_ContractAddress, + REUSD_asset_ID, + REGtokenDecimals as REUSDtokenDecimals, + USDConXdai_ContractAddress, + USDCtokenDecimals, + WXDAI_ContractAddress, + WXDAItokenDecimals, +} from 'src/utils/blockchain/consts/otherTokens' +import { getAddressesBalances } from 'src/utils/blockchain/erc20Infos' +import { + AssetPrice, + FeeAmounts, + averageValues, + getUniV2AssetPrice, + getUniV3AssetPrice, +} from 'src/utils/blockchain/poolPrice' + +/** + * + * @param addressList : user addresses list + * @param userRate : user selected currency rate + * @param currenciesRates : currencies rates + * @param includeETH : include balances on ETH in the calculation + * @returns + */ +const getREUSD = async ( + addressList: string[], + userRate: number, + currenciesRates: Record, + includeETH = false, +): Promise => { + const { GnosisRpcProvider, EthereumRpcProvider } = await initializeProviders() + const providers = [GnosisRpcProvider] + if (includeETH) { + providers.push(EthereumRpcProvider) + } + const ReusdContract_Gnosis = new Contract( + REUSD_ContractAddress, + ERC20ABI, + GnosisRpcProvider, + ) + const balance: BalanceByWalletType = { + [WalletType.Gnosis]: { + amount: 0, + value: 0, + }, + [WalletType.Ethereum]: { + amount: 0, + value: 0, + }, + [WalletType.RMM]: { + amount: 0, + value: 0, + }, + [WalletType.LevinSwap]: { + amount: 0, + value: 0, + }, + } + let availableBalance = await getAddressesBalances( + REUSD_ContractAddress, + addressList, + GnosisRpcProvider, + ) + + balance[WalletType.Gnosis].amount = availableBalance + + if (includeETH) { + balance[WalletType.Ethereum].amount = await getAddressesBalances( + REUSD_ContractAddress, + addressList, + EthereumRpcProvider, + ) + availableBalance += balance[WalletType.Ethereum].amount + } + + const totalAmount = availableBalance + const contractReusdTotalSupply = await ReusdContract_Gnosis.totalSupply() + const totalTokens = + Number(contractReusdTotalSupply) / 10 ** REUSDtokenDecimals + const amount = totalAmount / 10 ** REUSDtokenDecimals + + // Get REG token prices in USDC and WXDAI from LPs + const reusdPriceUsdcHoneyswap = await getUniV2AssetPrice( + HoneySwapFactory_Address, + REUSD_ContractAddress, + USDConXdai_ContractAddress, + REUSDtokenDecimals, + USDCtokenDecimals, + GnosisRpcProvider, + ) + // const reusdPriceWxdaiHoneyswap = await getUniV2AssetPrice( HoneySwapFactory_Address, REUSD_ContractAddress, WXDAI_ContractAddress, REUSDtokenDecimals, WXDAItokenDecimals, GnosisRpcProvider ) + const reusdPriceWxdaiHoneyswap = null // Honeyswap does not have WXDAI pool (yet) + + // Main pool on Gnosis: Sushiswap @ 0.01% fee + const reusdPriceUsdcSushiv3 = await getUniV3AssetPrice( + SUSHISWAP_DEPLOYMENTS, + REUSD_ContractAddress, + USDConXdai_ContractAddress, + REUSDtokenDecimals, + USDCtokenDecimals, + GnosisRpcProvider, + CHAIN_ID__GNOSIS_XDAI, + FeeAmounts.LOWEST, // 0.01% fee + AssetPrice.TokenA, // REUSD is token0, USDC is token1 + 20, // Amount of token to quote without decimals + ) + // const reusdPriceWxdaiSushiv3 = await getUniV3AssetPrice( SUSHISWAP_DEPLOYMENTS, REUSD_ContractAddress, WXDAI_ContractAddress, REUSDtokenDecimals, WXDAItokenDecimals, GnosisRpcProvider, CHAIN_ID__GNOSIS_XDAI, FeeAmounts.LOWEST ) // 0.01% fee AssetPrice.TokenA, // REUSD is token0, WXDAI is token1 + const reusdPriceWxdaiSushiv3 = null // Sushiswap does not have WXDAI pool (yet) + + // Get rates for XDAI and USDC against USD + const rateXdaiUSD = currenciesRates?.XDAI + ? currenciesRates.XDAI + : DEFAULT_XDAI_USD_RATE + const rateUsdcUSD = currenciesRates?.USDC + ? currenciesRates.USDC + : DEFAULT_USDC_USD_RATE + // Convert Honeyswap token prices to USD + const assetPriceUsd1 = reusdPriceUsdcHoneyswap + ? reusdPriceUsdcHoneyswap * rateUsdcUSD + : null + const assetPriceUsd2 = reusdPriceWxdaiHoneyswap + ? reusdPriceWxdaiHoneyswap * rateXdaiUSD + : null + // Get Honeyswap average token prices in USD + const assetAveragePriceOnHoneyswapInUSD = averageValues([ + assetPriceUsd1, + assetPriceUsd2, + ]) + // Convert Sushiswap token price to USD + const assetPriceUsd3 = reusdPriceUsdcSushiv3 + ? reusdPriceUsdcSushiv3 * rateUsdcUSD + : null + + const assetPriceUsd4 = reusdPriceWxdaiSushiv3 + ? reusdPriceWxdaiSushiv3 * rateXdaiUSD + : null + + // Get Sushiswap average token prices in USD + const assetAveragePriceOnSushiswapInUSD = averageValues([ + assetPriceUsd3, + assetPriceUsd4, + ]) + + // Apply a 20 / 80 ratio between Honeyswap and Sushiswap prices + // we SHOULD COMPARE LIQUIDITY instead (todo) + const assetPriceInUSD = + 0.2 * + (assetAveragePriceOnHoneyswapInUSD + ? assetAveragePriceOnHoneyswapInUSD + : DEFAULT_REUSD_PRICE) + + 0.8 * + (assetAveragePriceOnSushiswapInUSD + ? assetAveragePriceOnSushiswapInUSD + : DEFAULT_REUSD_PRICE) + + // Convert prices in Currency by applying rate + const tokenPrice = assetPriceInUSD + ? assetPriceInUSD / userRate + : DEFAULT_REUSD_PRICE / userRate + const value = tokenPrice * amount + const totalInvestment = totalTokens * tokenPrice + // Update all balance values with token price + updateBalanceValues(balance, tokenPrice) + + return { + id: `${REUSD_asset_ID}`, + fullName: 'Realtoken Ecosystem USD', + shortName: 'REUSD', + productType: APIRealTokenProductType.EquityToken, + amount, + tokenPrice, + totalTokens, + imageLink: [ + 'https://static.debank.com/image/project/logo_url/xdai_realtrmm/05ce107c2155971276a46b920053f704.png', + ], + isRmmAvailable: false, + value, + totalInvestment, + unitPriceCost: tokenPrice, + balance, + } +} + +export const useREUSD = () => { + const [reusd, setReusd] = useState(null) + const addressList = useSelector(selectUserAddressList) + const { rate: userRate } = useSelector(selectUserCurrency) + const includeETH = useSelector(selectUserIncludesEth) + const currenciesRates = useSelector(selectCurrencyRates) + + useEffect(() => { + if (addressList.length) { + getREUSD(addressList, userRate, currenciesRates, includeETH).then( + setReusd, + ) + } + }, [addressList, userRate, currenciesRates, includeETH]) + + return reusd +} diff --git a/src/hooks/useRWA.ts b/src/hooks/useRWA.ts index 83167155..f77d7648 100644 --- a/src/hooks/useRWA.ts +++ b/src/hooks/useRWA.ts @@ -20,11 +20,11 @@ import { import { APIRealTokenProductType } from 'src/types/APIRealToken' import { Currency } from 'src/types/Currencies' import { ERC20ABI } from 'src/utils/blockchain/abi/ERC20ABI' +import { HoneySwapFactory_Address } from 'src/utils/blockchain/consts/misc' import { DEFAULT_RWA_PRICE, DEFAULT_USDC_USD_RATE, DEFAULT_XDAI_USD_RATE, - HoneySwapFactory_Address, RWA_ContractAddress, RWA_asset_ID, RWAtokenDecimals, diff --git a/src/i18next/locales/en/common.json b/src/i18next/locales/en/common.json index 55668c19..fbdf265c 100644 --- a/src/i18next/locales/en/common.json +++ b/src/i18next/locales/en/common.json @@ -32,7 +32,8 @@ "includesEth": "Includes Ethereum", "includesLevinSwap": "Includes LevinSwap", "includesRmmV2": "Includes RMM V2", - "includesOtherAssets": "Includes other assets" + "includesOtherAssets": "Includes other assets", + "displayAdditionalData": "Display additional data" }, "walletButton": { "connectWallet": "Connect wallet", @@ -77,6 +78,7 @@ "stableBorrow": "RMM borrow", "rwa": "RWA", "reg": "REG", + "reUsd": "REUSD", "regVote": "REG Vote Power" }, "worthCard": { @@ -146,7 +148,7 @@ "resetFilter": "Reset" }, "assetSubsidy": { - "label": "All", + "label": "Subsidized rents", "options": { "all": "No filtered", "subsidized": "Subsidized", @@ -196,6 +198,25 @@ "notAvailable": "No" } }, + "assetIssueStatus": { + "label": "Issue Status", + "options": { + "all": "All", + "noExhibit": "No exhibit", + "scheduled": "Scheduled", + "upgradedAndReady": "Upgraded and ready" + } + }, + "assetIssuePriority": { + "label": "Issue Priority", + "options": { + "all": "All", + "none": "No priority", + "high": "High", + "medium": "Medium", + "low": "Low" + } + }, "assetUserProtocol": { "label": "Owned on", "options": { @@ -227,7 +248,36 @@ "full": "Subsidized", "partial": "Partially subsidized" }, - "subsidy": "Subsidy" + "subsidy": "Subsidy", + "assetIssues": { + "title": "Asset issues", + "status": "Status", + "priority": "Priority", + "lawsuit": "Lawsuit" + } + }, + "assetIssues": { + "status": { + "noExhibit": "Not applicable", + "scheduled": "Scheduled", + "upgradedReady": "Upgraded and ready", + "unknown": "Unknown", + "na": "Not applicable" + }, + "priority": { + "1": "Priority 1 (High)", + "2": "Priority 2 (Medium)", + "3": "Priority 3 (Low)", + "na": "Not applicable", + "unknown": "Unknown" + }, + "lawsuit": { + "na": "Not applicable", + "exhibit": "exhibit", + "unknown": "Unknown", + "exhibitNumber": "number", + "volumeNumber": "volume number" + } }, "assetTable": { "property": "Property", @@ -238,18 +288,28 @@ "unitPriceCost": "Unit price cost", "unrealizedCapitalGain": "Capital gain", "apr": "Yield", - "fullyRentedAPR":"Fully rented APR *", + "fullyRentedAPR": "Fully rented APR *", "weeklyRents": "Weekly rents", "yearlyRents": "Yearly rents", "rentedUnits": "Rented units", "propertyValue": "Property value", - "lastChange": "Last change" + "lastChange": "Last change", + "status": { + "header": "Status" + }, + "priority": { + "header": "Priority" + }, + "lawsuit": { + "header": "Lawsuit" + } }, "assetPage": { "home": "Home", "viewOnRealt": "View on RealT", "tabs": { "main": "Main informations", + "issues": "Issues", "property": "Property details", "history": "Property changes", "transfers": "My transactions", @@ -272,6 +332,11 @@ "rentYear": "Yearly rent", "rentedUnits": "Rented units" }, + "issues": { + "status": "Status", + "priority": "Priority", + "lawsuit": "Lawsuit" + }, "property": { "initialLaunchDate": "Launch date", "totalValue": "Investment value", @@ -422,10 +487,10 @@ "title": "Initial data loading", "description": "Retrieving your transactions in progress. This loading can take some time depending on the number of transactions performed (10-20 seconds / 1000 transactions). On your next visits, only new transactions will be retrieved." }, - "disclaimer":{ + "disclaimer": { "fullyRentedAPR": "This is a beta estimation done by RealT community. Please report any issues. Please note that is an indicative value and not a guarantee. RealT community or RealT does not take any responsibility for user actions based on this value." }, - "errors" : { + "errors": { "userNotFound": "User not found" } -} +} \ No newline at end of file diff --git a/src/i18next/locales/fr/common.json b/src/i18next/locales/fr/common.json index 70c34a87..ef5d27d1 100644 --- a/src/i18next/locales/fr/common.json +++ b/src/i18next/locales/fr/common.json @@ -32,7 +32,8 @@ "includesEth": "Inclure Ethereum", "includesLevinSwap": "Inclure LevinSwap", "includesRmmV2": "Inclure RMM V2", - "includesOtherAssets": "Inclure d'autres actifs" + "includesOtherAssets": "Inclure d'autres actifs", + "displayAdditionalData": "Afficher des données supplémentaires" }, "walletButton": { "connectWallet": "Connecter mon portefeuille", @@ -77,6 +78,7 @@ "stableBorrow": "Emprunt RMM", "rwa": "RWA", "reg": "REG", + "reUsd": "REUSD", "regVote": "Pouvoir de vote" }, "worthCard": { @@ -196,6 +198,25 @@ "notAvailable": "Non" } }, + "assetIssueStatus": { + "label": "Statut des problèmes", + "options": { + "all": "Tous", + "noExhibit": "Non concerné", + "scheduled": "Planifié", + "upgradedAndReady": "Réparé, prêt" + } + }, + "assetIssuePriority": { + "label": "Priorité des problèmes", + "options": { + "all": "Tous", + "none": "Sans problème", + "high": "Priorité 1 (Haute)", + "medium": "Priorité 2 (Moyenne)", + "low": "Priorité 3 (Basse)" + } + }, "assetUserProtocol": { "label": "Possédées sur", "options": { @@ -212,7 +233,7 @@ "weekly": "Loyers hebdomadaires", "yearly": "Loyers annuels", "rentedUnits": "Logements loués", - "tokenPrice":"Prix d'un token", + "tokenPrice": "Prix d'un token", "propertyValue": "Valeur de la propriété", "rentStartDate": "Date du premier loyer", "fullyRentedEstimation": "Rendement 100% loué", @@ -227,7 +248,36 @@ "full": "Subventionnée", "partial": "Partiellement subventionnée" }, - "subsidy": "Loyer subventioné" + "subsidy": "Loyer subventioné", + "assetIssues": { + "title": "Problèmes", + "status": "Etat", + "priority": "Priorité", + "lawsuit": "Poursuite" + } + }, + "assetIssues": { + "status": { + "noExhibit": "Non concerné", + "scheduled": "Planifié", + "upgradedReady": "Réparé, prêt", + "unknown": "Inconnu", + "na": "Non concerné" + }, + "priority": { + "1": "Priorité 1 (Haute)", + "2": "Priorité 2 (Moyenne)", + "3": "Priorité 3 (Basse)", + "na": "Non concerné", + "unknown": "Inconnu" + }, + "lawsuit": { + "na": "Sans objet", + "exhibit": "plainte", + "unknown": "Inconnu", + "exhibitNumber": "numéro", + "volumeNumber": "volume" + } }, "assetTable": { "property": "Propriété", @@ -238,18 +288,28 @@ "tokenPrice": "Prix du token", "unitPriceCost": "Prix de revient", "apr": "Rendement annuel", - "fullyRentedAPR":"Rendement 100% loué *", + "fullyRentedAPR": "Rendement 100% loué *", "weeklyRents": "Loyer hebdo", "yearlyRents": "Loyer annuel", "rentedUnits": "Logements loués", "propertyValue": "Valeur de la propriété", - "lastChange": "Dernier changement" + "lastChange": "Dernier changement", + "status": { + "header": "Statut" + }, + "priority": { + "header": "Priorité" + }, + "lawsuit": { + "header": "Poursuite" + } }, "assetPage": { "home": "Accueil", "viewOnRealt": "Voir sur RealT", "tabs": { "main": "Informations générales", + "issues": "Problèmes", "property": "Détail du bien", "history": "Changements du bien", "transfers": "Mes transactions", @@ -272,6 +332,11 @@ "rentYear": "Loyer annuel", "rentedUnits": "Logements loués" }, + "issues": { + "status": "Statut", + "priority": "Priorité", + "lawsuit": "Poursuite" + }, "property": { "initialLaunchDate": "Mise en vente", "totalValue": "Valeur de l'investissement", @@ -423,11 +488,10 @@ "title": "Chargement initial des données", "description": "Récupération de vos transactions en cours. Ce chargement peut prendre un certain temps en fonction du nombre de transactions effecutées (10-20 secondes / 1000 transactions). Lors de vos prochaines visites, seul les nouvelles transactions seront récupérées." }, - "disclaimer":{ + "disclaimer": { "fullyRentedAPR": "Cette estimation est en phase bêta et a été développée par la communauté RealT. Nous vous invitons à signaler tout problème éventuel. Les informations fournies sont à titre indicatif uniquement. La communauté RealT ou RealT ne peut être tenue responsable en cas de décision prise à partir de données inexactes." - } - , - "errors" : { + }, + "errors": { "userNotFound": "User not found" } -} +} \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 1ede46ef..73e66d6e 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useMemo } from 'react' import { QueryClient, QueryClientProvider } from 'react-query' import { Provider } from 'react-redux' @@ -116,45 +116,54 @@ const App = ({ // Customize chains config for Gnosis and Ethereum // using rpc urls from props - const CustomChainsConfig = { - // Keep Goerli as testnet else an error will arise at init - [ChainsID.Goerli]: RealtCommonsDefaultChainsConfig[ChainsID.Goerli], - [ChainsID.Gnosis]: { - ...RealtCommonsDefaultChainsConfig[ChainsID.Gnosis], - rpcUrl: - GnosisRpcUrl || RealtCommonsDefaultChainsConfig[ChainsID.Gnosis].rpcUrl, - }, - [ChainsID.Ethereum]: { - ...RealtCommonsDefaultChainsConfig[ChainsID.Ethereum], - rpcUrl: - EthereumRpcUrl || - RealtCommonsDefaultChainsConfig[ChainsID.Ethereum].rpcUrl, - }, - // TODO: add Polygon - } + const CustomChainsConfig = useMemo( + () => ({ + // Keep Goerli as testnet else an error will arise at init + [ChainsID.Goerli]: RealtCommonsDefaultChainsConfig[ChainsID.Goerli], + [ChainsID.Gnosis]: { + ...RealtCommonsDefaultChainsConfig[ChainsID.Gnosis], + rpcUrl: + GnosisRpcUrl || + RealtCommonsDefaultChainsConfig[ChainsID.Gnosis].rpcUrl, + }, + [ChainsID.Ethereum]: { + ...RealtCommonsDefaultChainsConfig[ChainsID.Ethereum], + rpcUrl: + EthereumRpcUrl || + RealtCommonsDefaultChainsConfig[ChainsID.Ethereum].rpcUrl, + }, + // TODO: add Polygon + }), + [GnosisRpcUrl, EthereumRpcUrl], + ) - const dashbordChains: ChainSelectConfig = { - allowedChains: parseAllowedChain(ChainsID), - chainsConfig: CustomChainsConfig, - defaultChainId: ChainsID.Gnosis, // Explicitly setting Gnosis as the defaultChainId - } + const dashbordChains: ChainSelectConfig = useMemo( + () => ({ + allowedChains: parseAllowedChain(ChainsID), + chainsConfig: CustomChainsConfig, + defaultChainId: ChainsID.Gnosis, // Explicitly setting Gnosis as the defaultChainId + }), + [CustomChainsConfig], + ) const envName = process.env.NEXT_PUBLIC_ENV ?? 'development' const walletConnectKey = process.env.NEXT_PUBLIC_WALLET_CONNECT_KEY ?? '' - const readOnly = getReadOnlyConnector(dashbordChains) - const walletConnect = getWalletConnectV2( - dashbordChains, - envName, - walletConnectKey, - false, - ) - - const libraryConnectors = getConnectors({ - readOnly: readOnly, - metamask: [metaMask, metaMaskHooks], - walletConnectV2: walletConnect, - } as unknown as ConnectorsAvailable) + const libraryConnectors = useMemo(() => { + const readOnly = getReadOnlyConnector(dashbordChains) + const walletConnect = getWalletConnectV2( + dashbordChains, + envName, + walletConnectKey, + false, + ) + + return getConnectors({ + readOnly: readOnly, + metamask: [metaMask, metaMaskHooks], + walletConnectV2: walletConnect, + } as unknown as ConnectorsAvailable) + }, [dashbordChains, envName, walletConnectKey]) return ( diff --git a/src/pages/api/history/index.ts b/src/pages/api/history/index.ts index 7305ad8e..65ebd34a 100644 --- a/src/pages/api/history/index.ts +++ b/src/pages/api/history/index.ts @@ -1,20 +1,31 @@ import { NextApiHandler } from 'next' +import { APIRealTokenCommunityEnv } from 'src/types/APIRealToken' import { APIRealTokenHistory } from 'src/types/APIRealTokenHistory' import { useCache } from 'src/utils/useCache' const getRealTokenHistory = useCache( async (): Promise => { - if (!process.env.COMMUNITY_API_KEY) { - throw new Error('Missing COMMUNITY_API_KEY env variable') + if (!process.env[APIRealTokenCommunityEnv.API_KEY]) { + throw new Error( + `Missing ${APIRealTokenCommunityEnv.API_KEY} env variable`, + ) } - if (!process.env.REALTOKENAPI_HISTORY) { - throw new Error('Missing REALTOKENAPI_HISTORY env variable') + if (!process.env[APIRealTokenCommunityEnv.API_HISTORY]) { + throw new Error( + `Missing ${APIRealTokenCommunityEnv.API_HISTORY} env variable`, + ) } - const response = await fetch(process.env.REALTOKENAPI_HISTORY, { - method: 'GET', - headers: { 'X-AUTH-REALT-TOKEN': process.env.COMMUNITY_API_KEY }, - }) + const response = await fetch( + process.env[APIRealTokenCommunityEnv.API_HISTORY], + { + method: 'GET', + headers: { + [APIRealTokenCommunityEnv.AUTH]: + process.env[APIRealTokenCommunityEnv.API_KEY], + }, + }, + ) if (!response.ok) { throw new Error('Failed to fetch properties history') diff --git a/src/pages/api/pitsBiExtraProperties/index.ts b/src/pages/api/pitsBiExtraProperties/index.ts new file mode 100644 index 00000000..5f5ffec9 --- /dev/null +++ b/src/pages/api/pitsBiExtraProperties/index.ts @@ -0,0 +1,52 @@ +import { NextApiHandler } from 'next' + +import { APIPitsBiEnv } from 'src/types/APIPitsBI' +import { APIRealToken } from 'src/types/APIRealToken' +import { fetchWithRetry } from 'src/utils/general' +import { useCache } from 'src/utils/useCache' + +import { URIS } from '../uris' + +const getRealTokenListExtraData = useCache( + async (): Promise => { + let APIPitsBi_Env_available = true + for (const envVar of [ + APIPitsBiEnv.VERSION, + APIPitsBiEnv.BASE, + APIPitsBiEnv.GET_LASTUPDATE, + APIPitsBiEnv.GET_ALLTOKENS, + ]) { + if (!process.env[envVar]) { + APIPitsBi_Env_available = false + console.warn( + `extraProperties: Missing PitsBi API ${envVar} env variable`, + ) + } + } + if (!APIPitsBi_Env_available) { + console.warn('extraProperties: PitsBi API is not available') + // Return empty response + return [] + } + const pitsBiApiResponse = await fetchWithRetry( + URIS.PITSBI_API_GET_ALLTOKENS, + { method: 'GET' }, + 2, + 5_000, + ) + return pitsBiApiResponse.json() + }, + { duration: 1000 * 60 * 60 }, +) + +const handler: NextApiHandler = async (req, res) => { + try { + res.status(200).json(await getRealTokenListExtraData()) + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error' + console.log(message) + res.status(500).json(message) + } +} + +export default handler diff --git a/src/pages/api/properties/index.ts b/src/pages/api/properties/index.ts index 45f8c7be..11d8c327 100644 --- a/src/pages/api/properties/index.ts +++ b/src/pages/api/properties/index.ts @@ -1,26 +1,74 @@ import { NextApiHandler } from 'next' -import { APIRealToken } from 'src/types/APIRealToken' +import { APIPitsBiEnv } from 'src/types/APIPitsBI' +import { APIRealToken, APIRealTokenCommunityEnv } from 'src/types/APIRealToken' +import { fetchWithRetry } from 'src/utils/general' import { useCache } from 'src/utils/useCache' +import { URIS } from '../uris' + const getRealTokenList = useCache( async (): Promise => { - if (!process.env.COMMUNITY_API_KEY) { - throw new Error('Missing COMMUNITY_API_KEY env variable') + for (const envVar of [ + APIRealTokenCommunityEnv.API_KEY, + APIRealTokenCommunityEnv.API_BASE, + APIRealTokenCommunityEnv.VERSION, + APIRealTokenCommunityEnv.GET_ALLTOKENS, + ]) { + if (!process.env[envVar]) { + throw new Error( + `Missing RealToken Community API ${envVar} env variable`, + ) + } } - if (!process.env.REALTOKENAPI) { - throw new Error('Missing REALTOKENAPI env variable') + try { + const realTokenApiResponse = await fetchWithRetry( + URIS.REALTOKEN_COMMUNITY_API_GET_ALLTOKENS, + { + method: 'GET', + headers: { + [APIRealTokenCommunityEnv.AUTH]: process.env[ + APIRealTokenCommunityEnv.API_KEY + ] as string, + }, + }, + 2, + 1_000, + ) + return realTokenApiResponse.json() + } catch (error) { + console.error(`Failed to fetch RealToken API: ${error}`) } - const response = await fetch(process.env.REALTOKENAPI, { - method: 'GET', - headers: { 'X-AUTH-REALT-TOKEN': process.env.COMMUNITY_API_KEY }, - }) - - if (!response.ok) { - throw new Error('Failed to fetch properties : ' + (await response.text())) + // Use Pitsbi API as fallback datasource if RealToken API is not available + // Pitsbi is 100% compatible with RealToken API + let APIPitsbi_Env_available = true + for (const envVar of [ + APIPitsBiEnv.VERSION, + APIPitsBiEnv.BASE, + APIPitsBiEnv.GET_LASTUPDATE, + APIPitsBiEnv.GET_ALLTOKENS, + ]) { + if (!process.env[envVar]) { + APIPitsbi_Env_available = false + console.warn(`Missing Pitsbi API ${envVar} env variable`) + } } - - return response.json() + if (!APIPitsbi_Env_available) { + throw new Error( + `Failed to fetch properties from RealToken API ; PitsBI API environment variables are not set, unable to fetch data.`, + ) + } + const pitsbiApiResponse = await fetchWithRetry( + URIS.PITSBI_API_GET_ALLTOKENS, + { + method: 'GET', + }, + 2, + 10_000, + ) + // Return Pitsbi API response if RealToken API is not available + console.warn(`Pitsbi API used as fallback`) + return pitsbiApiResponse.json() }, { duration: 1000 * 60 * 60 }, ) diff --git a/src/pages/api/uris.ts b/src/pages/api/uris.ts new file mode 100644 index 00000000..eadf5d73 --- /dev/null +++ b/src/pages/api/uris.ts @@ -0,0 +1,11 @@ +// Define API Uris +import { APIPitsBiEnv } from 'src/types/APIPitsBI' +import { APIRealTokenCommunityEnv } from 'src/types/APIRealToken' + +const REALTOKEN_COMMUNITY_API_GET_ALLTOKENS = `${process.env[APIRealTokenCommunityEnv.API_BASE]}${process.env[APIRealTokenCommunityEnv.VERSION]}/${process.env[APIRealTokenCommunityEnv.GET_ALLTOKENS]}` +const PITSBI_API_GET_ALLTOKENS = `${process.env[APIPitsBiEnv.BASE]}${process.env[APIPitsBiEnv.VERSION]}/${process.env[APIPitsBiEnv.GET_ALLTOKENS]}` + +export const URIS = { + REALTOKEN_COMMUNITY_API_GET_ALLTOKENS, + PITSBI_API_GET_ALLTOKENS, +} diff --git a/src/pages/asset/[assetId].tsx b/src/pages/asset/[assetId].tsx index 9c93d541..cbd283dc 100644 --- a/src/pages/asset/[assetId].tsx +++ b/src/pages/asset/[assetId].tsx @@ -7,15 +7,19 @@ import Image from 'next/image' import { useRouter } from 'next/router' import { Anchor, Breadcrumbs, Button, Flex } from '@mantine/core' -import { IconExternalLink } from '@tabler/icons' +import { IconExternalLink } from '@tabler/icons-react' import { AssetPageHistoryTab } from 'src/components/assetPage/assetPageHistoryTab' +import { AssetPageIssuesTab } from 'src/components/assetPage/assetPageIssuesTab' import { AssetPageMainTab } from 'src/components/assetPage/assetPageMainTab' import { AssetPagePropertyTab } from 'src/components/assetPage/assetPagePropertyTab' import { AssetPageTransfersTab } from 'src/components/assetPage/assetPageTransfersTab' import { AssetPageYamStatisticsTab } from 'src/components/assetPage/assetPageYamStatisticsTab' import FullyRentedAPRDisclaimer from 'src/components/commons/others/FullyRentedAPRDisclaimer' -import { selectIsLoading } from 'src/store/features/settings/settingsSelector' +import { + selectIsLoading, + selectUserDisplayAdditionalData, +} from 'src/store/features/settings/settingsSelector' import { selectTransfersIsLoaded } from 'src/store/features/transfers/transfersSelector' import { selectAllUserRealtokens } from 'src/store/features/wallets/walletsSelector' @@ -23,6 +27,7 @@ import styles from './AssetPage.module.sass' enum Tabs { Main = 'main', + Issues = 'issues', Property = 'property', Transfers = 'transfers', History = 'history', @@ -51,7 +56,7 @@ const AssetPage: NextPage = () => { const { t } = useTranslation('common', { keyPrefix: 'assetPage' }) const realtokens = useSelector(selectAllUserRealtokens) const transfersIsLoaded = useSelector(selectTransfersIsLoaded) - + const userDisplayAdditionalData = useSelector(selectUserDisplayAdditionalData) const isLoading = useSelector(selectIsLoading) const router = useRouter() const { assetId } = router.query @@ -97,6 +102,21 @@ const AssetPage: NextPage = () => {
) : null} + {userDisplayAdditionalData && ( + <> + setActiveTab(Tabs.Issues)} + /> + {activeTab === Tabs.Issues ? ( +
+ +
+ ) : null} + + )} + { const realtokens = useSelector(selectUserRealtokens) const rwa = useRWA() const reg = useREG() + const reUsd = useREUSD() const regVotingPower = useRegVotingPower() const allAssetsData = useMemo(() => { @@ -33,19 +35,21 @@ const HomePage: NextPage = () => { ...realtokens, rwa, reg, + reUsd, regVotingPower, ].filter((asset) => !!asset) return assets as (UserRealtoken | OtherRealtoken)[] - }, [realtokens, rwa, reg, regVotingPower]) + }, [realtokens, rwa, reg, reUsd, regVotingPower]) const otherAssetsData = useMemo(() => { const assets = { rwa, reg, + reUsd, regVotingPower, } return assets - }, [rwa, reg, regVotingPower]) + }, [rwa, reg, reUsd, regVotingPower]) return ( diff --git a/src/pages/test-rpc.tsx b/src/pages/test-rpc.tsx new file mode 100644 index 00000000..b6d3c1c9 --- /dev/null +++ b/src/pages/test-rpc.tsx @@ -0,0 +1,104 @@ +// src/pages/test-rpc.tsx +import { useEffect, useState } from 'react' + +import { initializeProviders } from 'src/repositories/RpcProvider' + +interface TestResults { + success: boolean + duration?: number + providers?: { + gnosisUrl: string + ethereumUrl: string + } + error?: { + message: string + stack: string + } +} + +export default function TestRpcPage() { + const [results, setResults] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + testInitializeProviders() + }, []) + + const testInitializeProviders = async () => { + try { + console.log('🚀 Test de initializeProviders...') + + const startTime = Date.now() + const providers = await initializeProviders() + const duration = Date.now() - startTime + + setResults({ + success: true, + duration, + providers: { + gnosisUrl: providers.GnosisRpcUrl, + ethereumUrl: providers.EthereumRpcUrl, + }, + }) + + console.log('✅ initializeProviders réussi:', providers) + } catch (error) { + console.error('❌ initializeProviders échoué:', error) + setResults({ + success: false, + error: { + message: error instanceof Error ? error.message : 'Erreur inconnue', + stack: error instanceof Error ? error.stack || '' : '', + }, + }) + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+

{'Test RPC - Chargement...'}

+

{'Test de initializeProviders en cours...'}

+
+ ) + } + + if (!results?.success) { + return ( +
+

{'Test RPC - Erreur'}

+

{'❌ initializeProviders a échoué'}

+

+ {'Message:'} {results?.error?.message} +

+
+ {'Stack trace'} +
+            {results?.error?.stack}
+          
+
+
+ ) + } + + return ( +
+

{'Test RPC - Succès'}

+

{'✅ initializeProviders réussi'}

+

+ {'Durée:'} {results.duration} + {'ms\r'} +

+

+ {'Gnosis RPC:'} {results.providers?.gnosisUrl} +

+

+ {'Ethereum RPC:'} {results.providers?.ethereumUrl} +

+
+ ) +} diff --git a/src/repositories/RpcProvider.ts b/src/repositories/RpcProvider.ts index 87af7f76..f09a602d 100644 --- a/src/repositories/RpcProvider.ts +++ b/src/repositories/RpcProvider.ts @@ -11,10 +11,10 @@ import { import { ERC20ABI } from 'src/utils/blockchain/abi/ERC20ABI' import { CHAINS_NAMES, - CHAIN_ID_ETHEREUM, - CHAIN_ID_GNOSIS_XDAI, - REG_ContractAddress, -} from 'src/utils/blockchain/consts/otherTokens' + CHAIN_ID__ETHEREUM, + CHAIN_ID__GNOSIS_XDAI, +} from 'src/utils/blockchain/consts/misc' +import { REG_ContractAddress } from 'src/utils/blockchain/consts/otherTokens' import { batchCallOneContractOneFunctionMultipleParams } from 'src/utils/blockchain/contract' import { wait } from 'src/utils/general' import { WaitingQueue } from 'src/utils/waitingQueue' @@ -40,17 +40,17 @@ const getRpcUrls = (chainId: number): string[] => { let defaultUrls: string[] = [] switch (chainId) { - case CHAIN_ID_ETHEREUM: + case CHAIN_ID__ETHEREUM: envVarName = 'RPC_URLS_ETH_MAINNET' // Use the default Ethereum RPC URLs from realt-commons config defaultUrls = DEFAULT_ETHEREUM_RPC_URLS.concat( - RealtCommonsDefaultChainsConfig[CHAIN_ID_ETHEREUM].rpcUrl, + RealtCommonsDefaultChainsConfig[CHAIN_ID__ETHEREUM].rpcUrl, ) break - case CHAIN_ID_GNOSIS_XDAI: + case CHAIN_ID__GNOSIS_XDAI: envVarName = 'RPC_URLS_GNOSIS_MAINNET' defaultUrls = DEFAULT_GNOSIS_RPC_URLS.concat( - RealtCommonsDefaultChainsConfig[CHAIN_ID_GNOSIS_XDAI].rpcUrl, + RealtCommonsDefaultChainsConfig[CHAIN_ID__GNOSIS_XDAI].rpcUrl, ) break // TODO: Polygon @@ -252,36 +252,70 @@ interface Providers { // PolygonRpcProvider?: JsonRpcProvider // TODO: add Polygon provider } -interface ProvidersWithUrls extends Providers { +export interface ProvidersWithUrls extends Providers { GnosisRpcUrl: string EthereumRpcUrl: string // PolygonRpcUrl?: string // TODO: add Polygon provider } let initializeProvidersQueue: WaitingQueue | null = null -let providers: ProvidersWithUrls | undefined = undefined +// let providers: ProvidersWithUrls | undefined = undefined -export const initializeProviders = async () => { +export const initializeProviders = async (): Promise => { if (initializeProvidersQueue) { - return initializeProvidersQueue.wait() + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('RPC timeout')), 10000), + ) + + try { + return await Promise.race([ + initializeProvidersQueue.wait(), + timeoutPromise, + ]) + } catch (error) { + initializeProvidersQueue = null + // providers = undefined + // Relaunch directly without queue + return await initializeProvidersDirect() + } } + initializeProvidersQueue = new WaitingQueue() - const [GnosisRpcProviderWithUrl, EthereumRpcProviderWithUrl] = - await Promise.all([ - getWorkingRpc(CHAIN_ID_GNOSIS_XDAI), - getWorkingRpc(CHAIN_ID_ETHEREUM), - ]) - providers = { - GnosisRpcProvider: GnosisRpcProviderWithUrl.provider, - EthereumRpcProvider: EthereumRpcProviderWithUrl.provider, - GnosisRpcUrl: GnosisRpcProviderWithUrl.url, - EthereumRpcUrl: EthereumRpcProviderWithUrl.url, - // PolygonRpcProvider: undefined, // TODO: add Polygon provider - // PolygonRpcUrl: undefined, // TODO: add Polygon provider + try { + const result = await initializeProvidersDirect() + initializeProvidersQueue.resolve(result) + return result + } catch (error) { + initializeProvidersQueue.reject(error) + throw error + } +} + +async function initializeProvidersDirect(): Promise { + try { + const [GnosisRpcProviderWithUrl, EthereumRpcProviderWithUrl] = + await Promise.all([ + getWorkingRpc(CHAIN_ID__GNOSIS_XDAI), + getWorkingRpc(CHAIN_ID__ETHEREUM), + ]) + + return { + GnosisRpcProvider: GnosisRpcProviderWithUrl.provider, + EthereumRpcProvider: EthereumRpcProviderWithUrl.provider, + GnosisRpcUrl: GnosisRpcProviderWithUrl.url, + EthereumRpcUrl: EthereumRpcProviderWithUrl.url, + } + } catch (error) { + console.log('fallback to default RPC URLs') + + return { + GnosisRpcProvider: new JsonRpcProvider('https://rpc.gnosischain.com'), + EthereumRpcProvider: new JsonRpcProvider('https://rpc.eth.gateway.fm'), + GnosisRpcUrl: 'https://rpc.gnosischain.com', + EthereumRpcUrl: 'https://rpc.eth.gateway.fm', + } } - initializeProvidersQueue.resolve(providers) - return providers } /** diff --git a/src/repositories/realtoken.repository.ts b/src/repositories/realtoken.repository.ts index cd683244..ee5f383a 100644 --- a/src/repositories/realtoken.repository.ts +++ b/src/repositories/realtoken.repository.ts @@ -1,5 +1,9 @@ import _sortBy from 'lodash/sortBy' +import { + APIRealTokenPitsBI, + APIRealTokenPitsBI_ExtraData, +} from 'src/types/APIPitsBI' import { APIRealToken } from 'src/types/APIRealToken' import { APIRealTokenHistory } from 'src/types/APIRealTokenHistory' import { RealToken, RealTokenRentStatus } from 'src/types/RealToken' @@ -21,6 +25,18 @@ export const RealtokenRepository = { ), })) }, + async getTokensPitsBiExtraData(): Promise { + const [tokensExtraData] = await Promise.all([ + fetchTokenListPitsBiExtraData(), + ]) + return tokensExtraData.map((tokenExtraData: APIRealTokenPitsBI) => { + return { + uuid: tokenExtraData.uuid, + actions: tokenExtraData.actions, + historic: tokenExtraData.historic, + } + }) + }, } async function fetchTokenList() { @@ -30,6 +46,13 @@ async function fetchTokenList() { : Promise.reject(response.statusText) } +async function fetchTokenListPitsBiExtraData() { + const response = await fetch('/api/pitsBiExtraProperties', { method: 'GET' }) + return response.ok + ? (response.json() as Promise) + : Promise.reject(response.statusText) +} + async function fetchHistoryList() { const response = await fetch('/api/history', { method: 'GET' }) return response.ok diff --git a/src/states/index.ts b/src/states/index.ts index 66984504..9ea46ac5 100644 --- a/src/states/index.ts +++ b/src/states/index.ts @@ -1,6 +1,8 @@ import { atomWithStorage } from 'jotai/utils' import { + AssetIssuePriorityType, + AssetIssueStatusType, AssetProductType, AssetRentStatusType, AssetRmmStatusType, @@ -25,6 +27,8 @@ export interface AssetsViewFilterType { rentStatus: AssetRentStatusType rmmStatus: AssetRmmStatusType userProtocol: AssetUserProtocolType + issueStatus: AssetIssueStatusType + issuePriority: AssetIssuePriorityType } export const assetsViewDefaultFilter: AssetsViewFilterType = { @@ -36,6 +40,8 @@ export const assetsViewDefaultFilter: AssetsViewFilterType = { rentStatus: AssetRentStatusType.ALL, rmmStatus: AssetRmmStatusType.ALL, userProtocol: AssetUserProtocolType.ALL, + issueStatus: AssetIssueStatusType.ALL, + issuePriority: AssetIssuePriorityType.ALL, } export const assetsViewFilterAtom = atomWithStorage( diff --git a/src/store/features/realtokens/realtokensSelector.ts b/src/store/features/realtokens/realtokensSelector.ts index c07eb77e..0126f665 100644 --- a/src/store/features/realtokens/realtokensSelector.ts +++ b/src/store/features/realtokens/realtokensSelector.ts @@ -7,6 +7,11 @@ export const selectRealtokensIsLoading = createSelector( (state) => state.isLoading, ) +export const selectRealtokensIsLoadingExtraData = createSelector( + (state: RootState) => state.realtokens, + (state) => state.isLoadingExtraData, +) + export const selectRealtokens = createSelector( (state: RootState) => state.realtokens, (realtokens) => realtokens.realtokens, diff --git a/src/store/features/realtokens/realtokensSlice.ts b/src/store/features/realtokens/realtokensSlice.ts index a5f43913..6f3de56a 100644 --- a/src/store/features/realtokens/realtokensSlice.ts +++ b/src/store/features/realtokens/realtokensSlice.ts @@ -1,19 +1,36 @@ +import { useSelector } from 'react-redux' + import { createAction, createReducer } from '@reduxjs/toolkit' +import { forEach } from 'lodash' + import { RealtokenRepository } from 'src/repositories' +import { selectUserDisplayAdditionalData } from 'src/store/features/settings/settingsSelector' import { AppDispatch, RootState } from 'src/store/store' -import { APIRealTokenProductType } from 'src/types/APIRealToken' +import { APIRealTokenPitsBI_ExtraData } from 'src/types/APIPitsBI' +import { APIRealToken, APIRealTokenProductType } from 'src/types/APIRealToken' import { RealToken } from 'src/types/RealToken' interface RealtokenInitialStateType { realtokens: RealToken[] isLoading: boolean + isLoadingExtraData: boolean + isExtraDataLoaded: boolean } const realtokenInitialState: RealtokenInitialStateType = { realtokens: [], isLoading: false, + isLoadingExtraData: false, + isExtraDataLoaded: false, } +// Filter function for product types +export const filterProductType = (item: APIRealToken) => + [ + APIRealTokenProductType.RealEstateRental, + APIRealTokenProductType.LoanIncome, + APIRealTokenProductType.Factoring, + ].includes(item.productType) // DISPATCH TYPE export const realtokensChangedDispatchType = 'realtokens/realtokensChanged' @@ -27,23 +44,64 @@ export const realtokensIsLoading = createAction( realtokensIsLoadingDispatchType, ) +// DISPATCH TYPE +export const realtokensExtraDataChangedDispatchType = + 'realtokens/realtokensExtraDataChanged' +export const realtokensExtraDataIsLoadingDispatchType = + 'realtokens/realtokensExtraDataIsLoading' +export const realtokensExtraDataLoadedDispatchType = + 'realtokens/realtokensExtraDataLoaded' + +// ACTIONS +export const realtokensExtraDataChanged = createAction( + realtokensExtraDataChangedDispatchType, +) +export const realtokensExtraDataIsLoading = createAction( + realtokensExtraDataIsLoadingDispatchType, +) +export const realtokensExtraDataLoaded = createAction( + realtokensExtraDataLoadedDispatchType, +) + // THUNKS export function fetchRealtokens() { return async (dispatch: AppDispatch, getState: () => RootState) => { - const isLoading = getState().realtokens.isLoading + const { isLoading } = getState().realtokens if (isLoading) return dispatch({ type: realtokensIsLoadingDispatchType, payload: true }) try { const data = await RealtokenRepository.getTokens() + // Check for exiting extraData : if PitsBI has been used as fallback datasource + // json will already contain 'actions' and 'historic' properties + const hasExtraData = data.some( + (token) => + (token as unknown as APIRealTokenPitsBI_ExtraData).actions || + (token as unknown as APIRealTokenPitsBI_ExtraData).historic, + ) + if (hasExtraData) { + // Reformat APIRealTokenPitsBI_ExtraData data + forEach(data, (token: RealToken) => { + const { actions, historic } = + token as unknown as APIRealTokenPitsBI_ExtraData + // Restructure token data: extract PitsBI "extra data" + token.extraData = { + pitsBI: { + actions: actions, + historic: historic, + }, + } + // Remove 'historic', 'actions' from token + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (token as any).historic + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (token as any).actions + }) + dispatch({ type: realtokensExtraDataLoadedDispatchType, payload: true }) + } + // Dispatch filtered realtokens dispatch({ type: realtokensChangedDispatchType, - payload: data.filter((item) => - [ - APIRealTokenProductType.RealEstateRental, - APIRealTokenProductType.LoanIncome, - APIRealTokenProductType.Factoring, - ].includes(item.productType), - ), + payload: data.filter(filterProductType), }) } catch (error) { console.log(error) @@ -53,6 +111,54 @@ export function fetchRealtokens() { } } +export function fetchRealtokensExtraData() { + return async (dispatch: AppDispatch, getState: () => RootState) => { + // Check if user has enabled additional + const userDisplayAdditionalData = + selectUserDisplayAdditionalData(getState()) + if (!userDisplayAdditionalData) return + const { isLoading, isLoadingExtraData, isExtraDataLoaded } = + getState().realtokens + // Wait for the initial realtokens to be loaded, skip if already loading or loaded + if (isLoading || isLoadingExtraData || isExtraDataLoaded) return + dispatch({ type: realtokensExtraDataIsLoadingDispatchType, payload: true }) + + try { + const pitsBiExtraData = + await RealtokenRepository.getTokensPitsBiExtraData() + // Build a map of tokensExtraData by uuid + const tokensExtraDataMap = new Map() + pitsBiExtraData.forEach((item) => { + tokensExtraDataMap.set(item.uuid, item) + }) + const existingTokens = await RealtokenRepository.getTokens() + + existingTokens.forEach((token) => { + const { actions, historic } = tokensExtraDataMap.get(token.uuid) || {} + // Update existing token data with PitsBI "extra data" + token.extraData = { + pitsBI: { + actions: actions, + historic: historic, + }, + } + }) + dispatch({ + type: realtokensExtraDataChangedDispatchType, + payload: existingTokens.filter(filterProductType), + }) + dispatch({ type: realtokensExtraDataLoadedDispatchType, payload: true }) + } catch (error) { + console.log(error) + } finally { + dispatch({ + type: realtokensExtraDataIsLoadingDispatchType, + payload: false, + }) + } + } +} + export const realtokensReducers = createReducer( realtokenInitialState, (builder) => { @@ -62,5 +168,14 @@ export const realtokensReducers = createReducer( builder.addCase(realtokensIsLoading, (state, action) => { state.isLoading = action.payload }) + builder.addCase(realtokensExtraDataChanged, (state, action) => { + state.realtokens = action.payload + }) + builder.addCase(realtokensExtraDataIsLoading, (state, action) => { + state.isLoadingExtraData = action.payload + }) + builder.addCase(realtokensExtraDataLoaded, (state, action) => { + state.isExtraDataLoaded = action.payload + }) }, ) diff --git a/src/store/features/settings/settingsSelector.ts b/src/store/features/settings/settingsSelector.ts index e01b65c1..f0f3a639 100644 --- a/src/store/features/settings/settingsSelector.ts +++ b/src/store/features/settings/settingsSelector.ts @@ -16,6 +16,7 @@ export const selectIsLoading = createSelector( (state, realtokens, wallets, currencies, transfers) => !state.isInitialized || realtokens.isLoading || + // realtokens.isLoadingExtraData ? wallets.isLoading || currencies.isLoading || transfers.isLoading, @@ -82,3 +83,8 @@ export const selectUserIncludesOtherAssets = createSelector( (state: RootState) => state.settings, (state) => state.includesOtherAssets, ) + +export const selectUserDisplayAdditionalData = createSelector( + (state: RootState) => state.settings, + (state) => state.displayAdditionalData, +) diff --git a/src/store/features/settings/settingsSlice.ts b/src/store/features/settings/settingsSlice.ts index 1d35913b..9e070e72 100644 --- a/src/store/features/settings/settingsSlice.ts +++ b/src/store/features/settings/settingsSlice.ts @@ -20,6 +20,8 @@ const USER_INCLUDES_ETH_LS_KEY = 'store:settings/includesEth' const USER_INCLUDES_LEVIN_SWAP_LS_KEY = 'store:settings/includesLevinSwap' const USER_INCLUDES_RMM_V2_LS_KEY = 'store:settings/includesRmmV2' const USER_INCLUDES_OTHER_ASSETS_LS_KEY = 'store:settings/includesOtherAssets' +const USER_DISPLAY_ADDITIONAL_DATA_LS_KEY = + 'store:settings/displayAdditionalData' export interface User { id: string @@ -39,6 +41,7 @@ interface SettingsInitialStateType { includesLevinSwap: boolean includesRmmV2: boolean includesOtherAssets: boolean + displayAdditionalData: boolean version?: string } @@ -54,6 +57,7 @@ const settingsInitialState: SettingsInitialStateType = { includesLevinSwap: false, includesRmmV2: false, includesOtherAssets: false, + displayAdditionalData: false, } // DISPATCH TYPE @@ -69,6 +73,8 @@ export const userIncludesRmmV2ChangedDispatchType = 'settings/includesRmmV2Changed' export const userIncludesOtherAssetsDispatchType = 'settings/includesOtherAssets' +export const userDisplayAdditionalDataDispatchType = + 'settings/displayAdditionalData' // ACTIONS export const initializeSettings = createAction(initializeSettingsDispatchType) export const userChanged = createAction(userChangedDispatchType) @@ -96,6 +102,9 @@ export const userIncludesRmmV2Changed = createAction( export const userIncludesOtherAssetsChanged = createAction( userIncludesOtherAssetsDispatchType, ) +export const userDisplayAdditionalDataChanged = createAction( + userDisplayAdditionalDataDispatchType, +) // THUNKS export function setUserAddress(address: string) { return async (dispatch: AppDispatch) => { @@ -227,6 +236,14 @@ export const settingsReducers = createReducer( action.payload.toString(), ) }) + .addCase(userDisplayAdditionalDataChanged, (state, action) => { + state.displayAdditionalData = action.payload + localStorage.setItem( + USER_DISPLAY_ADDITIONAL_DATA_LS_KEY, + action.payload.toString(), + ) + }) + .addCase(initializeSettings, (state) => { const user = localStorage.getItem(USER_LS_KEY) const userCurrency = localStorage.getItem(USER_CURRENCY_LS_KEY) @@ -243,6 +260,9 @@ export const settingsReducers = createReducer( const userIncludesOtherAssets = localStorage.getItem( USER_INCLUDES_OTHER_ASSETS_LS_KEY, ) + const userDisplayAdditionalData = localStorage.getItem( + USER_DISPLAY_ADDITIONAL_DATA_LS_KEY, + ) state.user = user ? JSON.parse(user) : undefined state.userCurrency = userCurrency @@ -262,6 +282,7 @@ export const settingsReducers = createReducer( state.includesLevinSwap = userIncludesLevinSwap === 'true' state.includesRmmV2 = userIncludesRmmV2 === 'true' state.includesOtherAssets = userIncludesOtherAssets === 'true' + state.displayAdditionalData = userDisplayAdditionalData === 'true' const { publicRuntimeConfig } = getConfig() as { publicRuntimeConfig?: { version: string } diff --git a/src/store/features/wallets/walletsSelector.ts b/src/store/features/wallets/walletsSelector.ts index 7d2c319c..738451a6 100644 --- a/src/store/features/wallets/walletsSelector.ts +++ b/src/store/features/wallets/walletsSelector.ts @@ -20,11 +20,10 @@ import { import { computeUCP } from 'src/utils/transfer/computeUCP' import { - CHAIN_ID_ETHEREUM, - CHAIN_ID_GNOSIS_XDAI, - CHAIN_NAME_ETHEREUM, - CHAIN_NAME_GNOSIS_XDAI, -} from '../../../utils/blockchain/consts/otherTokens' + CHAINS_NAMES, + CHAIN_ID__ETHEREUM, + CHAIN_ID__GNOSIS_XDAI, +} from '../../../utils/blockchain/consts/misc' import { selectRealtokens } from '../realtokens/realtokensSelector' import { selectUserRentCalculation } from '../settings/settingsSelector' @@ -88,6 +87,8 @@ export interface RWARealtoken extends OtherRealtoken { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface REGRealtoken extends OtherRealtoken {} // eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface REUSDGRealtoken extends OtherRealtoken {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface REGVotingPowertoken extends OtherRealtoken {} const DAYS_PER_YEAR = 365 @@ -275,9 +276,9 @@ export const selectRmmDetails = createSelector( export const getWalletChainName = (chainId: number) => { switch (chainId) { - case CHAIN_ID_ETHEREUM: - return CHAIN_NAME_ETHEREUM - case CHAIN_ID_GNOSIS_XDAI: - return CHAIN_NAME_GNOSIS_XDAI + case CHAIN_ID__ETHEREUM: + return CHAINS_NAMES[CHAIN_ID__ETHEREUM] + case CHAIN_ID__GNOSIS_XDAI: + return CHAINS_NAMES[CHAIN_ID__GNOSIS_XDAI] } } diff --git a/src/types/APIPitsBI.ts b/src/types/APIPitsBI.ts new file mode 100644 index 00000000..d5492b27 --- /dev/null +++ b/src/types/APIPitsBI.ts @@ -0,0 +1,57 @@ +import { APIRealToken } from './APIRealToken' + +export enum APIPitsBiEnv { + VERSION = 'PITSBI_API_VERSION', + BASE = 'PITSBI_API_BASE', + GET_ALLTOKENS = 'PITSBI_API_GET_ALLTOKENS', + GET_LASTUPDATE = 'PITSBI_API_GET_LASTUPDATE', +} + +export enum RealTokenToBeRepairedPriority { + None = 0, + High = 1, + Medium = 2, + Low = 3, +} + +export enum RealTokenToBeFixedStatus { + NoExhibit = 'No Exhibit', + UpgradedAndReady = 'Upgraded & Ready', + Scheduled = 'Scheduled', +} + +export interface RealTokenPitsBI_Actions { + exhibit_number: number // 0 = no exhibit, 1+ = exhibit number + volume: number // Volume 0 = no exhibit, 1+ = exhibit volume number + priority: RealTokenToBeRepairedPriority // Priority of the action + realt_status: RealTokenToBeFixedStatus // Status of the action +} + +export interface RealTokenPitsBI_Historic { + yields: { + timsync: string + yield: number + days_rented: number + }[] + prices: { + timsync: string + price: number + }[] + avg_yield: number + init_yield: number + init_price: number +} + +export interface APIRealTokenPitsBI_ExtraData { + // PITS BI is 100% compliant with the APIRealToken structure + uuid: string + // ... + // PITS BI specific fields + // Only declare fields useful for us + actions: RealTokenPitsBI_Actions + historic: RealTokenPitsBI_Historic +} + +export interface APIRealTokenPitsBI + extends APIRealToken, + APIRealTokenPitsBI_ExtraData {} diff --git a/src/types/APIRealToken.ts b/src/types/APIRealToken.ts index b75a866a..4823ae27 100644 --- a/src/types/APIRealToken.ts +++ b/src/types/APIRealToken.ts @@ -14,6 +14,15 @@ export enum APIRealTokenProductType { Factoring = 'factoring_profitshare', } +export enum APIRealTokenCommunityEnv { + API_KEY = 'REALTOKEN_COMMUNITY_API_KEY', + AUTH = 'X-AUTH-REALT-TOKEN', + API_BASE = 'REALTOKEN_COMMUNITY_API_BASE', + VERSION = 'REALTOKEN_COMMUNITY_API_VERSION', + GET_ALLTOKENS = 'REALTOKEN_COMMUNITY_API_GET_ALLTOKENS', + API_HISTORY = 'REALTOKEN_COMMUNITY_API_HISTORY_BASE', +} + export interface APIRealTokenDate { date: string timezone_type: number diff --git a/src/types/RealToken.ts b/src/types/RealToken.ts index 7f3a41ff..3179a125 100644 --- a/src/types/RealToken.ts +++ b/src/types/RealToken.ts @@ -1,3 +1,4 @@ +import { RealTokenPitsBI_Actions, RealTokenPitsBI_Historic } from './APIPitsBI' import { APIRealToken } from './APIRealToken' import { RealTokenHistoryItem } from './APIRealTokenHistory' @@ -49,4 +50,10 @@ export interface RealToken extends APIRealToken { isRmmAvailable: boolean rentStatus: RealTokenRentStatus history: RealTokenHistoryItem[] + extraData?: { + pitsBI?: { + actions?: RealTokenPitsBI_Actions + historic?: RealTokenPitsBI_Historic + } + } } diff --git a/src/utils/blockchain/abi/UniswapV3PoolABI.ts b/src/utils/blockchain/abi/UniswapV3PoolABI.ts new file mode 100644 index 00000000..3ab6f908 --- /dev/null +++ b/src/utils/blockchain/abi/UniswapV3PoolABI.ts @@ -0,0 +1,644 @@ +// https://api.etherscan.io/v2/api?chainid=100&module=contract&action=getabi&address=0x17183182C4a94A895A8b31133e3B24800aF3c5e4&apikey=yourApiKey +export const UniswapV3PoolABI = [ + // Add the ABI definitions for the Uniswap V3 Quoter V2 here + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Burn', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount0', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount1', + type: 'uint128', + }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount0', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount1', + type: 'uint128', + }, + ], + name: 'CollectProtocol', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'paid0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'paid1', + type: 'uint256', + }, + ], + name: 'Flash', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint16', + name: 'observationCardinalityNextOld', + type: 'uint16', + }, + { + indexed: false, + internalType: 'uint16', + name: 'observationCardinalityNextNew', + type: 'uint16', + }, + ], + name: 'IncreaseObservationCardinalityNext', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + { indexed: false, internalType: 'int24', name: 'tick', type: 'int24' }, + ], + name: 'Initialize', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Mint', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol0Old', + type: 'uint8', + }, + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol1Old', + type: 'uint8', + }, + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol0New', + type: 'uint8', + }, + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol1New', + type: 'uint8', + }, + ], + name: 'SetFeeProtocol', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'int256', + name: 'amount0', + type: 'int256', + }, + { + indexed: false, + internalType: 'int256', + name: 'amount1', + type: 'int256', + }, + { + indexed: false, + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { indexed: false, internalType: 'int24', name: 'tick', type: 'int24' }, + ], + name: 'Swap', + type: 'event', + }, + { + inputs: [ + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint128', name: 'amount', type: 'uint128' }, + ], + name: 'burn', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint128', name: 'amount0Requested', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1Requested', type: 'uint128' }, + ], + name: 'collect', + outputs: [ + { internalType: 'uint128', name: 'amount0', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1', type: 'uint128' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint128', name: 'amount0Requested', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1Requested', type: 'uint128' }, + ], + name: 'collectProtocol', + outputs: [ + { internalType: 'uint128', name: 'amount0', type: 'uint128' }, + { internalType: 'uint128', name: 'amount1', type: 'uint128' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'fee', + outputs: [{ internalType: 'uint24', name: '', type: 'uint24' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'feeGrowthGlobal0X128', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'feeGrowthGlobal1X128', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'flash', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint16', + name: 'observationCardinalityNext', + type: 'uint16', + }, + ], + name: 'increaseObservationCardinalityNext', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'liquidity', + outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'maxLiquidityPerTick', + outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + { internalType: 'uint128', name: 'amount', type: 'uint128' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'mint', + outputs: [ + { internalType: 'uint256', name: 'amount0', type: 'uint256' }, + { internalType: 'uint256', name: 'amount1', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'observations', + outputs: [ + { internalType: 'uint32', name: 'blockTimestamp', type: 'uint32' }, + { internalType: 'int56', name: 'tickCumulative', type: 'int56' }, + { + internalType: 'uint160', + name: 'secondsPerLiquidityCumulativeX128', + type: 'uint160', + }, + { internalType: 'bool', name: 'initialized', type: 'bool' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint32[]', name: 'secondsAgos', type: 'uint32[]' }, + ], + name: 'observe', + outputs: [ + { internalType: 'int56[]', name: 'tickCumulatives', type: 'int56[]' }, + { + internalType: 'uint160[]', + name: 'secondsPerLiquidityCumulativeX128s', + type: 'uint160[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + name: 'positions', + outputs: [ + { internalType: 'uint128', name: 'liquidity', type: 'uint128' }, + { + internalType: 'uint256', + name: 'feeGrowthInside0LastX128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside1LastX128', + type: 'uint256', + }, + { internalType: 'uint128', name: 'tokensOwed0', type: 'uint128' }, + { internalType: 'uint128', name: 'tokensOwed1', type: 'uint128' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'protocolFees', + outputs: [ + { internalType: 'uint128', name: 'token0', type: 'uint128' }, + { internalType: 'uint128', name: 'token1', type: 'uint128' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint8', name: 'feeProtocol0', type: 'uint8' }, + { internalType: 'uint8', name: 'feeProtocol1', type: 'uint8' }, + ], + name: 'setFeeProtocol', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'slot0', + outputs: [ + { internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, + { internalType: 'int24', name: 'tick', type: 'int24' }, + { internalType: 'uint16', name: 'observationIndex', type: 'uint16' }, + { + internalType: 'uint16', + name: 'observationCardinality', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'observationCardinalityNext', + type: 'uint16', + }, + { internalType: 'uint8', name: 'feeProtocol', type: 'uint8' }, + { internalType: 'bool', name: 'unlocked', type: 'bool' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'int24', name: 'tickLower', type: 'int24' }, + { internalType: 'int24', name: 'tickUpper', type: 'int24' }, + ], + name: 'snapshotCumulativesInside', + outputs: [ + { internalType: 'int56', name: 'tickCumulativeInside', type: 'int56' }, + { + internalType: 'uint160', + name: 'secondsPerLiquidityInsideX128', + type: 'uint160', + }, + { internalType: 'uint32', name: 'secondsInside', type: 'uint32' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'bool', name: 'zeroForOne', type: 'bool' }, + { internalType: 'int256', name: 'amountSpecified', type: 'int256' }, + { internalType: 'uint160', name: 'sqrtPriceLimitX96', type: 'uint160' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'swap', + outputs: [ + { internalType: 'int256', name: 'amount0', type: 'int256' }, + { internalType: 'int256', name: 'amount1', type: 'int256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'int16', name: '', type: 'int16' }], + name: 'tickBitmap', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'tickSpacing', + outputs: [{ internalType: 'int24', name: '', type: 'int24' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'int24', name: '', type: 'int24' }], + name: 'ticks', + outputs: [ + { internalType: 'uint128', name: 'liquidityGross', type: 'uint128' }, + { internalType: 'int128', name: 'liquidityNet', type: 'int128' }, + { + internalType: 'uint256', + name: 'feeGrowthOutside0X128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthOutside1X128', + type: 'uint256', + }, + { internalType: 'int56', name: 'tickCumulativeOutside', type: 'int56' }, + { + internalType: 'uint160', + name: 'secondsPerLiquidityOutsideX128', + type: 'uint160', + }, + { internalType: 'uint32', name: 'secondsOutside', type: 'uint32' }, + { internalType: 'bool', name: 'initialized', type: 'bool' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'token0', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'token1', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] diff --git a/src/utils/blockchain/abi/UniswapV3QuoterV2ABI.ts b/src/utils/blockchain/abi/UniswapV3QuoterV2ABI.ts new file mode 100644 index 00000000..b8974a75 --- /dev/null +++ b/src/utils/blockchain/abi/UniswapV3QuoterV2ABI.ts @@ -0,0 +1,152 @@ +// https://api.etherscan.io/v2/api?chainid=100&module=contract&action=getabi&address=0xb1E835Dc2785b52265711e17fCCb0fd018226a6e&apikey=yourApiKey +export const UniswapV3QuoterV2ABI = [ + /* + { + inputs: [ + { internalType: 'address', name: '_factory', type: 'address' }, + { internalType: 'address', name: '_WETH9', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'WETH9', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + ], + name: 'quoteExactInput', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { + internalType: 'uint160[]', + name: 'sqrtPriceX96AfterList', + type: 'uint160[]', + }, + { + internalType: 'uint32[]', + name: 'initializedTicksCrossedList', + type: 'uint32[]', + }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +*/ + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + ], + internalType: 'struct IQuoterV2.QuoteExactInputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactInputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + { internalType: 'uint160', name: 'sqrtPriceX96After', type: 'uint160' }, + { + internalType: 'uint32', + name: 'initializedTicksCrossed', + type: 'uint32', + }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + /* + { + inputs: [ + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'uint256', name: 'amountOut', type: 'uint256' }, + ], + name: 'quoteExactOutput', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { + internalType: 'uint160[]', + name: 'sqrtPriceX96AfterList', + type: 'uint160[]', + }, + { + internalType: 'uint32[]', + name: 'initializedTicksCrossedList', + type: 'uint32[]', + }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'uint24', name: 'fee', type: 'uint24' }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + ], + internalType: 'struct IQuoterV2.QuoteExactOutputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'quoteExactOutputSingle', + outputs: [ + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint160', name: 'sqrtPriceX96After', type: 'uint160' }, + { + internalType: 'uint32', + name: 'initializedTicksCrossed', + type: 'uint32', + }, + { internalType: 'uint256', name: 'gasEstimate', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'int256', name: 'amount0Delta', type: 'int256' }, + { internalType: 'int256', name: 'amount1Delta', type: 'int256' }, + { internalType: 'bytes', name: 'path', type: 'bytes' }, + ], + name: 'uniswapV3SwapCallback', + outputs: [], + stateMutability: 'view', + type: 'function', + }, +*/ +] diff --git a/src/utils/blockchain/consts/misc.ts b/src/utils/blockchain/consts/misc.ts new file mode 100644 index 00000000..075ef896 --- /dev/null +++ b/src/utils/blockchain/consts/misc.ts @@ -0,0 +1,115 @@ +const CHAIN_ID_GNOSIS_XDAI = 100 +const CHAIN_ID_ETHEREUM = 1 +// const CHAIN_ID_POLYGON = 137 + +const CHAIN_NAME__GNOSIS_XDAI = 'gnosis' +const CHAIN_NAME__ETHEREUM = 'ethereum' +// const CHAIN_NAME__POLYGON = 'polygon' + +// Array of supported chain IDs +const SUPPORTED_CHAINS_IDS = [CHAIN_ID_GNOSIS_XDAI, CHAIN_ID_ETHEREUM] + +// Mapping chain IDs to their respective names +const CHAINS_NAMES: { [key: number]: string } = { + [CHAIN_ID_GNOSIS_XDAI]: CHAIN_NAME__GNOSIS_XDAI, + [CHAIN_ID_ETHEREUM]: CHAIN_NAME__ETHEREUM, +} + +type ChainId = number +type Address = string +type Decimals = number + +const CHAIN_ID__GNOSIS_XDAI: ChainId = 100 +const CHAIN_ID__ETHEREUM: ChainId = 1 +// const CHAIN_ID__POLYGON: ChainId = 137 + +// export declare enum ChainIds { +// GNOSIS_XDAI, +// ETHEREUM, +// // POLYGON, // Todo +// } + +// Reg Vault only deployed on Gnosis/xDai +const REG_Vault_Gnosis_ContractAddress = + '0xe1877d33471e37fe0f62d20e60c469eff83fb4a0' +// Reg Voting Power only deployed on Gnosis/xDai + +const HoneySwapFactory_Address = '0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7' + +// SushiSwap Quoter deployed on Gnosis/xDai +const SushiSwap_Factory__GnosisXdai_Address = + '0xf78031CBCA409F2FB6876BDFDBc1b2df24cF9bEf' +const SushiSwap_Router__GnosisXdai_Address = + '0x4F54dd2F4f30347d841b7783aD08c050d8410a9d' +const SushiSwap_Quoter__GnosisXdai_Address = + '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e' + +// interface UniV2DeployedAddresses { +// factory: Address +// router: Address +// } + +// interface UniV3DeployedAddresses extends UniV2DeployedAddresses { +// quoter: Address +// } + +// const SUSHISWAP_DEPLOYMENTS: { [key: ChainId]: UniV3DeployedAddresses } = { +// [CHAIN_ID__GNOSIS_XDAI]: { +// factory: SushiSwap_Factory__GnosisXdai_Address, +// router: SushiSwap_Router__GnosisXdai_Address, +// quoter: SushiSwap_Quoter__GnosisXdai_Address, +// }, +// } + +interface UniV2DeployedAddresses { + factory: Address + router: Address +} + +interface UniV3DeployedAddresses extends UniV2DeployedAddresses { + quoter: Address +} + +// Define type for deployments +// export +type UniV3Deployment = { [key: ChainId]: UniV3DeployedAddresses } + +const SUSHISWAP_DEPLOYMENTS: UniV3Deployment = { + [CHAIN_ID__GNOSIS_XDAI]: { + factory: SushiSwap_Factory__GnosisXdai_Address, + router: SushiSwap_Router__GnosisXdai_Address, + quoter: SushiSwap_Quoter__GnosisXdai_Address, + }, +} + +// console.dir(SUSHISWAP_DEPLOYMENTS) +// console.dir(ChainIds) + +// Export type for SUSHISWAP_DEPLOYMENTS +// export type SushiswapDeployments = typeof SUSHISWAP_DEPLOYMENTS + +export { + CHAIN_ID__GNOSIS_XDAI, + CHAIN_ID__ETHEREUM, + // CHAIN_ID__POLYGON, + SUPPORTED_CHAINS_IDS, + CHAINS_NAMES, + REG_Vault_Gnosis_ContractAddress, + HoneySwapFactory_Address, + SUSHISWAP_DEPLOYMENTS, +} +// export declare const SUPPORTED_CHAINS: readonly [ +// // ChainId.GNOSIS_XDAI, +// // ChainIds.ETHEREUM, +// CHAIN_ID__GNOSIS_XDAI, +// CHAIN_ID__ETHEREUM, +// ] + +export type { + Address, + ChainId, + Decimals, + UniV2DeployedAddresses, + UniV3DeployedAddresses, + UniV3Deployment, +} diff --git a/src/utils/blockchain/consts/otherTokens.ts b/src/utils/blockchain/consts/otherTokens.ts index b4b278b3..766b8616 100644 --- a/src/utils/blockchain/consts/otherTokens.ts +++ b/src/utils/blockchain/consts/otherTokens.ts @@ -1,18 +1,20 @@ -// Each asset must have a different ID (used as KEY assets view) -const RWA_asset_ID = 0 -const REG_asset_ID = 1 -const REGVotingPower_asset_ID = 2 - // Gnosis/xDai, Ethereum const RWA_ContractAddress = '0x0675e8F4A52eA6c845CB6427Af03616a2af42170' // Gnosis/xDai, Ethereum const REG_ContractAddress = '0x0AA1e96D2a46Ec6beB2923dE1E61Addf5F5f1dce' -// Reg Vault only deployed on Gnosis/xDai -const REG_Vault_Gnosis_ContractAddress = - '0xe1877d33471e37fe0f62d20e60c469eff83fb4a0' // Reg Voting Power only deployed on Gnosis/xDai const RegVotingPower_Gnosis_ContractAddress = '0x6382856a731Af535CA6aea8D364FCE67457da438' +// Gnosis/xDai, Ethereum +const REUSD_ContractAddress = '0x3390742ac0dce14ea6fcbd5ae02e2303c5d62ad9' + +const REUSD_asset_ID = REUSD_ContractAddress + +// Asset IDs for RWA, REG, and REG Voting Power +// Each asset must have a different ID (used as KEY assets view) +const RWA_asset_ID = RWA_ContractAddress +const REG_asset_ID = REG_ContractAddress +const REGVotingPower_asset_ID = RegVotingPower_Gnosis_ContractAddress // Gnosis/xDai tokens for prices calculation const WXDAI_ContractAddress = '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d' @@ -20,6 +22,7 @@ const USDConXdai_ContractAddress = '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83' const DEFAULT_RWA_PRICE = 50 // USD const DEFAULT_REG_PRICE = 0 // USD +const DEFAULT_REUSD_PRICE = 1 // USD const DEFAULT_REGVotingPower_PRICE = 0 // USD const DEFAULT_XDAI_USD_RATE = 1 @@ -28,30 +31,14 @@ const DEFAULT_USDC_USD_RATE = 1 const RWAtokenDecimals = 9 const REGtokenDecimals = 18 const REGVotingPowertokenDecimals = 18 +const REGUSDtokenDecimals = 18 const USDCtokenDecimals = 6 const WXDAItokenDecimals = 18 -const HoneySwapFactory_Address = '0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7' - -const CHAIN_ID_GNOSIS_XDAI = 100 -const CHAIN_ID_ETHEREUM = 1 -const CHAIN_ID_POLYGON = 137 - -const CHAIN_NAME_GNOSIS_XDAI = 'gnosis' -const CHAIN_NAME_ETHEREUM = 'ethereum' -const CHAIN_NAME_POLYGON = 'polygon' - -// Mapping chain IDs to their respective names -const CHAINS_NAMES: { [key: number]: string } = { - [CHAIN_ID_GNOSIS_XDAI]: CHAIN_NAME_GNOSIS_XDAI, - [CHAIN_ID_ETHEREUM]: CHAIN_NAME_ETHEREUM, - [CHAIN_ID_POLYGON]: CHAIN_NAME_POLYGON, -} - export { RWA_ContractAddress, REG_ContractAddress, - REG_Vault_Gnosis_ContractAddress, + REUSD_ContractAddress, RegVotingPower_Gnosis_ContractAddress, WXDAI_ContractAddress, USDConXdai_ContractAddress, @@ -59,21 +46,23 @@ export { RWAtokenDecimals, USDCtokenDecimals, REGtokenDecimals, - HoneySwapFactory_Address, + REGUSDtokenDecimals, REGVotingPowertokenDecimals, DEFAULT_RWA_PRICE, DEFAULT_REG_PRICE, + DEFAULT_REUSD_PRICE, DEFAULT_REGVotingPower_PRICE, DEFAULT_XDAI_USD_RATE, DEFAULT_USDC_USD_RATE, RWA_asset_ID, REG_asset_ID, + REUSD_asset_ID, REGVotingPower_asset_ID, - CHAIN_ID_GNOSIS_XDAI, - CHAIN_ID_ETHEREUM, - CHAIN_ID_POLYGON, - CHAIN_NAME_GNOSIS_XDAI, - CHAIN_NAME_ETHEREUM, - CHAIN_NAME_POLYGON, - CHAINS_NAMES, + // CHAIN_ID_GNOSIS_XDAI, + // CHAIN_ID_ETHEREUM, + // CHAIN_ID_POLYGON, + // CHAIN_NAME_GNOSIS_XDAI, + // CHAIN_NAME_ETHEREUM, + // CHAIN_NAME_POLYGON, + // CHAINS_NAMES, } diff --git a/src/utils/blockchain/poolPrice.ts b/src/utils/blockchain/poolPrice.ts index 52fa70f1..9f753e40 100644 --- a/src/utils/blockchain/poolPrice.ts +++ b/src/utils/blockchain/poolPrice.ts @@ -1,7 +1,43 @@ +import { Token } from '@uniswap/sdk-core' +import { + FeeAmount, + FeeAmount as Univ3FeeAmount, + computePoolAddress, +} from '@uniswap/v3-sdk' + import { Contract, JsonRpcProvider, ethers } from 'ethers' import { LevinswapABI as UniswapV2PairABI } from './abi/LevinswapABI' import { UniswapV2FactoryABI } from './abi/UniswapV2FactoryABI' +import { UniswapV3PoolABI as UniV3PoolABI } from './abi/UniswapV3PoolABI' +import { UniswapV3QuoterV2ABI as Univ3QuoterABI } from './abi/UniswapV3QuoterV2ABI' +import { + Address, + ChainId, + Decimals, + SUPPORTED_CHAINS_IDS, + UniV3Deployment, +} from './consts/misc' + +// const FeeAmounts = { +// LOWEST: Univ3FeeAmount.LOWEST, +// LOW: Univ3FeeAmount.LOW, +// MEDIUM: Univ3FeeAmount.MEDIUM, +// HIGH: Univ3FeeAmount.HIGH, +// } + +const AssetPrice = { + TokenA: 0, + TokenB: 1, +} + +const FeeAmounts = { + // 1e6 + LOWEST: Univ3FeeAmount.LOWEST, + LOW: Univ3FeeAmount.LOW, + MEDIUM: Univ3FeeAmount.MEDIUM, + HIGH: Univ3FeeAmount.HIGH, +} const isAddressLowererThan = (address0: string, address1: string): boolean => { if (!address0 || !address1) { @@ -152,4 +188,123 @@ const averageValues = ( return average } -export { getUniV2AssetPrice, averageValues } +const getUniV3PoolAddress = ( + tokenA_Address: Address, + tokenA_Decimals: Decimals, + tokenB_Address: Address, + tokenB_Decimals: Decimals, + fee: number, + poolFactoryAddress: Address, + chainId: ChainId, +): string => { + // Validate inputs + // chainId + if (!SUPPORTED_CHAINS_IDS.includes(chainId)) { + throw new Error(`Unsupported chainId: ${chainId}`) + } + // token addresses + if (!tokenA_Address || !tokenB_Address || tokenA_Address === tokenB_Address) { + throw new Error('Invalid token addresses') + } + // token decimals + if (!tokenA_Decimals || !tokenB_Decimals) { + throw new Error('Invalid token decimals') + } + // fee + if (!Object.values(FeeAmounts).includes(fee)) { + throw new Error( + `Invalid fee: ${fee} - must be one of ${Object.values(FeeAmounts)}`, + ) + } + // pool factory address + if (!poolFactoryAddress) { + throw new Error('Invalid fee or pool factory address') + } + const tokenA = new Token(chainId, tokenA_Address, tokenA_Decimals) + const tokenB = new Token(chainId, tokenB_Address, tokenB_Decimals) + + return computePoolAddress({ + factoryAddress: poolFactoryAddress, + tokenA: tokenA, + tokenB: tokenB, + fee, + }) +} + +const getUniV3AssetPrice = async ( + uniV3Deployment: UniV3Deployment, + token0Address: Address, + token1Address: Address, + token0Decimals: Decimals, + token1Decimals: Decimals, + provider: JsonRpcProvider, + chainId: ChainId, + fee = FeeAmount.MEDIUM, // Default to 3000 bips (0.3%) + whichAssetPrice = AssetPrice.TokenA, // : 0 for token0 price, 1 for token1 price + amountIn = 10, // Amount of token to quote without decimals +): Promise => { + let price: number | null = null + try { + // Validate inputs + if (!uniV3Deployment[chainId]) { + throw new Error(`No deployment found for chainId ${chainId}`) + } + const poolAddress = getUniV3PoolAddress( + token0Address, + token0Decimals, + token1Address, + token1Decimals, + fee, + uniV3Deployment[chainId].factory, + chainId, + ) + // Check pool address result + if (!poolAddress) { + throw new Error('Failed to get pool address') + } + // Quoter + const quoterContract = new Contract( + uniV3Deployment[chainId].quoter, + Univ3QuoterABI, + provider, + ) + // Amounts + const amountInBI = + BigInt(amountIn) * + BigInt( + 10 ** + (whichAssetPrice === AssetPrice.TokenA + ? token0Decimals + : token1Decimals), + ) + + const quotedAmountOut = + await quoterContract.quoteExactInputSingle.staticCall({ + tokenIn: + whichAssetPrice === AssetPrice.TokenA ? token0Address : token1Address, + tokenOut: + whichAssetPrice === AssetPrice.TokenA ? token1Address : token0Address, + amountIn: amountInBI, + fee: fee, + sqrtPriceLimitX96: 0n, // No price limit + }) + + price = + Number(quotedAmountOut[0]) / + amountIn / + 10 ** + (whichAssetPrice === AssetPrice.TokenA + ? token1Decimals + : token0Decimals) + } catch (error) { + console.warn( + `Failed to get asset price for factoryAddress ${uniV3Deployment[chainId].factory} token0Address ${token0Address} token1Address ${token1Address} fee ${fee} chainId ${chainId} amountIn ${amountIn} whichAssetPrice ${whichAssetPrice}`, + error, + provider, + ) + } + return price +} + +export { AssetPrice, FeeAmounts } +export { getUniV2AssetPrice, averageValues, getUniV3AssetPrice } diff --git a/src/utils/general.ts b/src/utils/general.ts index 2053daf1..4c218236 100644 --- a/src/utils/general.ts +++ b/src/utils/general.ts @@ -1,3 +1,65 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) -export { wait } +const FETCH_RETRIES_DEFAULT_COUNT = 2 +const FETCH_RETRIES_DEFAULT_DELAY = 500 + +/** + * fetchWithRetry + * @param url + * @param options + * @param retries + * @param delay : number of milliseconds to wait before retrying; zero = no delay, negative = random delay, positive = fixed delay + */ +const fetchWithRetry = async ( + url: string, + options = {}, + retries = FETCH_RETRIES_DEFAULT_COUNT, + delay = FETCH_RETRIES_DEFAULT_DELAY, +): Promise => { + if (retries <= 0) { + throw new Error('Max retries reached') + } + try { + const response = await fetch(url, options) + if (response.ok) { + return response + } else { + // Handle specific error cases if needed + // if (response.status === 401) throw ... + // if (response.status === 503) throw ... + throw response + } + } catch (error) { + const errorMsg = + error instanceof Error + ? error.message + : error instanceof Response + ? `error.statusText: ${error.statusText}, error.status: ${error.status}` + : String(error) + console.error( + `Fetch error: ${errorMsg} for URL: ${url}`, + `Retries left: ${retries - 1}, Delay: ${delay}ms`, + ) + if (retries - 1 < 0) { + throw error + } + if (delay > 0) { + await wait(delay) + return fetchWithRetry(url, options, retries - 1, delay) + } else if (delay === 0) { + return fetchWithRetry(url, options, retries - 1, delay) + } else { + // Random delay for retry + const randomDelay = Math.floor(Math.random() * delay) + await wait(randomDelay) + return fetchWithRetry(url, options, retries - 1, delay) + } + } +} + +export { + wait, + fetchWithRetry, + FETCH_RETRIES_DEFAULT_COUNT, + FETCH_RETRIES_DEFAULT_DELAY, +} diff --git a/yarn.lock b/yarn.lock index b2509a32..c644f0f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -516,7 +516,7 @@ ethereum-cryptography "^2.0.0" micro-ftch "^0.3.1" -"@ethersproject/abi@5.8.0", "@ethersproject/abi@^5.8.0": +"@ethersproject/abi@5.8.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.8.0.tgz#e79bb51940ac35fe6f3262d7fe2cdb25ad5f07d9" integrity sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q== @@ -555,7 +555,7 @@ "@ethersproject/logger" "^5.8.0" "@ethersproject/properties" "^5.8.0" -"@ethersproject/address@5.8.0", "@ethersproject/address@^5", "@ethersproject/address@^5.8.0": +"@ethersproject/address@5.8.0", "@ethersproject/address@^5", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.8.0.tgz#3007a2c352eee566ad745dca1dbbebdb50a6a983" integrity sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA== @@ -590,14 +590,14 @@ "@ethersproject/logger" "^5.8.0" bn.js "^5.2.1" -"@ethersproject/bytes@5.8.0", "@ethersproject/bytes@^5.8.0": +"@ethersproject/bytes@5.8.0", "@ethersproject/bytes@^5.7.0", "@ethersproject/bytes@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.8.0.tgz#9074820e1cac7507a34372cadeb035461463be34" integrity sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A== dependencies: "@ethersproject/logger" "^5.8.0" -"@ethersproject/constants@5.8.0", "@ethersproject/constants@^5.8.0": +"@ethersproject/constants@5.8.0", "@ethersproject/constants@^5.7.0", "@ethersproject/constants@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.8.0.tgz#12f31c2f4317b113a4c19de94e50933648c90704" integrity sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg== @@ -672,6 +672,14 @@ aes-js "3.0.0" scrypt-js "3.0.1" +"@ethersproject/keccak256@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + "@ethersproject/keccak256@5.8.0", "@ethersproject/keccak256@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.8.0.tgz#d2123a379567faf2d75d2aaea074ffd4df349e6a" @@ -680,7 +688,7 @@ "@ethersproject/bytes" "^5.8.0" js-sha3 "0.8.0" -"@ethersproject/logger@5.8.0", "@ethersproject/logger@^5.8.0": +"@ethersproject/logger@5.8.0", "@ethersproject/logger@^5.7.0", "@ethersproject/logger@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.8.0.tgz#f0232968a4f87d29623a0481690a2732662713d6" integrity sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA== @@ -770,7 +778,7 @@ elliptic "6.6.1" hash.js "1.1.7" -"@ethersproject/solidity@5.8.0": +"@ethersproject/solidity@5.8.0", "@ethersproject/solidity@^5.0.9": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.8.0.tgz#429bb9fcf5521307a9448d7358c26b93695379b9" integrity sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA== @@ -782,6 +790,15 @@ "@ethersproject/sha2" "^5.8.0" "@ethersproject/strings" "^5.8.0" +"@ethersproject/strings@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/strings@5.8.0", "@ethersproject/strings@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.8.0.tgz#ad79fafbf0bd272d9765603215ac74fd7953908f" @@ -1334,6 +1351,16 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@3.4.1-solc-0.7-2": + version "3.4.1-solc-0.7-2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" + integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q== + +"@openzeppelin/contracts@3.4.2-solc-0.7": + version "3.4.2-solc-0.7" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" + integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA== + "@parcel/watcher-android-arm64@2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" @@ -1580,7 +1607,19 @@ resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== -"@tabler/icons@^1.119.0", "@tabler/icons@^1.74.0": +"@tabler/icons-react@^3.34.1": + version "3.34.1" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.34.1.tgz#852b8efcab5382e44cc0f09b3de7d25ef3cba6f9" + integrity sha512-Ld6g0NqOO05kyyHsfU8h787PdHBm7cFmOycQSIrGp45XcXYDuOK2Bs0VC4T2FWSKZ6bx5g04imfzazf/nqtk1A== + dependencies: + "@tabler/icons" "3.34.1" + +"@tabler/icons@3.34.1": + version "3.34.1" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.34.1.tgz#a04a8cf79a5ebdcc44796dfa3de51d04811d2f9f" + integrity sha512-9gTnUvd7Fd/DmQgr3MKY+oJLa1RfNsQo8c/ir3TJAWghOuZXodbtbVp0QBY2DxWuuvrSZFys0HEbv1CoiI5y6A== + +"@tabler/icons@^1.74.0": version "1.119.0" resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-1.119.0.tgz#8c590bc5a563c8673a78ccd451bedabd584b376e" integrity sha512-Fk3Qq4w2SXcTjc/n1cuL5bccPkylrOMo7cYpQIf/yw6zP76LQV9dtLcHQUjFiUnaYuswR645CnURIhlafyAh9g== @@ -1817,6 +1856,87 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== +"@uniswap/lib@^4.0.1-alpha": + version "4.0.1-alpha" + resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" + integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== + +"@uniswap/sdk-core@^7.7.1", "@uniswap/sdk-core@^7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-7.7.2.tgz#dc3a9b63b343640754860dce05d06972815eaee6" + integrity sha512-0KqXw+y0opBo6eoPAEoLHEkNpOu0NG9gEk7GAYIGok+SHX89WlykWsRYeJKTg9tOwhLpcG9oHg8xZgQ390iOrA== + dependencies: + "@ethersproject/address" "^5.0.2" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/strings" "5.7.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + jsbi "^3.1.4" + tiny-invariant "^1.1.0" + toformat "^2.0.0" + +"@uniswap/swap-router-contracts@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.3.1.tgz#0ebbb93eb578625618ed9489872de381f9c66fb4" + integrity sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + "@uniswap/v3-periphery" "^1.4.4" + dotenv "^14.2.0" + hardhat-watcher "^2.1.1" + +"@uniswap/v2-core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + +"@uniswap/v3-core@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" + integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA== + +"@uniswap/v3-core@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" + integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== + +"@uniswap/v3-periphery@^1.0.1", "@uniswap/v3-periphery@^1.1.1", "@uniswap/v3-periphery@^1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz#d2756c23b69718173c5874f37fd4ad57d2f021b7" + integrity sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + base64-sol "1.0.1" + +"@uniswap/v3-sdk@^3.25.2": + version "3.25.2" + resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.25.2.tgz#cb6ee174b58d86a3b3b18b3ba72f662e58c415da" + integrity sha512-0oiyJNGjUVbc958uZmAr+m4XBCjV7PfMs/OUeBv+XDl33MEYF/eH86oBhvqGDM8S/cYaK55tCXzoWkmRUByrHg== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^7.7.1" + "@uniswap/swap-router-contracts" "^1.3.0" + "@uniswap/v3-periphery" "^1.1.1" + "@uniswap/v3-staker" "1.0.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + +"@uniswap/v3-staker@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-staker/-/v3-staker-1.0.0.tgz#9a6915ec980852479dfc903f50baf822ff8fa66e" + integrity sha512-JV0Qc46Px5alvg6YWd+UIaGH9lDuYG/Js7ngxPit1SPaIP30AlVer1UYB7BRYeUVVxE+byUyIeN5jeQ7LLDjIw== + dependencies: + "@openzeppelin/contracts" "3.4.1-solc-0.7-2" + "@uniswap/v3-core" "1.0.0" + "@uniswap/v3-periphery" "^1.0.1" + "@walletconnect/core@2.19.1": version "2.19.1" resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.19.1.tgz#71738940341b438326b65b3f49226decbe070bae" @@ -2230,7 +2350,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -anymatch@^3.1.3: +anymatch@^3.1.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -2405,6 +2525,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64-sol@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6" + integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg== + bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -2415,6 +2540,16 @@ big-integer@^1.6.16: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + bn.js@^4.11.9: version "4.12.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.1.tgz#215741fe3c9dba2d7e12c001d0cfdbae43975ba7" @@ -2433,7 +2568,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.3: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -2543,6 +2678,21 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chokidar@^4.0.0, chokidar@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" @@ -2749,6 +2899,11 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decimal.js-light@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decode-uri-component@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -2866,6 +3021,11 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dotenv@^14.2.0: + version "14.3.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369" + integrity sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ== + dotenv@^16.0.3: version "16.4.7" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" @@ -3401,10 +3561,10 @@ ethers@^5.6.8: "@ethersproject/web" "5.8.0" "@ethersproject/wordlists" "5.8.0" -ethers@^6.14.1: - version "6.14.1" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.14.1.tgz#96b5e967d9c3c66c6b64304d8e7669a761d6fca3" - integrity sha512-JnFiPFi3sK2Z6y7jZ3qrafDMwiXmU+6cNZ0M+kPq+mTy9skqEzwqAdFW3nb/em2xjlIVXX6Lz8ID6i3LmS4+fQ== +ethers@^6.15.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.15.0.tgz#2980f2a3baf0509749b7e21f8692fa8a8349c0e3" + integrity sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ== dependencies: "@adraffy/ens-normalize" "1.10.1" "@noble/curves" "1.2.0" @@ -3538,6 +3698,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -3608,7 +3773,7 @@ get-symbol-description@^1.1.0: es-errors "^1.3.0" get-intrinsic "^1.2.6" -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3727,6 +3892,13 @@ h3@^1.15.0: ufo "^1.5.4" uncrypto "^0.1.3" +hardhat-watcher@^2.1.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.5.0.tgz#3ee76c3cb5b99f2875b78d176207745aa484ed4a" + integrity sha512-Su2qcSMIo2YO2PrmJ0/tdkf+6pSt8zf9+4URR5edMVti6+ShI8T3xhPrwugdyTOFuyj8lKHrcTZNKUFYowYiyA== + dependencies: + chokidar "^3.5.3" + has-bigints@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" @@ -3942,6 +4114,13 @@ is-bigint@^1.1.0: dependencies: has-bigints "^1.0.2" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" @@ -4006,7 +4185,7 @@ is-generator-function@^1.0.10: has-tostringtag "^1.0.2" safe-regex-test "^1.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -4176,6 +4355,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbi@^3.1.4: + version "3.2.5" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6" + integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -4537,7 +4721,7 @@ node-releases@^2.0.19: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -4787,7 +4971,7 @@ picocolors@^1.0.0, picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -5108,6 +5292,13 @@ readdirp@^4.0.1: resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + real-require@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" @@ -5632,6 +5823,16 @@ thread-stream@^0.15.1: dependencies: real-require "^0.1.0" +tiny-invariant@^1.1.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -5644,6 +5845,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8" + integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ== + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"