diff --git a/packages/common-components/src/Icons/icons/FileWithImage.icon.tsx b/packages/common-components/src/Icons/icons/FileWithImage.icon.tsx new file mode 100644 index 0000000000..c7dbddc615 --- /dev/null +++ b/packages/common-components/src/Icons/icons/FileWithImage.icon.tsx @@ -0,0 +1,7 @@ +import * as React from "react" +import createSvgIcon from "../createSvgIcon" +import { ReactComponent as FileWithImageSvg } from "../svgs/file-with-image.svg" + +export { FileWithImageSvg } + +export default createSvgIcon() diff --git a/packages/common-components/src/Icons/index.ts b/packages/common-components/src/Icons/index.ts index 02290953a4..65bc4cf983 100644 --- a/packages/common-components/src/Icons/index.ts +++ b/packages/common-components/src/Icons/index.ts @@ -49,6 +49,7 @@ export { default as FileImageIcon, FileImageSvg } from "./icons/FileImage.icon" export { default as FilePdfIcon, FilePdfSvg } from "./icons/FilePdf.icon" export { default as FileTextIcon, FileTextSvg } from "./icons/FileText.icon" export { default as FileVideoIcon, FileVideoSvg } from "./icons/FileVideo.icon" +export { default as FileWithImageIcon, FileWithImageSvg } from "./icons/FileWithImage.icon" export { default as FolderIcon, FolderSvg } from "./icons/Folder.icon" export { default as FolderFilledIcon, FolderFilledSvg } from "./icons/FolderFilled.icon" export { default as FullscreenIcon, FullscreenSvg } from "./icons/Fullscreen.icon" diff --git a/packages/common-components/src/Icons/svgs/file-with-image.svg b/packages/common-components/src/Icons/svgs/file-with-image.svg new file mode 100644 index 0000000000..0eb3466d63 --- /dev/null +++ b/packages/common-components/src/Icons/svgs/file-with-image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/storage-ui/package.json b/packages/storage-ui/package.json index e44ad5ffe9..37d11fa268 100644 --- a/packages/storage-ui/package.json +++ b/packages/storage-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.18.37", + "@chainsafe/files-api-client": "1.18.42", "@chainsafe/web3-context": "1.3.0", "@emeraldpay/hashicon-react": "0.5.2", "@lingui/core": "^3.7.2", diff --git a/packages/storage-ui/src/Components/Elements/CidRow.tsx b/packages/storage-ui/src/Components/Elements/CidRow.tsx index 3e774af211..538d5e288c 100644 --- a/packages/storage-ui/src/Components/Elements/CidRow.tsx +++ b/packages/storage-ui/src/Components/Elements/CidRow.tsx @@ -8,6 +8,7 @@ import { CSSTheme } from "../../Themes/types" import { useStorage } from "../../Contexts/StorageContext" import { desktopGridSettings, mobileGridSettings } from "../Pages/CidsPage" import { trimChar } from "../../Utils/pathUtils" +import { IPFS_GATEWAY } from "../../Utils/Constants" const useStyles = makeStyles(({ animation, constants, breakpoints, palette }: CSSTheme) => createStyles({ @@ -66,8 +67,6 @@ interface Props { pinStatus: PinStatus } -const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY || "https://ipfs.io/ipfs/" - const CidRow = ({ pinStatus }: Props) => { const classes = useStyles() const { unpin } = useStorage() diff --git a/packages/storage-ui/src/Components/Layouts/AppNav.tsx b/packages/storage-ui/src/Components/Layouts/AppNav.tsx index 7b0f55fc32..4cc4d30471 100644 --- a/packages/storage-ui/src/Components/Layouts/AppNav.tsx +++ b/packages/storage-ui/src/Components/Layouts/AppNav.tsx @@ -17,7 +17,8 @@ import { PowerDownIcon, useLocation, KeySvg, - CreditCardOutlinedSvg + CreditCardOutlinedSvg, + FileWithImageSvg } from "@chainsafe/common-components" import { ROUTE_LINKS } from "../StorageRoutes" import { Trans } from "@lingui/macro" @@ -257,7 +258,7 @@ interface IAppNav { setNavOpen: (state: boolean) => void } -type AppNavTab = "buckets" | "cids" | "settings" | "api-keys" | "subscription" +type AppNavTab = "buckets" | "cids" | "nfts" | "settings" | "api-keys" | "subscription" const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { const { desktop } = useThemeSwitcher() @@ -283,6 +284,7 @@ const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { case "cids": return "cids" case "buckets": return "buckets" case "bucket": return "buckets" + case "nfts": return "nfts" case "api-keys": return "api-keys" case "subscription": return "subscription" case "settings": return "settings" @@ -342,6 +344,19 @@ const AppNav: React.FC = ({ navOpen, setNavOpen }: IAppNav) => { CIDs + + + + NFTs + + { const getItemOperations = useCallback( (contentType: string) => { + if (!itemOperations) return [] const result = Object.keys(itemOperations).reduce( (acc: FileOperation[], item: string) => { const matcher = new MimeMatcher(item) diff --git a/packages/storage-ui/src/Components/Modules/NFTsList/NFTItem.tsx b/packages/storage-ui/src/Components/Modules/NFTsList/NFTItem.tsx new file mode 100644 index 0000000000..b1555849e1 --- /dev/null +++ b/packages/storage-ui/src/Components/Modules/NFTsList/NFTItem.tsx @@ -0,0 +1,149 @@ +import React, { useEffect, useState } from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { + Typography, + Button +} from "@chainsafe/common-components" +import { CSSTheme } from "../../../Themes/types" +import { Trans } from "@lingui/macro" +import clsx from "clsx" +import axios from "axios" +import { trimChar } from "../../../Utils/pathUtils" +import { IPFS_GATEWAY } from "../../../Utils/Constants" + +const useStyles = makeStyles(({ constants, palette }: CSSTheme) => + createStyles({ + root: { + maxWidth: "100%" + }, + imageBox: { + width: "100%", + objectFit: "cover", + marginBottom: constants.generalUnit + }, + nameTitle: { + marginBottom: constants.generalUnit * 0.5 + }, + nameContainer: { + borderBottom: `1px solid ${palette.additional["gray"][6]}`, + marginBottom: constants.generalUnit * 0.5 + }, + cidRow: { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + "&.clickable": { + cursor: "pointer" + } + }, + cidStartSection: { + display: "table", + tableLayout: "fixed", + width: "100%", + whiteSpace: "nowrap" + }, + cidSubtitle: { + width: "35px", + display: "table-cell", + color: palette.additional["gray"][7] + }, + cid: { + display: "table-cell", + overflow: "hidden", + textOverflow: "ellipsis" + }, + copyButton: { + marginLeft: constants.generalUnit, + color: palette.primary.main, + padding: `${constants.generalUnit * 0.5}px 0 !important` + }, + copiedText: { + marginLeft: constants.generalUnit, + padding: `${constants.generalUnit * 0.5}px 0 !important` + } + }) +) + +interface NFTData { + name: string + CID: string + image?: string +} + +const NFTItem = ({ CID }: {CID: string}) => { + const classes = useStyles() + const [isCopied, setIsCopied] = useState(false) + const [NFTData, setNFTData] = useState() + + useEffect(() => { + axios.get(`${trimChar(IPFS_GATEWAY, "/")}/${CID}`) + .then(({ data }) => { setNFTData({ + CID, + name: data.name, + image: data.image?.replace("ipfs://", `${trimChar(IPFS_GATEWAY, "/")}/`) + }) }) + .catch(console.error) + }, [CID]) + + if (!NFTData) return null + + return ( +
+ {NFTData.name} +
+ + {NFTData.name} + +
+
{ + navigator.clipboard.writeText(CID) + setIsCopied(true) + setInterval(() => setIsCopied(false), 3000) + }} + > +
+ + CID :  + + + {CID} + +
+ {isCopied + ? + Copied! + + : + } +
+
+ ) +} + +export default NFTItem diff --git a/packages/storage-ui/src/Components/Modules/NFTsList/NFTsList.tsx b/packages/storage-ui/src/Components/Modules/NFTsList/NFTsList.tsx new file mode 100644 index 0000000000..ca90eeb48a --- /dev/null +++ b/packages/storage-ui/src/Components/Modules/NFTsList/NFTsList.tsx @@ -0,0 +1,152 @@ +import React from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { + Typography, + Button, + PlusIcon, + Divider, + Link, + Loading +} from "@chainsafe/common-components" +import { CSSTheme } from "../../../Themes/types" +import { Trans } from "@lingui/macro" +import { ROUTE_LINKS } from "../../StorageRoutes" +import NFTItem from "./NFTItem" +import { useFileBrowser } from "../../../Contexts/FileBrowserContext" +import EmptySvg from "../../../Media/Empty.svg" + +const useStyles = makeStyles(({ constants, breakpoints }: CSSTheme) => + createStyles({ + root: { + position: "relative", + [breakpoints.down("md")]: { + margin: constants.generalUnit + } + }, + loaderText: { + marginTop: constants.generalUnit * 2 + }, + loadingContainer: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + marginTop: constants.generalUnit * 8 + }, + noFiles: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + marginTop: constants.generalUnit * 20, + "& svg": { + maxWidth: 180, + marginBottom: constants.generalUnit * 3 + } + }, + header: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginTop: constants.generalUnit * 2, + marginBottom: constants.generalUnit * 2, + [breakpoints.down("md")]: { + marginTop: constants.generalUnit + } + }, + controls: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + "& > button": { + marginLeft: constants.generalUnit + }, + "& > a": { + textDecoration: "none" + } + }, + nftGrid: { + display: "grid", + gridColumnGap: constants.generalUnit * 5, + gridRowGap: constants.generalUnit * 7, + gridTemplateColumns: "1fr 1fr 1fr 1fr", + marginBottom: constants.generalUnit * 12, + [breakpoints.down("lg")]: { + gridTemplateColumns: "1fr 1fr 1fr", + gridColumnGap: constants.generalUnit * 3, + gridRowGap: constants.generalUnit * 5 + }, + [breakpoints.down("sm")]: { + gridTemplateColumns: "1fr 1fr" + } + } + }) +) + +const NFTsList = () => { + const classes = useStyles() + const { sourceFiles, loadingCurrentPath } = useFileBrowser() + + return ( +
+
+ + NFTs + +
+ + + +
+
+ + {loadingCurrentPath + ?
+ + + One sec, getting NFTs... + +
+ : !sourceFiles.length + ?
+ + + No NFTs to show + +
+ :
+ {sourceFiles?.map((sourceFile, i) => + + ) + } +
+ } +
+ ) +} + +export default NFTsList diff --git a/packages/storage-ui/src/Components/Pages/NFTsPage.tsx b/packages/storage-ui/src/Components/Pages/NFTsPage.tsx new file mode 100644 index 0000000000..2f7078a5c6 --- /dev/null +++ b/packages/storage-ui/src/Components/Pages/NFTsPage.tsx @@ -0,0 +1,77 @@ +import React, { useCallback, useEffect, useState } from "react" +import { useStorage, FileSystemItem } from "../../Contexts/StorageContext" +import { IFileBrowserModuleProps } from "../../Contexts/types" +import { useStorageApi } from "../../Contexts/StorageApiContext" +import { FileBrowserContext } from "../../Contexts/FileBrowserContext" +import { parseFileContentResponse } from "../../Utils/Helpers" +import { useLocalStorage } from "@chainsafe/browser-storage-hooks" +import { DISMISSED_SURVEY_KEY } from "../Modules/SurveyBanner" +import { usePageTrack } from "../../Contexts/PosthogContext" +import { Helmet } from "react-helmet-async" +import NFTsList from "../Modules/NFTsList/NFTsList" + + +const BucketPage: React.FC = () => { + const { NFTBucket, getStorageSummary } = useStorage() + const { storageApiClient } = useStorageApi() + const { localStorageGet, localStorageSet } = useLocalStorage() + + const [loadingCurrentPath, setLoadingCurrentPath] = useState(false) + const [pathContents, setPathContents] = useState([]) + const showSurvey = localStorageGet(DISMISSED_SURVEY_KEY) === "false" + usePageTrack() + + useEffect(() => { + const dismissedFlag = localStorageGet(DISMISSED_SURVEY_KEY) + + if (dismissedFlag === undefined || dismissedFlag === null) { + localStorageSet(DISMISSED_SURVEY_KEY, "false") + } + }, [localStorageGet, localStorageSet]) + + const refreshContents = useCallback((showLoading?: boolean) => { + if (!NFTBucket) return + showLoading && setLoadingCurrentPath(true) + storageApiClient.getBucketObjectChildrenList(NFTBucket.id, { path: "/" }) + .then((newContents) => { + showLoading && setLoadingCurrentPath(false) + const mappedContents = newContents.map((fcr) => parseFileContentResponse(fcr)) + setPathContents(mappedContents) + }).catch(error => { + console.error(error) + }).finally(() => { + getStorageSummary() + showLoading && setLoadingCurrentPath(false) + }) + }, [NFTBucket, storageApiClient, getStorageSummary]) + + useEffect(() => { + refreshContents(true) + }, [refreshContents]) + + return ( + <> + + + NFTs - Chainsafe Storage + + + + + ) +} + +export default BucketPage diff --git a/packages/storage-ui/src/Components/StorageRoutes.tsx b/packages/storage-ui/src/Components/StorageRoutes.tsx index 54dadcae39..e482637d4e 100644 --- a/packages/storage-ui/src/Components/StorageRoutes.tsx +++ b/packages/storage-ui/src/Components/StorageRoutes.tsx @@ -10,6 +10,7 @@ import BillingHistory from "./Pages/BillingHistory" import UploadNFTPage from "./Pages/UploadNFTPage" import ApiKeysPage from "./Pages/ApiKeysPage" import SubscriptionPage from "./Pages/SubscriptionPage" +import NFTsPage from "./Pages/NFTsPage" export const SETTINGS_BASE = "/settings" export const SETTINGS_PATHS = ["apiKeys", "plan"] as const @@ -33,7 +34,8 @@ export const ROUTE_LINKS = { BucketRoot: "/bucket", Bucket: (id: string, bucketPath: string) => `/bucket/${id}${bucketPath}`, DiscordInvite: "https://discord.gg/YYFqgHp4Tu", - UploadNFT: "/upload-nft" + UploadNFT: "/upload-nft", + NFTs: "/nfts" } const StorageRoutes = () => { @@ -103,6 +105,13 @@ const StorageRoutes = () => { redirectPath={ROUTE_LINKS.Buckets} redirectToSource /> + Promise moveItems?: (toMove: ISelectedFile[], newPath: string) => Promise diff --git a/packages/storage-ui/src/Contexts/StorageContext.tsx b/packages/storage-ui/src/Contexts/StorageContext.tsx index d0cad27474..e0053d0533 100644 --- a/packages/storage-ui/src/Contexts/StorageContext.tsx +++ b/packages/storage-ui/src/Contexts/StorageContext.tsx @@ -57,6 +57,7 @@ type StorageContext = { refreshPins: (params?: RefreshPinParams) => void unpin: (requestId: string) => void storageBuckets: Bucket[] + NFTBucket: Bucket | undefined createBucket: (name: string, fileSystemType: FileSystemType) => Promise removeBucket: (id: string) => Promise refreshBuckets: () => void @@ -85,6 +86,7 @@ const StorageProvider = ({ children }: StorageContextProps) => { const { storageApiClient, isLoggedIn } = useStorageApi() const [storageSummary, setBucketSummary] = useState() const [storageBuckets, setStorageBuckets] = useState([]) + const [NFTBucket, setNFTBucket] = useState() const [pins, setPins] = useState([]) const [pinsParams, setPinsParams] = useState<{ pageNumber: number @@ -221,7 +223,10 @@ const StorageProvider = ({ children }: StorageContextProps) => { const refreshBuckets = useCallback(() => { storageApiClient.listBuckets() - .then((buckets) => setStorageBuckets(buckets.filter(b => b.type === "fps"))) + .then((buckets) => { + setNFTBucket(buckets.find(b => b.type === "nft")) + setStorageBuckets(buckets.filter(b => b.type === "fps")) + }) .catch(console.error) .finally(() => getStorageSummary()) }, [storageApiClient, getStorageSummary]) @@ -529,6 +534,7 @@ const StorageProvider = ({ children }: StorageContextProps) => { onPreviousPins, unpin, storageBuckets, + NFTBucket, downloadFile, createBucket, removeBucket, diff --git a/packages/storage-ui/src/Utils/Constants.ts b/packages/storage-ui/src/Utils/Constants.ts index 0cff32342c..9ef76b1ac5 100644 --- a/packages/storage-ui/src/Utils/Constants.ts +++ b/packages/storage-ui/src/Utils/Constants.ts @@ -9,3 +9,6 @@ export enum CONTENT_TYPES { Audio = "audio/*" } +export const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY || "https://ipfs.io/ipfs/" + + diff --git a/packages/storage-ui/src/locales/en/messages.po b/packages/storage-ui/src/locales/en/messages.po index 47f2666e99..d9cc58ffb9 100644 --- a/packages/storage-ui/src/locales/en/messages.po +++ b/packages/storage-ui/src/locales/en/messages.po @@ -133,6 +133,9 @@ msgstr "By connecting your wallet, you agree to our <0>Terms of Service and msgid "By switching plan, you will loose access to:" msgstr "By switching plan, you will loose access to:" +msgid "CID" +msgstr "CID" + msgid "CID invalid" msgstr "CID invalid" @@ -223,6 +226,9 @@ msgstr "Continue with Web3 Wallet" msgid "Copied!" msgstr "Copied!" +msgid "Copy" +msgstr "Copy" + msgid "Create" msgstr "Create" @@ -406,6 +412,9 @@ msgstr "Move to..." msgid "NFT" msgstr "NFT" +msgid "NFTs" +msgstr "NFTs" + msgid "Name" msgstr "Name" @@ -415,6 +424,9 @@ msgstr "Name (optional)" msgid "New Key" msgstr "New Key" +msgid "New NFT" +msgstr "New NFT" + msgid "New folder" msgstr "New folder" @@ -424,6 +436,9 @@ msgstr "Next payment" msgid "No Card" msgstr "No Card" +msgid "No NFTs to show" +msgstr "No NFTs to show" + msgid "No files to show" msgstr "No files to show" @@ -445,6 +460,9 @@ msgstr "Older notifications" msgid "Once you proceed, your account is expected to make a payment within 60 minutes. If no payment is received , your plan will not change." msgstr "Once you proceed, your account is expected to make a payment within 60 minutes. If no payment is received , your plan will not change." +msgid "One sec, getting NFTs..." +msgstr "One sec, getting NFTs..." + msgid "One sec, getting files ready..." msgstr "One sec, getting files ready..." diff --git a/yarn.lock b/yarn.lock index 94adec5971..37ffb5fcf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1941,6 +1941,23 @@ "@redocly/openapi-core" "^1.0.0-beta.69" redoc-cli "^0.12.3" +"@chainsafe/files-api-client@1.18.42": + version "1.18.42" + resolved "https://registry.yarnpkg.com/@chainsafe/files-api-client/-/files-api-client-1.18.42.tgz#529ac39b2bcffd5b15569149382b8328d782a29a" + integrity sha512-h0tSk5eUZZTBaiHt+rkNQ6RAsIoDnHMFSev4qYpRkZHAM4hGt4UT0l+4D6Nr+zefFiXYQ6xmGbqCiz/MdIDOeA== + dependencies: + "@chainsafe/form-data-encoder" "^1.0.0" + "@redocly/openapi-cli" "^1.0.0-beta.58" + "@redocly/openapi-core" "^1.0.0-beta.69" + browser-or-node "^2.0.0" + formdata-node "^4.3.3" + redoc-cli "^0.12.3" + +"@chainsafe/form-data-encoder@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/form-data-encoder/-/form-data-encoder-1.0.0.tgz#1e1df2c9c8153478f0021c71ef654f210c2ef280" + integrity sha512-rQpAMNJJm10IRCe43ZDLgvOgjf5l6G96VCAlpdhtuBeUhVURA+ybOOlmD036Q1tDfmecuNdCvxuOMkl0FXwZfA== + "@chainsafe/web3-context@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@chainsafe/web3-context/-/web3-context-1.3.0.tgz#e546f160145dce12e00845d53f1a00165f6a96b2" @@ -9228,6 +9245,11 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-or-node@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-2.0.0.tgz#808ea90282a670931cdc0ea98166538a50dd0d89" + integrity sha512-3Lrks/Okgof+/cRguUNG+qRXSeq79SO3hY4QrXJayJofwJwHiGC0qi99uDjsfTwULUFSr1OGVsBkdIkygKjTUA== + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -13800,6 +13822,14 @@ format@^0.2.0: resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= +formdata-node@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.3.3.tgz#21415225be66e2c87a917bfc0fedab30a119c23c" + integrity sha512-coTew7WODO2vF+XhpUdmYz4UBvlsiTMSNaFYZlrXIqYbFd4W7bMwnoALNLE6uvNgzTg2j1JDF0ZImEfF06VPAA== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.1" + formidable@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" @@ -18307,6 +18337,11 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch-h2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz#c6188325f9bd3d834020bf0f2d6dc17ced2241ac" @@ -24877,6 +24912,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-streams-polyfill@4.0.0-beta.1: + version "4.0.0-beta.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" + integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== + web3-core-helpers@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.3.6.tgz#c478246a9abe4e5456acf42657dac2f7c330be74"