diff --git a/packages/gaming-ui/package.json b/packages/gaming-ui/package.json index 7f9680629a..c7b8c6f7bd 100644 --- a/packages/gaming-ui/package.json +++ b/packages/gaming-ui/package.json @@ -6,7 +6,7 @@ "@babel/core": "^7.12.10", "@babel/runtime": "^7.0.0", "@chainsafe/browser-storage-hooks": "^1.0.1", - "@chainsafe/files-api-client": "1.17.5", + "@chainsafe/files-api-client": "^1.18.5", "@chainsafe/web3-context": "1.1.4", "@lingui/core": "^3.7.2", "@lingui/react": "^3.7.2", @@ -18,7 +18,6 @@ "babel-preset-env": "^1.7.0", "babel-preset-react": "^6.24.1", "bnc-onboard": "1.32.0", - "cids": "^1.1.6", "clsx": "^1.1.1", "dayjs": "^1.9.7", "eth-crypto": "^1.8.0", @@ -26,14 +25,8 @@ "formik": "^2.2.5", "mime-matcher": "^1.0.5", "react": "^16.14.0", - "react-beforeunload": "^2.4.0", - "react-dnd": "14.0.2", - "react-dnd-html5-backend": "14.0.0", "react-dom": "^16.14.0", - "react-h5-audio-player": "^3.5.0", "react-hotkeys-hook": "^2.4.0", - "react-markdown": "^5.0.3", - "react-pdf": "5.3.0", "react-scripts": "3.4.4", "react-swipeable": "^6.0.1", "react-toast-notifications": "^2.4.0", @@ -73,9 +66,8 @@ "build": "craco --max_old_space_size=4096 build", "sentry": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); node scripts/sentry.js)", "release": "(export REACT_APP_SENTRY_RELEASE=$(sentry-cli releases propose-version); yarn compile && yarn build && node scripts/sentry.js)", - "test": "yarn test:clean && cypress open", - "test:ci": "yarn test:clean && cypress run --browser chrome --headless", - "test:clean": "rimraf cypress/fixtures/storage", + "test": "cypress open", + "test:ci": "cypress run --browser chrome --headless", "analyze": "source-map-explorer 'build/static/js/*.js'", "extract": "lingui extract", "compile": "lingui compile", diff --git a/packages/gaming-ui/public/ChainSafe-logo.png b/packages/gaming-ui/public/ChainSafe-logo.png new file mode 100644 index 0000000000..cfffdc4a82 Binary files /dev/null and b/packages/gaming-ui/public/ChainSafe-logo.png differ diff --git a/packages/gaming-ui/public/abstract-image-large.png b/packages/gaming-ui/public/abstract-image-large.png deleted file mode 100644 index 8780e05d66..0000000000 Binary files a/packages/gaming-ui/public/abstract-image-large.png and /dev/null differ diff --git a/packages/gaming-ui/public/android-chrome-192x192.png b/packages/gaming-ui/public/android-chrome-192x192.png index f4b70a3279..cfffdc4a82 100644 Binary files a/packages/gaming-ui/public/android-chrome-192x192.png and b/packages/gaming-ui/public/android-chrome-192x192.png differ diff --git a/packages/gaming-ui/public/android-chrome-512x512.png b/packages/gaming-ui/public/android-chrome-512x512.png index 7a884ae88d..300ed95cd2 100644 Binary files a/packages/gaming-ui/public/android-chrome-512x512.png and b/packages/gaming-ui/public/android-chrome-512x512.png differ diff --git a/packages/gaming-ui/public/apple-touch-icon.png b/packages/gaming-ui/public/apple-touch-icon.png index f4b70a3279..cfffdc4a82 100644 Binary files a/packages/gaming-ui/public/apple-touch-icon.png and b/packages/gaming-ui/public/apple-touch-icon.png differ diff --git a/packages/gaming-ui/public/favicon-16x16.png b/packages/gaming-ui/public/favicon-16x16.png index 977cea724a..c515dca67e 100644 Binary files a/packages/gaming-ui/public/favicon-16x16.png and b/packages/gaming-ui/public/favicon-16x16.png differ diff --git a/packages/gaming-ui/public/favicon-32x32.png b/packages/gaming-ui/public/favicon-32x32.png index 2b6e4c1c82..ee6522854f 100644 Binary files a/packages/gaming-ui/public/favicon-32x32.png and b/packages/gaming-ui/public/favicon-32x32.png differ diff --git a/packages/gaming-ui/public/favicon.ico b/packages/gaming-ui/public/favicon.ico index ee2819a2e7..6333263689 100644 Binary files a/packages/gaming-ui/public/favicon.ico and b/packages/gaming-ui/public/favicon.ico differ diff --git a/packages/gaming-ui/src/App.tsx b/packages/gaming-ui/src/App.tsx index 6eaf3956b7..ea78e7f068 100644 --- a/packages/gaming-ui/src/App.tsx +++ b/packages/gaming-ui/src/App.tsx @@ -11,6 +11,7 @@ import { lightTheme } from "./Themes/LightTheme" import { darkTheme } from "./Themes/DarkTheme" import { useLocalStorage } from "@chainsafe/browser-storage-hooks" import { GamingApiProvider } from "./Contexts/GamingApiContext" +import { UserProvider } from "./Contexts/UserContext" if ( process.env.NODE_ENV === "production" && @@ -101,11 +102,13 @@ const App = () => { apiUrl={apiUrl} withLocalStorage={true} > - - - - - + + + + + + + diff --git a/packages/gaming-ui/src/Components/Elements/CustomModal.tsx b/packages/gaming-ui/src/Components/Elements/CustomModal.tsx index a7aa276273..224b120ab3 100644 --- a/packages/gaming-ui/src/Components/Elements/CustomModal.tsx +++ b/packages/gaming-ui/src/Components/Elements/CustomModal.tsx @@ -25,7 +25,7 @@ const useStyles = makeStyles(({ constants, breakpoints }: CSGTheme) => borderRadiusRightBottom: 0 } }, - close: { + closeIcon: { [breakpoints.down("md")]: {} } }) @@ -48,7 +48,7 @@ const CustomModal: React.FC = ({ `/settings/${path}`, PrivacyPolicy: "https://files.chainsafe.io/privacy-policy", @@ -16,24 +18,28 @@ export const ROUTE_LINKS = { ChainSafe: "https://chainsafe.io/" } - - const GamingRoutes = () => { const { isLoggedIn } = useGamingApi() return ( + diff --git a/packages/gaming-ui/src/Components/Layouts/AppHeader.tsx b/packages/gaming-ui/src/Components/Layouts/AppHeader.tsx index b9fe3d31fc..45ed28b61a 100644 --- a/packages/gaming-ui/src/Components/Layouts/AppHeader.tsx +++ b/packages/gaming-ui/src/Components/Layouts/AppHeader.tsx @@ -14,6 +14,7 @@ import { ROUTE_LINKS } from "../GamingRoutes" import { Trans } from "@lingui/macro" import { CSGTheme } from "../../Themes/types" import { useGamingApi } from "../../Contexts/GamingApiContext" +import { useUser } from "../../Contexts/UserContext" const useStyles = makeStyles( ({ palette, animation, breakpoints, constants, zIndex }: CSGTheme) => { @@ -154,6 +155,7 @@ const AppHeader = ({ navOpen, setNavOpen }: IAppHeader) => { const classes = useStyles() const { isLoggedIn, logout } = useGamingApi() const { history } = useHistory() + const { getProfileTitle } = useUser() const signOut = useCallback(async () => { logout() @@ -173,17 +175,21 @@ const AppHeader = ({ navOpen, setNavOpen }: IAppHeader) => { <>
signOut(), contents: ( -
+
Sign Out @@ -205,14 +211,14 @@ const AppHeader = ({ navOpen, setNavOpen }: IAppHeader) => { /> - Gaming + Dashboard diff --git a/packages/gaming-ui/src/Components/Layouts/AppNav.tsx b/packages/gaming-ui/src/Components/Layouts/AppNav.tsx index c7e5705535..b312cb4e21 100644 --- a/packages/gaming-ui/src/Components/Layouts/AppNav.tsx +++ b/packages/gaming-ui/src/Components/Layouts/AppNav.tsx @@ -229,7 +229,7 @@ const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => {
diff --git a/packages/gaming-ui/src/Components/Modules/ApiKeys.tsx b/packages/gaming-ui/src/Components/Modules/ApiKeys.tsx index cfa12bd5b2..d22d350fe2 100644 --- a/packages/gaming-ui/src/Components/Modules/ApiKeys.tsx +++ b/packages/gaming-ui/src/Components/Modules/ApiKeys.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useCallback } from "react" -import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { makeStyles, createStyles, debounce } from "@chainsafe/common-theme" import { useGamingApi } from "../../Contexts/GamingApiContext" import { AccessKey } from "@chainsafe/files-api-client" import { @@ -14,16 +14,18 @@ import { TableCell, MenuDropdown, DeleteSvg, - MoreIcon } from "@chainsafe/common-components" + MoreIcon, + CopyIcon, + Modal +} from "@chainsafe/common-components" import { CSGTheme } from "../../Themes/types" import { Trans } from "@lingui/macro" import dayjs from "dayjs" -import SecretField from "../Elements/SecretField" -export const desktopGridSettings = "3fr 400px 100px 200px 70px !important" -export const mobileGridSettings = "3fr 400px 100px 200px 70px !important" +export const desktopGridSettings = "2fr 2fr 1fr 1.5fr 70px !important" +export const mobileGridSettings = "2fr 2fr 1fr 1.5fr 70px !important" -const useStyles = makeStyles(({ constants, breakpoints, animation }: CSGTheme) => +const useStyles = makeStyles(({ constants, breakpoints, animation, zIndex, palette }: CSGTheme) => createStyles({ root: { position: "relative" @@ -82,6 +84,55 @@ const useStyles = makeStyles(({ constants, breakpoints, animation }: CSGTheme) = "& svg": { fill: constants.fileSystemItemRow.menuIcon } + }, + modalRoot: { + zIndex: zIndex?.blocker, + [breakpoints.down("md")]: {} + }, + modalInner: { + [breakpoints.down("md")]: { + bottom: + Number(constants?.mobileButtonHeight) + constants.generalUnit, + borderTopLeftRadius: `${constants.generalUnit * 1.5}px`, + borderTopRightRadius: `${constants.generalUnit * 1.5}px`, + maxWidth: `${breakpoints.width("md")}px !important` + } + }, + modalHeading: { + textAlign: "center", + marginBottom: constants.generalUnit * 4 + }, + modalContent: { + display: "flex", + flexDirection: "column", + padding: constants.generalUnit * 4 + }, + secretContainer: { + display: "flex", + justifyContent: "space-between", + marginBottom: constants.generalUnit * 0.5 + }, + copyBox: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + cursor: "pointer", + color: palette.text.secondary + }, + copyIcon: { + fontSize: "14px", + fill: constants.profile.icon, + [breakpoints.down("md")]: { + fontSize: "18px", + fill: palette.additional["gray"][9] + } + }, + secret: { + maxWidth: "95%", + overflowWrap: "anywhere" + }, + field: { + marginBottom: constants.generalUnit * 4 } }) ) @@ -90,16 +141,35 @@ const ApiKeys = () => { const classes = useStyles() const { gamingApiClient } = useGamingApi() const [keys, setKeys] = useState([]) + const [newKey, setNewKey] = useState() + const [isNewKeyModalOpen, setIsNewKeyModalOpen] = useState(false) + const [copiedSecret, setCopiedSecret] = useState(false) + const debouncedCopiedSecret = + debounce(() => setCopiedSecret(false), 3000) + + const copySecret = async () => { + if (newKey?.secret) { + try { + await navigator.clipboard.writeText(newKey.secret) + setCopiedSecret(true) + debouncedCopiedSecret() + } catch (err) { + console.error(err) + } + } + } const fetchAccessKeys = useCallback(() => { gamingApiClient.listAccessKeys() - .then(result => setKeys(result)) + .then(setKeys) .catch(console.error) }, [gamingApiClient]) - const createAccessKey = useCallback(() => { - gamingApiClient.createAccessKey() + const createStorageAccessKey = useCallback(() => { + gamingApiClient.createAccessKey({ type: "gaming" }) + .then(setNewKey) .then(fetchAccessKeys) + .then(() => setIsNewKeyModalOpen(true)) .catch(console.error) }, [fetchAccessKeys, gamingApiClient]) @@ -114,121 +184,184 @@ const ApiKeys = () => { }, [fetchAccessKeys]) return ( -
-
- +
+
+ + + API Keys + + +
+ +
+
+ - - API Keys - - -
+ + + + Id + + + Type + + + Status + + + Created At + + {/* Menu */} + + + + {keys.map(k => + + + + {k.id} + + + + + {k.type} + + + + + {k.status} + + + + + {dayjs(k.created_at).format("DD MMM YYYY h:mm a")} + + + + + + + Delete Key + + + ), + onClick: () => deleteAccessKey(k.id) + }]} + classNames={{ + icon: classes.dropdownIcon, + options: classes.dropdownOptions, + item: classes.dropdownItem + }} + indicator={MoreIcon} + /> + + )} + +
+
+ +
+ + New Key + + + Key ID + + {newKey?.id} + + Secret + +
+
+ + Make sure to save the secret, as it can only be displayed once. + + {copiedSecret && ( + + Copied! + + )} +
+
+ + {newKey?.secret} + + +
+
-
- - - - - Id - - - Secret - - - Status - - - Created At - - {/* Menu */} - - - - {keys.map(k => - - - - {k.id} - - - - - - - - {k.status} - - - - - {dayjs(k.created_at).format("DD MMM YYYY h:mm a")} - - - - - - - Delete Key - - - ), - onClick: () => deleteAccessKey(k.id) - }]} - classNames={{ - icon: classes.dropdownIcon, - options: classes.dropdownOptions, - item: classes.dropdownItem - }} - indicator={MoreIcon} - /> - - )} - -
-
+ + ) } diff --git a/packages/gaming-ui/src/Components/Pages/DashboardPage.tsx b/packages/gaming-ui/src/Components/Pages/DashboardPage.tsx new file mode 100644 index 0000000000..8a3df055c5 --- /dev/null +++ b/packages/gaming-ui/src/Components/Pages/DashboardPage.tsx @@ -0,0 +1,28 @@ +import React from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { Typography } from "@chainsafe/common-components" + +const useStyles = makeStyles(() => + createStyles({ + root: { + position: "relative" + } + }) +) + +const DashboardPage = () => { + const classes = useStyles() + + return ( +
+ + Dashboard + +
+ ) +} + +export default DashboardPage diff --git a/packages/gaming-ui/src/Components/Pages/LoginPage.tsx b/packages/gaming-ui/src/Components/Pages/LoginPage.tsx index b91b30d7a0..9b93273727 100644 --- a/packages/gaming-ui/src/Components/Pages/LoginPage.tsx +++ b/packages/gaming-ui/src/Components/Pages/LoginPage.tsx @@ -128,7 +128,7 @@ const LoginPage = () => {
- Gaming + Gaming Dashboard <> @@ -142,6 +142,7 @@ const LoginPage = () => { + } ) => TabPaneOrigin(props) -const useStyles = makeStyles(({ constants, breakpoints, palette }: ITheme) => +const useStyles = makeStyles(({ constants, breakpoints, palette }: CSGTheme) => createStyles({ title: { marginTop: constants.generalUnit, @@ -90,7 +89,7 @@ const useStyles = makeStyles(({ constants, breakpoints, palette }: ITheme) => marginBottom: 0, display: "flex", flexDirection: "column", - width: 226, + width: 200, borderRightColor: palette.additional["gray"][4], borderRightWidth: 1, borderRightStyle: "solid", @@ -137,19 +136,9 @@ const SettingsPage: React.FC = () => { (path: string) => redirect(ROUTE_LINKS.Settings(path as SettingsPath)) , [redirect]) - const crumbs: Crumb[] = useMemo(() => [ - { - text: t`Settings` - } - ], []) - return (
- redirect(ROUTE_LINKS.SettingsRoot)} - /> { @@ -19,14 +21,14 @@ const getProviderSpecificParams = (loginType: LOGIN_TYPE): return { typeOfLogin: loginType, clientId: process.env.REACT_APP_GOOGLE_CLIENT_ID || "", - verifier: "" + verifier: "chainsafe-uuid-testnet" } } case "github":{ return { typeOfLogin: loginType, clientId: process.env.REACT_APP_AUTH0_CLIENT_ID || "", - verifier: "", + verifier: "chainsafe-uuid-testnet", jwtParams: { domain: process.env.REACT_APP_AUTH0_DOMAIN || "" } @@ -60,6 +62,7 @@ type GamingApiContext = { userInfo?: GamingUserInfo gamingApiClient: IFilesApiClient isLoggedIn: boolean | undefined + isReturningUser: boolean selectWallet: () => Promise resetAndSelectWallet: () => Promise login(loginType: IdentityProvider, tokenInfo?: {token: IdentityToken; email: string}): Promise @@ -75,7 +78,7 @@ const GamingApiProvider = ({ apiUrl, withLocalStorage = true, children }: Gaming const maintenanceMode = process.env.REACT_APP_MAINTENANCE_MODE === "true" const { wallet, onboard, checkIsReady, isReady, provider, address } = useWeb3() - const { localStorageRemove, localStorageGet, localStorageSet } = useLocalStorage() + const { canUseLocalStorage, localStorageRemove, localStorageGet, localStorageSet } = useLocalStorage() const { sessionStorageRemove, sessionStorageGet, sessionStorageSet } = useSessionStorage() // initializing api @@ -102,17 +105,30 @@ const GamingApiProvider = ({ apiUrl, withLocalStorage = true, children }: Gaming { exp: number; enckey?: string; mps?: string; uuid: string } | undefined >(undefined) + // returning user + const isReturningUserLocal = localStorageGet(isReturningUserStorageKey) + const [isReturningUser, setIsReturningUser] = useState(!!isReturningUserLocal) + const setTokensAndSave = useCallback((accessToken: Token, refreshToken: Token) => { setAccessToken(accessToken) setRefreshToken(refreshToken) - - withLocalStorage - ? localStorageSet(tokenStorageKey, refreshToken.token) - : sessionStorageSet(tokenStorageKey, refreshToken.token) - + refreshToken.token && withLocalStorage && localStorageSet(tokenStorageKey, refreshToken.token) + !withLocalStorage && sessionStorageSet(tokenStorageKey, refreshToken.token) accessToken.token && gamingApiClient.setToken(accessToken.token) }, [gamingApiClient, localStorageSet, sessionStorageSet, withLocalStorage]) + const setReturningUser = () => { + // set returning user + localStorageSet(isReturningUserStorageKey, "returning") + setIsReturningUser(true) + } + + useEffect(() => { + if (userInfo) { + sessionStorage.setItem(TORUS_USERINFO_KEY, JSON.stringify(userInfo)) + } + }, [userInfo]) + useEffect(() => { const loginType = userInfo?.typeOfLogin as LOGIN_TYPE @@ -151,8 +167,8 @@ const GamingApiProvider = ({ apiUrl, withLocalStorage = true, children }: Gaming error.config._retry = true const refreshTokenLocal = (withLocalStorage) - ? localStorage.getItem(tokenStorageKey) - : sessionStorage.getItem(tokenStorageKey) + ? localStorageGet(tokenStorageKey) + : sessionStorageGet(tokenStorageKey) if (refreshTokenLocal) { const refreshTokenApiClient = new FilesApiClient( {}, @@ -211,7 +227,7 @@ const GamingApiProvider = ({ apiUrl, withLocalStorage = true, children }: Gaming initializeApiClient() // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, [canUseLocalStorage]) const selectWallet = async () => { if (onboard && !isReady) { @@ -363,6 +379,7 @@ const GamingApiProvider = ({ apiUrl, withLocalStorage = true, children }: Gaming publicAddress: userInfo.address }) setTokensAndSave(access_token, refresh_token) + setReturningUser() } catch(error) { console.error(error) throw new Error("Login Error") @@ -385,6 +402,7 @@ const GamingApiProvider = ({ apiUrl, withLocalStorage = true, children }: Gaming value={{ gamingApiClient: gamingApiClient, isLoggedIn: isLoggedIn(), + isReturningUser: isReturningUser, login, resetStatus: () => setStatus("initialized"), status, @@ -403,7 +421,7 @@ const GamingApiProvider = ({ apiUrl, withLocalStorage = true, children }: Gaming const useGamingApi = () => { const context = React.useContext(GamingApiContext) if (context === undefined) { - throw new Error("useStorage must be used within a StorageProvider") + throw new Error("useGamingApi must be used within a GamingApiProvider") } return context } diff --git a/packages/gaming-ui/src/Contexts/UserContext.tsx b/packages/gaming-ui/src/Contexts/UserContext.tsx new file mode 100644 index 0000000000..a91ac10712 --- /dev/null +++ b/packages/gaming-ui/src/Contexts/UserContext.tsx @@ -0,0 +1,93 @@ +import * as React from "react" +import { useCallback, useEffect } from "react" +import { useGamingApi } from "./GamingApiContext" +import { useState } from "react" + +type UserContextProps = { + children: React.ReactNode | React.ReactNode[] +} + +export type Profile = { + userId: string + firstName?: string + lastName?: string + publicAddress?: string + email?: string + createdAt?: Date + username?: string +} + +interface IUserContext { + profile: Profile | undefined + refreshProfile(): Promise + getProfileTitle(): string +} + +const UserContext = React.createContext(undefined) + +const UserProvider = ({ children }: UserContextProps) => { + const { gamingApiClient, isLoggedIn } = useGamingApi() + + const [profile, setProfile] = useState(undefined) + + const refreshProfile = useCallback(async () => { + try { + const profileApiData = await gamingApiClient.getUser() + + const profileState = { + userId: profileApiData.uuid, + firstName: profileApiData.first_name, + lastName: profileApiData.last_name, + email: profileApiData.email, + publicAddress: profileApiData.public_address?.toLowerCase(), + createdAt: profileApiData.created_at, + username: profileApiData.username + } + setProfile(profileState) + return Promise.resolve() + } catch (error) { + return Promise.reject("There was an error getting profile.") + } + }, [gamingApiClient]) + + useEffect(() => { + if (isLoggedIn) { + refreshProfile() + .catch(console.error) + } + }, [isLoggedIn, refreshProfile]) + + const getProfileTitle = () => { + if (profile?.publicAddress) { + const { publicAddress } = profile + return `${publicAddress.substr(0, 6)}...${publicAddress.substr( + publicAddress.length - 6, + publicAddress.length + )}` + } else { + return profile?.firstName || profile?.email || "" + } + } + + return ( + + {children} + + ) +} + +const useUser = () => { + const context = React.useContext(UserContext) + if (context === undefined) { + throw new Error("useUser must be used within a UserProvider") + } + return context +} + +export { UserProvider, useUser } diff --git a/packages/gaming-ui/src/locales/en/messages.po b/packages/gaming-ui/src/locales/en/messages.po index 00daf5aaa3..e314e4a4a9 100644 --- a/packages/gaming-ui/src/locales/en/messages.po +++ b/packages/gaming-ui/src/locales/en/messages.po @@ -52,6 +52,9 @@ msgstr "Continue with Google" msgid "Continue with Web3 Wallet" msgstr "Continue with Web3 Wallet" +msgid "Copied!" +msgstr "Copied!" + msgid "Created At" msgstr "Created At" @@ -94,9 +97,18 @@ msgstr "Hold on, we are logging you in…" msgid "Id" msgstr "Id" +msgid "Key ID" +msgstr "Key ID" + msgid "Learn more about ChainSafe" msgstr "Learn more about ChainSafe" +msgid "Make sure to save the secret, as it can only be displayed once." +msgstr "Make sure to save the secret, as it can only be displayed once." + +msgid "New Key" +msgstr "New Key" + msgid "Please enter a valid email" msgstr "Please enter a valid email" @@ -151,6 +163,9 @@ msgstr "There was an error connecting your wallet" msgid "Try again" msgstr "Try again" +msgid "Type" +msgstr "Type" + msgid "Use a different login method" msgstr "Use a different login method" diff --git a/yarn.lock b/yarn.lock index c1d9aa920f..436471c3cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1925,11 +1925,6 @@ resolved "https://registry.yarnpkg.com/@chainsafe/browser-storage-hooks/-/browser-storage-hooks-1.0.1.tgz#26d32cde1999914db755a631e2643823c54959f7" integrity sha512-Q4b5gQAZnsRXKeADspd5isqfwwhhXjDk70y++YadufA6EZ3tf340oW0OVszp74KaGEw+CAYFGQR4X7bzpZ3x9Q== -"@chainsafe/files-api-client@1.17.5": - version "1.17.5" - resolved "https://registry.yarnpkg.com/@chainsafe/files-api-client/-/files-api-client-1.17.5.tgz#35dd9c4baf0833ee9b654495979ed1a66fb02f8d" - integrity sha512-5L7bAJr3siPgadDkGwhfo31MCkda01v/rTkLxUta13g6QG+pu0oKDoug+dtm8f9EPoH/rI89L2JHFM7XFrYveQ== - "@chainsafe/files-api-client@^1.18.5": version "1.18.5" resolved "https://registry.yarnpkg.com/@chainsafe/files-api-client/-/files-api-client-1.18.5.tgz#2f4ed5989fa12c0e5823388384f61281a73cf27c"