From fe8f3767593684dc9d825d603a894c3b0934d894 Mon Sep 17 00:00:00 2001 From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:55:02 +0100 Subject: [PATCH 01/12] feat(wallets): add metaMask SDK connector (#5028) * feat: add metaMask SDK connector * fix: display wallet connectors once * fix: load balances and switch network * fix: display installed wallets first when MetaMask isn't installed * fix: babel syntax error * feat: add metaMask SDK connector 0.31.2 * chore: update yarnlock * chore: fix wallets ordering * chore: remove excessive condition --------- Co-authored-by: Baptiste Marchand <75846779+baptiste-marchand@users.noreply.github.com> Co-authored-by: shoom3301 --- .../containers/ConnectWalletOptions.tsx | 37 +-- .../api/state/multiInjectedProvidersAtom.ts | 2 +- libs/wallet/src/api/types.ts | 1 + libs/wallet/src/api/utils/connection.ts | 3 + libs/wallet/src/index.ts | 3 +- .../web3-react/connection/injectedOptions.tsx | 30 -- .../src/web3-react/connection/metaMaskSdk.tsx | 54 +++ .../connectors/metaMaskSdk/index.ts | 307 ++++++++++++++++++ .../utils/getWeb3ReactConnection.ts | 2 + .../src/web3-react/utils/isChainAllowed.ts | 1 + package.json | 3 +- yarn.lock | 272 +++++++++++++++- 12 files changed, 653 insertions(+), 62 deletions(-) create mode 100644 libs/wallet/src/web3-react/connection/metaMaskSdk.tsx create mode 100644 libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts diff --git a/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx b/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx index 1bf6d0bb2d..9885ea144e 100644 --- a/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx +++ b/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx @@ -3,8 +3,7 @@ import { isMobile, isInjectedWidget } from '@cowprotocol/common-utils' import { CoinbaseWalletOption, InjectedOption as DefaultInjectedOption, - InstallMetaMaskOption, - OpenMetaMaskMobileOption, + MetaMaskSdkOption, TrezorOption, WalletConnectV2Option, getIsInjected, @@ -31,14 +30,17 @@ export function ConnectWalletOptions({ tryActivation }: { tryActivation: TryActi const connectionProps = { darkMode, selectedWallet, tryActivation } + const metaMaskSdkOption = const coinbaseWalletOption = (!hasCoinbaseEip6963 && ) ?? null const walletConnectionV2Option = ((!isInjectedMobileBrowser || isWidget) && ) ?? null const trezorOption = (!isInjectedMobileBrowser && !isMobile && ) ?? null + const injectedOption = (getIsInjected() && ) ?? null return ( <> - + {injectedOption} + {metaMaskSdkOption} {walletConnectionV2Option} {coinbaseWalletOption} {trezorOption} @@ -57,19 +59,13 @@ interface InjectedOptionsProps { } function InjectedOptions({ connectionProps, multiInjectedProviders }: InjectedOptionsProps) { - const isInjected = getIsInjected() - - if (!isInjected) { - if (!isMobile) { - return - } else { - return - } - } else { - if (multiInjectedProviders.length) { - return ( - <> - {multiInjectedProviders.map((providerInfo) => { + if (multiInjectedProviders.length) { + return ( + <> + {multiInjectedProviders + // Even if we detect the MetaMask Extension, we prefer to use the MetaMask SDK + .filter((providerInfo) => !providerInfo.info.rdns.startsWith('io.metamask')) + .map((providerInfo) => { return ( ) })} - - ) - } - - return + + ) } + + return } diff --git a/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts b/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts index ac5e1abd2f..c5a1a9b9f3 100644 --- a/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts +++ b/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts @@ -26,7 +26,7 @@ window.addEventListener('eip6963:announceProvider', (event: Event) => { jotaiStore.set(multiInjectedProvidersAtom, (prev: EIP6963ProviderDetail[]) => { const newProvider = providerEvent.detail - const existingProvider = prev.find((p) => p.info.rdns === newProvider.info.uuid) + const existingProvider = prev.find((p) => p.info.rdns === newProvider.info.rdns) if (existingProvider) return prev diff --git a/libs/wallet/src/api/types.ts b/libs/wallet/src/api/types.ts index f177ca6b11..c43a7721e9 100644 --- a/libs/wallet/src/api/types.ts +++ b/libs/wallet/src/api/types.ts @@ -8,6 +8,7 @@ export enum ConnectionType { INJECTED = 'INJECTED', WALLET_CONNECT_V2 = 'WALLET_CONNECT_V2', COINBASE_WALLET = 'COINBASE_WALLET', + METAMASK = 'METAMASK', GNOSIS_SAFE = 'GNOSIS_SAFE', TREZOR = 'TREZOR', } diff --git a/libs/wallet/src/api/utils/connection.ts b/libs/wallet/src/api/utils/connection.ts index c936123d7a..318a20404e 100644 --- a/libs/wallet/src/api/utils/connection.ts +++ b/libs/wallet/src/api/utils/connection.ts @@ -1,5 +1,6 @@ import { isMobile } from '@cowprotocol/common-utils' +import { default as MetamaskImage } from '../../api/assets/metamask.png' import CoinbaseWalletIcon from '../assets/coinbase.svg' import TrezorIcon from '../assets/trezor.svg' import WalletConnectIcon from '../assets/walletConnectIcon.svg' @@ -7,6 +8,7 @@ import { ConnectionType } from '../types' const connectionTypeToName: Record = { [ConnectionType.INJECTED]: 'Injected', + [ConnectionType.METAMASK]: 'MetaMask', [ConnectionType.COINBASE_WALLET]: 'Coinbase Wallet', [ConnectionType.WALLET_CONNECT_V2]: 'WalletConnect', [ConnectionType.NETWORK]: 'Network', @@ -18,6 +20,7 @@ const IDENTICON_KEY = 'Identicon' const connectionTypeToIcon: Record = { [ConnectionType.INJECTED]: IDENTICON_KEY, + [ConnectionType.METAMASK]: MetamaskImage, [ConnectionType.GNOSIS_SAFE]: IDENTICON_KEY, [ConnectionType.NETWORK]: IDENTICON_KEY, [ConnectionType.COINBASE_WALLET]: CoinbaseWalletIcon, diff --git a/libs/wallet/src/index.ts b/libs/wallet/src/index.ts index 68a2559d75..a781aa93ad 100644 --- a/libs/wallet/src/index.ts +++ b/libs/wallet/src/index.ts @@ -42,8 +42,6 @@ export { walletConnectConnectionV2 } from './web3-react/connection/walletConnect // Connect options export { InjectedOption, - InstallMetaMaskOption, - OpenMetaMaskMobileOption, Eip6963Option, } from './web3-react/connection/injectedOptions' @@ -51,6 +49,7 @@ export { ConnectWalletOption } from './api/pure/ConnectWalletOption' export { TrezorOption } from './web3-react/connection/trezor' export { WalletConnectV2Option } from './web3-react/connection/walletConnectV2' export { CoinbaseWalletOption } from './web3-react/connection/coinbase' +export { MetaMaskSdkOption } from './web3-react/connection/metaMaskSdk' // State // TODO: this export is discussable, however it's already used outside diff --git a/libs/wallet/src/web3-react/connection/injectedOptions.tsx b/libs/wallet/src/web3-react/connection/injectedOptions.tsx index 9b4e8cc090..e4d8c16451 100644 --- a/libs/wallet/src/web3-react/connection/injectedOptions.tsx +++ b/libs/wallet/src/web3-react/connection/injectedOptions.tsx @@ -3,7 +3,6 @@ import { useCallback } from 'react' import { injectedWalletConnection } from './injectedWallet' import { default as InjectedImage, default as InjectedImageDark } from '../../api/assets/arrow-right.svg' -import { default as MetamaskImage } from '../../api/assets/metamask.png' import { useSelectedEip6963ProviderRdns, useSetEip6963Provider } from '../../api/hooks' import { ConnectWalletOption } from '../../api/pure/ConnectWalletOption' import { ConnectionType, type EIP1193Provider, EIP6963ProviderDetail } from '../../api/types' @@ -11,14 +10,6 @@ import { getConnectionName } from '../../api/utils/connection' import { useIsActiveConnection } from '../hooks/useIsActiveConnection' import { ConnectionOptionProps, TryActivation } from '../types' -const METAMASK_DEEP_LINK = 'https://metamask.app.link/dapp/' - -const metamaskCommonOption = { - color: '#E8831D', - icon: MetamaskImage, - id: 'metamask', -} - const injectedCommon = { color: '#010101', id: 'injected', @@ -33,27 +24,6 @@ export const injectedOptionDark = { icon: InjectedImageDark, } -export const metamaskInstallOption = { - ...metamaskCommonOption, - header: 'Install MetaMask', - link: 'https://metamask.io/', -} - -export const metamaskInjectedOption = { - ...metamaskCommonOption, - header: 'MetaMask', -} - -export function InstallMetaMaskOption() { - return -} - -export function OpenMetaMaskMobileOption() { - return ( - - ) -} - export function InjectedOption({ darkMode, tryActivation, selectedWallet }: ConnectionOptionProps) { const options = darkMode ? injectedOptionDark : injectedOption diff --git a/libs/wallet/src/web3-react/connection/metaMaskSdk.tsx b/libs/wallet/src/web3-react/connection/metaMaskSdk.tsx new file mode 100644 index 0000000000..01f63209c2 --- /dev/null +++ b/libs/wallet/src/web3-react/connection/metaMaskSdk.tsx @@ -0,0 +1,54 @@ +import { RPC_URLS } from '@cowprotocol/common-const' +import { initializeConnector } from '@web3-react/core' + +import { onError } from './onError' + +import { default as MetamaskImage } from '../../api/assets/metamask.png' +import { ConnectWalletOption } from '../../api/pure/ConnectWalletOption' +import { ConnectionType } from '../../api/types' +import { getConnectionName } from '../../api/utils/connection' +import { MetaMaskSDK } from '../connectors/metaMaskSdk' +import { useIsActiveConnection } from '../hooks/useIsActiveConnection' +import { ConnectionOptionProps, Web3ReactConnection } from '../types' + +const metaMaskOption = { + color: '#E8831D', + icon: MetamaskImage, + id: 'metamask', +} + +const [web3MetaMask, web3MetaMaskHooks] = initializeConnector( + (actions) => + new MetaMaskSDK({ + actions, + options: { + dappMetadata: { + name: 'CoW Swap', + url: 'https://swap.cow.fi', + }, + readonlyRPCMap: Object.fromEntries( + Object.entries(RPC_URLS).map(([chainId, url]) => [`0x${Number(chainId).toString(16)}`, url]), + ), + }, + onError, + }), +) + +export const metaMaskSdkConnection: Web3ReactConnection = { + connector: web3MetaMask, + hooks: web3MetaMaskHooks, + type: ConnectionType.METAMASK, +} + +export function MetaMaskSdkOption({ tryActivation, selectedWallet }: ConnectionOptionProps) { + const isActive = useIsActiveConnection(selectedWallet, metaMaskSdkConnection) + + return ( + tryActivation(metaMaskSdkConnection.connector)} + header={getConnectionName(ConnectionType.METAMASK)} + /> + ) +} diff --git a/libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts b/libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts new file mode 100644 index 0000000000..69c4f15698 --- /dev/null +++ b/libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts @@ -0,0 +1,307 @@ +import type { + Actions, + AddEthereumChainParameter, + Provider, + ProviderConnectInfo, + ProviderRpcError, + WatchAssetParameters, +} from '@web3-react/types' +import { Connector } from '@web3-react/types' + +import type { MetaMaskSDK as _MetaMaskSDK, MetaMaskSDKOptions as _MetaMaskSDKOptions, SDKProvider } from '@metamask/sdk' + +/** + * MetaMaskSDK options. + */ +type MetaMaskSDKOptions = Pick<_MetaMaskSDKOptions, 'infuraAPIKey' | 'readonlyRPCMap'> & { + dappMetadata: Pick<_MetaMaskSDKOptions['dappMetadata'], 'name' | 'url' | 'iconUrl'> +} + +/** + * Listener type for MetaMaskSDK events. + */ +type Listener = Parameters[1] + +/** + * Error thrown when the MetaMaskSDK is not installed. + */ +export class NoMetaMaskSDKError extends Error { + public constructor() { + super('MetaMaskSDK not installed') + this.name = NoMetaMaskSDKError.name + Object.setPrototypeOf(this, NoMetaMaskSDKError.prototype) + } +} + +/** + * Parses a chainId from a string or number. + */ +function parseChainId(chainId: string | number) { + return typeof chainId === 'number' ? chainId : Number.parseInt(chainId, chainId.startsWith('0x') ? 16 : 10) +} + +/** + * @param options - Options to pass to `@metamask/sdk` + * @param onError - Handler to report errors thrown from eventListeners. + */ +export interface MetaMaskSDKConstructorArgs { + actions: Actions + options?: MetaMaskSDKOptions + onError?: (error: Error) => void +} + +/** + * Connector for the MetaMaskSDK. + */ +export class MetaMaskSDK extends Connector { + private sdk?: _MetaMaskSDK + provider?: SDKProvider = undefined + private readonly options: MetaMaskSDKOptions + private eagerConnection?: Promise + + /** + * @inheritdoc Connector.constructor + */ + constructor({ actions, options, onError }: MetaMaskSDKConstructorArgs) { + super(actions, onError) + + const defaultUrl = typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}` : '' + + this.options = { + ...options, + dappMetadata: options?.dappMetadata ?? { + url: defaultUrl, + name: defaultUrl !== '' ? undefined : 'wagmi', + }, + } + } + + /** + * Indicates whether the user is connected to the MetaMaskSDK. + */ + private async isConnected() { + try { + if (this.provider?.isConnected?.() === true) { + if (this.sdk?.isExtensionActive() === true) { + const accounts = ((await this.provider?.request({ method: 'eth_accounts' })) ?? []) as string[] + return accounts.length > 0 + } + + return true + } + } catch { + // ignore + } + + return false + } + + /** + * @inheritdoc Connector.isomorphicInitialize + */ + private async isomorphicInitialize(): Promise { + if (this.eagerConnection) return + + return (this.eagerConnection = import('@metamask/sdk').then(async (m) => { + if (!this.sdk) { + this.sdk = new m.default({ + _source: 'web3React', + useDeeplink: true, + injectProvider: false, + forceInjectProvider: false, + forceDeleteProvider: false, + ...this.options, + }) + await this.sdk.init() + } + + this.provider = this.sdk.getProvider()! + + this.provider.on('connect', (({ chainId }: ProviderConnectInfo): void => { + this.actions.update({ chainId: parseChainId(chainId) }) + }) as Listener) + + this.provider.on('disconnect', (async (error: ProviderRpcError): Promise => { + const originalError = ((error.data as any)?.originalError ?? error) as ProviderRpcError + + // If MetaMask emits a `code: 1013` error, wait for reconnection before disconnecting + // https://github.com/MetaMask/providers/pull/120 + if (error && originalError.code === 1013 && this.provider) { + const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[] + if (accounts.length > 0) return + } + + this.clearCache() + + this.actions.resetState() + this.onError?.(error) + }) as Listener) + + this.provider.on('chainChanged', ((chainId: string): void => { + this.actions.update({ chainId: parseChainId(chainId) }) + }) as Listener) + + this.provider.on('accountsChanged', ((accounts: string[]): void => { + // Disconnect if there are no accounts + if (accounts.length === 0) { + // ... and using browser extension + if (this.sdk?.isExtensionActive()) { + this.clearCache() + this.actions.resetState() + } + // FIXME(upstream): Mobile app sometimes emits invalid `accountsChanged` event with empty accounts array + else return + } else { + this.actions.update({ accounts }) + } + }) as Listener) + })) + } + + /** + * @inheritdoc Connector.connectEagerly + */ + public async connectEagerly(): Promise { + const cancelActivation = this.actions.startActivation() + + try { + await this.isomorphicInitialize() + if (!this.provider) return cancelActivation() + + // Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing + // chains; they should be requested serially, with accounts first, so that the chainId can settle. + const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[] + if (!accounts.length) throw new Error('No accounts returned') + const chainId = (await this.provider.request({ method: 'eth_chainId' })) as string + this.actions.update({ chainId: parseChainId(chainId), accounts }) + } catch { + // we should be able to use `cancelActivation` here, but on mobile, metamask emits a 'connect' + // event, meaning that chainId is updated, and cancelActivation doesn't work because an intermediary + // update has occurred, so we reset state instead + this.actions.resetState() + } + } + + /** + * Initiates a connection. + * + * @param desiredChainIdOrChainParameters - If defined, indicates the desired chain to connect to. If the user is + * already connected to this chain, no additional steps will be taken. Otherwise, the user will be prompted to switch + * to the chain, if one of two conditions is met: either they already have it added in their extension, or the + * argument is of type AddEthereumChainParameter, in which case the user will be prompted to add the chain with the + * specified parameters first, before being prompted to switch. + */ + public async activate(desiredChainIdOrChainParameters?: number | AddEthereumChainParameter): Promise { + const [desiredChainId, desiredChain] = + typeof desiredChainIdOrChainParameters === 'number' + ? [desiredChainIdOrChainParameters, undefined] + : [desiredChainIdOrChainParameters?.chainId, desiredChainIdOrChainParameters] + + // If user already connected, only switch chain + if (this.provider && (await this.isConnected())) { + await this.switchChain(desiredChainId, desiredChain) + return + } + + // If user not connected, connect eagerly + // Then switch chain + const cancelActivation = this.actions.startActivation() + return this.isomorphicInitialize() + .then(async () => { + if (!this.provider || !this.sdk) throw new NoMetaMaskSDKError() + + const accounts = await this.sdk.connect() + const currentChainIdHex = (await this.provider.request({ method: 'eth_chainId' })) as string + const currentChainId = parseChainId(currentChainIdHex) + + await this.actions.update({ chainId: currentChainId, accounts }) + }) + .catch((error) => { + cancelActivation?.() + throw error + }) + } + + /** + * @inheritdoc Connector.deactivate + */ + public deactivate(): void { + this.sdk?.terminate() + } + + /** + * Watches an asset in the MetaMask wallet. + */ + public async watchAsset({ address, symbol, decimals, image }: WatchAssetParameters): Promise { + if (!this.provider) throw new NoMetaMaskSDKError() + + return this.provider + .request({ + method: 'wallet_watchAsset', + params: { + type: 'ERC20', // Initially only supports ERC20, but eventually more! + options: { + address, // The address that the token is at. + symbol, // A ticker symbol or shorthand, up to 5 chars. + decimals, // The number of decimals in the token + image, // A string url of the token logo + }, + }, + }) + .then((success) => { + if (!success) throw new Error('Rejected') + return true + }) + } + + /** + * Switches the chain of the MetaMask wallet. + * + * Only switches the chain if the desired chain is different from the current chain. + * Else returns the current chain id. + */ + private async switchChain(desiredChainId?: number, desiredChain?: AddEthereumChainParameter): Promise { + if (!this.provider) throw new NoMetaMaskSDKError() + + const currentChainIdHex = (await this.provider.request({ method: 'eth_chainId' })) as string + const currentChainId = parseChainId(currentChainIdHex) + + if (!desiredChainId || currentChainId === desiredChainId) return currentChainId + + const chainIdHex = `0x${desiredChainId.toString(16)}` + this.provider + .request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: chainIdHex }], + }) + .catch(async (error: ProviderRpcError) => { + const originalError = ((error.data as any)?.originalError ?? error) as ProviderRpcError + + if (originalError.code === 4902 && desiredChain !== undefined) { + if (!this.provider) throw new NoMetaMaskSDKError() + // if we're here, we can try to add a new network + return this.provider.request({ + method: 'wallet_addEthereumChain', + params: [{ ...desiredChain, chainId: chainIdHex }], + }) + } + + throw error + }) + + const newChainIdHex = (await this.provider.request({ method: 'eth_chainId' })) as string + const newChainId = parseChainId(newChainIdHex) + + return newChainId + } + + /** + * Clears the cache. + */ + private clearCache() { + localStorage.removeItem('.MMSDK_cached_address') + localStorage.removeItem('.MMSDK_cached_chainId') + localStorage.removeItem('.sdk-comm') + localStorage.removeItem('.MetaMaskSDKLng') + } +} diff --git a/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts b/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts index f929969914..2b8f7fb69a 100644 --- a/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts +++ b/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts @@ -3,6 +3,7 @@ import { Connector } from '@web3-react/types' import { ConnectionType } from '../../api/types' import { coinbaseWalletConnection } from '../connection/coinbase' import { injectedWalletConnection } from '../connection/injectedWallet' +import { metaMaskSdkConnection } from '../connection/metaMaskSdk' import { networkConnection } from '../connection/network' import { gnosisSafeConnection } from '../connection/safe' import { trezorConnection } from '../connection/trezor' @@ -11,6 +12,7 @@ import { Web3ReactConnection } from '../types' const connectionTypeToConnection: Record = { [ConnectionType.INJECTED]: injectedWalletConnection, + [ConnectionType.METAMASK]: metaMaskSdkConnection, [ConnectionType.COINBASE_WALLET]: coinbaseWalletConnection, [ConnectionType.WALLET_CONNECT_V2]: walletConnectConnectionV2, [ConnectionType.NETWORK]: networkConnection, diff --git a/libs/wallet/src/web3-react/utils/isChainAllowed.ts b/libs/wallet/src/web3-react/utils/isChainAllowed.ts index a53710fff4..86c19de13c 100644 --- a/libs/wallet/src/web3-react/utils/isChainAllowed.ts +++ b/libs/wallet/src/web3-react/utils/isChainAllowed.ts @@ -7,6 +7,7 @@ import { ConnectionType } from '../../api/types' const allowedChainsByWallet: Record = { [ConnectionType.INJECTED]: ALL_SUPPORTED_CHAIN_IDS, + [ConnectionType.METAMASK]: ALL_SUPPORTED_CHAIN_IDS, [ConnectionType.COINBASE_WALLET]: ALL_SUPPORTED_CHAIN_IDS, [ConnectionType.WALLET_CONNECT_V2]: ALL_SUPPORTED_CHAIN_IDS, [ConnectionType.NETWORK]: ALL_SUPPORTED_CHAIN_IDS, diff --git a/package.json b/package.json index af52146600..1821b9e530 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@material-ui/core": "^4.11.0", "@metamask/eth-sig-util": "^5.0.2", "@metamask/jazzicon": "^2.0.0", + "@metamask/sdk": "^0.31.2", "@mui/icons-material": "^5.14.13", "@mui/lab": "^5.0.0-alpha.148", "@mui/material": "^5.14.13", @@ -342,4 +343,4 @@ "vite-tsconfig-paths": "~4.3.2", "vitest": "~0.32.0" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 486f9b583b..aec760d5f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2063,6 +2063,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -2615,6 +2622,11 @@ mersenne-twister "^1.1.0" react-blockies "^1.4.1" +"@ecies/ciphers@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@ecies/ciphers/-/ciphers-0.2.2.tgz#82a15b10a6e502b63fb30915d944b2eaf3ff17ff" + integrity sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg== + "@emnapi/runtime@^1.2.0": version "1.3.1" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" @@ -4886,6 +4898,58 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" +"@metamask/json-rpc-engine@^8.0.1", "@metamask/json-rpc-engine@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz#29510a871a8edef892f838ee854db18de0bf0d14" + integrity sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA== + dependencies: + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + +"@metamask/json-rpc-middleware-stream@^7.0.1": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.2.tgz#2e8b2cbc38968e3c6239a9144c35bbb08a8fb57d" + integrity sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg== + dependencies: + "@metamask/json-rpc-engine" "^8.0.2" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + readable-stream "^3.6.2" + +"@metamask/object-multiplex@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz#5e2e908fc46aee581cbba809870eeee0e571cbb6" + integrity sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA== + dependencies: + once "^1.4.0" + readable-stream "^3.6.2" + +"@metamask/onboarding@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@metamask/onboarding/-/onboarding-1.0.1.tgz#14a36e1e175e2f69f09598e2008ab6dc1b3297e6" + integrity sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ== + dependencies: + bowser "^2.9.0" + +"@metamask/providers@16.1.0": + version "16.1.0" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-16.1.0.tgz#7da593d17c541580fa3beab8d9d8a9b9ce19ea07" + integrity sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g== + dependencies: + "@metamask/json-rpc-engine" "^8.0.1" + "@metamask/json-rpc-middleware-stream" "^7.0.1" + "@metamask/object-multiplex" "^2.0.0" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.1.1" + "@metamask/utils" "^8.3.0" + detect-browser "^5.2.0" + extension-port-stream "^3.0.0" + fast-deep-equal "^3.1.3" + is-stream "^2.0.0" + readable-stream "^3.6.2" + webextension-polyfill "^0.10.0" + "@metamask/rpc-errors@^6.2.1": version "6.2.1" resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-6.2.1.tgz#f5daf429ededa7cb83069dc621bd5738fe2a1d80" @@ -4904,6 +4968,54 @@ resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.1.tgz#e89b840a7af8097a8ed4953d8dc8470d1302d3ef" integrity sha512-ihb3B0T/wJm1eUuArYP4lCTSEoZsClHhuWyfo/kMX3m/odpqNcPfsz5O2A3NT7dXCAgWPGDQGPqygCpgeniKMw== +"@metamask/safe-event-emitter@^3.1.1": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz#bfac8c7a1a149b5bbfe98f59fbfea512dfa3bad4" + integrity sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA== + +"@metamask/sdk-communication-layer@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.31.0.tgz#0acc063b62aa09d044c7aab65801712d760e53b2" + integrity sha512-V9CxdzabDPjQVgmKGHsyU3SYt4Af27g+4DbGCx0fLoHqN/i1RBDZqs/LYbJX3ykJCANzE+llz/MolMCMrzM2RA== + dependencies: + bufferutil "^4.0.8" + date-fns "^2.29.3" + debug "^4.3.4" + utf-8-validate "^5.0.2" + uuid "^8.3.2" + +"@metamask/sdk-install-modal-web@0.31.2": + version "0.31.2" + resolved "https://registry.yarnpkg.com/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.31.2.tgz#bb8c92a6844a632be8525e7bb5a35924a926d6cd" + integrity sha512-KPv36kQjmTwErU8g2neuHHSgkD5+1hp4D6ERfk5Kc2r73aOYNCdG9wDGRUmFmcY2MKkeK1EuDyZfJ4FPU30fxQ== + dependencies: + "@paulmillr/qr" "^0.2.1" + +"@metamask/sdk@^0.31.2": + version "0.31.2" + resolved "https://registry.yarnpkg.com/@metamask/sdk/-/sdk-0.31.2.tgz#2ec1c1c7cf6a444e65104862e83814a493047d72" + integrity sha512-6MWON2g1j7XwAHWam4trusGxeyhQweNLEHPsfuIxSwcsXoEm08Jj80OglJxQI4KwjcDnjSWBkQGG3mmK6ug/cA== + dependencies: + "@babel/runtime" "^7.26.0" + "@metamask/onboarding" "^1.0.1" + "@metamask/providers" "16.1.0" + "@metamask/sdk-communication-layer" "0.31.0" + "@metamask/sdk-install-modal-web" "0.31.2" + "@paulmillr/qr" "^0.2.1" + bowser "^2.9.0" + cross-fetch "^4.0.0" + debug "^4.3.4" + eciesjs "^0.4.11" + eth-rpc-errors "^4.0.3" + eventemitter2 "^6.4.9" + obj-multiplex "^1.0.0" + pump "^3.0.0" + readable-stream "^3.6.2" + socket.io-client "^4.5.1" + tslib "^2.6.0" + util "^0.12.4" + uuid "^8.3.2" + "@metamask/utils@^3.0.1": version "3.6.0" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-3.6.0.tgz#b218b969a05ca7a8093b5d1670f6625061de707d" @@ -5252,6 +5364,11 @@ dependencies: eslint-scope "5.1.1" +"@noble/ciphers@^1.0.0": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.1.3.tgz#eb27085aa7ce94d8c6eaeb64299bab0589920ec1" + integrity sha512-Ygv6WnWJHLLiW4fnNDC1z+i13bud+enXOFRBlpxI+NJliPWx5wdR+oWlTjLuBPTqjUjtHXtjkU6w3kuuH6upZA== + "@noble/curves@1.1.0", "@noble/curves@^1.0.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -5266,6 +5383,13 @@ dependencies: "@noble/hashes" "1.3.2" +"@noble/curves@^1.6.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" @@ -5281,11 +5405,21 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + "@noble/hashes@^1.3.1": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@noble/hashes@^1.5.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== + "@noble/hashes@~1.3.2": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" @@ -5802,6 +5936,11 @@ "@parcel/watcher-win32-ia32" "2.3.0" "@parcel/watcher-win32-x64" "2.3.0" +"@paulmillr/qr@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@paulmillr/qr/-/qr-0.2.1.tgz#76ade7080be4ac4824f638146fd8b6db1805eeca" + integrity sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ== + "@phenomnomnominal/tsquery@~5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz#a2a5abc89f92c01562a32806655817516653a388" @@ -6685,6 +6824,11 @@ chalk "^2.3.0" shell-quote "^1.6.1" +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + "@solana/buffer-layout@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" @@ -11909,6 +12053,11 @@ borsh@^0.7.0: bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" +bowser@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -12151,6 +12300,13 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" +bufferutil@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== + dependencies: + node-gyp-build "^4.3.0" + builtin-modules@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -14308,7 +14464,7 @@ debug@^4.3.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@^4.3.6: +debug@^4.3.6, debug@~4.3.1, debug@~4.3.2: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -14545,7 +14701,7 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-browser@5.3.0, detect-browser@^5.1.0, detect-browser@^5.3.0: +detect-browser@5.3.0, detect-browser@^5.1.0, detect-browser@^5.2.0, detect-browser@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca" integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== @@ -14910,6 +15066,16 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +eciesjs@^0.4.11: + version "0.4.12" + resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.4.12.tgz#0ce482454953592e07b79b4824751f3b5c508b56" + integrity sha512-DGejvMCihsRAmKRFQiL6KZDE34vWVd0gvXlykFq1aEzJy/rD65AVyAIUZKZOvgvaP9ATQRcHGEZV5DfgrgjA4w== + dependencies: + "@ecies/ciphers" "^0.2.1" + "@noble/ciphers" "^1.0.0" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -15013,13 +15179,29 @@ encoding@^0.1.11, encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" +engine.io-client@~6.6.1: + version "6.6.2" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.2.tgz#e0a09e1c90effe5d6264da1c56d7281998f1e50b" + integrity sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + xmlhttprequest-ssl "~2.1.1" + +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== + enhanced-resolve@^5.0.0, enhanced-resolve@^5.12.0, enhanced-resolve@^5.16.0, enhanced-resolve@^5.7.0: version "5.16.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" @@ -16269,7 +16451,7 @@ eth-rpc-errors@4.0.2: dependencies: fast-safe-stringify "^2.0.6" -eth-rpc-errors@^4.0.2: +eth-rpc-errors@^4.0.2, eth-rpc-errors@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a" integrity sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg== @@ -16418,6 +16600,11 @@ eventemitter2@6.4.7: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== +eventemitter2@^6.4.9: + version "6.4.9" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" + integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== + eventemitter3@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -16621,6 +16808,14 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +extension-port-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/extension-port-stream/-/extension-port-stream-3.0.0.tgz#00a7185fe2322708a36ed24843c81bd754925fef" + integrity sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw== + dependencies: + readable-stream "^3.6.2 || ^4.4.2" + webextension-polyfill ">=0.10.0 <1.0" + external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -23729,6 +23924,15 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +obj-multiplex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/obj-multiplex/-/obj-multiplex-1.0.0.tgz#2f2ae6bfd4ae11befe742ea9ea5b36636eabffc1" + integrity sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA== + dependencies: + end-of-stream "^1.4.0" + once "^1.4.0" + readable-stream "^2.3.3" + object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -26484,7 +26688,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -26493,7 +26697,7 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stre string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.1, readable-stream@~2.3.6: +readable-stream@^2.0.1, readable-stream@^2.3.3, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -26506,6 +26710,17 @@ readable-stream@^2.0.1, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +"readable-stream@^3.6.2 || ^4.4.2": + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + readable-web-to-node-stream@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" @@ -27805,6 +28020,24 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +socket.io-client@^4.5.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb" + integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.6.1" + socket.io-parser "~4.2.4" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -28309,7 +28542,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.0.0, string_decoder@^1.1.1: +string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -29376,6 +29609,11 @@ tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== +tslib@^2.6.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -30947,6 +31185,16 @@ web3modal@1.9.0: styled-components "^5.1.1" tslib "^1.10.0" +"webextension-polyfill@>=0.10.0 <1.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz#f62c57d2cd42524e9fbdcee494c034cae34a3d69" + integrity sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q== + +webextension-polyfill@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz#ccb28101c910ba8cf955f7e6a263e662d744dbb8" + integrity sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -31822,6 +32070,11 @@ ws@^3.0.0: safe-buffer "~5.1.0" ultron "~1.1.0" +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -31872,6 +32125,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmlhttprequest-ssl@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23" + integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ== + xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From fcc61ee7fb1e8a1748b0d89cd9950a61295a18fb Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 16 Dec 2024 19:03:58 +0500 Subject: [PATCH 02/12] fix: compare recipient address with no case sensitivity (#5208) --- .../modules/account/containers/Transaction/ActivityDetails.tsx | 3 ++- .../src/utils/orderUtils/getIsCustomRecipient.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx b/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx index ba5a2e8a76..6e41f34912 100644 --- a/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx +++ b/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx @@ -35,6 +35,7 @@ import { useHideReceiverWalletBanner, useIsReceiverWalletBannerHidden, } from 'common/state/receiverWalletBannerVisibility' +import { getIsCustomRecipient } from 'utils/orderUtils/getIsCustomRecipient' import { getUiOrderType } from 'utils/orderUtils/getUiOrderType' import { StatusDetails } from './StatusDetails' @@ -298,7 +299,7 @@ export function ActivityDetails(props: { outputToken = COW[chainId as SupportedChainId] } - const isCustomRecipient = Boolean(order?.receiver && order.owner !== order.receiver) + const isCustomRecipient = !!order && getIsCustomRecipient(order) return ( <> diff --git a/apps/cowswap-frontend/src/utils/orderUtils/getIsCustomRecipient.ts b/apps/cowswap-frontend/src/utils/orderUtils/getIsCustomRecipient.ts index 1260aad6b2..149b083ccb 100644 --- a/apps/cowswap-frontend/src/utils/orderUtils/getIsCustomRecipient.ts +++ b/apps/cowswap-frontend/src/utils/orderUtils/getIsCustomRecipient.ts @@ -1,5 +1,5 @@ import { Order } from 'legacy/state/orders/actions' export function getIsCustomRecipient({ owner, receiver }: Pick): boolean { - return Boolean(receiver && owner !== receiver) + return Boolean(receiver && owner.toLowerCase() !== receiver.toLowerCase()) } From 889b78ed8b9e2092abbe503ed69c797e482b2edc Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 17 Dec 2024 15:39:00 +0500 Subject: [PATCH 03/12] Revert "feat(wallets): add metaMask SDK connector (#5028)" (#5215) This reverts commit fe8f3767593684dc9d825d603a894c3b0934d894. --- .../containers/ConnectWalletOptions.tsx | 37 ++- .../api/state/multiInjectedProvidersAtom.ts | 2 +- libs/wallet/src/api/types.ts | 1 - libs/wallet/src/api/utils/connection.ts | 3 - libs/wallet/src/index.ts | 3 +- .../web3-react/connection/injectedOptions.tsx | 30 ++ .../src/web3-react/connection/metaMaskSdk.tsx | 54 --- .../connectors/metaMaskSdk/index.ts | 307 ------------------ .../utils/getWeb3ReactConnection.ts | 2 - .../src/web3-react/utils/isChainAllowed.ts | 1 - package.json | 3 +- yarn.lock | 272 +--------------- 12 files changed, 62 insertions(+), 653 deletions(-) delete mode 100644 libs/wallet/src/web3-react/connection/metaMaskSdk.tsx delete mode 100644 libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts diff --git a/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx b/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx index 9885ea144e..1bf6d0bb2d 100644 --- a/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx +++ b/apps/cowswap-frontend/src/modules/wallet/containers/ConnectWalletOptions.tsx @@ -3,7 +3,8 @@ import { isMobile, isInjectedWidget } from '@cowprotocol/common-utils' import { CoinbaseWalletOption, InjectedOption as DefaultInjectedOption, - MetaMaskSdkOption, + InstallMetaMaskOption, + OpenMetaMaskMobileOption, TrezorOption, WalletConnectV2Option, getIsInjected, @@ -30,17 +31,14 @@ export function ConnectWalletOptions({ tryActivation }: { tryActivation: TryActi const connectionProps = { darkMode, selectedWallet, tryActivation } - const metaMaskSdkOption = const coinbaseWalletOption = (!hasCoinbaseEip6963 && ) ?? null const walletConnectionV2Option = ((!isInjectedMobileBrowser || isWidget) && ) ?? null const trezorOption = (!isInjectedMobileBrowser && !isMobile && ) ?? null - const injectedOption = (getIsInjected() && ) ?? null return ( <> - {injectedOption} - {metaMaskSdkOption} + {walletConnectionV2Option} {coinbaseWalletOption} {trezorOption} @@ -59,13 +57,19 @@ interface InjectedOptionsProps { } function InjectedOptions({ connectionProps, multiInjectedProviders }: InjectedOptionsProps) { - if (multiInjectedProviders.length) { - return ( - <> - {multiInjectedProviders - // Even if we detect the MetaMask Extension, we prefer to use the MetaMask SDK - .filter((providerInfo) => !providerInfo.info.rdns.startsWith('io.metamask')) - .map((providerInfo) => { + const isInjected = getIsInjected() + + if (!isInjected) { + if (!isMobile) { + return + } else { + return + } + } else { + if (multiInjectedProviders.length) { + return ( + <> + {multiInjectedProviders.map((providerInfo) => { return ( ) })} - - ) - } + + ) + } - return + return + } } diff --git a/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts b/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts index c5a1a9b9f3..ac5e1abd2f 100644 --- a/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts +++ b/libs/wallet/src/api/state/multiInjectedProvidersAtom.ts @@ -26,7 +26,7 @@ window.addEventListener('eip6963:announceProvider', (event: Event) => { jotaiStore.set(multiInjectedProvidersAtom, (prev: EIP6963ProviderDetail[]) => { const newProvider = providerEvent.detail - const existingProvider = prev.find((p) => p.info.rdns === newProvider.info.rdns) + const existingProvider = prev.find((p) => p.info.rdns === newProvider.info.uuid) if (existingProvider) return prev diff --git a/libs/wallet/src/api/types.ts b/libs/wallet/src/api/types.ts index c43a7721e9..f177ca6b11 100644 --- a/libs/wallet/src/api/types.ts +++ b/libs/wallet/src/api/types.ts @@ -8,7 +8,6 @@ export enum ConnectionType { INJECTED = 'INJECTED', WALLET_CONNECT_V2 = 'WALLET_CONNECT_V2', COINBASE_WALLET = 'COINBASE_WALLET', - METAMASK = 'METAMASK', GNOSIS_SAFE = 'GNOSIS_SAFE', TREZOR = 'TREZOR', } diff --git a/libs/wallet/src/api/utils/connection.ts b/libs/wallet/src/api/utils/connection.ts index 318a20404e..c936123d7a 100644 --- a/libs/wallet/src/api/utils/connection.ts +++ b/libs/wallet/src/api/utils/connection.ts @@ -1,6 +1,5 @@ import { isMobile } from '@cowprotocol/common-utils' -import { default as MetamaskImage } from '../../api/assets/metamask.png' import CoinbaseWalletIcon from '../assets/coinbase.svg' import TrezorIcon from '../assets/trezor.svg' import WalletConnectIcon from '../assets/walletConnectIcon.svg' @@ -8,7 +7,6 @@ import { ConnectionType } from '../types' const connectionTypeToName: Record = { [ConnectionType.INJECTED]: 'Injected', - [ConnectionType.METAMASK]: 'MetaMask', [ConnectionType.COINBASE_WALLET]: 'Coinbase Wallet', [ConnectionType.WALLET_CONNECT_V2]: 'WalletConnect', [ConnectionType.NETWORK]: 'Network', @@ -20,7 +18,6 @@ const IDENTICON_KEY = 'Identicon' const connectionTypeToIcon: Record = { [ConnectionType.INJECTED]: IDENTICON_KEY, - [ConnectionType.METAMASK]: MetamaskImage, [ConnectionType.GNOSIS_SAFE]: IDENTICON_KEY, [ConnectionType.NETWORK]: IDENTICON_KEY, [ConnectionType.COINBASE_WALLET]: CoinbaseWalletIcon, diff --git a/libs/wallet/src/index.ts b/libs/wallet/src/index.ts index a781aa93ad..68a2559d75 100644 --- a/libs/wallet/src/index.ts +++ b/libs/wallet/src/index.ts @@ -42,6 +42,8 @@ export { walletConnectConnectionV2 } from './web3-react/connection/walletConnect // Connect options export { InjectedOption, + InstallMetaMaskOption, + OpenMetaMaskMobileOption, Eip6963Option, } from './web3-react/connection/injectedOptions' @@ -49,7 +51,6 @@ export { ConnectWalletOption } from './api/pure/ConnectWalletOption' export { TrezorOption } from './web3-react/connection/trezor' export { WalletConnectV2Option } from './web3-react/connection/walletConnectV2' export { CoinbaseWalletOption } from './web3-react/connection/coinbase' -export { MetaMaskSdkOption } from './web3-react/connection/metaMaskSdk' // State // TODO: this export is discussable, however it's already used outside diff --git a/libs/wallet/src/web3-react/connection/injectedOptions.tsx b/libs/wallet/src/web3-react/connection/injectedOptions.tsx index e4d8c16451..9b4e8cc090 100644 --- a/libs/wallet/src/web3-react/connection/injectedOptions.tsx +++ b/libs/wallet/src/web3-react/connection/injectedOptions.tsx @@ -3,6 +3,7 @@ import { useCallback } from 'react' import { injectedWalletConnection } from './injectedWallet' import { default as InjectedImage, default as InjectedImageDark } from '../../api/assets/arrow-right.svg' +import { default as MetamaskImage } from '../../api/assets/metamask.png' import { useSelectedEip6963ProviderRdns, useSetEip6963Provider } from '../../api/hooks' import { ConnectWalletOption } from '../../api/pure/ConnectWalletOption' import { ConnectionType, type EIP1193Provider, EIP6963ProviderDetail } from '../../api/types' @@ -10,6 +11,14 @@ import { getConnectionName } from '../../api/utils/connection' import { useIsActiveConnection } from '../hooks/useIsActiveConnection' import { ConnectionOptionProps, TryActivation } from '../types' +const METAMASK_DEEP_LINK = 'https://metamask.app.link/dapp/' + +const metamaskCommonOption = { + color: '#E8831D', + icon: MetamaskImage, + id: 'metamask', +} + const injectedCommon = { color: '#010101', id: 'injected', @@ -24,6 +33,27 @@ export const injectedOptionDark = { icon: InjectedImageDark, } +export const metamaskInstallOption = { + ...metamaskCommonOption, + header: 'Install MetaMask', + link: 'https://metamask.io/', +} + +export const metamaskInjectedOption = { + ...metamaskCommonOption, + header: 'MetaMask', +} + +export function InstallMetaMaskOption() { + return +} + +export function OpenMetaMaskMobileOption() { + return ( + + ) +} + export function InjectedOption({ darkMode, tryActivation, selectedWallet }: ConnectionOptionProps) { const options = darkMode ? injectedOptionDark : injectedOption diff --git a/libs/wallet/src/web3-react/connection/metaMaskSdk.tsx b/libs/wallet/src/web3-react/connection/metaMaskSdk.tsx deleted file mode 100644 index 01f63209c2..0000000000 --- a/libs/wallet/src/web3-react/connection/metaMaskSdk.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { RPC_URLS } from '@cowprotocol/common-const' -import { initializeConnector } from '@web3-react/core' - -import { onError } from './onError' - -import { default as MetamaskImage } from '../../api/assets/metamask.png' -import { ConnectWalletOption } from '../../api/pure/ConnectWalletOption' -import { ConnectionType } from '../../api/types' -import { getConnectionName } from '../../api/utils/connection' -import { MetaMaskSDK } from '../connectors/metaMaskSdk' -import { useIsActiveConnection } from '../hooks/useIsActiveConnection' -import { ConnectionOptionProps, Web3ReactConnection } from '../types' - -const metaMaskOption = { - color: '#E8831D', - icon: MetamaskImage, - id: 'metamask', -} - -const [web3MetaMask, web3MetaMaskHooks] = initializeConnector( - (actions) => - new MetaMaskSDK({ - actions, - options: { - dappMetadata: { - name: 'CoW Swap', - url: 'https://swap.cow.fi', - }, - readonlyRPCMap: Object.fromEntries( - Object.entries(RPC_URLS).map(([chainId, url]) => [`0x${Number(chainId).toString(16)}`, url]), - ), - }, - onError, - }), -) - -export const metaMaskSdkConnection: Web3ReactConnection = { - connector: web3MetaMask, - hooks: web3MetaMaskHooks, - type: ConnectionType.METAMASK, -} - -export function MetaMaskSdkOption({ tryActivation, selectedWallet }: ConnectionOptionProps) { - const isActive = useIsActiveConnection(selectedWallet, metaMaskSdkConnection) - - return ( - tryActivation(metaMaskSdkConnection.connector)} - header={getConnectionName(ConnectionType.METAMASK)} - /> - ) -} diff --git a/libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts b/libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts deleted file mode 100644 index 69c4f15698..0000000000 --- a/libs/wallet/src/web3-react/connectors/metaMaskSdk/index.ts +++ /dev/null @@ -1,307 +0,0 @@ -import type { - Actions, - AddEthereumChainParameter, - Provider, - ProviderConnectInfo, - ProviderRpcError, - WatchAssetParameters, -} from '@web3-react/types' -import { Connector } from '@web3-react/types' - -import type { MetaMaskSDK as _MetaMaskSDK, MetaMaskSDKOptions as _MetaMaskSDKOptions, SDKProvider } from '@metamask/sdk' - -/** - * MetaMaskSDK options. - */ -type MetaMaskSDKOptions = Pick<_MetaMaskSDKOptions, 'infuraAPIKey' | 'readonlyRPCMap'> & { - dappMetadata: Pick<_MetaMaskSDKOptions['dappMetadata'], 'name' | 'url' | 'iconUrl'> -} - -/** - * Listener type for MetaMaskSDK events. - */ -type Listener = Parameters[1] - -/** - * Error thrown when the MetaMaskSDK is not installed. - */ -export class NoMetaMaskSDKError extends Error { - public constructor() { - super('MetaMaskSDK not installed') - this.name = NoMetaMaskSDKError.name - Object.setPrototypeOf(this, NoMetaMaskSDKError.prototype) - } -} - -/** - * Parses a chainId from a string or number. - */ -function parseChainId(chainId: string | number) { - return typeof chainId === 'number' ? chainId : Number.parseInt(chainId, chainId.startsWith('0x') ? 16 : 10) -} - -/** - * @param options - Options to pass to `@metamask/sdk` - * @param onError - Handler to report errors thrown from eventListeners. - */ -export interface MetaMaskSDKConstructorArgs { - actions: Actions - options?: MetaMaskSDKOptions - onError?: (error: Error) => void -} - -/** - * Connector for the MetaMaskSDK. - */ -export class MetaMaskSDK extends Connector { - private sdk?: _MetaMaskSDK - provider?: SDKProvider = undefined - private readonly options: MetaMaskSDKOptions - private eagerConnection?: Promise - - /** - * @inheritdoc Connector.constructor - */ - constructor({ actions, options, onError }: MetaMaskSDKConstructorArgs) { - super(actions, onError) - - const defaultUrl = typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}` : '' - - this.options = { - ...options, - dappMetadata: options?.dappMetadata ?? { - url: defaultUrl, - name: defaultUrl !== '' ? undefined : 'wagmi', - }, - } - } - - /** - * Indicates whether the user is connected to the MetaMaskSDK. - */ - private async isConnected() { - try { - if (this.provider?.isConnected?.() === true) { - if (this.sdk?.isExtensionActive() === true) { - const accounts = ((await this.provider?.request({ method: 'eth_accounts' })) ?? []) as string[] - return accounts.length > 0 - } - - return true - } - } catch { - // ignore - } - - return false - } - - /** - * @inheritdoc Connector.isomorphicInitialize - */ - private async isomorphicInitialize(): Promise { - if (this.eagerConnection) return - - return (this.eagerConnection = import('@metamask/sdk').then(async (m) => { - if (!this.sdk) { - this.sdk = new m.default({ - _source: 'web3React', - useDeeplink: true, - injectProvider: false, - forceInjectProvider: false, - forceDeleteProvider: false, - ...this.options, - }) - await this.sdk.init() - } - - this.provider = this.sdk.getProvider()! - - this.provider.on('connect', (({ chainId }: ProviderConnectInfo): void => { - this.actions.update({ chainId: parseChainId(chainId) }) - }) as Listener) - - this.provider.on('disconnect', (async (error: ProviderRpcError): Promise => { - const originalError = ((error.data as any)?.originalError ?? error) as ProviderRpcError - - // If MetaMask emits a `code: 1013` error, wait for reconnection before disconnecting - // https://github.com/MetaMask/providers/pull/120 - if (error && originalError.code === 1013 && this.provider) { - const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[] - if (accounts.length > 0) return - } - - this.clearCache() - - this.actions.resetState() - this.onError?.(error) - }) as Listener) - - this.provider.on('chainChanged', ((chainId: string): void => { - this.actions.update({ chainId: parseChainId(chainId) }) - }) as Listener) - - this.provider.on('accountsChanged', ((accounts: string[]): void => { - // Disconnect if there are no accounts - if (accounts.length === 0) { - // ... and using browser extension - if (this.sdk?.isExtensionActive()) { - this.clearCache() - this.actions.resetState() - } - // FIXME(upstream): Mobile app sometimes emits invalid `accountsChanged` event with empty accounts array - else return - } else { - this.actions.update({ accounts }) - } - }) as Listener) - })) - } - - /** - * @inheritdoc Connector.connectEagerly - */ - public async connectEagerly(): Promise { - const cancelActivation = this.actions.startActivation() - - try { - await this.isomorphicInitialize() - if (!this.provider) return cancelActivation() - - // Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing - // chains; they should be requested serially, with accounts first, so that the chainId can settle. - const accounts = (await this.provider.request({ method: 'eth_accounts' })) as string[] - if (!accounts.length) throw new Error('No accounts returned') - const chainId = (await this.provider.request({ method: 'eth_chainId' })) as string - this.actions.update({ chainId: parseChainId(chainId), accounts }) - } catch { - // we should be able to use `cancelActivation` here, but on mobile, metamask emits a 'connect' - // event, meaning that chainId is updated, and cancelActivation doesn't work because an intermediary - // update has occurred, so we reset state instead - this.actions.resetState() - } - } - - /** - * Initiates a connection. - * - * @param desiredChainIdOrChainParameters - If defined, indicates the desired chain to connect to. If the user is - * already connected to this chain, no additional steps will be taken. Otherwise, the user will be prompted to switch - * to the chain, if one of two conditions is met: either they already have it added in their extension, or the - * argument is of type AddEthereumChainParameter, in which case the user will be prompted to add the chain with the - * specified parameters first, before being prompted to switch. - */ - public async activate(desiredChainIdOrChainParameters?: number | AddEthereumChainParameter): Promise { - const [desiredChainId, desiredChain] = - typeof desiredChainIdOrChainParameters === 'number' - ? [desiredChainIdOrChainParameters, undefined] - : [desiredChainIdOrChainParameters?.chainId, desiredChainIdOrChainParameters] - - // If user already connected, only switch chain - if (this.provider && (await this.isConnected())) { - await this.switchChain(desiredChainId, desiredChain) - return - } - - // If user not connected, connect eagerly - // Then switch chain - const cancelActivation = this.actions.startActivation() - return this.isomorphicInitialize() - .then(async () => { - if (!this.provider || !this.sdk) throw new NoMetaMaskSDKError() - - const accounts = await this.sdk.connect() - const currentChainIdHex = (await this.provider.request({ method: 'eth_chainId' })) as string - const currentChainId = parseChainId(currentChainIdHex) - - await this.actions.update({ chainId: currentChainId, accounts }) - }) - .catch((error) => { - cancelActivation?.() - throw error - }) - } - - /** - * @inheritdoc Connector.deactivate - */ - public deactivate(): void { - this.sdk?.terminate() - } - - /** - * Watches an asset in the MetaMask wallet. - */ - public async watchAsset({ address, symbol, decimals, image }: WatchAssetParameters): Promise { - if (!this.provider) throw new NoMetaMaskSDKError() - - return this.provider - .request({ - method: 'wallet_watchAsset', - params: { - type: 'ERC20', // Initially only supports ERC20, but eventually more! - options: { - address, // The address that the token is at. - symbol, // A ticker symbol or shorthand, up to 5 chars. - decimals, // The number of decimals in the token - image, // A string url of the token logo - }, - }, - }) - .then((success) => { - if (!success) throw new Error('Rejected') - return true - }) - } - - /** - * Switches the chain of the MetaMask wallet. - * - * Only switches the chain if the desired chain is different from the current chain. - * Else returns the current chain id. - */ - private async switchChain(desiredChainId?: number, desiredChain?: AddEthereumChainParameter): Promise { - if (!this.provider) throw new NoMetaMaskSDKError() - - const currentChainIdHex = (await this.provider.request({ method: 'eth_chainId' })) as string - const currentChainId = parseChainId(currentChainIdHex) - - if (!desiredChainId || currentChainId === desiredChainId) return currentChainId - - const chainIdHex = `0x${desiredChainId.toString(16)}` - this.provider - .request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: chainIdHex }], - }) - .catch(async (error: ProviderRpcError) => { - const originalError = ((error.data as any)?.originalError ?? error) as ProviderRpcError - - if (originalError.code === 4902 && desiredChain !== undefined) { - if (!this.provider) throw new NoMetaMaskSDKError() - // if we're here, we can try to add a new network - return this.provider.request({ - method: 'wallet_addEthereumChain', - params: [{ ...desiredChain, chainId: chainIdHex }], - }) - } - - throw error - }) - - const newChainIdHex = (await this.provider.request({ method: 'eth_chainId' })) as string - const newChainId = parseChainId(newChainIdHex) - - return newChainId - } - - /** - * Clears the cache. - */ - private clearCache() { - localStorage.removeItem('.MMSDK_cached_address') - localStorage.removeItem('.MMSDK_cached_chainId') - localStorage.removeItem('.sdk-comm') - localStorage.removeItem('.MetaMaskSDKLng') - } -} diff --git a/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts b/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts index 2b8f7fb69a..f929969914 100644 --- a/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts +++ b/libs/wallet/src/web3-react/utils/getWeb3ReactConnection.ts @@ -3,7 +3,6 @@ import { Connector } from '@web3-react/types' import { ConnectionType } from '../../api/types' import { coinbaseWalletConnection } from '../connection/coinbase' import { injectedWalletConnection } from '../connection/injectedWallet' -import { metaMaskSdkConnection } from '../connection/metaMaskSdk' import { networkConnection } from '../connection/network' import { gnosisSafeConnection } from '../connection/safe' import { trezorConnection } from '../connection/trezor' @@ -12,7 +11,6 @@ import { Web3ReactConnection } from '../types' const connectionTypeToConnection: Record = { [ConnectionType.INJECTED]: injectedWalletConnection, - [ConnectionType.METAMASK]: metaMaskSdkConnection, [ConnectionType.COINBASE_WALLET]: coinbaseWalletConnection, [ConnectionType.WALLET_CONNECT_V2]: walletConnectConnectionV2, [ConnectionType.NETWORK]: networkConnection, diff --git a/libs/wallet/src/web3-react/utils/isChainAllowed.ts b/libs/wallet/src/web3-react/utils/isChainAllowed.ts index 86c19de13c..a53710fff4 100644 --- a/libs/wallet/src/web3-react/utils/isChainAllowed.ts +++ b/libs/wallet/src/web3-react/utils/isChainAllowed.ts @@ -7,7 +7,6 @@ import { ConnectionType } from '../../api/types' const allowedChainsByWallet: Record = { [ConnectionType.INJECTED]: ALL_SUPPORTED_CHAIN_IDS, - [ConnectionType.METAMASK]: ALL_SUPPORTED_CHAIN_IDS, [ConnectionType.COINBASE_WALLET]: ALL_SUPPORTED_CHAIN_IDS, [ConnectionType.WALLET_CONNECT_V2]: ALL_SUPPORTED_CHAIN_IDS, [ConnectionType.NETWORK]: ALL_SUPPORTED_CHAIN_IDS, diff --git a/package.json b/package.json index 1821b9e530..af52146600 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "@material-ui/core": "^4.11.0", "@metamask/eth-sig-util": "^5.0.2", "@metamask/jazzicon": "^2.0.0", - "@metamask/sdk": "^0.31.2", "@mui/icons-material": "^5.14.13", "@mui/lab": "^5.0.0-alpha.148", "@mui/material": "^5.14.13", @@ -343,4 +342,4 @@ "vite-tsconfig-paths": "~4.3.2", "vitest": "~0.32.0" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index aec760d5f8..486f9b583b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2063,13 +2063,6 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" - integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -2622,11 +2615,6 @@ mersenne-twister "^1.1.0" react-blockies "^1.4.1" -"@ecies/ciphers@^0.2.1": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@ecies/ciphers/-/ciphers-0.2.2.tgz#82a15b10a6e502b63fb30915d944b2eaf3ff17ff" - integrity sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg== - "@emnapi/runtime@^1.2.0": version "1.3.1" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" @@ -4898,58 +4886,6 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" -"@metamask/json-rpc-engine@^8.0.1", "@metamask/json-rpc-engine@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz#29510a871a8edef892f838ee854db18de0bf0d14" - integrity sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA== - dependencies: - "@metamask/rpc-errors" "^6.2.1" - "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^8.3.0" - -"@metamask/json-rpc-middleware-stream@^7.0.1": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.2.tgz#2e8b2cbc38968e3c6239a9144c35bbb08a8fb57d" - integrity sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg== - dependencies: - "@metamask/json-rpc-engine" "^8.0.2" - "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^8.3.0" - readable-stream "^3.6.2" - -"@metamask/object-multiplex@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz#5e2e908fc46aee581cbba809870eeee0e571cbb6" - integrity sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA== - dependencies: - once "^1.4.0" - readable-stream "^3.6.2" - -"@metamask/onboarding@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@metamask/onboarding/-/onboarding-1.0.1.tgz#14a36e1e175e2f69f09598e2008ab6dc1b3297e6" - integrity sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ== - dependencies: - bowser "^2.9.0" - -"@metamask/providers@16.1.0": - version "16.1.0" - resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-16.1.0.tgz#7da593d17c541580fa3beab8d9d8a9b9ce19ea07" - integrity sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g== - dependencies: - "@metamask/json-rpc-engine" "^8.0.1" - "@metamask/json-rpc-middleware-stream" "^7.0.1" - "@metamask/object-multiplex" "^2.0.0" - "@metamask/rpc-errors" "^6.2.1" - "@metamask/safe-event-emitter" "^3.1.1" - "@metamask/utils" "^8.3.0" - detect-browser "^5.2.0" - extension-port-stream "^3.0.0" - fast-deep-equal "^3.1.3" - is-stream "^2.0.0" - readable-stream "^3.6.2" - webextension-polyfill "^0.10.0" - "@metamask/rpc-errors@^6.2.1": version "6.2.1" resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-6.2.1.tgz#f5daf429ededa7cb83069dc621bd5738fe2a1d80" @@ -4968,54 +4904,6 @@ resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.1.tgz#e89b840a7af8097a8ed4953d8dc8470d1302d3ef" integrity sha512-ihb3B0T/wJm1eUuArYP4lCTSEoZsClHhuWyfo/kMX3m/odpqNcPfsz5O2A3NT7dXCAgWPGDQGPqygCpgeniKMw== -"@metamask/safe-event-emitter@^3.1.1": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz#bfac8c7a1a149b5bbfe98f59fbfea512dfa3bad4" - integrity sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA== - -"@metamask/sdk-communication-layer@0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.31.0.tgz#0acc063b62aa09d044c7aab65801712d760e53b2" - integrity sha512-V9CxdzabDPjQVgmKGHsyU3SYt4Af27g+4DbGCx0fLoHqN/i1RBDZqs/LYbJX3ykJCANzE+llz/MolMCMrzM2RA== - dependencies: - bufferutil "^4.0.8" - date-fns "^2.29.3" - debug "^4.3.4" - utf-8-validate "^5.0.2" - uuid "^8.3.2" - -"@metamask/sdk-install-modal-web@0.31.2": - version "0.31.2" - resolved "https://registry.yarnpkg.com/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.31.2.tgz#bb8c92a6844a632be8525e7bb5a35924a926d6cd" - integrity sha512-KPv36kQjmTwErU8g2neuHHSgkD5+1hp4D6ERfk5Kc2r73aOYNCdG9wDGRUmFmcY2MKkeK1EuDyZfJ4FPU30fxQ== - dependencies: - "@paulmillr/qr" "^0.2.1" - -"@metamask/sdk@^0.31.2": - version "0.31.2" - resolved "https://registry.yarnpkg.com/@metamask/sdk/-/sdk-0.31.2.tgz#2ec1c1c7cf6a444e65104862e83814a493047d72" - integrity sha512-6MWON2g1j7XwAHWam4trusGxeyhQweNLEHPsfuIxSwcsXoEm08Jj80OglJxQI4KwjcDnjSWBkQGG3mmK6ug/cA== - dependencies: - "@babel/runtime" "^7.26.0" - "@metamask/onboarding" "^1.0.1" - "@metamask/providers" "16.1.0" - "@metamask/sdk-communication-layer" "0.31.0" - "@metamask/sdk-install-modal-web" "0.31.2" - "@paulmillr/qr" "^0.2.1" - bowser "^2.9.0" - cross-fetch "^4.0.0" - debug "^4.3.4" - eciesjs "^0.4.11" - eth-rpc-errors "^4.0.3" - eventemitter2 "^6.4.9" - obj-multiplex "^1.0.0" - pump "^3.0.0" - readable-stream "^3.6.2" - socket.io-client "^4.5.1" - tslib "^2.6.0" - util "^0.12.4" - uuid "^8.3.2" - "@metamask/utils@^3.0.1": version "3.6.0" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-3.6.0.tgz#b218b969a05ca7a8093b5d1670f6625061de707d" @@ -5364,11 +5252,6 @@ dependencies: eslint-scope "5.1.1" -"@noble/ciphers@^1.0.0": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.1.3.tgz#eb27085aa7ce94d8c6eaeb64299bab0589920ec1" - integrity sha512-Ygv6WnWJHLLiW4fnNDC1z+i13bud+enXOFRBlpxI+NJliPWx5wdR+oWlTjLuBPTqjUjtHXtjkU6w3kuuH6upZA== - "@noble/curves@1.1.0", "@noble/curves@^1.0.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -5383,13 +5266,6 @@ dependencies: "@noble/hashes" "1.3.2" -"@noble/curves@^1.6.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" - integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== - dependencies: - "@noble/hashes" "1.6.0" - "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" @@ -5405,21 +5281,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/hashes@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" - integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== - "@noble/hashes@^1.3.1": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@^1.5.0": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" - integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== - "@noble/hashes@~1.3.2": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" @@ -5936,11 +5802,6 @@ "@parcel/watcher-win32-ia32" "2.3.0" "@parcel/watcher-win32-x64" "2.3.0" -"@paulmillr/qr@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@paulmillr/qr/-/qr-0.2.1.tgz#76ade7080be4ac4824f638146fd8b6db1805eeca" - integrity sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ== - "@phenomnomnominal/tsquery@~5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz#a2a5abc89f92c01562a32806655817516653a388" @@ -6824,11 +6685,6 @@ chalk "^2.3.0" shell-quote "^1.6.1" -"@socket.io/component-emitter@~3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" - integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== - "@solana/buffer-layout@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" @@ -12053,11 +11909,6 @@ borsh@^0.7.0: bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" -bowser@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -12300,13 +12151,6 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" -bufferutil@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" - integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== - dependencies: - node-gyp-build "^4.3.0" - builtin-modules@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -14464,7 +14308,7 @@ debug@^4.3.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@^4.3.6, debug@~4.3.1, debug@~4.3.2: +debug@^4.3.6: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -14701,7 +14545,7 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-browser@5.3.0, detect-browser@^5.1.0, detect-browser@^5.2.0, detect-browser@^5.3.0: +detect-browser@5.3.0, detect-browser@^5.1.0, detect-browser@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca" integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== @@ -15066,16 +14910,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -eciesjs@^0.4.11: - version "0.4.12" - resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.4.12.tgz#0ce482454953592e07b79b4824751f3b5c508b56" - integrity sha512-DGejvMCihsRAmKRFQiL6KZDE34vWVd0gvXlykFq1aEzJy/rD65AVyAIUZKZOvgvaP9ATQRcHGEZV5DfgrgjA4w== - dependencies: - "@ecies/ciphers" "^0.2.1" - "@noble/ciphers" "^1.0.0" - "@noble/curves" "^1.6.0" - "@noble/hashes" "^1.5.0" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -15179,29 +15013,13 @@ encoding@^0.1.11, encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -engine.io-client@~6.6.1: - version "6.6.2" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.2.tgz#e0a09e1c90effe5d6264da1c56d7281998f1e50b" - integrity sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw== - dependencies: - "@socket.io/component-emitter" "~3.1.0" - debug "~4.3.1" - engine.io-parser "~5.2.1" - ws "~8.17.1" - xmlhttprequest-ssl "~2.1.1" - -engine.io-parser@~5.2.1: - version "5.2.3" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" - integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== - enhanced-resolve@^5.0.0, enhanced-resolve@^5.12.0, enhanced-resolve@^5.16.0, enhanced-resolve@^5.7.0: version "5.16.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" @@ -16451,7 +16269,7 @@ eth-rpc-errors@4.0.2: dependencies: fast-safe-stringify "^2.0.6" -eth-rpc-errors@^4.0.2, eth-rpc-errors@^4.0.3: +eth-rpc-errors@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a" integrity sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg== @@ -16600,11 +16418,6 @@ eventemitter2@6.4.7: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== -eventemitter2@^6.4.9: - version "6.4.9" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" - integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== - eventemitter3@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -16808,14 +16621,6 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extension-port-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/extension-port-stream/-/extension-port-stream-3.0.0.tgz#00a7185fe2322708a36ed24843c81bd754925fef" - integrity sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw== - dependencies: - readable-stream "^3.6.2 || ^4.4.2" - webextension-polyfill ">=0.10.0 <1.0" - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -23924,15 +23729,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -obj-multiplex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/obj-multiplex/-/obj-multiplex-1.0.0.tgz#2f2ae6bfd4ae11befe742ea9ea5b36636eabffc1" - integrity sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA== - dependencies: - end-of-stream "^1.4.0" - once "^1.4.0" - readable-stream "^2.3.3" - object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -26688,7 +26484,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -26697,7 +26493,7 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stre string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.1, readable-stream@^2.3.3, readable-stream@~2.3.6: +readable-stream@^2.0.1, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -26710,17 +26506,6 @@ readable-stream@^2.0.1, readable-stream@^2.3.3, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@^3.6.2 || ^4.4.2": - version "4.5.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" - integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== - dependencies: - abort-controller "^3.0.0" - buffer "^6.0.3" - events "^3.3.0" - process "^0.11.10" - string_decoder "^1.3.0" - readable-web-to-node-stream@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" @@ -28020,24 +27805,6 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" -socket.io-client@^4.5.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb" - integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ== - dependencies: - "@socket.io/component-emitter" "~3.1.0" - debug "~4.3.2" - engine.io-client "~6.6.1" - socket.io-parser "~4.2.4" - -socket.io-parser@~4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" - integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== - dependencies: - "@socket.io/component-emitter" "~3.1.0" - debug "~4.3.1" - sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -28542,7 +28309,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@^1.3.0: +string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -29609,11 +29376,6 @@ tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== -tslib@^2.6.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -31185,16 +30947,6 @@ web3modal@1.9.0: styled-components "^5.1.1" tslib "^1.10.0" -"webextension-polyfill@>=0.10.0 <1.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz#f62c57d2cd42524e9fbdcee494c034cae34a3d69" - integrity sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q== - -webextension-polyfill@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz#ccb28101c910ba8cf955f7e6a263e662d744dbb8" - integrity sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g== - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -32070,11 +31822,6 @@ ws@^3.0.0: safe-buffer "~5.1.0" ultron "~1.1.0" -ws@~8.17.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" - integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -32125,11 +31872,6 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmlhttprequest-ssl@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23" - integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ== - xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From e473c2b5e250b65691928a801897c3b5453ad776 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 17 Dec 2024 15:46:06 +0500 Subject: [PATCH 04/12] fix(hooks-store): disable selling eth (#5209) * fix(hooks-store): disable selling eth * chore: fix wranings conds * chore: fix condition --- .../swap/containers/SwapWidget/index.tsx | 13 ++++++- .../swap/helpers/getSwapButtonState.ts | 6 +++ .../swap/hooks/useSwapButtonContext.ts | 11 +++++- .../modules/swap/pure/SwapButtons/index.tsx | 11 ++++++ .../src/modules/swap/pure/warnings.tsx | 38 +++++++++++++------ .../containers/BundleTxWrapBanner/index.tsx | 5 ++- 6 files changed, 68 insertions(+), 16 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx index d52653c29a..a49a039628 100644 --- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx @@ -55,6 +55,7 @@ import { useRateInfoParams } from 'common/hooks/useRateInfoParams' import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types' import { SWAP_QUOTE_CHECK_INTERVAL } from 'common/updaters/FeesUpdater' +import { SwapButtonState } from '../../helpers/getSwapButtonState' import { useDerivedSwapInfo, useSwapActionHandlers, useSwapState } from '../../hooks/useSwapState' import { useTradeQuoteStateFromLegacy } from '../../hooks/useTradeQuoteStateFromLegacy' import { ConfirmSwapModalSetup } from '../ConfirmSwapModalSetup' @@ -198,6 +199,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { showCowSubsidyModal, } const showTwapSuggestionBanner = !enabledTradeTypes || enabledTradeTypes.includes(TradeType.ADVANCED) + const isNativeSellInHooksStore = swapButtonContext.swapButtonState === SwapButtonState.SellNativeInHooks const swapWarningsTopProps: SwapWarningsTopProps = useMemo( () => ({ @@ -207,8 +209,17 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) { buyingFiatAmount, priceImpact: priceImpactParams.priceImpact, tradeUrlParams, + isNativeSellInHooksStore, }), - [chainId, trade, showTwapSuggestionBanner, buyingFiatAmount, priceImpactParams.priceImpact, tradeUrlParams], + [ + chainId, + trade, + showTwapSuggestionBanner, + buyingFiatAmount, + priceImpactParams.priceImpact, + tradeUrlParams, + isNativeSellInHooksStore, + ], ) const swapWarningsBottomProps: SwapWarningsBottomProps = useMemo( diff --git a/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts b/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts index 677617b953..b852467fd4 100644 --- a/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts +++ b/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts @@ -29,6 +29,7 @@ export enum SwapButtonState { SwapWithWrappedToken = 'SwapWithWrappedToken', RegularEthFlowSwap = 'EthFlowSwap', ApproveAndSwap = 'ApproveAndSwap', + SellNativeInHooks = 'SellNativeInHooks', WrapAndSwap = 'WrapAndSwap', } @@ -53,6 +54,7 @@ export interface SwapButtonStateParams { isBestQuoteLoading: boolean wrappedToken: Token isPermitSupported: boolean + isHooksStore: boolean quoteDeadlineParams: QuoteDeadlineParams } @@ -140,6 +142,10 @@ export function getSwapButtonState(input: SwapButtonStateParams): SwapButtonStat } if (input.isNativeIn) { + if (input.isHooksStore) { + return SwapButtonState.SellNativeInHooks + } + if (!input.isSmartContractWallet) { return SwapButtonState.RegularEthFlowSwap } else if (input.isBundlingSupported) { diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts index 7b2b65ae8b..91fe6ec473 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts @@ -1,6 +1,5 @@ import { useMemo } from 'react' -// import { useCurrencyAmountBalance } from '@cowprotocol/balances-and-allowances' import { currencyAmountToTokenAmount, getWrappedToken } from '@cowprotocol/common-utils' import { useIsTradeUnsupported } from '@cowprotocol/tokens' import { @@ -21,7 +20,13 @@ import { useInjectedWidgetParams } from 'modules/injectedWidget' import { useTokenSupportsPermit } from 'modules/permit' import { getSwapButtonState } from 'modules/swap/helpers/getSwapButtonState' import { SwapButtonsContext } from 'modules/swap/pure/SwapButtons' -import { TradeType, TradeWidgetActions, useTradeConfirmActions, useWrapNativeFlow } from 'modules/trade' +import { + TradeType, + TradeWidgetActions, + useIsHooksTradeType, + useTradeConfirmActions, + useWrapNativeFlow, +} from 'modules/trade' import { useIsNativeIn } from 'modules/trade/hooks/useIsNativeInOrOut' import { useIsWrappedOut } from 'modules/trade/hooks/useIsWrappedInOrOut' import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken' @@ -57,6 +62,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge const isBestQuoteLoading = useIsBestQuoteLoading() const tradeConfirmActions = useTradeConfirmActions() const { standaloneMode } = useInjectedWidgetParams() + const isHooksStore = useIsHooksTradeType() const currencyIn = currencies[Field.INPUT] const currencyOut = currencies[Field.OUTPUT] @@ -118,6 +124,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge isBestQuoteLoading, isPermitSupported, quoteDeadlineParams, + isHooksStore, }) return useSafeMemo( diff --git a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx index 11c34bce1f..28c5618757 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx @@ -173,6 +173,17 @@ const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext ), [SwapButtonState.RegularEthFlowSwap]: (props: SwapButtonsContext) => , + [SwapButtonState.SellNativeInHooks]: (props: SwapButtonsContext) => { + const currency = props.inputAmount?.currency + + return ( + + + Selling {currency?.symbol} is not supported + + + ) + }, } function EthFlowSwapButton(props: SwapButtonsContext) { diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index efaeef37e8..481c469f17 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -6,6 +6,7 @@ import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import TradeGp from 'legacy/state/swap/TradeGp' +import { SellNativeWarningBanner } from 'modules/trade/containers/SellNativeWarningBanner' import { CompatibilityIssuesWarning } from 'modules/trade/pure/CompatibilityIssuesWarning' import { TradeUrlParams } from 'modules/trade/types/TradeRawState' import { BundleTxWrapBanner, HighFeeWarning } from 'modules/tradeWidgetAddons' @@ -19,6 +20,7 @@ export interface SwapWarningsTopProps { buyingFiatAmount: CurrencyAmount | null priceImpact: Percent | undefined tradeUrlParams: TradeUrlParams + isNativeSellInHooksStore: boolean } export interface SwapWarningsBottomProps { @@ -29,21 +31,35 @@ export interface SwapWarningsBottomProps { } export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps) { - const { chainId, trade, showTwapSuggestionBanner, buyingFiatAmount, priceImpact, tradeUrlParams } = props + const { + chainId, + trade, + showTwapSuggestionBanner, + buyingFiatAmount, + priceImpact, + tradeUrlParams, + isNativeSellInHooksStore, + } = props return ( <> - - + {isNativeSellInHooksStore ? ( + + ) : ( + <> + + - {showTwapSuggestionBanner && ( - + {showTwapSuggestionBanner && ( + + )} + )} ) diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx index dfc6ddcdd5..392c668b3f 100644 --- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx +++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/BundleTxWrapBanner/index.tsx @@ -1,7 +1,7 @@ import { InlineBanner } from '@cowprotocol/ui' import { useIsBundlingSupported, useIsSmartContractWallet } from '@cowprotocol/wallet' -import { useIsNativeIn, useWrappedToken } from 'modules/trade' +import { useIsHooksTradeType, useIsNativeIn, useWrappedToken } from 'modules/trade' import useNativeCurrency from 'lib/hooks/useNativeCurrency' @@ -9,10 +9,11 @@ export function BundleTxWrapBanner() { const nativeCurrencySymbol = useNativeCurrency().symbol || 'ETH' const wrappedCurrencySymbol = useWrappedToken().symbol || 'WETH' + const isHooksStore = useIsHooksTradeType() const isBundlingSupported = useIsBundlingSupported() const isNativeIn = useIsNativeIn() const isSmartContractWallet = useIsSmartContractWallet() - const showWrapBundlingBanner = Boolean(isNativeIn && isSmartContractWallet && isBundlingSupported) + const showWrapBundlingBanner = Boolean(isNativeIn && isSmartContractWallet && isBundlingSupported) && !isHooksStore if (!showWrapBundlingBanner) return null From 362c9a53de4069597af9521bc4230389434b4d98 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 17 Dec 2024 16:17:53 +0500 Subject: [PATCH 05/12] feat(volume-fee): free some assets from fee (#5180) * feat(volume-fee): free some assets from fee * chore: fix log * chore: fix pagination * chore: trigger build * chore: do not skip fee when there is widget partner fee * chore: update tax-free-assets request * chore: reduce cache time * chore: refactor ifs --- .../application/containers/App/Updaters.tsx | 2 + .../src/modules/volumeFee/index.ts | 1 + .../modules/volumeFee/state/cowswapFeeAtom.ts | 1 - .../volumeFee/state/taxFreeAssetsAtom.ts | 11 ++ .../modules/volumeFee/state/volumeFeeAtom.ts | 40 ++++++- .../updaters/TaxFreeAssetsUpdater.tsx | 106 ++++++++++++++++++ 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/volumeFee/state/taxFreeAssetsAtom.ts create mode 100644 apps/cowswap-frontend/src/modules/volumeFee/updaters/TaxFreeAssetsUpdater.tsx diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx index f20a5420bc..f08dd8c7c7 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/Updaters.tsx @@ -15,6 +15,7 @@ import { EthFlowDeadlineUpdater } from 'modules/swap/state/EthFlow/updaters' import { useOnTokenListAddingError } from 'modules/tokensList' import { TradeType, useTradeTypeInfo } from 'modules/trade' import { UsdPricesUpdater } from 'modules/usdAmount' +import { TaxFreeAssetsUpdater } from 'modules/volumeFee' import { LpTokensWithBalancesUpdater, PoolsInfoUpdater, VampireAttackUpdater } from 'modules/yield/shared' import { ProgressBarV2ExecutingOrdersUpdater } from 'common/hooks/orderProgressBarV2' @@ -92,6 +93,7 @@ export function Updaters() { + ) } diff --git a/apps/cowswap-frontend/src/modules/volumeFee/index.ts b/apps/cowswap-frontend/src/modules/volumeFee/index.ts index 5032efb982..7520b8f6a9 100644 --- a/apps/cowswap-frontend/src/modules/volumeFee/index.ts +++ b/apps/cowswap-frontend/src/modules/volumeFee/index.ts @@ -2,4 +2,5 @@ export { useVolumeFee } from './hooks/useVolumeFee' export { useVolumeFeeTooltip } from './hooks/useVolumeFeeTooltip' export type { VolumeFeeTooltip } from './hooks/useVolumeFeeTooltip' export { volumeFeeAtom } from './state/volumeFeeAtom' +export { TaxFreeAssetsUpdater } from './updaters/TaxFreeAssetsUpdater' export * from './types' diff --git a/apps/cowswap-frontend/src/modules/volumeFee/state/cowswapFeeAtom.ts b/apps/cowswap-frontend/src/modules/volumeFee/state/cowswapFeeAtom.ts index b22878a4e3..547bc783ef 100644 --- a/apps/cowswap-frontend/src/modules/volumeFee/state/cowswapFeeAtom.ts +++ b/apps/cowswap-frontend/src/modules/volumeFee/state/cowswapFeeAtom.ts @@ -27,7 +27,6 @@ const COWSWAP_VOLUME_FEES: Record = { export const cowSwapFeeAtom = atom((get) => { const { chainId, account } = get(walletInfoAtom) - const volumeFee = COWSWAP_VOLUME_FEES[chainId] // Early exit if fee is not set for this network diff --git a/apps/cowswap-frontend/src/modules/volumeFee/state/taxFreeAssetsAtom.ts b/apps/cowswap-frontend/src/modules/volumeFee/state/taxFreeAssetsAtom.ts new file mode 100644 index 0000000000..f34fb57bd3 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/volumeFee/state/taxFreeAssetsAtom.ts @@ -0,0 +1,11 @@ +import { atomWithStorage } from 'jotai/utils' + +import { mapSupportedNetworks } from '@cowprotocol/cow-sdk' +import { PersistentStateByChain } from '@cowprotocol/types' + +type TokenId = string + +export const taxFreeAssetsAtom = atomWithStorage>( + 'taxFreeAssetsAtom:v1', + mapSupportedNetworks([]), +) diff --git a/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts b/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts index 45e9cbc83c..4b053722c1 100644 --- a/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts +++ b/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts @@ -1,26 +1,64 @@ import { atom } from 'jotai' +import { getCurrencyAddress } from '@cowprotocol/common-utils' import { walletInfoAtom } from '@cowprotocol/wallet' import { resolveFlexibleConfig, TradeType as WidgetTradeType } from '@cowprotocol/widget-lib' import { injectedWidgetPartnerFeeAtom } from 'modules/injectedWidget' -import { tradeTypeAtom } from 'modules/trade' +import { derivedTradeStateAtom, tradeTypeAtom } from 'modules/trade' import { TradeType } from 'modules/trade/types/TradeType' import { cowSwapFeeAtom } from './cowswapFeeAtom' import { safeAppFeeAtom } from './safeAppFeeAtom' +import { taxFreeAssetsAtom } from './taxFreeAssetsAtom' import { VolumeFee } from '../types' export const volumeFeeAtom = atom((get) => { + const tradeState = get(derivedTradeStateAtom) const cowSwapFee = get(cowSwapFeeAtom) const widgetPartnerFee = get(widgetPartnerFeeAtom) const safeAppFee = get(safeAppFeeAtom) + const shouldSkipFee = get(shouldSkipFeeAtom) + + if (!widgetPartnerFee && shouldSkipFee) { + console.debug('Tax free trade detected', tradeState) + return undefined + } // CoW Swap Fee won't be enabled when in Widget mode, thus it takes precedence here return safeAppFee || cowSwapFee || widgetPartnerFee }) +const shouldSkipFeeAtom = atom((get) => { + const { chainId } = get(walletInfoAtom) + const tradeState = get(derivedTradeStateAtom) + const taxFreeAssetsState = get(taxFreeAssetsAtom) + + if (!tradeState) return false + + const taxFreeAssets = taxFreeAssetsState[chainId] + + if (!taxFreeAssets) return false + + const { inputCurrency, outputCurrency } = tradeState + + if (!inputCurrency || !outputCurrency) return false + + const inputCurrencyAddress = getCurrencyAddress(inputCurrency).toLowerCase() + const outputCurrencyAddress = getCurrencyAddress(outputCurrency).toLowerCase() + + return taxFreeAssets.some((assets) => { + // If there is only one asset in the list, it means that it is a global tax free asset + if (assets.length === 1) { + return assets[0] === inputCurrencyAddress || assets[0] === outputCurrencyAddress + // If there are two assets in the list, it means that it is a pair tax free asset + } else { + return assets.includes(inputCurrencyAddress) && assets.includes(outputCurrencyAddress) + } + }) +}) + const widgetPartnerFeeAtom = atom((get) => { const { chainId } = get(walletInfoAtom) const partnerFee = get(injectedWidgetPartnerFeeAtom) diff --git a/apps/cowswap-frontend/src/modules/volumeFee/updaters/TaxFreeAssetsUpdater.tsx b/apps/cowswap-frontend/src/modules/volumeFee/updaters/TaxFreeAssetsUpdater.tsx new file mode 100644 index 0000000000..8f00afc496 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/volumeFee/updaters/TaxFreeAssetsUpdater.tsx @@ -0,0 +1,106 @@ +import { useSetAtom } from 'jotai' + +import { getCmsClient } from '@cowprotocol/core' +import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' + +import ms from 'ms.macro' +import qs from 'qs' +import useSWR, { SWRConfiguration } from 'swr' + +import { taxFreeAssetsAtom } from '../state/taxFreeAssetsAtom' + +type TokenId = string + +type TaxFreeAssetItem = { + attributes: { + tokenIds: string + chainId: { + data: { + attributes: { + chainId: number + } + } + } + } +} + +const UPDATE_INTERVAL = ms`10m` + +const SWR_CONFIG: SWRConfiguration = { + refreshInterval: UPDATE_INTERVAL, + revalidateOnFocus: false, +} + +const UPDATE_TIME_KEY = 'taxFreeAssetsUpdateTime' + +const cmsClient = getCmsClient() + +const querySerializer = (params: any) => { + return qs.stringify(params, { encodeValuesOnly: true, arrayFormat: 'brackets' }) +} + +export function TaxFreeAssetsUpdater() { + const setTaxFreeAssets = useSetAtom(taxFreeAssetsAtom) + + useSWR( + ['/tax-free-assets', setTaxFreeAssets], + async ([method, setTaxFreeAssets]) => { + const lastUpdateTime = localStorage.getItem(UPDATE_TIME_KEY) + + // Update only once per interval in order to not load the CMS + if (lastUpdateTime !== null && Date.now() - +lastUpdateTime < UPDATE_INTERVAL) { + return + } + + let items: TaxFreeAssetItem[] | null = null + + try { + const { data, error } = await cmsClient.GET(method, { + params: { + query: { + fields: ['tokenIds'], + populate: { + chainId: { + fields: ['chainId'], + }, + }, + }, + pagination: { pageSize: 500 }, + }, + querySerializer, + }) + + items = data.data as TaxFreeAssetItem[] + + if (error) { + localStorage.removeItem(UPDATE_TIME_KEY) + console.error('Failed to fetch tax free assets', error) + return undefined + } + } catch (e) { + localStorage.removeItem(UPDATE_TIME_KEY) + console.error('Failed to fetch tax free assets', e) + } + + if (!items) return + + localStorage.setItem(UPDATE_TIME_KEY, Date.now().toString()) + + const state = items.reduce( + (acc, item) => { + const chainId = item.attributes.chainId.data.attributes.chainId as SupportedChainId + const tokenIds = item.attributes.tokenIds.toLowerCase().split(',') + + acc[chainId].push(tokenIds) + return acc + }, + mapSupportedNetworks(() => []), + ) + + setTaxFreeAssets(state) + }, + SWR_CONFIG, + ) + + return null +} From 353fb756b59fbcdd81700133d3b57c18bb84937a Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 17 Dec 2024 16:18:34 +0500 Subject: [PATCH 06/12] fix(twap): display a warning when fbh was changed after twap order (#5202) * fix(twap): display a warning when fbh was changed after twap order * chore: add loading state * chore: fix pendingOrders * feat: style TWAP warning banner (#5204) * feat: style TWAP warning banner * feat: fix typo and change orientation of banner * fix: persist pendingTxHash in atom --------- Co-authored-by: fairlight <31534717+fairlighteth@users.noreply.github.com> --- .../containers/OrdersTableWidget/index.tsx | 11 +- .../SetupFallbackHandlerWarning/index.tsx | 133 ++++++++++++++++++ .../twap/hooks/useSetupFallbackHandler.ts | 26 ++++ .../src/modules/twap/index.ts | 2 + .../src/pages/AdvancedOrders/index.tsx | 12 +- 5 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 apps/cowswap-frontend/src/modules/twap/containers/SetupFallbackHandlerWarning/index.tsx create mode 100644 apps/cowswap-frontend/src/modules/twap/hooks/useSetupFallbackHandler.ts diff --git a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx index 6b4a274f3a..229753b6db 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx @@ -1,5 +1,5 @@ import { useAtomValue, useSetAtom } from 'jotai' -import { useCallback, useEffect, useMemo } from 'react' +import { ReactNode, useCallback, useEffect, useMemo } from 'react' import { useTokensAllowances, useTokensBalances } from '@cowprotocol/balances-and-allowances' import { useIsSafeViaWc, useWalletDetails, useWalletInfo } from '@cowprotocol/wallet' @@ -60,12 +60,14 @@ interface OrdersTableWidgetProps { displayOrdersOnlyForSafeApp: boolean orders: Order[] orderType: TabOrderTypes + children?: ReactNode } export function OrdersTableWidget({ orders: allOrders, orderType, displayOrdersOnlyForSafeApp, + children, }: OrdersTableWidgetProps) { const { chainId, account } = useWalletInfo() const location = useLocation() @@ -122,14 +124,14 @@ export function OrdersTableWidget({ (orders: ParsedOrder[]) => { updateOrdersToCancel(orders) }, - [updateOrdersToCancel] + [updateOrdersToCancel], ) const toggleOrderForCancellation = useCallback( (order: ParsedOrder) => { updateOrdersToCancel(toggleOrderInCancellationList(ordersToCancel, order)) }, - [ordersToCancel, updateOrdersToCancel] + [ordersToCancel, updateOrdersToCancel], ) const getShowCancellationModal = useCallback( @@ -138,7 +140,7 @@ export function OrdersTableWidget({ return rawOrder ? cancelOrder(rawOrder) : null }, - [allOrders, cancelOrder] + [allOrders, cancelOrder], ) const getAlternativeOrderModalContext = useGetAlternativeOrderModalContextCallback() @@ -167,6 +169,7 @@ export function OrdersTableWidget({ <> + {children} span { + gap: 20px; + } + + > span > span { + gap: 20px; + } + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(${UI.COLOR_PAPER}); + z-index: -1; + border-radius: inherit; + } + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: inherit; + z-index: -1; + border-radius: inherit; + } +` + +const ActionButton = styled(ButtonPrimary)` + display: inline-block; + width: 100%; + font-size: 16px; + padding: 16px 24px; + min-height: auto; +` + +const pendingTxHashAtom = atom(null) + +export function SetupFallbackHandlerWarning() { + const [pendingTxHash, setPendingTxHash] = useAtom(pendingTxHashAtom) + + const { account } = useWalletInfo() + const setupFallbackHandler = useSetupFallbackHandler() + const isTransactionPending = useIsTransactionPending(pendingTxHash) + const prevIsTransactionPending = usePrevious(isTransactionPending) + const txWasMined = prevIsTransactionPending === true && isTransactionPending === false + + const updateFallbackHandlerVerification = useSetAtom(updateFallbackHandlerVerificationAtom) + + const extensibleFallbackContext = useExtensibleFallbackContext() + + const handleUpdateClick = async () => { + const txHash = await setupFallbackHandler() + + if (txHash) { + setPendingTxHash(txHash) + } + } + + useEffect(() => { + if (!txWasMined) return + + setPendingTxHash(null) + + if (!extensibleFallbackContext || !account) return + + verifyExtensibleFallback(extensibleFallbackContext).then((result) => { + updateFallbackHandlerVerification({ [account]: result }) + }) + }, [txWasMined, account, extensibleFallbackContext, updateFallbackHandlerVerification, setPendingTxHash]) + + return ( + + +

+ Your Safe fallback handler was changed after TWAP orders were placed. All open TWAP orders are not getting + created because of that. Please, update the fallback handler in order to make the orders work again. +

+ + {isTransactionPending ? : 'Update fallback handler'} + +
+
+ ) +} + +function useIsTransactionPending(txHash: string | null): boolean { + const allTransactions = useAllTransactions() + + if (!txHash) return false + + return Object.keys(allTransactions).some((hash) => { + const tx = allTransactions[hash] + + if (!tx || tx.receipt || tx.replacementType || tx.errorMessage) return false + + return tx.hash === txHash + }) +} diff --git a/apps/cowswap-frontend/src/modules/twap/hooks/useSetupFallbackHandler.ts b/apps/cowswap-frontend/src/modules/twap/hooks/useSetupFallbackHandler.ts new file mode 100644 index 0000000000..68a31b895a --- /dev/null +++ b/apps/cowswap-frontend/src/modules/twap/hooks/useSetupFallbackHandler.ts @@ -0,0 +1,26 @@ +import { useSafeAppsSdk } from '@cowprotocol/wallet' + +import { useExtensibleFallbackContext } from './useExtensibleFallbackContext' + +import { useTransactionAdder } from '../../../legacy/state/enhancedTransactions/hooks' +import { extensibleFallbackSetupTxs } from '../services/extensibleFallbackSetupTxs' + +export function useSetupFallbackHandler() { + const safeAppsSdk = useSafeAppsSdk() + const extensibleFallbackContext = useExtensibleFallbackContext() + const addTransaction = useTransactionAdder() + + return async () => { + if (!safeAppsSdk || !extensibleFallbackContext) return + + const fallbackSetupTxs = await extensibleFallbackSetupTxs(extensibleFallbackContext) + const { safeTxHash } = await safeAppsSdk.txs.send({ txs: fallbackSetupTxs }) + + addTransaction({ + hash: safeTxHash, + summary: 'Setup TWAP fallback handler', + }) + + return safeTxHash + } +} diff --git a/apps/cowswap-frontend/src/modules/twap/index.ts b/apps/cowswap-frontend/src/modules/twap/index.ts index 7f57359c67..7f7be3e5ed 100644 --- a/apps/cowswap-frontend/src/modules/twap/index.ts +++ b/apps/cowswap-frontend/src/modules/twap/index.ts @@ -10,5 +10,7 @@ export * from './hooks/useCancelTwapOrder' export * from './hooks/useMapTwapCurrencyInfo' export * from './hooks/useTwapFormState' export * from './hooks/useTwapSlippage' +export { useIsFallbackHandlerRequired } from './hooks/useFallbackHandlerVerification' +export { SetupFallbackHandlerWarning } from './containers/SetupFallbackHandlerWarning' export * from './updaters/index' export * from './types' diff --git a/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx b/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx index db9d944cb9..9443164f31 100644 --- a/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx +++ b/apps/cowswap-frontend/src/pages/AdvancedOrders/index.tsx @@ -1,5 +1,7 @@ import { useAtomValue } from 'jotai' +import { PENDING_STATES } from 'legacy/state/orders/actions' + import { advancedOrdersAtom, AdvancedOrdersWidget, @@ -10,31 +12,32 @@ import { useInjectedWidgetParams } from 'modules/injectedWidget' import { OrdersTableWidget, TabOrderTypes } from 'modules/ordersTable' import * as styledEl from 'modules/trade/pure/TradePageLayout' import { + SetupFallbackHandlerWarning, TwapConfirmModal, TwapFormWidget, TwapUpdaters, useAllEmulatedOrders, + useIsFallbackHandlerRequired, useMapTwapCurrencyInfo, useTwapFormState, useTwapSlippage, } from 'modules/twap' import { TwapFormState } from 'modules/twap/pure/PrimaryActionButton/getTwapFormState' - export default function AdvancedOrdersPage() { const { isUnlocked } = useAtomValue(advancedOrdersAtom) const allEmulatedOrders = useAllEmulatedOrders() + const isFallbackHandlerRequired = useIsFallbackHandlerRequired() const twapFormValidation = useTwapFormState() const twapSlippage = useTwapSlippage() const mapTwapCurrencyInfo = useMapTwapCurrencyInfo() + const { hideOrdersTable } = useInjectedWidgetParams() const disablePriceImpact = twapFormValidation === TwapFormState.SELL_AMOUNT_TOO_SMALL - const advancedWidgetParams = { disablePriceImpact } - - const { hideOrdersTable } = useInjectedWidgetParams() + const pendingOrders = allEmulatedOrders.filter((order) => PENDING_STATES.includes(order.status)) return ( <> @@ -42,6 +45,7 @@ export default function AdvancedOrdersPage() { + {isFallbackHandlerRequired && pendingOrders.length > 0 && } } confirmContent={} From fa8935851bc61ff881cc664dca6938fad44c508f Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 17 Dec 2024 16:20:09 +0500 Subject: [PATCH 07/12] chore: remove console log (#5216) --- .../src/modules/volumeFee/state/volumeFeeAtom.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts b/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts index 4b053722c1..1c4f4dfa2c 100644 --- a/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts +++ b/apps/cowswap-frontend/src/modules/volumeFee/state/volumeFeeAtom.ts @@ -15,14 +15,12 @@ import { taxFreeAssetsAtom } from './taxFreeAssetsAtom' import { VolumeFee } from '../types' export const volumeFeeAtom = atom((get) => { - const tradeState = get(derivedTradeStateAtom) const cowSwapFee = get(cowSwapFeeAtom) const widgetPartnerFee = get(widgetPartnerFeeAtom) const safeAppFee = get(safeAppFeeAtom) const shouldSkipFee = get(shouldSkipFeeAtom) if (!widgetPartnerFee && shouldSkipFee) { - console.debug('Tax free trade detected', tradeState) return undefined } From 8be3a458d7918bd79c3f2bb636620c77b560f3b2 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 17 Dec 2024 16:20:23 +0500 Subject: [PATCH 08/12] feat(wallets): add coinshift to allowed domains (#4931) * feat(wallets): add coinshift to allowed domains * chore: fix regex --- libs/wallet/src/web3-react/connection/safe.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libs/wallet/src/web3-react/connection/safe.tsx b/libs/wallet/src/web3-react/connection/safe.tsx index 9126f9aed7..98fd713a94 100644 --- a/libs/wallet/src/web3-react/connection/safe.tsx +++ b/libs/wallet/src/web3-react/connection/safe.tsx @@ -7,7 +7,19 @@ import { Web3ReactConnection } from '../types' const [web3GnosisSafe, web3GnosisSafeHooks] = initializeConnector( (actions) => - new AsyncConnector(() => import('@web3-react/gnosis-safe').then((m) => new m.GnosisSafe({ actions })), actions) + new AsyncConnector( + () => + import('@web3-react/gnosis-safe').then( + (m) => + new m.GnosisSafe({ + actions, + options: { + allowedDomains: [/app\.safe\.global$/, /(.+\.)?coinshift\.global$/, /localhost:5173$/], + }, + }), + ), + actions, + ), ) export const gnosisSafeConnection: Web3ReactConnection = { connector: web3GnosisSafe, From 7db4b72d54759394087e4c816dce9eeb6f282588 Mon Sep 17 00:00:00 2001 From: fairlight <31534717+fairlighteth@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:12:40 +0000 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20add=20winter=20theme=20?= =?UTF-8?q?=F0=9F=8E=85=20(#5213)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add winter theme * feat: restore inline comment * feat: show improved christmas success sound * chore: improve handling of feature flags * chore: remove unnecessary casting * chore: refactor detection of christmas theme * fix: fix lint --------- Co-authored-by: Anxo Rodriguez --- .../public/audio/success-winterTheme.mp3 | Bin 0 -> 98055 bytes .../application/containers/App/index.tsx | 37 ++++++++--- .../application/containers/App/styled.ts | 36 +++++++++- .../src/modules/sounds/utils/sound.ts | 2 + ...ckground-cowswap-christmas-dark-medium.svg | 1 + ...ackground-cowswap-christmas-dark-small.svg | 1 + .../background-cowswap-christmas-dark.svg | 1 + ...kground-cowswap-christmas-light-medium.svg | 1 + ...ckground-cowswap-christmas-light-small.svg | 1 + .../background-cowswap-christmas-light.svg | 1 + .../images/logo-cowswap-christmas-dark.svg | 1 + .../images/logo-cowswap-christmas-light.svg | 1 + libs/common-const/src/theme.ts | 3 +- libs/ui/src/pure/ProductLogo/index.tsx | 62 +++++++++++++++--- libs/ui/src/types.ts | 2 +- package.json | 2 +- yarn.lock | 43 +++--------- 17 files changed, 137 insertions(+), 58 deletions(-) create mode 100644 apps/cowswap-frontend/public/audio/success-winterTheme.mp3 create mode 100644 libs/assets/src/images/background-cowswap-christmas-dark-medium.svg create mode 100644 libs/assets/src/images/background-cowswap-christmas-dark-small.svg create mode 100644 libs/assets/src/images/background-cowswap-christmas-dark.svg create mode 100644 libs/assets/src/images/background-cowswap-christmas-light-medium.svg create mode 100644 libs/assets/src/images/background-cowswap-christmas-light-small.svg create mode 100644 libs/assets/src/images/background-cowswap-christmas-light.svg create mode 100644 libs/assets/src/images/logo-cowswap-christmas-dark.svg create mode 100644 libs/assets/src/images/logo-cowswap-christmas-light.svg diff --git a/apps/cowswap-frontend/public/audio/success-winterTheme.mp3 b/apps/cowswap-frontend/public/audio/success-winterTheme.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f35a393f5dfe484ca1f3baa5a9d92b1e3928bf43 GIT binary patch literal 98055 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�%`X>HcW0%Tl79T8Uj0uluj+RT*((ZK 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 literal 0 HcmV?d00001 diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx index 8f47e30a73..caac0b966e 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx @@ -4,10 +4,11 @@ import { ACTIVE_CUSTOM_THEME, CustomTheme } from '@cowprotocol/common-const' import { useMediaQuery } from '@cowprotocol/common-hooks' import { useFeatureFlags } from '@cowprotocol/common-hooks' import { isInjectedWidget } from '@cowprotocol/common-utils' -import { Color, Footer, GlobalCoWDAOStyles, Media, MenuBar, CowSwapTheme } from '@cowprotocol/ui' +import { Color, Footer, GlobalCoWDAOStyles, Media, MenuBar } from '@cowprotocol/ui' import SVG from 'react-inlinesvg' import { NavLink } from 'react-router-dom' +import Snowfall from 'react-snowfall' import { ThemeProvider } from 'theme' import ErrorBoundary from 'legacy/components/ErrorBoundary' @@ -53,8 +54,7 @@ export function App() { useAnalyticsReporterCowSwap() useInitializeUtm() - const featureFlags = useFeatureFlags() - const { isYieldEnabled } = featureFlags + const { isYieldEnabled, isChristmasEnabled, isHalloweenEnabled } = useFeatureFlags() const isInjectedWidgetMode = isInjectedWidget() const menuItems = useMenuItems() @@ -97,11 +97,14 @@ export function App() { const { pendingActivity } = useCategorizeRecentActivity() const isMobile = useMediaQuery(Media.upToMedium(false)) const customTheme = useMemo(() => { - if (ACTIVE_CUSTOM_THEME === CustomTheme.HALLOWEEN && darkMode && featureFlags.isHalloweenEnabled) { - return 'darkHalloween' as CowSwapTheme + if (ACTIVE_CUSTOM_THEME === CustomTheme.HALLOWEEN && darkMode && isHalloweenEnabled) { + return 'darkHalloween' + } + if (ACTIVE_CUSTOM_THEME === CustomTheme.CHRISTMAS && isChristmasEnabled) { + return darkMode ? 'darkChristmas' : 'lightChristmas' } return undefined - }, [darkMode, featureFlags.isHalloweenEnabled]) + }, [darkMode, isHalloweenEnabled, isChristmasEnabled]) const persistentAdditionalContent = ( @@ -112,6 +115,8 @@ export function App() { ) + const isChristmasTheme = ACTIVE_CUSTOM_THEME === CustomTheme.CHRISTMAS && isChristmasEnabled + return ( }> @@ -151,12 +156,28 @@ export function App() { - - + {!isInjectedWidgetMode && isChristmasTheme && ( + + )} + {!isInjectedWidgetMode && (