mobile-app/app/contexts/CustomServiceProvider.tsx
import { EnvironmentNetwork } from "@waveshq/walletkit-core";
import { useNetworkContext } from "@waveshq/walletkit-ui";
import { BaseLogger } from "@waveshq/walletkit-ui/dist/contexts/logger";
import React, {
createContext,
PropsWithChildren,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { useDomainContext } from "./DomainContext";
export enum CustomServiceProviderType {
DVM = "DVM",
EVM = "EVM",
ETHRPC = "ETHRPC",
}
interface CustomServiceProviderContextProps {
api: {
get: (type: CustomServiceProviderType) => Promise<string | undefined>;
set: (
url: NonNullable<string>,
type?: CustomServiceProviderType,
) => Promise<void>;
};
logger: BaseLogger;
}
interface CustomServiceProviderURLProps
extends CustomServiceProviderContextProps {
network: EnvironmentNetwork;
defaultUrl: string;
type: CustomServiceProviderType;
}
interface CustomServiceProviderLoader {
isUrlLoaded: boolean;
url: NonNullable<string>;
}
function useCustomServiceProviderUrl({
api,
network,
logger,
defaultUrl,
type,
}: CustomServiceProviderURLProps): CustomServiceProviderLoader {
const [isUrlLoaded, setIsUrlLoaded] = useState<boolean>(false);
const [url, setUrl] = useState<NonNullable<string>>(defaultUrl);
useEffect(() => {
api
.get(type)
.then((val) => {
setUrl(val !== undefined ? val : defaultUrl);
})
.catch((err) => logger.error(err))
.finally(() => setIsUrlLoaded(true));
}, [url, network]);
return {
isUrlLoaded,
url,
};
}
interface CustomServiceProviderContextI {
evmUrl: NonNullable<string>;
ethRpcUrl: NonNullable<string>;
isCustomEvmUrl: boolean;
isCustomEthRpcUrl: boolean;
defaultEvmUrl: string;
defaultEthRpcUrl: string;
setCustomUrl: (
url: NonNullable<string>,
type?: CustomServiceProviderType,
) => Promise<void>;
}
const CustomServiceProviderContext =
createContext<CustomServiceProviderContextI>(undefined as any);
export function useCustomServiceProviderContext(): CustomServiceProviderContextI {
return useContext(CustomServiceProviderContext);
}
function getBlockscoutUrl(network: EnvironmentNetwork) {
// TODO: Add proper blockscout url for each network
switch (network) {
case EnvironmentNetwork.LocalPlayground:
case EnvironmentNetwork.RemotePlayground:
case EnvironmentNetwork.DevNet:
case EnvironmentNetwork.Changi:
return "https://blockscout.changi.ocean.jellyfishsdk.com";
case EnvironmentNetwork.TestNet:
return "https://blockscout.testnet.ocean.jellyfishsdk.com";
case EnvironmentNetwork.MainNet:
default:
return "https://blockscout.mainnet.ocean.jellyfishsdk.com";
}
}
function getEthRpcUrl(network: EnvironmentNetwork) {
// TODO: Add proper ethereum RPC URLs for each network
switch (network) {
case EnvironmentNetwork.LocalPlayground:
return "http://localhost:19551";
case EnvironmentNetwork.RemotePlayground:
case EnvironmentNetwork.DevNet:
case EnvironmentNetwork.Changi:
return "http://34.34.156.49:20551"; // TODO: add final eth rpc url for changi, devnet and remote playground
case EnvironmentNetwork.MainNet:
return "https://eth.mainnet.ocean.jellyfishsdk.com"; // TODO: add final eth rpc url for mainnet, with proper domain name
case EnvironmentNetwork.TestNet:
default:
return "https://eth.testnet.ocean.jellyfishsdk.com";
}
}
export function CustomServiceProvider(
props: CustomServiceProviderContextProps & PropsWithChildren<any>,
): JSX.Element | null {
const { api, children, logger } = props;
const { network } = useNetworkContext();
const { isEvmFeatureEnabled } = useDomainContext();
const params = { api, network, logger };
// EVM
const defaultEvmUrl = getBlockscoutUrl(network);
const { url: evmUrl } = useCustomServiceProviderUrl({
...params,
defaultUrl: defaultEvmUrl,
type: CustomServiceProviderType.EVM,
});
// ETH-RPC
const defaultEthRpcUrl = getEthRpcUrl(network);
const { url: ethRpcUrl } = useCustomServiceProviderUrl({
...params,
defaultUrl: defaultEthRpcUrl,
type: CustomServiceProviderType.ETHRPC,
});
const [currentUrl, setCurrentUrl] = useState<{
[key in CustomServiceProviderType]: string;
}>({
[CustomServiceProviderType.DVM]: "", // not used here, added only to satify `key` type
[CustomServiceProviderType.EVM]: evmUrl,
[CustomServiceProviderType.ETHRPC]: ethRpcUrl,
});
useEffect(() => {
const url = isEvmFeatureEnabled ? evmUrl : defaultEvmUrl;
setCurrentUrl((prevState) => ({
...prevState,
[CustomServiceProviderType.EVM]: url,
}));
}, [evmUrl, isEvmFeatureEnabled]);
useEffect(() => {
const url = isEvmFeatureEnabled ? ethRpcUrl : defaultEthRpcUrl;
setCurrentUrl((prevState) => ({
...prevState,
[CustomServiceProviderType.ETHRPC]: url,
}));
}, [ethRpcUrl, isEvmFeatureEnabled]);
const isCustomEvmUrl = useMemo(
() => currentUrl.EVM !== defaultEvmUrl,
[currentUrl.EVM, defaultEvmUrl],
);
const isCustomEthRpcUrl = useMemo(
() => currentUrl.ETHRPC !== defaultEthRpcUrl,
[currentUrl.ETHRPC, defaultEthRpcUrl],
);
const setCustomUrl = async (
newUrl: string,
type: CustomServiceProviderType = CustomServiceProviderType.DVM,
): Promise<void> => {
setCurrentUrl((prevState) => ({ ...prevState, [type]: newUrl }));
await api.set(newUrl, type);
};
const context: CustomServiceProviderContextI = {
evmUrl: currentUrl.EVM ?? defaultEvmUrl,
ethRpcUrl: currentUrl.ETHRPC ?? defaultEthRpcUrl,
isCustomEvmUrl,
isCustomEthRpcUrl,
defaultEvmUrl,
defaultEthRpcUrl,
setCustomUrl,
};
return (
<CustomServiceProviderContext.Provider value={context}>
{children}
</CustomServiceProviderContext.Provider>
);
}