diff --git a/modules/address/components/Address.tsx b/modules/address/components/Address.tsx index 5d79be46f..dd0b104cb 100644 --- a/modules/address/components/Address.tsx +++ b/modules/address/components/Address.tsx @@ -11,6 +11,8 @@ import { formatAddress } from 'lib/utils'; import { useWeb3 } from 'modules/web3/hooks/useWeb3'; import { getENS } from 'modules/web3/helpers/ens'; import React, { useEffect, useState } from 'react'; +import { getDefaultProvider } from 'modules/web3/helpers/getDefaultProvider'; +import { SupportedNetworks } from 'modules/web3/constants/networks'; export const Address = React.memo(function Address({ address, @@ -19,14 +21,15 @@ export const Address = React.memo(function Address({ address: string; maxLength?: number; }): React.ReactElement { - const { provider } = useWeb3(); const [addressFormated, setAddressFormatted] = useState(formatAddress(address || '').toLowerCase()); async function fetchENSName() { - if (!address || !provider) { + if (!address) { return; } + const provider = getDefaultProvider(SupportedNetworks.MAINNET); + const ens = await getENS({ address, provider }); ens ? setAddressFormatted(ens) : setAddressFormatted(formatAddress(address).toLowerCase()); diff --git a/modules/app/components/DateWithHover.tsx b/modules/app/components/DateWithHover.tsx index 6d39038cd..068d3b5d9 100644 --- a/modules/app/components/DateWithHover.tsx +++ b/modules/app/components/DateWithHover.tsx @@ -16,12 +16,15 @@ export function DateWithHover({ timeago, label }: { - date: Date | string | number; + date: Date | string | number | null; timeago?: boolean; label?: string; }): React.ReactElement { + if (!date) { + return N/A; + } return ( - + {timeago ? `${formatTimeAgo(date ?? '')}` : `${formatDateWithTime(date ?? '')}`} ); diff --git a/modules/contracts/eth-sdk.config.ts b/modules/contracts/eth-sdk.config.ts index d744de5c7..8c980aa97 100644 --- a/modules/contracts/eth-sdk.config.ts +++ b/modules/contracts/eth-sdk.config.ts @@ -53,7 +53,7 @@ const config: EthSdkConfig = { pollingOld: '0xF9be8F0945acDdeeDaA64DFCA5Fe9629D0CF8E5D', pot: '0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7', vat: '0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B', - voteDelegateFactory: '0xD897F108670903D1d6070fcf818f9db3615AF272', + voteDelegateFactory: '0x4E76FbE44fa5Dae076a7f4f676250e7941421fbA', voteProxyFactory: '0x6FCD258af181B3221073A96dD90D1f7AE7eEc408', voteProxyFactoryOld: '0xa63E145309cadaa6A903a19993868Ef7E85058BE', vow: '0xA950524441892A31ebddF91d3cEEFa04Bf454466' diff --git a/modules/delegates/api/fetchDelegatedTo.ts b/modules/delegates/api/fetchDelegatedTo.ts index 88931a372..bd6682324 100644 --- a/modules/delegates/api/fetchDelegatedTo.ts +++ b/modules/delegates/api/fetchDelegatedTo.ts @@ -9,34 +9,45 @@ SPDX-License-Identifier: AGPL-3.0-or-later import { add } from 'date-fns'; import { utils } from 'ethers'; import logger from 'lib/logger'; -import { Query } from 'modules/gql/generated/graphql'; import { gqlRequest } from 'modules/gql/gqlRequest'; import { allDelegates } from 'modules/gql/queries/allDelegates'; -import { mkrDelegatedToV2 } from 'modules/gql/queries/mkrDelegatedTo'; +import { delegatorHistory } from 'modules/gql/queries/subgraph/delegatorHistory'; import { SupportedNetworks } from 'modules/web3/constants/networks'; import { networkNameToChainId } from 'modules/web3/helpers/chain'; import { isAboutToExpireCheck, isExpiredCheck } from 'modules/migration/helpers/expirationChecks'; -import { DelegationHistoryWithExpirationDate, MKRDelegatedToDAIResponse } from '../types'; +import { DelegationHistoryWithExpirationDate, MKRDelegatedToResponse } from '../types'; import { getNewOwnerFromPrevious } from 'modules/migration/delegateAddressLinks'; +import { Query, AllDelegatesRecord } from 'modules/gql/generated/graphql'; export async function fetchDelegatedTo( address: string, network: SupportedNetworks ): Promise { try { - // Returns the records with the aggregated delegated data - const data = await gqlRequest({ - chainId: networkNameToChainId(network), - query: mkrDelegatedToV2, - variables: { argAddress: address.toLowerCase() } - }); // We fetch the delegates information from the DB to extract the expiry date of each delegate // TODO: This information could be aggregated in the "mkrDelegatedTo" query in gov-polling-db, and returned there, as an improvement. const chainId = networkNameToChainId(network); const delegatesData = await gqlRequest({ chainId, query: allDelegates }); const delegates = delegatesData.allDelegates.nodes; - const res: MKRDelegatedToDAIResponse[] = data.mkrDelegatedToV2.nodes; + // Returns the records with the aggregated delegated data + const data = await gqlRequest({ + chainId: networkNameToChainId(network), + useSubgraph: true, + query: delegatorHistory, + variables: { address: address.toLowerCase() } + }); + const res: MKRDelegatedToResponse[] = data.delegationHistories.map(x => { + return { + delegateContractAddress: x.delegate.id, + lockAmount: x.amount, + blockTimestamp: x.timestamp, + hash: x.txnHash, + blockNumber: x.blockNumber, + immediateCaller: address + }; + }); + const delegatedTo = res.reduce((acc, { delegateContractAddress, lockAmount, blockTimestamp, hash }) => { const existing = acc.find(({ address }) => address === delegateContractAddress) as | DelegationHistoryWithExpirationDate @@ -44,22 +55,25 @@ export async function fetchDelegatedTo( // We sum the total of lockAmounts in different events to calculate the current delegated amount if (existing) { - existing.lockAmount = utils.formatEther( - utils.parseEther(existing.lockAmount).add(utils.parseEther(lockAmount)) - ); - existing.events.push({ lockAmount, blockTimestamp, hash }); + existing.lockAmount = utils.formatEther(utils.parseEther(existing.lockAmount).add(lockAmount)); + existing.events.push({ lockAmount: utils.formatEther(lockAmount), blockTimestamp, hash }); } else { const delegatingTo = delegates.find( i => i?.voteDelegate?.toLowerCase() === delegateContractAddress.toLowerCase() - ); + ) as (AllDelegatesRecord & { version: string }) | undefined; + + if (!delegatingTo) { + return acc; + } const delegatingToWalletAddress = delegatingTo?.delegate?.toLowerCase(); // Get the expiration date of the delegate const expirationDate = add(new Date(delegatingTo?.blockTimestamp), { years: 1 }); - const isAboutToExpire = isAboutToExpireCheck(expirationDate); - const isExpired = isExpiredCheck(expirationDate); + //only v1 delegate contracts expire + const isAboutToExpire = delegatingTo.version !== '2' && isAboutToExpireCheck(expirationDate); + const isExpired = delegatingTo.version !== '2' && isExpiredCheck(expirationDate); // If it has a new owner address, check if it has renewed the contract const newOwnerAddress = getNewOwnerFromPrevious(delegatingToWalletAddress as string, network); @@ -73,9 +87,9 @@ export async function fetchDelegatedTo( expirationDate, isExpired, isAboutToExpire: !isExpired && isAboutToExpire, - lockAmount: utils.formatEther(utils.parseEther(lockAmount)), + lockAmount: utils.formatEther(lockAmount), isRenewed: !!newRenewedContract, - events: [{ lockAmount, blockTimestamp, hash }] + events: [{ lockAmount: utils.formatEther(lockAmount), blockTimestamp, hash }] } as DelegationHistoryWithExpirationDate); } diff --git a/modules/delegates/api/fetchDelegates.ts b/modules/delegates/api/fetchDelegates.ts index 2ebf5887d..0ccbef709 100644 --- a/modules/delegates/api/fetchDelegates.ts +++ b/modules/delegates/api/fetchDelegates.ts @@ -268,7 +268,7 @@ export async function fetchDelegates( const aSupport = a.mkrDelegated ? a.mkrDelegated : 0; return new BigNumberJS(aSupport).gt(new BigNumberJS(bSupport)) ? -1 : 1; } else if (sortBy === 'date') { - return a.expirationDate > b.expirationDate ? -1 : 1; + return a.expirationDate && b.expirationDate ? (a.expirationDate > b.expirationDate ? -1 : 1) : 0; } else if (sortBy === 'delegators') { const delegationHistoryA = formatDelegationHistory(a.mkrLockedDelegate); const delegationHistoryB = formatDelegationHistory(b.mkrLockedDelegate); @@ -444,7 +444,7 @@ export async function fetchDelegatesPaginated({ orderDirection, seed, delegateType, - searchTerm, + searchTerm }: DelegatesValidatedQueryParams): Promise { const chainId = networkNameToChainId(network); @@ -473,8 +473,7 @@ export async function fetchDelegatesPaginated({ } ] }; - (searchTerm) && - delegatesQueryFilter.and.push({ voteDelegate: { in: filteredDelegateAddresses } }); + searchTerm && delegatesQueryFilter.and.push({ voteDelegate: { in: filteredDelegateAddresses } }); } const delegatesQueryVariables = { @@ -492,8 +491,8 @@ export async function fetchDelegatesPaginated({ delegatesQueryVariables['seed'] = seed; } - const [githubExecutives, delegatesExecSupport, delegatesQueryRes, delegationMetricsRes] = - await Promise.all([ + const [githubExecutives, delegatesExecSupport, delegatesQueryRes, delegationMetricsRes] = await Promise.all( + [ getGithubExecutives(network), fetchDelegatesExecSupport(network), gqlRequest({ @@ -505,7 +504,8 @@ export async function fetchDelegatesPaginated({ chainId, query: delegationMetricsQuery }) - ]); + ] + ); const delegatesData = { paginationInfo: { @@ -562,7 +562,7 @@ export async function fetchDelegatesPaginated({ previous: allDelegatesEntry?.previous, next: allDelegatesEntry?.next }; - }) as DelegatePaginated[], + }) as DelegatePaginated[] }; return delegatesData; diff --git a/modules/delegates/types/delegate.d.ts b/modules/delegates/types/delegate.d.ts index 02bfd4bb9..8007bc334 100644 --- a/modules/delegates/types/delegate.d.ts +++ b/modules/delegates/types/delegate.d.ts @@ -29,6 +29,8 @@ export type DelegateContractInformation = { mkrDelegated: string; proposalsSupported: number; mkrLockedDelegate: MKRLockedDelegateAPIResponse[]; + version: string; + lastVoteDate: number | null; }; export type Delegate = { @@ -42,7 +44,7 @@ export type Delegate = { lastVoteDate: number | null; expired: boolean; isAboutToExpire: boolean; - expirationDate: Date; + expirationDate: Date | null; externalUrl?: string; combinedParticipation?: string; pollParticipation?: string; @@ -116,7 +118,7 @@ export type MKRLockedDelegateAPIResponse = { hash: string; }; -export type MKRDelegatedToDAIResponse = MKRLockedDelegateAPIResponse & { +export type MKRDelegatedToResponse = MKRLockedDelegateAPIResponse & { hash: string; immediateCaller: string; }; diff --git a/modules/gql/gql.constants.ts b/modules/gql/gql.constants.ts index 99de022c1..768a69b37 100644 --- a/modules/gql/gql.constants.ts +++ b/modules/gql/gql.constants.ts @@ -12,6 +12,15 @@ export const STAGING_MAINNET_SPOCK_URL = 'https://pollingdb2-mainnet-staging.mak export const MAINNET_SPOCK_URL = 'https://pollingdb2-mainnet-prod.makerdao.com/api/v1'; export const TENDERLY_SPOCK_URL = 'https://pollingdb2-tenderly-staging.makerdao.com/api/v1'; +/* Subgraph URLs */ + +// const usePrivateSubgraph = process.env.USE_PRIVATE_SUBGRAPH === 'true'; +// const permission = usePrivateSubgraph ? 'private' : 'public'; +export const TENDERLY_SUBGRAPH_URL = + 'https://query-subgraph-staging.sky.money/private/subgraphs/name/jetstreamgg/subgraph-testnet'; +export const MAINNET_SUBGRAPH_URL = + 'https://query-subgraph-staging.sky.money/private/subgraphs/name/jetstreamgg/subgraph-mainnet'; + export enum QueryFilterNames { Active = 'active', PollId = 'pollId', diff --git a/modules/gql/gqlRequest.ts b/modules/gql/gqlRequest.ts index 647e287bb..534777b71 100644 --- a/modules/gql/gqlRequest.ts +++ b/modules/gql/gqlRequest.ts @@ -15,6 +15,7 @@ import { CHAIN_INFO } from 'modules/web3/constants/networks'; type GqlRequestProps = { chainId?: SupportedChainId; + useSubgraph?: boolean; query: RequestDocument; variables?: Variables | null; }; @@ -22,15 +23,24 @@ type GqlRequestProps = { // TODO we'll be able to remove the "any" if we update all the instances of gqlRequest to pass export const gqlRequest = async ({ chainId, + useSubgraph = false, query, variables }: GqlRequestProps): Promise => { try { const id = chainId ?? SupportedChainId.MAINNET; - const url = CHAIN_INFO[id].spockUrl; + let url; + if (useSubgraph) { + url = CHAIN_INFO[id].subgraphUrl; + } else { + url = CHAIN_INFO[id].spockUrl; + } if (!url) { - return Promise.reject(new ApiError(`Missing spock url in configuration for chainId: ${id}`)); + return Promise.reject( + new ApiError(`Missing ${useSubgraph ? 'subgraph' : 'spock'} url in configuration for chainId: ${id}`) + ); } + const resp = await backoffRetry( 3, () => request(url, query, variables), @@ -42,7 +52,7 @@ export const gqlRequest = async ({ return resp; } catch (e) { const status = e.response ? e.response.status : 500; - const errorMessage = status === 403 ? 'Rate limited on gov polling' : e.message; + const errorMessage = e.message; const message = `Error on GraphQL query, Chain ID: ${chainId}, query: ${query}, message: ${errorMessage}`; throw new ApiError(message, status, 'Error fetching gov polling data'); } diff --git a/modules/gql/queries/subgraph/allDelegations.ts b/modules/gql/queries/subgraph/allDelegations.ts new file mode 100644 index 000000000..843618986 --- /dev/null +++ b/modules/gql/queries/subgraph/allDelegations.ts @@ -0,0 +1,18 @@ +/* +SPDX-FileCopyrightText: © 2023 Dai Foundation +SPDX-License-Identifier: AGPL-3.0-or-later +*/ + +import { gql } from 'graphql-request'; + +export const allDelegations = gql` + { + delegations(first: 1000) { + delegator + delegate { + id + } + amount + } + } +`; diff --git a/modules/gql/queries/subgraph/delegateHistoryArray.ts b/modules/gql/queries/subgraph/delegateHistoryArray.ts new file mode 100644 index 000000000..215ce14ed --- /dev/null +++ b/modules/gql/queries/subgraph/delegateHistoryArray.ts @@ -0,0 +1,24 @@ +/* +SPDX-FileCopyrightText: © 2023 Dai Foundation +SPDX-License-Identifier: AGPL-3.0-or-later +*/ + +import { gql } from 'graphql-request'; + +export const delegateHistoryArray = gql` + query delegateHistoryArray($delegates: [String!]!) { + delegates(first: 1000, where: { id_in: $delegates }) { + delegationHistory { + amount + accumulatedAmount + delegator + blockNumber + timestamp + txnHash + delegate { + id + } + } + } + } +`; diff --git a/modules/gql/queries/subgraph/delegatorHistory.ts b/modules/gql/queries/subgraph/delegatorHistory.ts new file mode 100644 index 000000000..cea74d36c --- /dev/null +++ b/modules/gql/queries/subgraph/delegatorHistory.ts @@ -0,0 +1,22 @@ +/* +SPDX-FileCopyrightText: © 2023 Dai Foundation + +SPDX-License-Identifier: AGPL-3.0-or-later +*/ + +import { gql } from 'graphql-request'; + +export const delegatorHistory = gql` + query delegatorHistory($address: String!) { + delegationHistories(first: 1000, where: { delegator: $address }) { + amount + accumulatedAmount + delegate { + id + } + timestamp + txnHash + blockNumber + } + } +`; diff --git a/modules/migration/components/MigrationBanner.tsx b/modules/migration/components/MigrationBanner.tsx index 5a9f77bd0..b25a561f6 100644 --- a/modules/migration/components/MigrationBanner.tsx +++ b/modules/migration/components/MigrationBanner.tsx @@ -21,19 +21,25 @@ export function MigrationBanner(): React.ReactElement | null { isDelegatedToExpiredContract, isDelegateContractExpired, isDelegateContractExpiring, - isShadowDelegate + isShadowDelegate, + isDelegateV1Contract, + isDelegatedToV1Contract } = useMigrationStatus(); const showDelegationMigrationBanner = (isDelegateContractExpired && !isShadowDelegate) || (isDelegateContractExpiring && !isShadowDelegate) || + (isDelegateV1Contract && !isShadowDelegate) || isDelegatedToExpiringContract || - isDelegatedToExpiredContract; + isDelegatedToExpiredContract || + isDelegatedToV1Contract; const { variant, href, copy } = getMigrationBannerContent({ isDelegatedToExpiredContract, isDelegateContractExpired: isDelegateContractExpired && !isShadowDelegate, isDelegatedToExpiringContract, - isDelegateContractExpiring: isDelegateContractExpiring && !isShadowDelegate + isDelegateContractExpiring: isDelegateContractExpiring && !isShadowDelegate, + isDelegateV1Contract, + isDelegatedToV1Contract }); return showDelegationMigrationBanner ? ( diff --git a/modules/migration/components/MigrationInfo.tsx b/modules/migration/components/MigrationInfo.tsx index 852871749..6dee1912d 100644 --- a/modules/migration/components/MigrationInfo.tsx +++ b/modules/migration/components/MigrationInfo.tsx @@ -25,7 +25,7 @@ export function MigrationInfo({ - Maker delegate contracts are{' '} + Maker v1 delegate contracts are{' '} {' '} in order to protect the Maker protocol against stale MKR tokens participating in Maker governance. + The new v2 delegate contracts, however, do not expire. diff --git a/modules/migration/helpers/getMigrationBannerContent.ts b/modules/migration/helpers/getMigrationBannerContent.ts index 73b04911e..f6929b943 100644 --- a/modules/migration/helpers/getMigrationBannerContent.ts +++ b/modules/migration/helpers/getMigrationBannerContent.ts @@ -10,12 +10,16 @@ export const getMigrationBannerContent = ({ isDelegateContractExpired, isDelegateContractExpiring, isDelegatedToExpiredContract, - isDelegatedToExpiringContract + isDelegatedToExpiringContract, + isDelegateV1Contract, + isDelegatedToV1Contract }: { isDelegateContractExpired: boolean; isDelegateContractExpiring: boolean; isDelegatedToExpiredContract: boolean; isDelegatedToExpiringContract: boolean; + isDelegateV1Contract: boolean; + isDelegatedToV1Contract: boolean; }): { variant: string; href: string; copy: string } => { // a delegate having an expired contract is if (isDelegateContractExpired) { @@ -35,6 +39,14 @@ export const getMigrationBannerContent = ({ }; } + if (isDelegateV1Contract) { + return { + variant: 'bannerNotice', + href: '/migration/delegate', + copy: 'Your delegate contract needs to be migrated to v2. Please visit the migration page to migrate it.' + }; + } + // next check if user has delegated to an expired contract if (isDelegatedToExpiredContract) { return { @@ -53,6 +65,15 @@ export const getMigrationBannerContent = ({ }; } + // next check if user has delegated to a v1 contract + if (isDelegatedToV1Contract) { + return { + variant: 'bannerNotice', + href: '/migration/delegator', + copy: 'You have MKR delegated to a v1 delegate contract. Please visit the migration page to migrate your MKR to a v2 delegate contract.' + }; + } + // if we're here, something went wrong return { variant: '', diff --git a/modules/migration/hooks/useMigrationStatus.tsx b/modules/migration/hooks/useMigrationStatus.tsx index 41d5fbc3f..9cfb2fd18 100644 --- a/modules/migration/hooks/useMigrationStatus.tsx +++ b/modules/migration/hooks/useMigrationStatus.tsx @@ -21,6 +21,8 @@ export function useMigrationStatus(): { isDelegateContractExpired: boolean; isDelegateContractExpiring: boolean; isShadowDelegate: boolean; + isDelegateV1Contract: boolean; + isDelegatedToV1Contract: boolean; } { const { account: address, network } = useWeb3(); const { cache } = useSWRConfig(); @@ -48,6 +50,8 @@ export function useMigrationStatus(): { : false : false; + const isDelegateV1Contract = !!delegateContractExpirationDate; // TODO: update with version === '1' when available + const isDelegateContractExpiring = delegateContractExpirationDate ? isAboutToExpireCheck(delegateContractExpirationDate) : false; @@ -70,11 +74,19 @@ export function useMigrationStatus(): { }, false) : false; + const isDelegatedToV1Contract = delegatedToData + ? delegatedToData.delegatedTo.reduce((acc, cur) => { + return acc || !!cur.expirationDate; + }, false) + : false; + return { isDelegatedToExpiredContract, isDelegatedToExpiringContract, isDelegateContractExpired, isDelegateContractExpiring, - isShadowDelegate + isShadowDelegate, + isDelegateV1Contract, + isDelegatedToV1Contract }; } diff --git a/modules/web3/constants/networks.ts b/modules/web3/constants/networks.ts index 457500850..1759fad97 100644 --- a/modules/web3/constants/networks.ts +++ b/modules/web3/constants/networks.ts @@ -16,7 +16,9 @@ export const NetworkContextName = 'NETWORK'; import { MAINNET_SPOCK_URL, STAGING_MAINNET_SPOCK_URL, - TENDERLY_SPOCK_URL + TENDERLY_SPOCK_URL, + TENDERLY_SUBGRAPH_URL, + MAINNET_SUBGRAPH_URL } from 'modules/gql/gql.constants'; export enum SupportedConnectors { @@ -47,6 +49,7 @@ type ChainInfo = { }; const { TENDERLY_RPC_URL } = tenderlyTestnetData; +const TENDERLY_CONTAINER_ID = 'c91028eb-78e1-4305-a289-5cd07adc7fb9'; //todo: change name to SUPPORTED_CHAIN_INFO export const CHAIN_INFO: ChainInfo = { @@ -60,6 +63,7 @@ export const CHAIN_INFO: ChainInfo = { defaultRpc: NodeProviders.ALCHEMY, spockUrl: process.env.NEXT_PUBLIC_VERCEL_ENV === 'development' ? STAGING_MAINNET_SPOCK_URL : MAINNET_SPOCK_URL, + subgraphUrl: MAINNET_SUBGRAPH_URL, rpcs: { [NodeProviders.INFURA]: `https://mainnet.infura.io/v3/${config.INFURA_KEY}`, [NodeProviders.ALCHEMY]: `https://eth-mainnet.g.alchemy.com/v2/${config.ALCHEMY_KEY}` @@ -93,7 +97,7 @@ export const CHAIN_INFO: ChainInfo = { showInProduction: false }, [SupportedChainId.TENDERLY]: { - blockExplorerUrl: `dashboard.tenderly.co/explorer/vnet/${config.TENDERLY_RPC_KEY}`, + blockExplorerUrl: `dashboard.tenderly.co/pullup-labs/endgame-0/testnet/${TENDERLY_CONTAINER_ID}`, blockExplorerName: 'Etherscan', chainId: SupportedChainId.TENDERLY, label: 'Tenderly', @@ -101,8 +105,12 @@ export const CHAIN_INFO: ChainInfo = { network: SupportedNetworks.TENDERLY, defaultRpc: NodeProviders.TENDERLY, spockUrl: TENDERLY_SPOCK_URL, + subgraphUrl: TENDERLY_SUBGRAPH_URL, rpcs: { - [NodeProviders.TENDERLY]: config.USE_MOCK_WALLET && TENDERLY_RPC_URL ? TENDERLY_RPC_URL : `https://virtual.mainnet.rpc.tenderly.co/${config.TENDERLY_RPC_KEY}` + [NodeProviders.TENDERLY]: + config.USE_MOCK_WALLET && TENDERLY_RPC_URL + ? TENDERLY_RPC_URL + : `https://virtual.mainnet.rpc.tenderly.co/${config.TENDERLY_RPC_KEY}` }, showInProduction: false } diff --git a/modules/web3/helpers/ens.ts b/modules/web3/helpers/ens.ts index e4f7271cc..4454f9f09 100644 --- a/modules/web3/helpers/ens.ts +++ b/modules/web3/helpers/ens.ts @@ -16,7 +16,7 @@ export async function getENS({ provider }: { address: string; - provider: providers.Web3Provider; + provider: providers.BaseProvider; }): Promise { try { const name = await provider.lookupAddress(address); diff --git a/modules/web3/helpers/getEtherscanLink.ts b/modules/web3/helpers/getEtherscanLink.ts index 760b3dbd7..312bd260f 100644 --- a/modules/web3/helpers/getEtherscanLink.ts +++ b/modules/web3/helpers/getEtherscanLink.ts @@ -20,6 +20,9 @@ export function getEtherscanLink( switch (type) { case 'transaction': + if (network === SupportedNetworks.TENDERLY) { + return `${prefix}/tx/mainnet/${data}`; + } return `${prefix}/tx/${data}`; case 'address': default: diff --git a/modules/web3/types/chain.d.ts b/modules/web3/types/chain.d.ts index 85bbecc8f..0852c4f60 100644 --- a/modules/web3/types/chain.d.ts +++ b/modules/web3/types/chain.d.ts @@ -19,6 +19,7 @@ export type SupportedChain = { network: SupportedNetworks; defaultRpc: string; spockUrl?: string; + subgraphUrl?: string; type: 'gasless' | 'normal'; showInProduction: boolean; rpcs: { diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03dc..a4a7b3f5c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. diff --git a/pages/delegates/index.tsx b/pages/delegates/index.tsx index 31be102ad..5aadc0a8e 100644 --- a/pages/delegates/index.tsx +++ b/pages/delegates/index.tsx @@ -9,7 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later import { useMemo, useState, useRef, useEffect } from 'react'; import { Heading, Box, Flex, Card, Text, Button } from 'theme-ui'; import { GetStaticProps } from 'next'; -import { useRouter } from 'next/router'; import ErrorPage from 'modules/app/components/ErrorPage'; import { useBreakpointIndex } from '@theme-ui/match-media'; import shallow from 'zustand/shallow'; @@ -130,8 +129,6 @@ const Delegates = ({ setIsRendering(false); }, []); - const router = useRouter(); - useEffect(() => { if (shouldLoadMore) { if (shadowDelegates.length >= 15 && !loadAllDelegates) { diff --git a/pages/migration/delegate.tsx b/pages/migration/delegate.tsx index 095a4726d..39bd10502 100644 --- a/pages/migration/delegate.tsx +++ b/pages/migration/delegate.tsx @@ -27,7 +27,8 @@ export default function DelegateMigrationPage(): React.ReactElement { const { account, provider } = useWeb3(); const [migrationInfoAcknowledged, setMigrationInfoAcknowledged] = useState(false); - const { isDelegateContractExpiring, isDelegateContractExpired } = useMigrationStatus(); + const { isDelegateContractExpiring, isDelegateContractExpired, isDelegateV1Contract } = + useMigrationStatus(); const { newOwnerAddress, @@ -40,7 +41,7 @@ export default function DelegateMigrationPage(): React.ReactElement { const connectedAddressFound = !!previousOwnerAddress || !!newOwnerAddress; // the user should be shown the steps to take action if: - // a - the connected account has an expired/expiring contract + // a - the connected account has an expired/expiring contract or needs to migrate to v2 // or // b - the connected count is the new account of a previous delegate // and has not created the delegate contract yet @@ -48,6 +49,7 @@ export default function DelegateMigrationPage(): React.ReactElement { // a isDelegateContractExpiring || isDelegateContractExpired || + isDelegateV1Contract || // b (!!newOwnerAddress && !newOwnerHasDelegateContract); @@ -55,7 +57,7 @@ export default function DelegateMigrationPage(): React.ReactElement { // delegate contract is either expired or expiring and we don't have // a request to migrate the address yet, show migration info if ( - (isDelegateContractExpired || isDelegateContractExpiring) && + (isDelegateContractExpired || isDelegateContractExpiring || isDelegateV1Contract) && !connectedAddressFound && !migrationInfoAcknowledged ) { @@ -65,18 +67,18 @@ export default function DelegateMigrationPage(): React.ReactElement { // same status as above, but user has acknowledged migration info, // show new address step if ( - (isDelegateContractExpiring || isDelegateContractExpired) && + (isDelegateContractExpiring || isDelegateContractExpired || isDelegateV1Contract) && !connectedAddressFound && migrationInfoAcknowledged ) { return STEPS.NEW_ADDRESS; } - // delegate contract is either expired or expiring + // delegate contract is either expired or expiring or needs to migrate to v2 // and we have processed the request to migrate // but user is connected with old address if ( - (isDelegateContractExpiring || isDelegateContractExpired) && + (isDelegateContractExpiring || isDelegateContractExpired || isDelegateV1Contract) && connectedAddressFound && previousOwnerConnected ) { @@ -93,6 +95,7 @@ export default function DelegateMigrationPage(): React.ReactElement { }, [ isDelegateContractExpired, isDelegateContractExpiring, + isDelegateV1Contract, previousOwnerAddress, newOwnerAddress, newOwnerHasDelegateContract, @@ -133,12 +136,21 @@ export default function DelegateMigrationPage(): React.ReactElement { {isDelegateContractExpiring && !isDelegateContractExpired && 'Your delegate contract is expiring soon. Please migrate as soon as possible.'} - {((!isDelegateContractExpired && !isDelegateContractExpiring && newOwnerHasDelegateContract) || + {/* TODO: Add the right message for v2 migration when decided */} + {isDelegateV1Contract && + !isDelegateContractExpired && + !isDelegateContractExpiring && + 'Your delegate contract needs to be migrated to v2.'} + {((!isDelegateV1Contract && + !isDelegateContractExpired && + !isDelegateContractExpiring && + newOwnerHasDelegateContract) || !actionNeeded) && 'No contract migration is necessary at this time'} {isDelegateContractExpired || + isDelegateV1Contract || (isDelegateContractExpiring && ( i.address.toLowerCase() === delegate.previous?.address.toLowerCase() ); - return !delegate.expired && !delegate.isAboutToExpire && isPreviousDelegate; + return ( + !delegate.expirationDate || (!delegate.expired && !delegate.isAboutToExpire && isPreviousDelegate) + ); }); }, [addressDelegations, delegatesThatAreAboutToExpiry]);