diff --git a/README.md b/README.md index c0dd5dfb5..a0148a9ba 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,17 @@ There's a github action commenting the appropriate IPFS hash embedded in the Clo For ease of use: -- the DNS of [https://app.sparkprotocol.io](https://app.sparkprotocol.io) will always point to the latest `spark` IPFS hash with disabled test networks +- the DNS of [https://app.spark.fi](https://app.spark.fi) will always point to the latest `spark` IPFS hash with disabled test networks ### Troubleshooting -Issue: I cannot connect to `app.sparkprotocol.io` +Issue: I cannot connect to `app.spark.fi` -The spark-interface is hosted on IPFS in a decentralized manner. `app.sparkprotocol.io` just holds a CNAME record to the Cloudflare IPFS gateway. You can use [any](https://ipfs.github.io/public-gateway-checker/) public or private IPFS gateway supporting origin isolation to access spark-interface if for some reason the Cloudflare gateway doesn't work for you +The spark-interface is hosted on IPFS in a decentralized manner. `app.spark.fi` just holds a CNAME record to the Cloudflare IPFS gateway. You can use [any](https://ipfs.github.io/public-gateway-checker/) public or private IPFS gateway supporting origin isolation to access spark-interface if for some reason the Cloudflare gateway doesn't work for you -Just go to `/ipns/app.sparkprotocol.io` +Just go to `/ipns/app.spark.fi` -⚠️ Make sure the gateway supports origin isolation to avoid possible security issues: you should be redirected to URL that looks like `https://app-sparkprotocol-io.` +⚠️ Make sure the gateway supports origin isolation to avoid possible security issues: you should be redirected to URL that looks like `https://app-spark-fi.` ## License @@ -50,7 +50,7 @@ To all the Ethereum community ## Plug and Play License -This code is open source, but use within a Maker SubDAO requires a 15% revenue share of all income generated by the usage of code inside this repository to the Ethereum address labelled by the ENS domain of sparkprotocol.eth. This Plug and Play revenue share license expires on January 1st, 2026 at 00:00 UTC. +This code is open source, but use within a Maker SubDAO requires a 15% revenue share of all income generated by the usage of code inside this repository to the Ethereum address labelled by the ENS domain of sparkfi.eth. This Plug and Play revenue share license expires on January 1st, 2026 at 00:00 UTC. *** *The IP in this repository was assigned to Mars SPC Limited in respect of the MarsOne SP* diff --git a/src/components/AddressBlockedModal.tsx b/src/components/AddressBlockedModal.tsx index a4af8a56c..b31d2d6d7 100644 --- a/src/components/AddressBlockedModal.tsx +++ b/src/components/AddressBlockedModal.tsx @@ -35,10 +35,10 @@ export const AddressBlockedModal = ({ address, onDisconnectWallet }: AddressBloc - This address is blocked on app.sparkprotocol.io because it is associated with one or + This address is blocked on app.spark.fi because it is associated with one or more {' '} - + blocked activities {'.'} diff --git a/src/components/ConnectWalletPaper.tsx b/src/components/ConnectWalletPaper.tsx index 46af09281..4fa0e15a3 100644 --- a/src/components/ConnectWalletPaper.tsx +++ b/src/components/ConnectWalletPaper.tsx @@ -82,11 +82,11 @@ export const Disclaimers = () => { ( By using this Site, I have read and agree to the{' '} - + Terms of Use {' '} and{' '} - + Privacy Policy . @@ -94,13 +94,13 @@ export const Disclaimers = () => {
- I am not the person or entities who reside in, are citizens of, are incorporated in, or have a registered office in the United States of America or any Prohibited Localities, as defined in the{' '} - + Terms of Use .
- I will not in the future access this site while located within the United States or any Prohibited Localities, as defined in the{' '} - + Terms of Use . diff --git a/src/components/WalletConnection/WalletSelector.tsx b/src/components/WalletConnection/WalletSelector.tsx index 625bc90bd..3488ec71f 100644 --- a/src/components/WalletConnection/WalletSelector.tsx +++ b/src/components/WalletConnection/WalletSelector.tsx @@ -236,7 +236,7 @@ export const WalletSelector = () => { Need help connecting a wallet?{' '} diff --git a/src/components/Warnings/AMPLWarning.tsx b/src/components/Warnings/AMPLWarning.tsx index 6d7eacf52..feedea10f 100644 --- a/src/components/Warnings/AMPLWarning.tsx +++ b/src/components/Warnings/AMPLWarning.tsx @@ -7,7 +7,7 @@ export const AMPLWarning = () => { Ampleforth is a rebasing asset. Visit the{' '} documentation diff --git a/src/components/Warnings/CooldownWarning.tsx b/src/components/Warnings/CooldownWarning.tsx index c1593011f..91fb48352 100644 --- a/src/components/Warnings/CooldownWarning.tsx +++ b/src/components/Warnings/CooldownWarning.tsx @@ -15,7 +15,7 @@ export const CooldownWarning = () => { The cooldown period is the time required prior to unstaking your tokens(10 days). You can only withdraw your assets from the Security Module after the cooldown period and within the active the unstake window. - + Learn more diff --git a/src/components/caps/DebtCeilingStatus.tsx b/src/components/caps/DebtCeilingStatus.tsx index 5aee0282e..ae6e217ae 100644 --- a/src/components/caps/DebtCeilingStatus.tsx +++ b/src/components/caps/DebtCeilingStatus.tsx @@ -60,7 +60,7 @@ export const DebtCeilingStatus = ({ users. Debt ceiling is specific to assets in isolation mode and is denoted in USD. {' '} Learn more diff --git a/src/components/infoTooltips/BorrowCapMaxedTooltip.tsx b/src/components/infoTooltips/BorrowCapMaxedTooltip.tsx index 54906aeac..330a526a5 100644 --- a/src/components/infoTooltips/BorrowCapMaxedTooltip.tsx +++ b/src/components/infoTooltips/BorrowCapMaxedTooltip.tsx @@ -19,7 +19,7 @@ export const BorrowCapMaxedTooltip = ({ borrowCap, ...rest }: BorrowCapMaxedTool <> Protocol borrow cap at 100% for this asset. Further borrowing unavailable.{' '} Learn more diff --git a/src/components/infoTooltips/DebtCeilingMaxedTooltip.tsx b/src/components/infoTooltips/DebtCeilingMaxedTooltip.tsx index 1468ed857..f55d31099 100644 --- a/src/components/infoTooltips/DebtCeilingMaxedTooltip.tsx +++ b/src/components/infoTooltips/DebtCeilingMaxedTooltip.tsx @@ -22,7 +22,7 @@ export const DebtCeilingMaxedTooltip = ({ debtCeiling, ...rest }: DebtCeilingMax unavailable.
{' '} Learn more diff --git a/src/components/infoTooltips/EModeTooltip.tsx b/src/components/infoTooltips/EModeTooltip.tsx index b808509e7..e5aaaaad9 100644 --- a/src/components/infoTooltips/EModeTooltip.tsx +++ b/src/components/infoTooltips/EModeTooltip.tsx @@ -20,7 +20,7 @@ export const EModeTooltip = ({ /> .{' '} Protocol supply cap at 100% for this asset. Further supply unavailable.{' '} Learn more diff --git a/src/components/isolationMode/IsolatedTooltip.tsx b/src/components/isolationMode/IsolatedTooltip.tsx index 3b79328f8..072efcc73 100644 --- a/src/components/isolationMode/IsolatedTooltip.tsx +++ b/src/components/isolationMode/IsolatedTooltip.tsx @@ -42,7 +42,7 @@ export const IsolatedTooltip = () => { Learn more in our{' '} FAQ guide diff --git a/src/components/transactions/Emode/EmodeModalContent.tsx b/src/components/transactions/Emode/EmodeModalContent.tsx index 8bb7d5b39..c74d7b684 100644 --- a/src/components/transactions/Emode/EmodeModalContent.tsx +++ b/src/components/transactions/Emode/EmodeModalContent.tsx @@ -189,7 +189,7 @@ export const EmodeModalContent = ({ mode }: EmodeModalContentProps) => { Enabling E-Mode only allows you to borrow assets belonging to the selected category. Please visit our{' '} diff --git a/src/components/transactions/StakeCooldown/StakeCooldownModalContent.tsx b/src/components/transactions/StakeCooldown/StakeCooldownModalContent.tsx index fbd964e2c..fdd6459d4 100644 --- a/src/components/transactions/StakeCooldown/StakeCooldownModalContent.tsx +++ b/src/components/transactions/StakeCooldown/StakeCooldownModalContent.tsx @@ -123,7 +123,7 @@ export const StakeCooldownModalContent = ({ stakeAssetName }: StakeCooldownProps {' '} Learn more diff --git a/src/components/transactions/Warnings/BorrowCapWarning.tsx b/src/components/transactions/Warnings/BorrowCapWarning.tsx index b7381dd6e..de72f3020 100644 --- a/src/components/transactions/Warnings/BorrowCapWarning.tsx +++ b/src/components/transactions/Warnings/BorrowCapWarning.tsx @@ -30,7 +30,7 @@ export const BorrowCapWarning = ({ borrowCap, icon = true, ...rest }: BorrowCapW {renderText()}{' '} Learn more diff --git a/src/components/transactions/Warnings/DebtCeilingWarning.tsx b/src/components/transactions/Warnings/DebtCeilingWarning.tsx index 420ef2100..3e8013765 100644 --- a/src/components/transactions/Warnings/DebtCeilingWarning.tsx +++ b/src/components/transactions/Warnings/DebtCeilingWarning.tsx @@ -38,7 +38,7 @@ export const DebtCeilingWarning = ({ {renderText()}{' '} Learn more diff --git a/src/components/transactions/Warnings/IsolationModeWarning.tsx b/src/components/transactions/Warnings/IsolationModeWarning.tsx index b459322ec..f3b0e1d18 100644 --- a/src/components/transactions/Warnings/IsolationModeWarning.tsx +++ b/src/components/transactions/Warnings/IsolationModeWarning.tsx @@ -20,7 +20,7 @@ export const IsolationModeWarning = ({ asset, severity }: IsolationModeWarningPr In Isolation mode, you cannot supply other assets as collateral. A global debt ceiling limits the borrowing power of the isolated asset. To exit isolation mode disable{' '} {asset ? asset : ''} as collateral before borrowing another asset. Read more in our{' '} - + FAQ diff --git a/src/components/transactions/Warnings/SupplyCapWarning.tsx b/src/components/transactions/Warnings/SupplyCapWarning.tsx index cf8ac35ba..a379a90f1 100644 --- a/src/components/transactions/Warnings/SupplyCapWarning.tsx +++ b/src/components/transactions/Warnings/SupplyCapWarning.tsx @@ -31,7 +31,7 @@ export const SupplyCapWarning = ({ supplyCap, icon = true, ...rest }: SupplyCapW {renderText()}{' '} Learn more diff --git a/src/layouts/AppHeader.tsx b/src/layouts/AppHeader.tsx index 3a413d654..acdf3e056 100644 --- a/src/layouts/AppHeader.tsx +++ b/src/layouts/AppHeader.tsx @@ -70,7 +70,7 @@ export function AppHeader() { The app is running in testnet mode. Learn how it works in{' '} FAQ. diff --git a/src/layouts/SettingsMenu.tsx b/src/layouts/SettingsMenu.tsx index a39488e7c..49e40649b 100644 --- a/src/layouts/SettingsMenu.tsx +++ b/src/layouts/SettingsMenu.tsx @@ -8,6 +8,8 @@ import { DarkModeSwitcher } from './components/DarkModeSwitcher'; import { LanguageListItem, LanguagesList } from './components/LanguageSwitcher'; import { TestNetModeSwitcher } from './components/TestNetModeSwitcher'; +import { PlaygroundsControl } from './components/PlaygroundControls' + export function SettingsMenu() { const [settingsOpen, setSettingsOpen] = useState(false); const [languagesOpen, setLanguagesOpen] = useState(false); @@ -72,6 +74,7 @@ export function SettingsMenu() { {PROD_ENV && } + { + await aNewTenderlyForkProvider(chainId) + }, [chainId, aNewTenderlyForkProvider]) + + const blockExternalLinkHref = useMemo(() => { + if (!chainId || !blockNumber) { + return '' + } + + if (tenderlyForkProvider) { + return tenderlyForkProvider?.publicUrl || '' + } + + return getExplorerLink(chainId, blockNumber.toString(), ExplorerDataType.BLOCK) + }, [chainId, tenderlyForkProvider, blockNumber]) + + const connectChain = useCallback(async () => { + if (!tenderlyForkProvider) { + return + } + await selectChain(Number.parseInt(tenderlyForkProvider.baseChainId)) + await discardPlayground() + }, [discardPlayground, tenderlyForkProvider, selectChain]) + + return ( + + + {!isPlayground && } + {isPlayground && } + {isPlayground && } + + ) +} + +const ControlsPane = styled.a` + display: inline-flex; + height: 70px; + padding: 16px; + align-items: center; + gap: 24px; + flex-shrink: 0; + + border-radius: 16px; + border: 1px solid #e5e4ef; + background: #fff; + margin-top: 50px; + position: absolute; + bottom: 30px; +` + +const Button = styled.a` + display: flex; + padding: 10px 24px; + align-items: flex-start; + border-radius: 8px; + border: 1px solid #e4e3ee; + background: #f5f6fc; + box-shadow: 0px 1px 4px 0px rgba(23, 23, 24, 0.07); + border-radius: 8px; + border: 1px solid #e4e3ee; + background: #f5f6fc; + box-shadow: 0px 1px 4px 0px rgba(23, 23, 24, 0.07); + color: #38363a; + font-size: 14px; + font-family: Inter; + font-weight: 600; + line-height: 20px; + cursor: pointer; + user-select: none; + text-decoration: none; +` \ No newline at end of file diff --git a/src/modules/dashboard/DashboardEModeButton.tsx b/src/modules/dashboard/DashboardEModeButton.tsx index 77a46ba71..eeedc57a2 100644 --- a/src/modules/dashboard/DashboardEModeButton.tsx +++ b/src/modules/dashboard/DashboardEModeButton.tsx @@ -190,7 +190,7 @@ export const DashboardEModeButton = ({ userEmodeCategoryId }: DashboardEModeButt E-Mode increases your LTV for a selected category of assets up to 97%.{' '} {' '} { {user?.isInIsolationMode && ( Borrowing power and assets are limited due to Isolation mode. - + Learn More diff --git a/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx b/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx index 59c2a91ec..def3d9b98 100644 --- a/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx +++ b/src/modules/dashboard/lists/SupplyAssetsList/SupplyAssetsList.tsx @@ -248,7 +248,7 @@ export const SupplyAssetsList = () => { Collateral usage is limited because of isolation mode.{' '} - + Learn More diff --git a/src/modules/faucet/FaucetTopPanel.tsx b/src/modules/faucet/FaucetTopPanel.tsx index 9c908c2d4..051ada88b 100644 --- a/src/modules/faucet/FaucetTopPanel.tsx +++ b/src/modules/faucet/FaucetTopPanel.tsx @@ -30,7 +30,7 @@ export const FaucetTopPanel = () => { testnet are not “real,” meaning they have no monetary value.{' '} Learn more diff --git a/src/modules/reserve-overview/BorrowInfo.tsx b/src/modules/reserve-overview/BorrowInfo.tsx index 59ac1c419..15c5fdf72 100644 --- a/src/modules/reserve-overview/BorrowInfo.tsx +++ b/src/modules/reserve-overview/BorrowInfo.tsx @@ -87,7 +87,7 @@ export const BorrowInfo = ({ pool insolvency. {' '} Learn more diff --git a/src/modules/reserve-overview/ReserveConfiguration.tsx b/src/modules/reserve-overview/ReserveConfiguration.tsx index e543bacfa..e5602b00b 100644 --- a/src/modules/reserve-overview/ReserveConfiguration.tsx +++ b/src/modules/reserve-overview/ReserveConfiguration.tsx @@ -208,7 +208,7 @@ export const ReserveConfiguration: React.FC = ({ rese . To learn more about E-Mode and applied restrictions in{' '} {' '} Learn more @@ -176,7 +176,7 @@ export const SupplyInfo = ({ In Isolation mode you cannot supply other assets as collateral for borrowing. Assets used as collateral in Isolation mode can only be borrowed to a specific debt ceiling.{' '} - + Learn more diff --git a/src/ui-config/menu-items/index.tsx b/src/ui-config/menu-items/index.tsx index 02ad84473..c476dae1c 100644 --- a/src/ui-config/menu-items/index.tsx +++ b/src/ui-config/menu-items/index.tsx @@ -64,12 +64,12 @@ interface MoreMenuItem extends Navigation { const moreMenuItems: MoreMenuItem[] = [ { - link: 'https://docs.sparkprotocol.io/developers/sparklend/faq', + link: 'https://docs.spark.fi/faq', title: t`FAQ`, icon: , }, { - link: 'https://docs.sparkprotocol.io/developers/sparklend/readme', + link: 'https://devs.spark.fi/', title: t`Developers`, icon: , }, diff --git a/tenderly-fork-api.ts b/tenderly-fork-api.ts new file mode 100644 index 000000000..0a5f8eaee --- /dev/null +++ b/tenderly-fork-api.ts @@ -0,0 +1,98 @@ +import { JsonRpcProvider } from '@ethersproject/providers' +import axios, { AxiosResponse } from 'axios' + +const { REACT_APP_TENDERLY_PROJECT_SLUG, REACT_APP_TENDERLY_ACCESS_KEY, REACT_APP_TENDERLY_USERNAME } = process.env + +export async function aTenderlyFork(fork: TenderlyForkRequest): Promise { + const forkResponse = await tenderlyBaseApi.post( + `account/${REACT_APP_TENDERLY_USERNAME}/project/${REACT_APP_TENDERLY_PROJECT_SLUG}/fork/`, + fork + ) + + const forkId = forkResponse.data.root_transaction.fork_id + + const rpcUrl = `https://rpc.tenderly.co/fork/${forkId}` + const forkProvider = new JsonRpcProvider(rpcUrl) + + const blockNumberStr = (forkResponse.data.root_transaction.receipt.blockNumber as string).replace('0x', '') + const blockNumber = Number.parseInt(blockNumberStr, 16) + const privateUrl = `https://dashboard.tenderly.co/account/${REACT_APP_TENDERLY_USERNAME}/project/${REACT_APP_TENDERLY_PROJECT_SLUG}/fork/${forkId}` + const publicUrl = `https://dashboard.tenderly.co/shared/fork/${forkId}/transactions` + + console.info( + `\nForked ${fork.network_id} + at block ${blockNumber} + with chain ID ${fork.chain_config?.chain_id} + fork ID: ${forkId} +` + ) + console.info('Fork:', privateUrl) + + return { + rpcUrl, + provider: forkProvider, + blockNumber, + forkUUID: forkId, + removeFork: () => removeFork(forkId), + networkId: fork.network_id as any, + publicUrl, + privateUrl, + chainId: forkResponse.data.simulation_fork.chain_config.chain_id, + baseChainId: fork.network_id, + } +} + +async function removeFork(forkId: string) { + console.log('Removing test fork', forkId) + return await tenderlyBaseApi.delete( + `account/${REACT_APP_TENDERLY_USERNAME}/project/${REACT_APP_TENDERLY_PROJECT_SLUG}/fork/${forkId}` + ) +} + +const tenderlyBaseApi = axios.create({ + baseURL: `https://api.tenderly.co/api/v1/`, + headers: { + 'X-Access-Key': REACT_APP_TENDERLY_ACCESS_KEY || '', + 'Content-Type': 'application/json', + }, +}) + +type TenderlyForkRequest = { + shared?: boolean + block_number?: number + network_id: string + transaction_index?: number + initial_balance?: number + alias?: string + description?: string + chain_config?: { + chain_id?: number + homestead_block?: number + dao_fork_support?: boolean + eip_150_block?: number + eip_150_hash?: string + eip_155_block?: number + eip_158_block?: number + byzantium_block?: number + constantinople_block?: number + petersburg_block?: number + istanbul_block?: number + berlin_block?: number + } +} + +export type TenderlyForkProvider = { + rpcUrl: string + provider: JsonRpcProvider + forkUUID: string + blockNumber: number + chainId: number + networkId: number + publicUrl: string + privateUrl: string + baseChainId: string + /** + * map from address to given address' balance + */ + removeFork: () => Promise> +} \ No newline at end of file diff --git a/useTenderlyFork.ts b/useTenderlyFork.ts new file mode 100644 index 000000000..ff464c685 --- /dev/null +++ b/useTenderlyFork.ts @@ -0,0 +1,128 @@ +import { useWeb3React } from '@web3-react/core' +import { aTenderlyFork, TenderlyForkProvider } from 'components/Web3Status/tenderly-fork-api' +import { removeTenderlyChainIdPrefix, TENDERLY_CHAIN_FORK_PREFIX } from 'constants/chains' +import { nativeOnChain } from 'constants/tokens' +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { ethers } from 'ethers' +import { atom } from 'jotai' +import { useAtomValue, useUpdateAtom } from 'jotai/utils' +import { useCallback } from 'react' + +const provider = atom(undefined) +const providerState = atom<'CREATING' | 'RUNNING'>('RUNNING') + +export function useActiveTenderlyFork() { + return { tenderlyFork: useAtomValue(provider) != null, forkProvider: useAtomValue(provider) || undefined } +} + +export function useTenderlyPlayground(): { + playgroundProvider?: TenderlyForkProvider + isPlayground: boolean + aNewPlayground: (chainId?: number) => Promise + discardPlayground: () => Promise +} { + const prov = useAtomValue(provider) + return { + playgroundProvider: prov, + isPlayground: !!prov, + aNewPlayground: useNewFork(), + discardPlayground: useRemoveFork(), + } +} + +function useNewFork() { + const currentProvider = useAtomValue(provider) + const updateProvider = useUpdateAtom(provider) + const updateProviderState = useUpdateAtom(providerState) + const currentChainId = useWeb3React().chainId + + return useCallback( + async (chainId: number | undefined = -1) => { + if (currentProvider) { + console.log('removing provider', currentProvider.rpcUrl) + try { + await currentProvider.removeFork() + } catch (error) { + console.warn('Errored out when deleting the old fork', error) + } + } else { + console.log('no fork provider') + } + + updateProviderState('CREATING') + + if (chainId == -1 && currentChainId) { + chainId = currentChainId + } + chainId = removeTenderlyChainIdPrefix(chainId) + const _forkProvider = await aTenderlyFork({ + network_id: '' + chainId, + chain_config: { chain_id: Number.parseInt(`${TENDERLY_CHAIN_FORK_PREFIX}${chainId}`) }, + shared: true, + }) + updateProvider(_forkProvider) + await addForkToMetamask(_forkProvider) + await topUpConnectedSigner(_forkProvider) + }, + [currentProvider, updateProvider, updateProviderState, currentChainId] + ) +} + +async function topUpConnectedSigner(forkProvider: TenderlyForkProvider) { + if (window.ethereum) { + const provider = new ethers.providers.Web3Provider(window.ethereum) + const signer = provider.getSigner() + + console.log('Address', await signer.getAddress()) + console.log('top up: Fork provider', forkProvider.rpcUrl) + + await forkProvider.provider.send('tenderly_setBalance', [ + [await signer.getAddress()], + ethers.utils.hexValue(ethers.utils.parseUnits('100', 'ether').toHexString()), + ]) + + const balanceRead = await forkProvider.provider.send('eth_getBalance', [await signer.getAddress(), 'latest']) + + console.log(`Balance of ${signer.getAddress()} is ${balanceRead}`) + } +} + +async function addForkToMetamask(forkProvider: TenderlyForkProvider) { + const forkUrl = forkProvider.rpcUrl + if (!forkUrl) { + console.log('No Fork') + return + } + + if (typeof window.ethereum !== 'undefined') { + const noc = nativeOnChain(forkProvider.chainId) + //@ts-ignore + await window.ethereum.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: `0x${forkProvider.chainId.toString(16)}`, + rpcUrls: [forkUrl], + chainName: `Forked ${forkProvider.chainId}`, + nativeCurrency: { + name: noc.name, + symbol: noc.symbol, + decimals: noc.decimals, + }, + blockExplorerUrls: ['https://polygonscan.com/'], + }, + ], + }) + } else { + console.log('Metamask is not installed') + } +} + +function useRemoveFork() { + const currentProvider = useAtomValue(provider) + const setProvider = useUpdateAtom(provider) + return useCallback(async () => { + await currentProvider?.removeFork() + setProvider(undefined) + }, [currentProvider, setProvider]) +} \ No newline at end of file