diff --git a/.eslintrc.json b/.eslintrc.json index c12af6bd29..c6600fc345 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,11 @@ { - "extends": ["next/core-web-vitals", "prettier", "plugin:import/recommended", "plugin:import/typescript", "plugin:@typescript-eslint/recommended"], + "extends": [ + "next/core-web-vitals", + "prettier", + "plugin:import/recommended", + "plugin:import/typescript", + "plugin:@typescript-eslint/recommended" + ], "plugins": ["prettier", "import", "simple-import-sort", "@typescript-eslint"], "rules": { "@typescript-eslint/no-empty-interface": "off", @@ -13,6 +19,7 @@ "import/newline-after-import": "error", "import/no-duplicates": "error", "import/no-named-as-default": "error", - "import/no-unresolved": "warn" + "import/no-unresolved": "warn", + "@next/next/no-img-element": "off" } } diff --git a/package.json b/package.json index e30f38634e..97dc26604d 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,6 @@ "@walletconnect/web3-provider": "^1.7.1", "eth-provider": "^0.10.0", "ethereum-blockies-base64": "^1.0.2", - "react-intl": "^5.24.3", - "walletlink": "^2.4.0", - "web3modal": "^1.9.5", "ethers": "^5.5.3", "graphql": "^16.2.0", "graphql-ws": "^5.5.5", @@ -45,7 +42,9 @@ "react": "latest", "react-dom": "latest", "reflect-metadata": "^0.1.13", - "subscriptions-transport-ws": "^0.11.0" + "subscriptions-transport-ws": "^0.11.0", + "walletlink": "^2.4.0", + "web3modal": "^1.9.5" }, "devDependencies": { "@babel/core": "^7.16.7", diff --git a/src/layouts/WalletWidget.tsx b/src/layouts/WalletWidget.tsx index 5f367540bb..284f2d91e0 100644 --- a/src/layouts/WalletWidget.tsx +++ b/src/layouts/WalletWidget.tsx @@ -6,17 +6,15 @@ import RemoveCircleOutlineRoundedIcon from '@mui/icons-material/RemoveCircleOutl import { Button, Divider, ListItemIcon, ListItemText } from '@mui/material'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; -import { useTheme } from '@mui/system'; import makeBlockie from 'ethereum-blockies-base64'; import React, { useEffect, useState } from 'react'; import useGetEns from 'src/libs/hooks/use-get-ens'; import { useWeb3Context } from 'src/libs/web3-data-provider/Web3ContextProvider'; import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig'; -import { ColorModeContext } from './AppGlobalStyles'; - export default function WalletWidget() { - const { connectWallet, disconnectWallet, currentAccount, connected, chainId } = useWeb3Context(); + const { connectWallet, disconnectWallet, currentAccount, connected, chainId, switchNetwork } = + useWeb3Context(); const { name: ensName, avatar: ensAvatar } = useGetEns(currentAccount); const ensNameAbbreviated = ensName @@ -25,10 +23,7 @@ export default function WalletWidget() { : ensName : undefined; - const theme = useTheme(); - const colorMode = React.useContext(ColorModeContext); - - const [anchorEl, setAnchorEl] = useState(null); + const [anchorEl, setAnchorEl] = useState(null); const [useBlockie, setUseBlockie] = useState(false); useEffect(() => { @@ -41,7 +36,7 @@ export default function WalletWidget() { const networkConfig = getNetworkConfig(chainId); - const handleClick = (event: { currentTarget: React.SetStateAction }) => { + const handleClick = (event: React.MouseEvent) => { if (!connected) { connectWallet(); } else { @@ -72,6 +67,11 @@ export default function WalletWidget() { setAnchorEl(null); }; + const handleSwitchNetwork = () => { + switchNetwork(137); + setAnchorEl(null); + }; + return (
); diff --git a/src/libs/web3-data-provider/Web3ContextProvider.tsx b/src/libs/web3-data-provider/Web3ContextProvider.tsx index f67bf260b5..2c607b7186 100644 --- a/src/libs/web3-data-provider/Web3ContextProvider.tsx +++ b/src/libs/web3-data-provider/Web3ContextProvider.tsx @@ -1,6 +1,7 @@ import { JsonRpcProvider, Network, Web3Provider } from '@ethersproject/providers'; import { providers } from 'ethers'; import React, { ReactElement, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig'; import Web3Modal from 'web3modal'; export type Web3Data = { @@ -11,6 +12,7 @@ export type Web3Data = { provider: JsonRpcProvider | undefined; web3Modal: Web3Modal; chainId: number; + switchNetwork: (chainId: number) => Promise; }; export type Web3ContextData = { @@ -31,7 +33,7 @@ export const useWeb3Context = () => { const { web3ProviderData } = web3Context; return useMemo(() => { return { ...web3ProviderData }; - }, [web3Context]); + }, [web3ProviderData]); }; export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ children }) => { @@ -39,15 +41,13 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ chil const [connected, setConnected] = useState(false); const [chainId, setChainId] = useState(1); const [currentAccount, setCurrentAccount] = useState(''); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [web3Provider, setWeb3Provider] = useState(undefined as any); const [web3Modal, setWeb3Modal] = useState(undefined as unknown as Web3Modal); useEffect(() => { - if (web3Modal?.cachedProvider) connectWallet(); - }, [web3Modal]); - - useEffect(() => { - import('./modalOptions').then((m) => setWeb3Modal(m.getWeb3Modal(chainId))); + import('./modalOptions').then((m) => setWeb3Modal(m.getWeb3Modal())); }, [chainId, currentAccount]); // provider events subscriptions @@ -75,6 +75,7 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ chil // web 3 modal const connectWallet = useCallback(async () => { const providerInstance = await web3Modal.connect(); + setWeb3Provider(providerInstance); await initSubscriptions(providerInstance); const ethProvider = new providers.Web3Provider(providerInstance); @@ -91,13 +92,59 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ chil setConnected(true); return ethProvider; - }, [provider, web3Modal, connected]); + }, [web3Modal, initSubscriptions]); const disconnectWallet = useCallback(async () => { web3Modal.clearCachedProvider(); setConnected(false); setCurrentAccount(''); - }, [provider, web3Modal, connected]); + if (web3Provider) { + if (web3Provider.close) { + await web3Provider.close(); + } else if (web3Provider.disconnect) { + web3Provider.disconnect(); + } else { + console.log('provider: ', web3Provider); + } + } + }, [web3Modal, web3Provider]); + + const switchNetwork = useCallback( + async (newChainId: number) => { + if (provider) { + try { + await provider.send('wallet_switchEthereumChain', [ + { chainId: `0x${newChainId.toString(16)}` }, + ]); + } catch (switchError) { + console.log(switchError); + const networkInfo = getNetworkConfig(newChainId); + // @ts-expect-error to correctly type we should add a conditional here to check instanceof Error + if (switchError.code === 4902) { + try { + await provider.send('wallet_addEthereumChain', [ + { + chainId: `0x${newChainId.toString(16)}`, + chainName: networkInfo.name, + nativeCurrency: networkInfo.baseAssetSymbol, + rpcUrls: [...networkInfo.publicJsonRPCUrl, networkInfo.publicJsonRPCWSUrl], + blockExplorerUrls: networkInfo.explorerLink, + }, + ]); + } catch (addError) { + console.log(addError); + // TODO: handle error somehow + } + } + } + } + }, + [provider] + ); + + useEffect(() => { + if (web3Modal?.cachedProvider) connectWallet(); + }, [web3Modal, connectWallet]); const web3ProviderData = useMemo( () => ({ @@ -108,8 +155,18 @@ export const Web3ContextProvider: React.FC<{ children: ReactElement }> = ({ chil currentAccount, web3Modal, chainId, + switchNetwork, }), - [connectWallet, disconnectWallet, provider, connected, currentAccount, web3Modal, chainId] + [ + connectWallet, + disconnectWallet, + provider, + connected, + currentAccount, + web3Modal, + chainId, + switchNetwork, + ] ); return ( diff --git a/src/libs/web3-data-provider/modalOptions.ts b/src/libs/web3-data-provider/modalOptions.ts index 53f6d88000..f1854482ab 100644 --- a/src/libs/web3-data-provider/modalOptions.ts +++ b/src/libs/web3-data-provider/modalOptions.ts @@ -9,9 +9,8 @@ const POLLING_INTERVAL = 12000; const APP_NAME = 'Aave'; const APP_LOGO_URL = 'https://aave.com/favicon.ico'; -export const getWeb3Modal = (networkId: number) => { +export const getWeb3Modal = () => { const supportedChainIds = getSupportedChainIds(); - const networkConfig = getNetworkConfig(networkId); return new Web3Modal({ cacheProvider: true, providerOptions: { @@ -36,12 +35,26 @@ export const getWeb3Modal = (networkId: number) => { options: { appName: APP_NAME, appLogoUrl: APP_LOGO_URL, - url: networkConfig.privateJsonRPCUrl || networkConfig.publicJsonRPCUrl[0], + rpc: supportedChainIds.reduce((acc, network) => { + const config = getNetworkConfig(network); + acc[network] = config.privateJsonRPCUrl || config.publicJsonRPCUrl[0]; + return acc; + }, {} as { [networkId: number]: string }), }, }, frame: { package: ethProvider, // required }, + // mewconnect: { + // package: MewConnect, // required + // options: { + // rpc: supportedChainIds.reduce((acc, network) => { + // const config = getNetworkConfig(network); + // acc[network] = config.privateJsonRPCUrl || config.publicJsonRPCUrl[0]; + // return acc; + // }, {} as { [networkId: number]: string }), + // }, + // }, }, }); }; diff --git a/yarn.lock b/yarn.lock index 6863621877..635b760bf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,76 +1762,6 @@ "@ethersproject/properties" "^5.5.0" "@ethersproject/strings" "^5.5.0" -"@formatjs/ecma402-abstract@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.1.tgz#595ba3ef4ba38634c012b3ca01954f8d12eb4bf4" - integrity sha512-tgtNODZUGuUI6PAcnvaLZpGrZLVkXnnAvgzOiueYMzFdOdcOw4iH1WKhCe3+r6VR8rHKToJ2HksUGNCB+zt/bg== - dependencies: - "@formatjs/intl-localematcher" "0.2.22" - tslib "^2.1.0" - -"@formatjs/fast-memoize@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz#e6f5aee2e4fd0ca5edba6eba7668e2d855e0fc21" - integrity sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg== - dependencies: - tslib "^2.1.0" - -"@formatjs/icu-messageformat-parser@2.0.16": - version "2.0.16" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.0.16.tgz#edfde10bbdea42658d2b8a28028314f2a694be3c" - integrity sha512-sYg0ImXsAqBbjU/LotoCD9yKC5nUpWVy3s4DwWerHXD4sm62FcjMF8mekwudRk3eZLHqSO+M21MpFUUjDQ+Q5Q== - dependencies: - "@formatjs/ecma402-abstract" "1.11.1" - "@formatjs/icu-skeleton-parser" "1.3.3" - tslib "^2.1.0" - -"@formatjs/icu-skeleton-parser@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.3.tgz#9462a3a7d0ff04e0957cafcb227b4baef4bb2b8c" - integrity sha512-ifWnzjmHPHUF89UpCvClTP66sXYFc8W/qg7Qt+qtTUB9BqRWlFeUsevAzaMYDJsRiOy4S2WJFrJoZgRKUFfPGQ== - dependencies: - "@formatjs/ecma402-abstract" "1.11.1" - tslib "^2.1.0" - -"@formatjs/intl-displaynames@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-5.4.0.tgz#0046fcd241417d4114fba93b2480f663ffe1a004" - integrity sha512-zWmTkq9eGOeJCmw22KPXW6rlnx3Z3CIV+rc/jh9ytEfm1Ps/OgOITe4h6ZTDrQC+nXVACvLO1Kpes4jMWcjWuQ== - dependencies: - "@formatjs/ecma402-abstract" "1.11.1" - "@formatjs/intl-localematcher" "0.2.22" - tslib "^2.1.0" - -"@formatjs/intl-listformat@6.5.0": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-6.5.0.tgz#d2a8fe10f2900feeb20583be52ee603c41713f7f" - integrity sha512-gVyAV5QWWtq84MK4cAyJITW+Wb74c2+FT+wa8jhSPxXUky9B5z/k/Ff7or4Vb3KV0YYZuVBQ/vMIoD8Gr182ww== - dependencies: - "@formatjs/ecma402-abstract" "1.11.1" - "@formatjs/intl-localematcher" "0.2.22" - tslib "^2.1.0" - -"@formatjs/intl-localematcher@0.2.22": - version "0.2.22" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.22.tgz#300708cf7067dbd49f258467cc386c0b32437415" - integrity sha512-z+TvbHW8Q/g2l7/PnfUl0mV9gWxV4d0HT6GQyzkO5QI6QjCvCZGiztnmLX7zoyS16uSMvZ2PoMDfSK9xvZkRRA== - dependencies: - tslib "^2.1.0" - -"@formatjs/intl@1.18.3": - version "1.18.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.18.3.tgz#dfbddd9ae2cf70ea8f76fb4d27c49322d209911d" - integrity sha512-eMdU2FBAvC2vMeQRjvBhJeRNsftZ2VXdB4jW1KKbP72O4JWB9lv2KqEdS2jo6DfhDvm0EAMZXMNEEK8ybTxfyA== - dependencies: - "@formatjs/ecma402-abstract" "1.11.1" - "@formatjs/fast-memoize" "1.2.1" - "@formatjs/icu-messageformat-parser" "2.0.16" - "@formatjs/intl-displaynames" "5.4.0" - "@formatjs/intl-listformat" "6.5.0" - intl-messageformat "9.11.2" - tslib "^2.1.0" - "@gar/promisify@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" @@ -3366,14 +3296,6 @@ dependencies: "@types/unist" "*" -"@types/hoist-non-react-statics@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== - dependencies: - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - "@types/html-minifier-terser@^5.0.0": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" @@ -3521,7 +3443,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16 || 17", "@types/react@latest": +"@types/react@*", "@types/react@latest": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd" integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ== @@ -5575,6 +5497,11 @@ clsx@^1.1.0, clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + collapse-white-space@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -6417,11 +6344,6 @@ elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -8526,16 +8448,6 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -intl-messageformat@9.11.2: - version "9.11.2" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.11.2.tgz#b94b1c5bcfff9de63017ffe2fb787a16a28967df" - integrity sha512-4wsinP2ObVK1Rz5C4121lgVeHeOCW32FOsqcVXtJNdlow+NypJKmnrije9rOc0bKxPwtto9IkXdgakXUmYXVHw== - dependencies: - "@formatjs/ecma402-abstract" "1.11.1" - "@formatjs/fast-memoize" "1.2.1" - "@formatjs/icu-messageformat-parser" "2.0.16" - tslib "^2.1.0" - invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -8735,10 +8647,12 @@ is-fn@^1.0.0: resolved "https://registry.yarnpkg.com/is-fn/-/is-fn-1.0.0.tgz#9543d5de7bcf5b08a22ec8a20bae6e286d510d8c" integrity sha1-lUPV3nvPWwiiLsiiC65uKG1RDYw= -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -10333,6 +10247,11 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + number-to-bn@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" @@ -11482,22 +11401,6 @@ react-inspector@^5.1.0: is-dom "^1.0.0" prop-types "^15.0.0" -react-intl@^5.24.3: - version "5.24.3" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.24.3.tgz#e5c929f71603f1aa04404f66205a8f55092ce964" - integrity sha512-SrV0Qs8Rg+Mlo2u0OqGJZ3pH3cF0lv3cVtHvVPksptrjlgvt6Lbc4vfzD1nPog/CPKzSSdNlLoMs5suHFdBnTw== - dependencies: - "@formatjs/ecma402-abstract" "1.11.1" - "@formatjs/icu-messageformat-parser" "2.0.16" - "@formatjs/intl" "1.18.3" - "@formatjs/intl-displaynames" "5.4.0" - "@formatjs/intl-listformat" "6.5.0" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/react" "16 || 17" - hoist-non-react-statics "^3.3.2" - intl-messageformat "9.11.2" - tslib "^2.1.0" - react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" @@ -12743,14 +12646,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.1: +strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.0.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==