From 03e09897222efbffb824e19baf54beadce20b775 Mon Sep 17 00:00:00 2001 From: Tuomas Koponen <432588+tumppi@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:15:07 +0200 Subject: [PATCH] Add Amoy support --- src/Store.tsx | 13 ++++++++++ src/components/App.tsx | 30 ++++++++++++----------- src/components/NetworkSelector/index.tsx | 29 ++++++++++++++++++---- src/components/Stats.tsx | 6 +++-- src/components/StreamrClientProvider.tsx | 6 +++-- src/getters.tsx | 10 +++++--- src/utils/chains.ts | 17 +++++++++++++ src/utils/neighbors.tsx | 9 ++++--- src/utils/nodes.ts | 13 +++++++--- src/utils/places.ts | 11 ++++++--- src/utils/queries.ts | 15 +++++++----- src/utils/streams.ts | 31 +++++++++++++++--------- 12 files changed, 135 insertions(+), 55 deletions(-) create mode 100644 src/utils/chains.ts diff --git a/src/Store.tsx b/src/Store.tsx index 1d4a5a8..4417e74 100644 --- a/src/Store.tsx +++ b/src/Store.tsx @@ -17,8 +17,10 @@ import { ActiveView, ConnectionsMode, OperatorNode } from './types' import { useHud } from './utils' import { useOperatorNodesForStreamQuery } from './utils/nodes' import { truncate } from './utils/text' +import { POLYGON_CHAIN_ID } from './utils/chains' interface Store { + chainId: number activeView: ActiveView connectionsMode: ConnectionsMode displaySearchPhrase: string @@ -29,6 +31,7 @@ interface Store { publishers: Record searchPhrase: string selectedNode: OperatorNode | null + setChainId(value: number): void setActiveView(value: ActiveView): void setConnectionsMode: Dispatch> setPublishers: Dispatch>> @@ -36,6 +39,7 @@ interface Store { } const StoreContext = createContext({ + chainId: POLYGON_CHAIN_ID, activeView: ActiveView.Map, connectionsMode: ConnectionsMode.Auto, displaySearchPhrase: '', @@ -46,6 +50,7 @@ const StoreContext = createContext({ publishers: {}, searchPhrase: '', selectedNode: null, + setChainId: () => {}, setActiveView: () => {}, setConnectionsMode: () => {}, setPublishers: () => ({}), @@ -72,6 +77,8 @@ export function StoreProvider(props: StoreProviderProps) { }) }) + const [chainId, setChainId] = useState(POLYGON_CHAIN_ID) + const [activeView, setActiveView] = useState(ActiveView.Map) const [rawSearchPhrase, setRawSearchPhrase] = useState('') @@ -99,10 +106,15 @@ export function StoreProvider(props: StoreProviderProps) { [showConnections], ) + useEffect(() => { + console.log('Store: Chain ID changed:', chainId) + }, [chainId]) + return ( + <> @@ -56,7 +56,7 @@ function Page() { - + ) } @@ -126,18 +126,20 @@ export function App() { return ( - - - - }> - } /> - } /> - } /> - } /> - - - - + + + + + }> + } /> + } /> + } /> + } /> + + + + + ) diff --git a/src/components/NetworkSelector/index.tsx b/src/components/NetworkSelector/index.tsx index 73816fa..44dcc63 100644 --- a/src/components/NetworkSelector/index.tsx +++ b/src/components/NetworkSelector/index.tsx @@ -2,6 +2,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import styled from 'styled-components' import { SANS } from '../../utils/styled' import { Tooltip } from '../Tooltip' +import { useStore } from '../../Store' +import { POLYGON_CHAIN_ID, POLYGON_AMOY_CHAIN_ID } from '../../utils/chains' const GlobeIcon = () => ( @@ -31,6 +33,10 @@ const MainnetTheme = { color: '#0EAC1B', } +const AmoyTheme = { + color: '#FF6B00', +} + const NetworkIndicator = styled.div` border-radius: 50%; width: 8px; @@ -120,6 +126,8 @@ const NetworkSelectorRoot = styled.div` ` export default function NetworkSelector() { + const { chainId, setChainId } = useStore() + const [open, setOpen] = useState(false) const containerRef = useRef(null) @@ -166,17 +174,28 @@ export default function NetworkSelector() { {!!open && ( - + setChainId(POLYGON_CHAIN_ID)}> Mainnet -
- -
+ {chainId === POLYGON_CHAIN_ID && ( +
+ +
+ )} + + setChainId(POLYGON_AMOY_CHAIN_ID)}> + + Amoy + {chainId === POLYGON_AMOY_CHAIN_ID && ( +
+ +
+ )}
)} diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx index f5f31a7..3010d28 100644 --- a/src/components/Stats.tsx +++ b/src/components/Stats.tsx @@ -28,6 +28,7 @@ import { Graphs } from './Graphs' import { Interval } from './Graphs/Graphs' import { Intervals } from './Graphs/Intervals' import { TimeSeries } from './Graphs/TimeSeries' +import { useStore } from '../Store' type StatProps = { id: string @@ -242,12 +243,13 @@ export const Stats = styled(UnstyledStats)` ` function useStreamStatsQuery(streamId: string) { + const { chainId } = useStore() return useQuery({ - queryKey: ['useStreamStatsQuery', streamId], + queryKey: ['useStreamStatsQuery', streamId, chainId], queryFn: async () => { const { data: { streams }, - } = await getIndexerClient().query({ + } = await getIndexerClient(chainId).query({ query: GetStreamsDocument, variables: { ids: [streamId], diff --git a/src/components/StreamrClientProvider.tsx b/src/components/StreamrClientProvider.tsx index febfc74..3f7e392 100644 --- a/src/components/StreamrClientProvider.tsx +++ b/src/components/StreamrClientProvider.tsx @@ -1,13 +1,15 @@ -import React, { useState } from 'react' +import React, { useMemo } from 'react' import Provider from 'streamr-client-react' import { getStreamrClientConfig } from '../utils/streams' +import { useStore } from '../Store' interface Props { children: React.ReactNode } const StreamrClientProvider = ({ children }: Props) => { - const [config] = useState(getStreamrClientConfig()) + const { chainId } = useStore() + const config = useMemo(() => getStreamrClientConfig(chainId), [chainId]) return {children} } diff --git a/src/getters.tsx b/src/getters.tsx index 09d6174..d73042f 100644 --- a/src/getters.tsx +++ b/src/getters.tsx @@ -13,12 +13,13 @@ import { getIndexerClient } from './utils/queries' interface GetOperatorNodesParams { ids?: string[] streamId?: string + chainId: number } export async function getOperatorNodes(params: GetOperatorNodesParams): Promise { const pageSize = 500 - const { ids, streamId } = params + const { ids, streamId, chainId } = params const items: OperatorNode[] = [] @@ -27,7 +28,7 @@ export async function getOperatorNodes(params: GetOperatorNodesParams): Promise< for (;;) { const { data: { nodes }, - } = await getIndexerClient().query({ + } = await getIndexerClient(chainId).query({ fetchPolicy: 'network-only', query: GetNodesDocument, variables: { @@ -70,12 +71,13 @@ interface GetNeighborsParams { node?: string streamId?: string streamPartitionId?: string + chainId: number } export async function getNeighbors(params: GetNeighborsParams): Promise { const pageSize = 1000 - const { node, streamPartitionId } = params + const { node, streamPartitionId, chainId } = params const items: Neighbour[] = [] @@ -86,7 +88,7 @@ export async function getNeighbors(params: GetNeighborsParams): Promise({ + } = await getIndexerClient(chainId).query({ fetchPolicy: 'network-only', query: GetNeighborsDocument, variables: { diff --git a/src/utils/chains.ts b/src/utils/chains.ts new file mode 100644 index 0000000..eb6cd61 --- /dev/null +++ b/src/utils/chains.ts @@ -0,0 +1,17 @@ +import { config } from '@streamr/config' + +export const POLYGON_CHAIN_ID = config.polygon.id +export const POLYGON_AMOY_CHAIN_ID = config.polygonAmoy.id + +const INDEXER_URLS: Record = { + [POLYGON_CHAIN_ID]: 'https://stream-metrics.streamr.network/api', + [POLYGON_AMOY_CHAIN_ID]: 'https://stream-metrics-polygonAmoy.streamr.network/api', +} + +export function getIndexerUrl(chainId: number): string { + const uri = INDEXER_URLS[chainId] + if (!uri) { + throw new Error(`No indexer URL configured for chain ID ${chainId}`) + } + return uri +} diff --git a/src/utils/neighbors.tsx b/src/utils/neighbors.tsx index 7e6e3c8..f21f151 100644 --- a/src/utils/neighbors.tsx +++ b/src/utils/neighbors.tsx @@ -1,9 +1,10 @@ import { useQuery } from '@tanstack/react-query' import { MinuteMs } from '../consts' import { getNeighbors } from '../getters' +import { useStore } from '../Store' -function getOperatorNodeNeighborsQueryKey(nodeId: string | undefined) { - return ['useOperatorNodeNeighborsQuery', nodeId || ''] +function getOperatorNodeNeighborsQueryKey(nodeId: string | undefined, chainId: number) { + return ['useOperatorNodeNeighborsQuery', nodeId || '', chainId] } interface UseOperatorNodeNeighborsQueryOptions { @@ -15,13 +16,15 @@ export function useOperatorNodeNeighborsQuery( options: UseOperatorNodeNeighborsQueryOptions = {}, ) { const { streamId } = options + const { chainId } = useStore() return useQuery({ - queryKey: getOperatorNodeNeighborsQueryKey(nodeId), + queryKey: getOperatorNodeNeighborsQueryKey(nodeId, chainId), queryFn: async () => { const neighbours = await getNeighbors({ node: nodeId, streamId, + chainId, }) if (!streamId) { diff --git a/src/utils/nodes.ts b/src/utils/nodes.ts index 1beb82e..564e446 100644 --- a/src/utils/nodes.ts +++ b/src/utils/nodes.ts @@ -1,26 +1,31 @@ import { useIsFetching, useQuery } from '@tanstack/react-query' import { MinuteMs } from '../consts' import { getOperatorNodes } from '../getters' +import { useStore } from '../Store' -function getOperatorNodesForStreamQueryKey(streamId: string | undefined) { - return ['useOperatorNodesForStreamQuery', streamId || ''] +function getOperatorNodesForStreamQueryKey(streamId: string | undefined, chainId: number) { + return ['useOperatorNodesForStreamQuery', streamId || '', chainId] } export function useOperatorNodesForStreamQuery(streamId: string | undefined) { + const { chainId } = useStore() + return useQuery({ - queryKey: getOperatorNodesForStreamQueryKey(streamId), + queryKey: getOperatorNodesForStreamQueryKey(streamId, chainId), queryFn: () => getOperatorNodes({ streamId, + chainId, }), staleTime: 5 * MinuteMs, }) } export function useIsFetchingOperatorNodesForStream(streamId: string | undefined) { + const { chainId } = useStore() const queryCount = useIsFetching({ exact: true, - queryKey: getOperatorNodesForStreamQueryKey(streamId), + queryKey: getOperatorNodesForStreamQueryKey(streamId, chainId), }) return queryCount > 0 diff --git a/src/utils/places.ts b/src/utils/places.ts index c5c4b7b..6f0f56d 100644 --- a/src/utils/places.ts +++ b/src/utils/places.ts @@ -1,6 +1,7 @@ import { useIsFetching, useQuery } from '@tanstack/react-query' import { MapboxToken } from '../consts' import { PlaceFeature, PlacesResponse } from '../types' +import { useStore } from '../Store' interface UseLocationFeaturesQueryParams { place?: string | [number, number] @@ -11,8 +12,8 @@ interface UseLocationFeaturesQueryOptions { eligible?: (feature: PlaceFeature) => boolean } -function getLocationFeaturesQueryKey(place: string) { - return ['useLocationFeaturesQuery', place] +function getLocationFeaturesQueryKey(place: string, chainId: number) { + return ['useLocationFeaturesQuery', place, chainId] } export function useLocationFeaturesQuery( @@ -20,11 +21,12 @@ export function useLocationFeaturesQuery( options: UseLocationFeaturesQueryOptions = {}, ) { const { place: placeParam = '' } = params + const { chainId } = useStore() const place = typeof placeParam === 'string' ? placeParam : placeParam.join(',') return useQuery({ - queryKey: getLocationFeaturesQueryKey(place), + queryKey: getLocationFeaturesQueryKey(place, chainId), queryFn: async ({ signal }) => { const result: T[] = [] @@ -60,9 +62,10 @@ export function useLocationFeaturesQuery( } export function useIsFetchingLocationFeatures(place: string) { + const { chainId } = useStore() const queryCount = useIsFetching({ exact: true, - queryKey: getLocationFeaturesQueryKey(place), + queryKey: getLocationFeaturesQueryKey(place, chainId), }) return queryCount > 0 diff --git a/src/utils/queries.ts b/src/utils/queries.ts index 4fee304..4555dd3 100644 --- a/src/utils/queries.ts +++ b/src/utils/queries.ts @@ -1,17 +1,20 @@ import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client' import { QueryClient } from '@tanstack/react-query' +import { getIndexerUrl } from './chains' -let indexerGraphClient: ApolloClient | undefined +let indexerGraphClients: Record> = {} -export function getIndexerClient(): ApolloClient { - if (!indexerGraphClient) { - indexerGraphClient = new ApolloClient({ - uri: 'https://stream-metrics.streamr.network/api', +export function getIndexerClient(chainId: number): ApolloClient { + if (!indexerGraphClients[chainId]) { + const uri = getIndexerUrl(chainId) + + indexerGraphClients[chainId] = new ApolloClient({ + uri, cache: new InMemoryCache(), }) } - return indexerGraphClient + return indexerGraphClients[chainId] } let queryClient: QueryClient | undefined diff --git a/src/utils/streams.ts b/src/utils/streams.ts index 1dd1ef6..9c49a81 100644 --- a/src/utils/streams.ts +++ b/src/utils/streams.ts @@ -15,8 +15,8 @@ import { import { getIndexerClient } from './queries' import { config } from '@streamr/config' -function getLimitedStreamsQueryKey(phrase: string, limit: number) { - return ['useLimitedStreamsQuery', phrase, limit] +function getLimitedStreamsQueryKey(phrase: string, limit: number, chainId: number) { + return ['useLimitedStreamsQuery', phrase, limit, chainId] } interface UseLimitedStreamsQueryParams { @@ -26,9 +26,10 @@ interface UseLimitedStreamsQueryParams { export function useLimitedStreamsQuery(params: UseLimitedStreamsQueryParams) { const { phrase, limit = 20 } = params + const { chainId } = useStore() return useQuery({ - queryKey: getLimitedStreamsQueryKey(phrase, limit), + queryKey: getLimitedStreamsQueryKey(phrase, limit, chainId), queryFn: async () => { if (!phrase) { return [] @@ -36,7 +37,7 @@ export function useLimitedStreamsQuery(params: UseLimitedStreamsQueryParams) { const { data: { streams }, - } = await getIndexerClient().query({ + } = await getIndexerClient(chainId).query({ query: GetStreamsDocument, variables: { searchTerm: phrase, @@ -192,29 +193,37 @@ export function useRecentOperatorNodeMetricEntry(nodeId: string) { return recent } -export function getStreamrClientConfig(): StreamrClientConfig { +export function getStreamrClientConfig(chainId: number): StreamrClientConfig { + const networkConfig = Object.values(config).find((network) => network.id === chainId) + + if (!networkConfig) { + throw new Error(`No Streamr Clientconfiguration found for chain ID ${chainId}`) + } + return { metrics: false, contracts: { ethereumNetwork: { - chainId: config.polygon.id, + chainId: networkConfig.id, }, - rpcs: config.polygon.rpcEndpoints.slice(0, 1) + rpcs: networkConfig.rpcEndpoints.slice(0, 1), }, } } -async function getStreamrClientInstance() { +async function getStreamrClientInstance(chainId: number) { const StreamrClient = (await import('@streamr/sdk')).default - return new StreamrClient(getStreamrClientConfig()) + return new StreamrClient(getStreamrClientConfig(chainId)) } export function useStreamFromClient(streamId: string) { + const { chainId } = useStore() + return useQuery({ - queryKey: ['useStreamFromClient', streamId], + queryKey: ['useStreamFromClient', streamId, chainId], queryFn: async () => { - const client = await getStreamrClientInstance() + const client = await getStreamrClientInstance(chainId) return client.getStream(streamId) },