Skip to content

Commit

Permalink
feat: allow selection of RPC providers (#1557)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Bulat committed Oct 26, 2023
1 parent 932db5f commit c0553c2
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 107 deletions.
38 changes: 38 additions & 0 deletions src/config/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ export const NetworkList: Networks = {
endpoints: {
rpc: 'wss://apps-rpc.polkadot.io',
lightClient: WellKnownChain.polkadot,
defaultRpcEndpoint: 'Automata 1RPC',
rpcEndpoints: {
'Automata 1RPC': 'wss://1rpc.io/dot',
Dwellir: 'wss://polkadot-rpc.dwellir.com',
'Dwellir Tunisia': 'wss://polkadot-rpc-tn.dwellir.com',
'IBP-GeoDNS1': 'wss://rpc.ibp.network/polkadot',
'IBP-GeoDNS2': 'wss://rpc.dotters.network/polkadot',
LuckyFriday: 'wss://rpc-polkadot.luckyfriday.io',
OnFinality: 'wss://polkadot.api.onfinality.io/public-ws',
RadiumBlock: 'wss://polkadot.public.curie.radiumblock.co/ws',
Stakeworld: 'wss://dot-rpc.stakeworld.io',
Parity: 'wss://apps-rpc.polkadot.io',
},
},
namespace: '91b171bb158e2d3848fa23a9f1c25182',
colors: {
Expand Down Expand Up @@ -79,6 +92,19 @@ export const NetworkList: Networks = {
endpoints: {
rpc: 'wss://kusama-rpc.polkadot.io',
lightClient: WellKnownChain.ksmcc3,
defaultRpcEndpoint: 'Automata 1RPC',
rpcEndpoints: {
'Automata 1RPC': 'wss://1rpc.io/ksm',
Dwellir: 'wss://kusama-rpc.dwellir.com',
'Dwellir Tunisia': 'wss://kusama-rpc-tn.dwellir.com',
'IBP-GeoDNS1': 'wss://rpc.ibp.network/kusama',
'IBP-GeoDNS2': 'wss://rpc.dotters.network/kusama',
LuckyFriday: 'wss://rpc-kusama.luckyfriday.io',
OnFinality: 'wss://kusama.api.onfinality.io/public-ws',
RadiumBlock: 'wss://kusama.public.curie.radiumblock.co/ws',
Stakeworld: 'wss://ksm-rpc.stakeworld.io',
Parity: 'wss://kusama-rpc.polkadot.io',
},
},
namespace: 'b0a8d493285c2df73290dfb7e61f870f',
colors: {
Expand Down Expand Up @@ -136,6 +162,18 @@ export const NetworkList: Networks = {
endpoints: {
rpc: 'wss://westend-rpc.polkadot.io',
lightClient: WellKnownChain.westend2,
defaultRpcEndpoint: 'OnFinality',
rpcEndpoints: {
Dwellir: 'wss://westend-rpc.dwellir.com',
'Dwellir Tunisia': 'wss://westend-rpc-tn.dwellir.com',
'IBP-GeoDNS1': 'wss://rpc.ibp.network/westend',
'IBP-GeoDNS2': 'wss://rpc.dotters.network/westend',
LuckyFriday: 'wss://rpc-westend.luckyfriday.io',
OnFinality: 'wss://westend.api.onfinality.io/public-ws',
RadiumBlock: 'wss://westend.public.curie.radiumblock.co/ws',
Stakeworld: 'wss://wnd-rpc.stakeworld.io',
Parity: 'wss://westend-rpc.polkadot.io',
},
},
namespace: 'e143f23803ac50e8f6f8e62695d1ce9e',
colors: {
Expand Down
2 changes: 2 additions & 0 deletions src/contexts/Api/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ export const defaultApiContext: APIContextInterface = {
apiStatus: 'disconnected',
isLightClient: false,
setIsLightClient: () => {},
rpcEndpoint: '',
setRpcEndpoint: (key) => {},
};
100 changes: 65 additions & 35 deletions src/contexts/Api/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {
APIProviderProps,
ApiStatus,
} from 'contexts/Api/types';
import type { AnyApi, NetworkName } from 'types';
import type { AnyApi } from 'types';
import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks';
import * as defaults from './defaults';

Expand All @@ -36,6 +36,22 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
// Store chain state.
const [chainState, setchainState] = useState<APIChainState>(undefined);

// Store the active RPC provider.
const initialRpcEndpoint = () => {
const local = localStorage.getItem(`${network}_rpc_endpoint`);
if (local)
if (NetworkList[network].endpoints.rpcEndpoints[local]) {
return local;
} else {
localStorage.removeItem(`${network}_rpc_endpoint`);
}

return NetworkList[network].endpoints.defaultRpcEndpoint;
};
const [rpcEndpoint, setRpcEndpointState] = useState<string>(
initialRpcEndpoint()
);

// Store whether in light client mode.
const [isLightClient, setIsLightClient] = useState<boolean>(
!!localStorage.getItem('light_client')
Expand All @@ -50,20 +66,21 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
// Store API connection status.
const [apiStatus, setApiStatus] = useState<ApiStatus>('disconnected');

// Handle an initial RPC connection.
useEffect(() => {
if (!provider && !isLightClient) {
connectProvider(network);
}
});
// Set RPC provider with local storage and validity checks.
const setRpcEndpoint = (key: string) => {
if (!NetworkList[network].endpoints.rpcEndpoints[key]) return;
localStorage.setItem(`${network}_rpc_endpoint`, key);

setRpcEndpointState(key);
};

// Handle light client connection.
const handleLightClientConnection = async (Sc: AnyApi) => {
const newProvider = new ScProvider(
Sc,
NetworkList[network].endpoints.lightClient
);
connectProvider(network, newProvider);
connectProvider(newProvider);
};

// Handle a switch in API.
Expand Down Expand Up @@ -101,32 +118,10 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
} else {
// if not light client, directly connect.
setApiStatus('connecting');
connectProvider(network);
connectProvider();
}
};

// Trigger API connection handler on network or light client change.
useEffect(() => {
handleConnectApi();

return () => {
cancelFn?.();
};
}, [isLightClient, network]);

// Initialise provider event handlers when provider is set.
useEffectIgnoreInitial(() => {
if (provider) {
provider.on('connected', () => {
setApiStatus('connected');
});
provider.on('error', () => {
setApiStatus('disconnected');
});
getChainState();
}
}, [provider]);

// Fetch chain state. Called once `provider` has been initialised.
const getChainState = async () => {
if (!provider) return;
Expand Down Expand Up @@ -243,25 +238,60 @@ export const APIProvider = ({ children, network }: APIProviderProps) => {
};

// connect function sets provider and updates active network.
const connectProvider = async (name: NetworkName, lc?: ScProvider) => {
const { endpoints } = NetworkList[name];
const newProvider = lc || new WsProvider(endpoints.rpc);
const connectProvider = async (lc?: ScProvider) => {
const newProvider =
lc || new WsProvider(NetworkList[network].endpoints.rpc);
if (lc) {
await newProvider.connect();
}
setProvider(newProvider);
};

// Handle an initial RPC connection.
useEffect(() => {
if (!provider && !isLightClient) {
connectProvider();
}
});

// if RPC endpoint changes, and not on light client, re-connect.
useEffectIgnoreInitial(() => {
if (!isLightClient) handleConnectApi();
}, [rpcEndpoint]);

// Trigger API connection handler on network or light client change.
useEffect(() => {
handleConnectApi();
return () => {
cancelFn?.();
};
}, [isLightClient, network]);

// Initialise provider event handlers when provider is set.
useEffectIgnoreInitial(() => {
if (provider) {
provider.on('connected', () => {
setApiStatus('connected');
});
provider.on('error', () => {
setApiStatus('disconnected');
});
getChainState();
}
}, [provider]);

return (
<APIContext.Provider
value={{
api,
consts,
chainState,
isReady: apiStatus === 'connected' && api !== null,
apiStatus,
isLightClient,
setIsLightClient,
rpcEndpoint,
setRpcEndpoint,
isReady: apiStatus === 'connected' && api !== null,
}}
>
{children}
Expand Down
2 changes: 2 additions & 0 deletions src/contexts/Api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ export interface APIContextInterface {
apiStatus: ApiStatus;
isLightClient: boolean;
setIsLightClient: (isLightClient: boolean) => void;
rpcEndpoint: string;
setRpcEndpoint: (key: string) => void;
}
1 change: 1 addition & 0 deletions src/contexts/UI/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const defaultUIContext: UIContextInterface = {
isSyncing: false,
isNetworkSyncing: false,
isPoolSyncing: false,
isBraveBrowser: false,
};
60 changes: 33 additions & 27 deletions src/contexts/UI/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { ImportedAccount } from '@polkadot-cloud/react/types';
import { useActivePools } from 'contexts/Pools/ActivePools';
import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks';
import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts';
import type { AnyJson } from 'types';
import { useApi } from '../Api';
import { useNetworkMetrics } from '../NetworkMetrics';
import { useStaking } from '../Staking';
Expand All @@ -24,19 +25,28 @@ export const UIProvider = ({ children }: { children: React.ReactNode }) => {
const { synced: activePoolsSynced } = useActivePools();
const { accounts: connectAccounts } = useImportedAccounts();

// set whether the network has been synced.
const [isNetworkSyncing, setIsNetworkSyncing] = useState(false);
// Set whether the network has been synced.
const [isNetworkSyncing, setIsNetworkSyncing] = useState<boolean>(false);

// set whether pools are being synced.
const [isPoolSyncing, setIsPoolSyncing] = useState(false);
// Set whether pools are being synced.
const [isPoolSyncing, setIsPoolSyncing] = useState<boolean>(false);

// set whether app is syncing. Includes workers (active nominations).
const [isSyncing, setIsSyncing] = useState(false);
// Set whether app is syncing. Includes workers (active nominations).
const [isSyncing, setIsSyncing] = useState<boolean>(false);

// side menu control
const [sideMenuOpen, setSideMenuOpen] = useState(false);
// Side whether the side menu is open.
const [sideMenuOpen, setSideMenu] = useState<boolean>(false);

// get side menu minimised state from local storage, default to false.
// Store whether in Brave browser. Used for light client warning.
const [isBraveBrowser, setIsBraveBrowser] = useState<boolean>(false);

// Store referneces for main app conainers.
const [containerRefs, setContainerRefsState] = useState({});
const setContainerRefs = (v: any) => {
setContainerRefsState(v);
};

// Get side menu minimised state from local storage, default to false.
const [userSideMenuMinimised, setUserSideMenuMinimisedState] = useState(
localStorageOrDefault('side_menu_minimised', false, true) as boolean
);
Expand All @@ -46,14 +56,14 @@ export const UIProvider = ({ children }: { children: React.ReactNode }) => {
setStateWithRef(v, setUserSideMenuMinimisedState, userSideMenuMinimisedRef);
};

// automatic side menu minimised
// Automatic side menu minimised.
const [sideMenuMinimised, setSideMenuMinimised] = useState(
window.innerWidth <= SideMenuStickyThreshold
? true
: userSideMenuMinimisedRef.current
);

// resize side menu callback
// Resize side menu callback.
const resizeCallback = () => {
if (window.innerWidth <= SideMenuStickyThreshold) {
setSideMenuMinimised(false);
Expand All @@ -62,20 +72,26 @@ export const UIProvider = ({ children }: { children: React.ReactNode }) => {
}
};

// resize event listener
// Resize event listener.
useEffect(() => {
(window.navigator as AnyJson)?.brave
?.isBrave()
.then(async (isBrave: boolean) => {
setIsBraveBrowser(isBrave);
});

window.addEventListener('resize', resizeCallback);
return () => {
window.removeEventListener('resize', resizeCallback);
};
}, []);

// re-configure minimised on user change
// Re-configure minimised on user change.
useEffectIgnoreInitial(() => {
resizeCallback();
}, [userSideMenuMinimised]);

// app syncing updates
// App syncing updates.
useEffect(() => {
let syncing = false;
let networkSyncing = false;
Expand Down Expand Up @@ -121,35 +137,25 @@ export const UIProvider = ({ children }: { children: React.ReactNode }) => {
setIsPoolSyncing(poolSyncing);

// eraStakers total active nominators has synced
if (!eraStakers.totalActiveNominators) {
syncing = true;
}
if (!eraStakers.totalActiveNominators) syncing = true;

setIsSyncing(syncing);
}, [isReady, staking, metrics, balances, eraStakers, activePoolsSynced]);

const setSideMenu = (v: boolean) => {
setSideMenuOpen(v);
};

const [containerRefs, setContainerRefsState] = useState({});
const setContainerRefs = (v: any) => {
setContainerRefsState(v);
};

return (
<UIContext.Provider
value={{
setSideMenu,
setUserSideMenuMinimised,
setContainerRefs,
sideMenuOpen,
userSideMenuMinimised: userSideMenuMinimisedRef.current,
sideMenuMinimised,
isSyncing,
isNetworkSyncing,
isPoolSyncing,
containerRefs,
isBraveBrowser,
userSideMenuMinimised: userSideMenuMinimisedRef.current,
}}
>
{children}
Expand Down
1 change: 1 addition & 0 deletions src/contexts/UI/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export interface UIContextInterface {
isSyncing: boolean;
isNetworkSyncing: boolean;
isPoolSyncing: boolean;
isBraveBrowser: boolean;
}
Loading

0 comments on commit c0553c2

Please sign in to comment.