+
)
}
From 411df4d83d5dfd52f6d1f57682e8e272200e11c7 Mon Sep 17 00:00:00 2001
From: Leandro
Date: Wed, 18 Dec 2024 13:44:11 +0000
Subject: [PATCH 02/15] feat: use new uni and coingecko token lists (#5225)
* feat: use new uni and coingecko token lists
* fix: linter errors
---
.../orders/OrdersUserDetailsTable/index.tsx | 6 +++---
libs/tokens/src/const/tokensList.json | 17 +++++++++++------
libs/tokens/src/const/tokensLists.ts | 3 ---
.../src/state/tokenLists/tokenListsStateAtom.ts | 16 +++++++---------
4 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx
index 0efd2ec7e8..4710ce14af 100644
--- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx
+++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'
+import { Command } from '@cowprotocol/types'
import { TruncatedText } from '@cowprotocol/ui/pure/TruncatedText'
import { faExchangeAlt } from '@fortawesome/free-solid-svg-icons'
@@ -23,11 +24,10 @@ import { getLimitPrice } from 'utils/getLimitPrice'
import { OrderSurplusDisplayStyledByRow } from './OrderSurplusTooltipStyledByRow'
import { ToggleFilter } from './ToggleFilter'
+import { TableState } from '../../../explorer/components/TokensTableWidget/useTable'
import { SimpleTable, SimpleTableProps } from '../../common/SimpleTable'
import { StatusLabel } from '../StatusLabel'
import { UnsignedOrderWarning } from '../UnsignedOrderWarning'
-import { TableState } from '../../../explorer/components/TokensTableWidget/useTable'
-import { Command } from '@cowprotocol/types'
const EXPIRED_CANCELED_STATES: OrderStatus[] = ['cancelled', 'cancelling', 'expired']
@@ -37,7 +37,7 @@ function isExpiredOrCanceled(order: Order): boolean {
if (!executedSellAmount.isZero() || !executedBuyAmount.isZero()) return false
// Otherwise, return if the order is expired or canceled
- return EXPIRED_CANCELED_STATES.includes(order.status)
+ return EXPIRED_CANCELED_STATES.includes(status)
}
const tooltip = {
diff --git a/libs/tokens/src/const/tokensList.json b/libs/tokens/src/const/tokensList.json
index 0e84cad295..7c45ca369e 100644
--- a/libs/tokens/src/const/tokensList.json
+++ b/libs/tokens/src/const/tokensList.json
@@ -8,7 +8,7 @@
{
"priority": 2,
"enabledByDefault": true,
- "source": "https://files.cow.fi/tokens/CoinGecko.json"
+ "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/CoinGecko.1.json"
},
{
"priority": 3,
@@ -65,12 +65,12 @@
{
"priority": 3,
"enabledByDefault": true,
- "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/GnosisUniswapTokensList.json"
+ "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/Uniswap.100.json"
},
{
"priority": 4,
"enabledByDefault": true,
- "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/GnosisCoingeckoTokensList.json"
+ "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/CoinGecko.100.json"
},
{
"priority": 5,
@@ -86,12 +86,12 @@
{
"priority": 2,
"enabledByDefault": true,
- "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/ArbitrumOneUniswapTokensList.json"
+ "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/Uniswap.42161.json"
},
{
"priority": 3,
"enabledByDefault": true,
- "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/ArbitrumOneCoingeckoTokensList.json"
+ "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/CoinGecko.42161.json"
},
{
"priority": 4,
@@ -119,6 +119,11 @@
"priority": 2,
"source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/CoinGecko.8453.json",
"enabledByDefault": true
+ },
+ {
+ "priority": 3,
+ "enabledByDefault": true,
+ "source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/Uniswap.8453.json"
}
]
-}
+}
\ No newline at end of file
diff --git a/libs/tokens/src/const/tokensLists.ts b/libs/tokens/src/const/tokensLists.ts
index 138aab9db3..cc4c7251a9 100644
--- a/libs/tokens/src/const/tokensLists.ts
+++ b/libs/tokens/src/const/tokensLists.ts
@@ -10,6 +10,3 @@ export const LP_TOKEN_LISTS = lpTokensList as Array
export const DEFAULT_TOKENS_LISTS: ListsSourcesByNetwork = mapSupportedNetworks((chainId) => tokensList[chainId])
export const UNISWAP_TOKENS_LIST = 'https://ipfs.io/ipns/tokens.uniswap.org'
-
-export const GNOSIS_UNISWAP_TOKENS_LIST =
- 'https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/GnosisUniswapTokensList.json'
diff --git a/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts b/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts
index a9ff33fa82..194a49a5c5 100644
--- a/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts
+++ b/libs/tokens/src/state/tokenLists/tokenListsStateAtom.ts
@@ -4,12 +4,7 @@ import { atomWithStorage } from 'jotai/utils'
import { getJotaiMergerStorage } from '@cowprotocol/core'
import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk'
-import {
- DEFAULT_TOKENS_LISTS,
- GNOSIS_UNISWAP_TOKENS_LIST,
- LP_TOKEN_LISTS,
- UNISWAP_TOKENS_LIST,
-} from '../../const/tokensLists'
+import { DEFAULT_TOKENS_LISTS, LP_TOKEN_LISTS, UNISWAP_TOKENS_LIST } from '../../const/tokensLists'
import {
ListSourceConfig,
ListsSourcesByNetwork,
@@ -21,9 +16,12 @@ import { environmentAtom } from '../environmentAtom'
const UNISWAP_TOKEN_LIST_URL: Record = {
[SupportedChainId.MAINNET]: UNISWAP_TOKENS_LIST,
- [SupportedChainId.GNOSIS_CHAIN]: GNOSIS_UNISWAP_TOKENS_LIST,
- [SupportedChainId.ARBITRUM_ONE]: UNISWAP_TOKENS_LIST,
- [SupportedChainId.BASE]: UNISWAP_TOKENS_LIST,
+ [SupportedChainId.GNOSIS_CHAIN]:
+ 'https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/Uniswap.100.json',
+ [SupportedChainId.ARBITRUM_ONE]:
+ 'https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/Uniswap.42161.json',
+ [SupportedChainId.BASE]:
+ 'https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/Uniswap.8453.json',
[SupportedChainId.SEPOLIA]: UNISWAP_TOKENS_LIST,
}
From a07740ced3e45db82f3fc4a5064fee81390b3537 Mon Sep 17 00:00:00 2001
From: Alexandr Kazachenko
Date: Thu, 19 Dec 2024 16:41:07 +0500
Subject: [PATCH 03/15] fix(limit-orders): do not override user entered price
(#5232)
---
.../updaters/InitialPriceUpdater/index.tsx | 29 ++++++++++---------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/apps/cowswap-frontend/src/modules/limitOrders/updaters/InitialPriceUpdater/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/updaters/InitialPriceUpdater/index.tsx
index c8695504e2..10f8d20bd8 100644
--- a/apps/cowswap-frontend/src/modules/limitOrders/updaters/InitialPriceUpdater/index.tsx
+++ b/apps/cowswap-frontend/src/modules/limitOrders/updaters/InitialPriceUpdater/index.tsx
@@ -1,26 +1,30 @@
-import { useSetAtom } from 'jotai'
-import { useLayoutEffect, useState } from 'react'
+import { useAtomValue, useSetAtom } from 'jotai'
+import { useEffect, useLayoutEffect, useState } from 'react'
import { usePrevious } from '@cowprotocol/common-hooks'
import { Writeable } from 'types'
-import { useGetInitialPrice } from 'modules/limitOrders/hooks/useGetInitialPrice'
-import { useUpdateActiveRate } from 'modules/limitOrders/hooks/useUpdateActiveRate'
-import { LimitRateState, updateLimitRateAtom } from 'modules/limitOrders/state/limitRateAtom'
-
-import { useLimitOrdersDerivedState } from '../../hooks/useLimitOrdersDerivedState'
+import { useGetInitialPrice } from '../../hooks/useGetInitialPrice'
+import { useLimitOrdersRawState } from '../../hooks/useLimitOrdersRawState'
+import { useUpdateActiveRate } from '../../hooks/useUpdateActiveRate'
+import { limitRateAtom, LimitRateState, updateLimitRateAtom } from '../../state/limitRateAtom'
// Fetch and update initial price for the selected token pair
export function InitialPriceUpdater() {
- const { inputCurrency, outputCurrency } = useLimitOrdersDerivedState()
+ const { inputCurrencyId, outputCurrencyId } = useLimitOrdersRawState()
+ const { isTypedValue } = useAtomValue(limitRateAtom)
const updateLimitRateState = useSetAtom(updateLimitRateAtom)
const updateRate = useUpdateActiveRate()
- const [isInitialPriceSet, setIsInitialPriceSet] = useState(false)
+ const [isInitialPriceSet, setIsInitialPriceSet] = useState(isTypedValue)
const { price, isLoading } = useGetInitialPrice()
const prevPrice = usePrevious(price)
+ useEffect(() => {
+ setIsInitialPriceSet(isTypedValue)
+ }, [isTypedValue])
+
useLayoutEffect(() => {
const update: Partial> = {
initialRate: price,
@@ -28,10 +32,6 @@ export function InitialPriceUpdater() {
isLoading: isInitialPriceSet ? false : isLoading,
}
- if (!isInitialPriceSet) {
- update.isTypedValue = false
- }
-
updateLimitRateState(update)
}, [isInitialPriceSet, price, isLoading, updateLimitRateState])
@@ -40,6 +40,7 @@ export function InitialPriceUpdater() {
if (!price || isInitialPriceSet || isLoading || prevPrice?.equalTo(price)) return
setIsInitialPriceSet(true)
+
updateRate({
activeRate: price,
isInitialPriceSet: true,
@@ -53,7 +54,7 @@ export function InitialPriceUpdater() {
// Reset initial price set flag when any token was changed
useLayoutEffect(() => {
setIsInitialPriceSet(false)
- }, [inputCurrency, outputCurrency])
+ }, [inputCurrencyId, outputCurrencyId])
return null
}
From fc13cc460747bb59aba374067899d8d1c4ba986f Mon Sep 17 00:00:00 2001
From: Alexandr Kazachenko
Date: Thu, 19 Dec 2024 18:14:29 +0500
Subject: [PATCH 04/15] fix: allow any safe-like apps (#5235)
---
libs/wallet/src/web3-react/connection/safe.tsx | 14 +-------------
1 file changed, 1 insertion(+), 13 deletions(-)
diff --git a/libs/wallet/src/web3-react/connection/safe.tsx b/libs/wallet/src/web3-react/connection/safe.tsx
index 98fd713a94..c200bb82d6 100644
--- a/libs/wallet/src/web3-react/connection/safe.tsx
+++ b/libs/wallet/src/web3-react/connection/safe.tsx
@@ -7,19 +7,7 @@ import { Web3ReactConnection } from '../types'
const [web3GnosisSafe, web3GnosisSafeHooks] = initializeConnector(
(actions) =>
- new AsyncConnector(
- () =>
- import('@web3-react/gnosis-safe').then(
- (m) =>
- new m.GnosisSafe({
- actions,
- options: {
- allowedDomains: [/app\.safe\.global$/, /(.+\.)?coinshift\.global$/, /localhost:5173$/],
- },
- }),
- ),
- actions,
- ),
+ new AsyncConnector(() => import('@web3-react/gnosis-safe').then((m) => new m.GnosisSafe({ actions })), actions),
)
export const gnosisSafeConnection: Web3ReactConnection = {
connector: web3GnosisSafe,
From 1626a04e2c0b530c2efae842776ba5af6014a1da Mon Sep 17 00:00:00 2001
From: Leandro
Date: Thu, 19 Dec 2024 14:39:02 +0000
Subject: [PATCH 05/15] fix(feature-flags): remove isBaseEnabled feature flag
(#5234)
---
libs/common-hooks/src/useAvailableChains.ts | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/libs/common-hooks/src/useAvailableChains.ts b/libs/common-hooks/src/useAvailableChains.ts
index 4e7c610179..82a9f8353e 100644
--- a/libs/common-hooks/src/useAvailableChains.ts
+++ b/libs/common-hooks/src/useAvailableChains.ts
@@ -3,8 +3,6 @@ import { useMemo } from 'react'
import { getAvailableChains } from '@cowprotocol/common-utils'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
-import { useFeatureFlags } from './useFeatureFlags'
-
/**
* Hook to get a list of SupportedChainId currently available/enabled
*
@@ -14,12 +12,12 @@ import { useFeatureFlags } from './useFeatureFlags'
*/
export function useAvailableChains(): SupportedChainId[] {
// 1. Load feature flag for chain being enabled
- const { isBaseEnabled } = useFeatureFlags()
+ // const { isBaseEnabled } = useFeatureFlags()
return useMemo(
// 2. Conditionally build a list of chain ids to exclude
- // () => getAvailableChains(isArbitrumOneEnabled ? undefined : [SupportedChainId.ARBITRUM_ONE]),
- () => getAvailableChains(isBaseEnabled ? undefined : [SupportedChainId.BASE]),
- [isBaseEnabled],
+ // () => getAvailableChains(isBaseEnabled ? undefined : [SupportedChainId.BASE]), <-- example usage, kept for reference
+ () => getAvailableChains(),
+ [],
)
}
From 28fcda95c7002a528c538917450bc38b67c0a5eb Mon Sep 17 00:00:00 2001
From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com>
Date: Thu, 19 Dec 2024 18:27:05 +0100
Subject: [PATCH 06/15] feat(wallets): reapply "feat(wallets): add metaMask SDK
connector (#5028)" (#5215) (#5223)
* Reapply "feat(wallets): add metaMask SDK connector (#5028)" (#5215)
This reverts commit 889b78ed8b9e2092abbe503ed69c797e482b2edc.
* chore: update MetaMask SDK to 0.31.4
---------
Co-authored-by: Leandro
---
.../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 | 303 ++++++++++++++++-
12 files changed, 681 insertions(+), 65 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 a6cd57946e..6d26d07d41 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.4",
"@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 ca3cc58390..cd5acc9218 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.4":
+ version "0.31.4"
+ resolved "https://registry.yarnpkg.com/@metamask/sdk/-/sdk-0.31.4.tgz#2f9266e994ba838652925dc83e3409adfcae75ae"
+ integrity sha512-HLUN4IZGdyiy5YeebXmXi+ndpmrl6zslCQLdR2QHplIy4JmUL/eDyKNFiK7eBLVKXVVIDYFIb6g1iSEb+i8Kew==
+ 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"
@@ -28170,7 +28403,7 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -28187,6 +28420,15 @@ string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -28300,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==
@@ -28331,7 +28573,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -28345,6 +28587,13 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -29360,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"
@@ -30931,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"
@@ -31709,7 +31973,7 @@ workbox-window@7.0.0, workbox-window@^7.0.0:
"@types/trusted-types" "^2.0.2"
workbox-core "7.0.0"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -31736,6 +32000,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@@ -31797,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"
@@ -31847,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 c3529aa7e825c5747c8d00e54f6c0ed30950f932 Mon Sep 17 00:00:00 2001
From: Anxo Rodriguez
Date: Thu, 19 Dec 2024 20:16:39 +0000
Subject: [PATCH 07/15] chore: merge main to dev (#5238)
* fix: allow any safe-like apps
* chore: release main (#5237)
* feat: fix pagination for fills (#5228)
* feat: fix pagination for fills
* fix: remove unnecessary tag
---------
Co-authored-by: Alexandr Kazachenko
---
.release-please-manifest.json | 2 +-
.../components/orders/OrderDetails/FillsTableWithData.tsx | 2 +-
.../orders/OrderDetails/context/FillsTableContext.tsx | 2 +-
apps/explorer/src/components/orders/OrderDetails/index.tsx | 4 ++--
libs/wallet/CHANGELOG.md | 7 +++++++
libs/wallet/package.json | 2 +-
6 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 62d355c459..b2519049c8 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -17,7 +17,7 @@
"libs/tokens": "1.13.1",
"libs/types": "1.5.0",
"libs/ui": "1.17.0",
- "libs/wallet": "1.8.0",
+ "libs/wallet": "1.8.1",
"apps/cow-fi": "1.19.3",
"libs/wallet-provider": "1.0.0",
"libs/ui-utils": "1.1.0",
diff --git a/apps/explorer/src/components/orders/OrderDetails/FillsTableWithData.tsx b/apps/explorer/src/components/orders/OrderDetails/FillsTableWithData.tsx
index 6fdf28b00a..920a3a43b8 100644
--- a/apps/explorer/src/components/orders/OrderDetails/FillsTableWithData.tsx
+++ b/apps/explorer/src/components/orders/OrderDetails/FillsTableWithData.tsx
@@ -18,7 +18,7 @@ type Props = {
}
export const FillsTableWithData: React.FC = ({ areTokensLoaded, order, isPriceInverted, invertPrice }) => {
- const { trades, tableState } = useContext(FillsTableContext)
+ const { data: trades, tableState } = useContext(FillsTableContext)
const isFirstRender = useFirstRender()
return isFirstRender || !areTokensLoaded ? (
diff --git a/apps/explorer/src/components/orders/OrderDetails/context/FillsTableContext.tsx b/apps/explorer/src/components/orders/OrderDetails/context/FillsTableContext.tsx
index 35dd94b813..f7ad3e361a 100644
--- a/apps/explorer/src/components/orders/OrderDetails/context/FillsTableContext.tsx
+++ b/apps/explorer/src/components/orders/OrderDetails/context/FillsTableContext.tsx
@@ -5,7 +5,7 @@ import { Trade } from 'api/operator'
import { TableState, TableStateSetters } from '../../../../explorer/components/TokensTableWidget/useTable'
type CommonState = {
- trades: Trade[]
+ data: Trade[]
isLoading: boolean
tableState: TableState
} & TableStateSetters
diff --git a/apps/explorer/src/components/orders/OrderDetails/index.tsx b/apps/explorer/src/components/orders/OrderDetails/index.tsx
index ac8e47d609..debf07668d 100644
--- a/apps/explorer/src/components/orders/OrderDetails/index.tsx
+++ b/apps/explorer/src/components/orders/OrderDetails/index.tsx
@@ -72,7 +72,7 @@ export type Props = {
export enum TabView {
OVERVIEW = 1,
- FILLS,
+ FILLS = 2,
}
const DEFAULT_TAB = TabView[1]
@@ -225,7 +225,7 @@ export const OrderDetails: React.FC = (props) => {
))}
Date: Fri, 20 Dec 2024 17:32:55 +0000
Subject: [PATCH 08/15] feat(token-lists): remove outdated token lists (#5233)
* feat: remove outdated token lists
* feat: add curve's list to base
* feat: add superchain list to base
---
.../src/app/configurator/consts.ts | 18 +-------
libs/tokens/src/const/tokensList.json | 46 ++++---------------
2 files changed, 10 insertions(+), 54 deletions(-)
diff --git a/apps/widget-configurator/src/app/configurator/consts.ts b/apps/widget-configurator/src/app/configurator/consts.ts
index 4d128e0e4c..4d91c0f1ab 100644
--- a/apps/widget-configurator/src/app/configurator/consts.ts
+++ b/apps/widget-configurator/src/app/configurator/consts.ts
@@ -22,28 +22,12 @@ export const TRADE_MODES = [TradeType.SWAP, TradeType.LIMIT, TradeType.ADVANCED,
// Sourced from https://tokenlists.org/
export const DEFAULT_TOKEN_LISTS: TokenListItem[] = [
{ url: 'https://files.cow.fi/tokens/CowSwap.json', enabled: true },
- { url: 'https://files.cow.fi/tokens/CoinGecko.json', enabled: true },
- { url: 'https://tokens.1inch.eth.link', enabled: false },
- { url: 'https://tokenlist.aave.eth.link', enabled: false },
- { url: 'https://datafi.theagora.eth.link', enabled: false },
- { url: 'https://defi.cmc.eth.link', enabled: false },
- { url: 'https://stablecoin.cmc.eth.link', enabled: false },
- { url: 'https://erc20.cmc.eth.link', enabled: false },
- {
- url: 'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json',
- enabled: false,
- },
- { url: 'https://tokenlist.dharma.eth.link', enabled: false },
+ { url: 'https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/CoinGecko.1.json', enabled: true },
{ url: 'https://www.gemini.com/uniswap/manifest.json', enabled: false },
{ url: 'https://messari.io/tokenlist/messari-verified', enabled: false },
- { url: 'https://uniswap.mycryptoapi.com', enabled: false },
{ url: 'https://static.optimism.io/optimism.tokenlist.json', enabled: false },
{ url: 'https://app.tryroll.com/tokens.json', enabled: false },
- { url: 'https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json', enabled: false },
- { url: 'https://synths.snx.eth.link', enabled: false },
- { url: 'https://testnet.tokenlist.eth.link', enabled: false },
{ url: 'https://ipfs.io/ipns/tokens.uniswap.org', enabled: false },
- { url: 'https://wrapped.tokensoft.eth.link', enabled: false },
]
// TODO: Move default palette to a new lib that only exposes the palette colors.
// This way it can be consumed by both the configurator and the widget.
diff --git a/libs/tokens/src/const/tokensList.json b/libs/tokens/src/const/tokensList.json
index 7c45ca369e..c384288811 100644
--- a/libs/tokens/src/const/tokensList.json
+++ b/libs/tokens/src/const/tokensList.json
@@ -12,42 +12,6 @@
},
{
"priority": 3,
- "source": "https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json"
- },
- {
- "priority": 4,
- "source": "tokenlist.aave.eth"
- },
- {
- "priority": 5,
- "source": "synths.snx.eth"
- },
- {
- "priority": 6,
- "source": "wrapped.tokensoft.eth"
- },
- {
- "priority": 7,
- "source": "https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json"
- },
- {
- "priority": 8,
- "source": "https://raw.githubusercontent.com/opynfinance/opyn-tokenlist/master/opyn-squeeth-tokenlist.json"
- },
- {
- "priority": 9,
- "source": "https://app.tryroll.com/tokens.json"
- },
- {
- "priority": 10,
- "source": "defi.cmc.eth"
- },
- {
- "priority": 11,
- "source": "stablecoin.cmc.eth"
- },
- {
- "priority": 12,
"source": "https://curvefi.github.io/curve-assets/ethereum.json"
}
],
@@ -124,6 +88,14 @@
"priority": 3,
"enabledByDefault": true,
"source": "https://raw.githubusercontent.com/cowprotocol/token-lists/main/src/public/Uniswap.8453.json"
+ },
+ {
+ "priority": 4,
+ "source": "https://curvefi.github.io/curve-assets/base.json"
+ },
+ {
+ "priority": 5,
+ "source": "https://static.optimism.io/optimism.tokenlist.json"
}
]
-}
\ No newline at end of file
+}
From 22b8f89453371b8406e53effa82b7e3de745f19a Mon Sep 17 00:00:00 2001
From: Alexandr Kazachenko
Date: Wed, 25 Dec 2024 16:24:17 +0500
Subject: [PATCH 09/15] chore: fix failed e2e test (#5257)
---
apps/cowswap-frontend-e2e/src/e2e/swap.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/cowswap-frontend-e2e/src/e2e/swap.test.ts b/apps/cowswap-frontend-e2e/src/e2e/swap.test.ts
index f799a523d2..b938f5f713 100644
--- a/apps/cowswap-frontend-e2e/src/e2e/swap.test.ts
+++ b/apps/cowswap-frontend-e2e/src/e2e/swap.test.ts
@@ -68,7 +68,7 @@ describe('Swap (custom)', () => {
})
it('should accept buyAmount url param', () => {
- cy.visit(`/#/${CHAIN_ID}/swap/${SELL_TOKEN}/${BUY_TOKEN}?buyAmount=0.5`)
+ cy.visit(`/#/${CHAIN_ID}/swap/${SELL_TOKEN}/${BUY_TOKEN}?buyAmount=0.5&orderKind=buy`)
cy.get('#output-currency-input .token-amount-input').should('have.value', '0.5')
})
From caeda2c6d313f112d2f8203581a3470857cb3d61 Mon Sep 17 00:00:00 2001
From: Leandro
Date: Tue, 31 Dec 2024 11:38:05 +0000
Subject: [PATCH 10/15] chore: address pr5244 comments (#5263)
* fix: use network instead of orderParams chainId
* refactor: update comment
---
.../ethFlow/steps/signEthFlowOrderStep.ts | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts
index 1c688a320d..3fda6d658c 100644
--- a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts
+++ b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/steps/signEthFlowOrderStep.ts
@@ -67,19 +67,25 @@ export async function signEthFlowOrderStep(
return GAS_LIMIT_DEFAULT
})
- // This used to be done with a higher level of abstraction like this:
+ // Ensure the Eth flow contract network matches the network where you place the transaction.
+ // There are multiple wallet implementations, and potential race conditions that can cause the chain of the wallet to be different,
+ // and therefore leaving the chainId implicit might lead the user to place an order in an unwanted chain.
+ // This is especially dangerous for Eth Flow orders, because the contract address is different for the distinct networks,
+ // and this can lead to loss of funds.
+ //
+ // Thus, we are not using a higher level of abstraction as it doesn't allow to explicitly set the chainId:
// const txReceipt = await ethFlowContract.createOrder(ethOrderParams, {
// ...ethTxOptions,
// gasLimit: calculateGasMargin(estimatedGas),
// })
- // However, to **try** to prevent wallet issues, we want to explicitly send along the chainId
- // But that wrapper doesn't accept it.
- // So we must build the tx first, then send it using the contract's signer
+ //
+ // So we must build the tx first:
const tx = await ethFlowContract.populateTransaction.createOrder(ethOrderParams, {
...ethTxOptions,
gasLimit: calculateGasMargin(estimatedGas),
})
- const txReceipt = await ethFlowContract.signer.sendTransaction({ ...tx, chainId: orderParams.chainId })
+ // Then send the is using the contract's signer where the chainId is an acceptable parameter
+ const txReceipt = await ethFlowContract.signer.sendTransaction({ ...tx, chainId: network.chainId })
addInFlightOrderId(orderId)
From a8efd5c641fc456f9d7369a06097bc29278d06b1 Mon Sep 17 00:00:00 2001
From: Alexandr Kazachenko
Date: Tue, 31 Dec 2024 18:19:50 +0500
Subject: [PATCH 11/15] ci: deploy some apps preview only when PR has label
(#5258)
---
README.md | 15 ++++++
tools/scripts/ignore-build-step.js | 74 ++++++++++++++++++++++++++++++
2 files changed, 89 insertions(+)
create mode 100644 tools/scripts/ignore-build-step.js
diff --git a/README.md b/README.md
index 43094caf91..f205db5385 100644
--- a/README.md
+++ b/README.md
@@ -286,6 +286,21 @@ In case of problems with the service worker cache you force a reset using
`emergency.js` is not cached by browser and loaded before all.
+## Vercel preview build
+
+Since this repo includes multiple apps, we do not want to build all of them on each PR because it causes long build queues in Vercel.
+Some apps (see the list bellow) are not required to be built on each PR so we run them only a PR is labeled with a specific label.
+This label is defined in the project settings on Vercel in `Settings`/`Git`/`Ignored Build Step` script.
+For example, the label for the widget-configurator is `preview-widget-cfg`:
+```
+node tools/scripts/ignore-build-step.js --app=preview-widget-cfg
+```
+
+List of applications and their labels:
+- widget-configurator: `preview-widget-cfg`
+- cosmos: `preview-cosmos`
+- sdk-tools: `preview-sdk-tools`
+
# 📚 Technical Documentation
1. [Oveall Architecture](docs/architecture-overview.md)
diff --git a/tools/scripts/ignore-build-step.js b/tools/scripts/ignore-build-step.js
new file mode 100644
index 0000000000..5977304705
--- /dev/null
+++ b/tools/scripts/ignore-build-step.js
@@ -0,0 +1,74 @@
+const owner = process.env.VERCEL_GIT_REPO_OWNER
+const repo = process.env.VERCEL_GIT_REPO_SLUG
+const pullRequestId = process.env.VERCEL_GIT_PULL_REQUEST_ID
+const commitRef = process.env.VERCEL_GIT_COMMIT_REF
+
+const APP_ARGV = '--app='
+const appName = (() => {
+ const argv = process.argv.find((arg) => arg.startsWith(APP_ARGV))
+ return argv ? argv.slice(APP_ARGV.length) : undefined
+})()
+
+const PREVIEW_IGNORE_BRANCHES = ['main', 'configuration', 'release-please--branches--main']
+
+/**
+ * Skip the build if:
+ * - The branch is in the list of branches to ignore
+ * - The app preview is configured to be deployed manually, and label is not present in the PR
+ */
+async function shouldSkipBuild() {
+ if (PREVIEW_IGNORE_BRANCHES.includes(commitRef)) {
+ console.log(`Skipping build for branch ${commitRef}.`)
+ return true
+ }
+
+ if (!pullRequestId) {
+ console.log('No PR ID found. Proceeding with build.')
+ return false
+ }
+
+ if (!appName) {
+ console.log(`No appName label: ${appName}, found. Proceeding with build.`)
+ return false
+ }
+
+ const url = `https://api.github.com/repos/${owner}/${repo}/issues/${pullRequestId}/labels`
+
+ try {
+ const response = await fetch(url)
+
+ if (!response.ok) {
+ console.error('Failed to fetch PR labels:', response.statusText)
+ return false // Proceed with the build in case of an error
+ }
+
+ const labels = await response.json()
+ console.log(
+ 'PR Labels:',
+ labels.map((label) => label.name),
+ )
+ const hasAppLabel = labels.some((label) => label.name === appName)
+
+ if (hasAppLabel) {
+ console.log(`Found label: ${appName}. Proceeding with build.`)
+ } else {
+ console.log(`Label ${appName} not found. Skipping build.`)
+ }
+
+ // Skip the build if the PR doesn't have the app label
+ return !hasAppLabel
+ } catch (error) {
+ console.error('Error fetching PR labels:', error)
+ return false // Proceed with the build in case of an error
+ }
+}
+
+shouldSkipBuild().then((skip) => {
+ if (skip) {
+ console.log('Skipping build.')
+ process.exit(0)
+ } else {
+ console.log('Proceeding with build.')
+ process.exit(1)
+ }
+})
From 3be8a65309048d4082f2ce25f5b39624e092dbf2 Mon Sep 17 00:00:00 2001
From: Leandro
Date: Mon, 6 Jan 2025 14:49:13 +0000
Subject: [PATCH 12/15] feat: executedSurplusFee removal (#5262)
* chore: bump cow-sdk to latest RC version
* feat: replace executedSurplusFee with executedSuplus
Also use totalFee where applicable
* feat: do same as previous, but on Explorer
* refactor: moved getFeeToken out and improved logic as suggested
* test: add unit tests
---
.../src/legacy/state/orders/actions.ts | 11 ++++--
.../src/legacy/state/orders/reducer.ts | 17 +++++----
.../pure/OrdersTableContainer/orders.mock.ts | 13 +++++++
.../pure/ReceiptModal/FeeField.tsx | 15 ++++----
.../ordersTable/utils/getFeeToken.test.ts | 37 +++++++++++++++++++
.../modules/ordersTable/utils/getFeeToken.ts | 14 +++++++
.../src/utils/orderUtils/parseOrder.ts | 12 ++++--
apps/explorer/src/api/operator/types.ts | 14 +++++--
apps/explorer/src/test/data/operator.ts | 4 +-
apps/explorer/src/utils/operator.ts | 4 +-
package.json | 4 +-
yarn.lock | 20 ++++++++--
12 files changed, 131 insertions(+), 34 deletions(-)
create mode 100644 apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.test.ts
create mode 100644 apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.ts
diff --git a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts
index 4630be0d51..89bd865995 100644
--- a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts
+++ b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts
@@ -107,7 +107,9 @@ export type OrderInfoApi = Pick<
| 'executedSellAmount'
| 'executedSellAmountBeforeFees'
| 'executedFeeAmount'
- | 'executedSurplusFee'
+ | 'executedFee'
+ | 'executedFeeToken'
+ | 'totalFee'
| 'invalidated'
| 'ethflowData'
| 'onchainOrderData'
@@ -139,6 +141,7 @@ export interface AddPendingOrderParams {
order: SerializedOrder
isSafeWallet: boolean
}
+
export type ChangeOrderStatusParams = { id: UID; chainId: ChainId }
export type SetOrderCancellationHashParams = ChangeOrderStatusParams & { hash: string }
@@ -177,11 +180,13 @@ export interface BatchOrdersUpdateParams {
}
export type PresignedOrdersParams = BatchOrdersUpdateParams
+
export interface UpdatePresignGnosisSafeTxParams {
orderId: UID
chainId: ChainId
safeTransaction: SafeMultisigTransactionResponse
}
+
export type ExpireOrdersBatchParams = BatchOrdersUpdateParams
export type InvalidateOrdersBatchParams = BatchOrdersUpdateParams
export type CancelOrdersBatchParams = BatchOrdersUpdateParams
@@ -196,7 +201,7 @@ export const fulfillOrdersBatch = createAction('order/
export const preSignOrders = createAction('order/presignOrders')
export const updatePresignGnosisSafeTx = createAction(
- 'order/updatePresignGnosisSafeTx'
+ 'order/updatePresignGnosisSafeTx',
)
export const expireOrdersBatch = createAction('order/expireOrdersBatch')
@@ -214,7 +219,7 @@ export const deleteOrders = createAction('order/deleteOrders
export const clearOrders = createAction<{ chainId: ChainId }>('order/clearOrders')
export const updateLastCheckedBlock = createAction<{ chainId: ChainId; lastCheckedBlock: number }>(
- 'order/updateLastCheckedBlock'
+ 'order/updateLastCheckedBlock',
)
export const clearOrdersStorage = createAction('order/clearOrdersStorage')
diff --git a/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts b/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts
index 4e32f45fd1..f3068c7554 100644
--- a/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts
+++ b/apps/cowswap-frontend/src/legacy/state/orders/reducer.ts
@@ -121,7 +121,7 @@ export function getDefaultNetworkState(chainId: ChainId): OrdersStateNetwork {
// makes sure there's always an object at state[chainId], state[chainId].pending | .fulfilled
function prefillState(
state: Writable,
- { payload: { chainId } }: PayloadAction
+ { payload: { chainId } }: PayloadAction,
): asserts state is Required {
const stateAtChainId = state[chainId]
@@ -174,7 +174,7 @@ function addOrderToState(
id: string,
status: OrderTypeKeys,
order: SerializedOrder,
- isSafeWallet: boolean
+ isSafeWallet: boolean,
): void {
// Attempt to fix `TypeError: Cannot add property , object is not extensible`
// seen on https://user-images.githubusercontent.com/34510341/138450105-bb94a2d1-656e-4e15-ae99-df9fb33c8ca4.png
@@ -200,7 +200,7 @@ function cancelOrderInState(
state: Required,
chainId: ChainId,
orderObject: OrderObject,
- isSafeWallet: boolean
+ isSafeWallet: boolean,
) {
const id = orderObject.id
@@ -368,12 +368,13 @@ export default createReducer(initialState, (builder) =>
orderObject.order.apiAdditionalInfo = {
creationDate: order.creationDate,
- availableBalance: order.availableBalance,
executedBuyAmount: order.executedBuyAmount,
executedSellAmount: order.executedSellAmount,
executedSellAmountBeforeFees: order.executedSellAmountBeforeFees,
executedFeeAmount: order.executedFeeAmount,
- executedSurplusFee: order.executedSurplusFee,
+ executedFee: order.executedFee,
+ executedFeeToken: order.executedFeeToken,
+ totalFee: order.totalFee,
invalidated: order.invalidated,
ethflowData: order.ethflowData,
onchainOrderData: order.onchainOrderData,
@@ -458,7 +459,7 @@ export default createReducer(initialState, (builder) =>
const allOrdersMap = flatOrdersStateNetwork(state[chainId])
const children = Object.values(allOrdersMap).filter(
- (item) => item?.order.composableCowInfo?.parentId === id
+ (item) => item?.order.composableCowInfo?.parentId === id,
)
children.forEach((child) => {
@@ -544,12 +545,12 @@ export default createReducer(initialState, (builder) =>
orderListByChain[status] = ordersCleaned
})
})
- })
+ }),
)
function reClassifyOrder(
newOrder: SerializedOrder,
- existingOrder: OrderObject | undefined
+ existingOrder: OrderObject | undefined,
): { status: OrderStatus; isCancelling: boolean | undefined } {
// Onchain cancellations are considered final
// Still, the order classification at apps/cowswap-frontend/src/legacy/state/orders/utils.ts can't tell
diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts
index 46c3abdb6d..b6d8259577 100644
--- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts
+++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/orders.mock.ts
@@ -100,6 +100,19 @@ export const ordersMock: ParsedOrder[] = [
signingScheme: SigningScheme.EIP712,
class: OrderClass.MARKET,
kind: OrderKind.SELL,
+ apiAdditionalInfo: {
+ executedFeeAmount: '1',
+ executedFee: '1',
+ executedFeeToken: USDC[chainId].address,
+ totalFee: '1',
+ creationDate: '2022-11-11T13:15:13.551Z',
+ executedBuyAmount: '23000000000000',
+ executedSellAmount: '5000300000000000',
+ executedSellAmountBeforeFees: '5000300000000000',
+ invalidated: false,
+ class: OrderClass.LIMIT,
+ signingScheme: SigningScheme.EIP712,
+ },
},
{
id: '5',
diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx
index eed9c96389..4d348db3ff 100644
--- a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx
+++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/FeeField.tsx
@@ -1,6 +1,8 @@
import { TokenAmount } from '@cowprotocol/ui'
import { CurrencyAmount } from '@uniswap/sdk-core'
+import { getFeeToken } from 'modules/ordersTable/utils/getFeeToken'
+
import { ParsedOrder } from 'utils/orderUtils/parseOrder'
import * as styledEl from './styled'
@@ -8,14 +10,13 @@ import * as styledEl from './styled'
export type Props = { order: ParsedOrder }
export function FeeField({ order }: Props): JSX.Element | null {
- const { inputToken } = order
- const { executedFeeAmount, executedSurplusFee } = order.executionData
+ const { totalFee } = order.executionData
+ const feeToken = getFeeToken(order)
- if (!inputToken) return
+ if (!feeToken) return
- // TODO: use the value from SDK
- const totalFee = CurrencyAmount.fromRawAmount(inputToken, (executedSurplusFee ?? executedFeeAmount) || 0)
- const quoteSymbol = inputToken.symbol
+ const totalFeeAmount = CurrencyAmount.fromRawAmount(feeToken, totalFee || 0)
+ const quoteSymbol = feeToken.symbol
return (
@@ -23,7 +24,7 @@ export function FeeField({ order }: Props): JSX.Element | null {
-
) : (
-
+
)}
diff --git a/apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.test.ts b/apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.test.ts
new file mode 100644
index 0000000000..0eba3524c7
--- /dev/null
+++ b/apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.test.ts
@@ -0,0 +1,37 @@
+import { getFeeToken } from './getFeeToken' // Adjust the import path as necessary
+
+import { ordersMock } from '../pure/OrdersTableContainer/orders.mock'
+
+const BASE_ORDER = ordersMock[3]
+
+describe('getFeeToken', () => {
+ it("should return inputToken when that's the fee token", () => {
+ const input = BASE_ORDER
+ const expectedOutput = BASE_ORDER.inputToken
+
+ const result = getFeeToken(input)
+
+ expect(result).toEqual(expectedOutput)
+ })
+
+ it("should return outputToken when that's the fee token", () => {
+ const input = {
+ ...BASE_ORDER,
+ executionData: { ...BASE_ORDER.executionData, executedFeeToken: BASE_ORDER.outputToken.address },
+ }
+ const expectedOutput = BASE_ORDER.outputToken
+
+ const result = getFeeToken(input)
+
+ expect(result).toEqual(expectedOutput)
+ })
+
+ it('should return inputToken when there is no fee token', () => {
+ const input = { ...BASE_ORDER, executionData: { ...BASE_ORDER.executionData, executedFeeToken: null } }
+ const expectedOutput = BASE_ORDER.inputToken
+
+ const result = getFeeToken(input)
+
+ expect(result).toEqual(expectedOutput)
+ })
+})
diff --git a/apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.ts b/apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.ts
new file mode 100644
index 0000000000..a41a094bd4
--- /dev/null
+++ b/apps/cowswap-frontend/src/modules/ordersTable/utils/getFeeToken.ts
@@ -0,0 +1,14 @@
+import { ParsedOrder } from 'utils/orderUtils/parseOrder'
+
+export function getFeeToken(order: ParsedOrder) {
+ const { inputToken, outputToken } = order
+ const { executedFeeToken } = order.executionData
+
+ const feeTokenAddress = executedFeeToken?.toLowerCase()
+
+ if (!feeTokenAddress) {
+ return inputToken
+ }
+
+ return [inputToken, outputToken].find((token) => token?.address.toLowerCase() === feeTokenAddress)
+}
diff --git a/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts b/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts
index 8031e391f3..f6a3d0b4e2 100644
--- a/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts
+++ b/apps/cowswap-frontend/src/utils/orderUtils/parseOrder.ts
@@ -24,7 +24,9 @@ export interface ParsedOrderExecutionData {
surplusAmount: BigNumber
surplusPercentage: BigNumber
executedFeeAmount: string | undefined
- executedSurplusFee: string | null
+ executedFee: string | null
+ executedFeeToken: string | null
+ totalFee: string | null
filledPercentDisplay: string
executedPrice: Price | null
activityId: string | undefined
@@ -60,7 +62,9 @@ export const parseOrder = (order: Order): ParsedOrder => {
const { executedBuyAmount, executedSellAmount } = getOrderExecutedAmounts(order)
const expirationTime = new Date(Number(order.validTo) * 1000)
const executedFeeAmount = order.apiAdditionalInfo?.executedFeeAmount
- const executedSurplusFee = order.apiAdditionalInfo?.executedSurplusFee || null
+ const executedFee = order.apiAdditionalInfo?.executedFee || null
+ const executedFeeToken = order.apiAdditionalInfo?.executedFeeToken || null
+ const totalFee = order.apiAdditionalInfo?.totalFee || null
const creationTime = new Date(order.creationTime)
const fullyFilled = isOrderFilled(order)
const partiallyFilled = isPartiallyFilled(order)
@@ -80,6 +84,7 @@ export const parseOrder = (order: Order): ParsedOrder => {
const activityTitle = showCreationTxLink ? 'Creation transaction' : 'Order ID'
const executionData: ParsedOrderExecutionData = {
+ executedFeeToken,
executedBuyAmount,
executedSellAmount,
filledAmount,
@@ -88,7 +93,8 @@ export const parseOrder = (order: Order): ParsedOrder => {
surplusAmount,
surplusPercentage,
executedFeeAmount,
- executedSurplusFee,
+ executedFee,
+ totalFee,
executedPrice,
fullyFilled,
partiallyFilled,
diff --git a/apps/explorer/src/api/operator/types.ts b/apps/explorer/src/api/operator/types.ts
index 8ef8bf3dba..54240f124e 100644
--- a/apps/explorer/src/api/operator/types.ts
+++ b/apps/explorer/src/api/operator/types.ts
@@ -18,7 +18,15 @@ export type RawOrder = EnrichedOrder
*/
export type Order = Pick<
RawOrder,
- 'owner' | 'uid' | 'appData' | 'kind' | 'partiallyFillable' | 'signature' | 'class' | 'fullAppData'
+ | 'owner'
+ | 'uid'
+ | 'appData'
+ | 'kind'
+ | 'partiallyFillable'
+ | 'signature'
+ | 'class'
+ | 'fullAppData'
+ | 'executedFeeToken'
> & {
receiver: string
txHash?: string
@@ -35,7 +43,7 @@ export type Order = Pick<
executedSellAmount: BigNumber
feeAmount: BigNumber
executedFeeAmount: BigNumber
- executedSurplusFee: BigNumber | null
+ executedFee: BigNumber | null
totalFee: BigNumber
cancelled: boolean
status: OrderStatus
@@ -60,7 +68,7 @@ export type Trade = Pick
Date: Wed, 8 Jan 2025 17:27:52 +0000
Subject: [PATCH 13/15] chore: comment out seasonal feature flags (#5271)
---
.../modules/application/containers/App/index.tsx | 8 +++++---
.../src/modules/sounds/utils/sound.ts | 13 ++++++++-----
2 files changed, 13 insertions(+), 8 deletions(-)
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 b4fbd70538..11ffafbdaf 100644
--- a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx
+++ b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx
@@ -1,8 +1,7 @@
import { lazy, PropsWithChildren, Suspense, useMemo } from 'react'
import { ACTIVE_CUSTOM_THEME, CustomTheme } from '@cowprotocol/common-const'
-import { useMediaQuery } from '@cowprotocol/common-hooks'
-import { useFeatureFlags } from '@cowprotocol/common-hooks'
+import { useFeatureFlags, useMediaQuery } from '@cowprotocol/common-hooks'
import { isInjectedWidget } from '@cowprotocol/common-utils'
import { Color, Footer, GlobalCoWDAOStyles, Media, MenuBar } from '@cowprotocol/ui'
@@ -55,7 +54,10 @@ export function App() {
useAnalyticsReporterCowSwap()
useInitializeUtm()
- const { isYieldEnabled, isChristmasEnabled, isHalloweenEnabled } = useFeatureFlags()
+ const { isYieldEnabled, } = useFeatureFlags()
+ // TODO: load them from feature flags when we want to enable again
+ const isChristmasEnabled = false
+ const isHalloweenEnabled = false
const isInjectedWidgetMode = isInjectedWidget()
const menuItems = useMenuItems()
diff --git a/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts b/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts
index ca127702dc..f4557a299f 100644
--- a/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts
+++ b/apps/cowswap-frontend/src/modules/sounds/utils/sound.ts
@@ -7,8 +7,6 @@ import { cowSwapStore } from 'legacy/state'
import { injectedWidgetParamsAtom } from 'modules/injectedWidget/state/injectedWidgetParamsAtom'
-import { featureFlagsAtom } from 'common/state/featureFlagsState'
-
type SoundType = 'SEND' | 'SUCCESS' | 'ERROR'
type Sounds = Record
type WidgetSounds = keyof NonNullable
@@ -47,7 +45,12 @@ function isDarkMode(): boolean {
}
function getThemeBasedSound(type: SoundType): string {
- const featureFlags = jotaiStore.get(featureFlagsAtom) as Record
+ // TODO: load featureFlags when enabling again
+ // const featureFlags = jotaiStore.get(featureFlagsAtom) as Record
+ // const { isChristmasEnabled, isHalloweenEnabled } = featureFlags
+ const isChristmasEnabled = false
+ const isHalloweenEnabled = false
+
const defaultSound = DEFAULT_COW_SOUNDS[type]
const themedOptions = THEMED_SOUNDS[type]
@@ -62,13 +65,13 @@ function getThemeBasedSound(type: SoundType): string {
return defaultSound
}
- if (ACTIVE_CUSTOM_THEME === CustomTheme.CHRISTMAS && featureFlags.isChristmasEnabled && themedOptions.winterSound) {
+ if (ACTIVE_CUSTOM_THEME === CustomTheme.CHRISTMAS && isChristmasEnabled && themedOptions.winterSound) {
return themedOptions.winterSound
}
if (
ACTIVE_CUSTOM_THEME === CustomTheme.HALLOWEEN &&
- featureFlags.isHalloweenEnabled &&
+ isHalloweenEnabled &&
themedOptions.halloweenSound &&
isDarkMode()
) {
From f080ffdb098612e729f3a3f829410ce78697979f Mon Sep 17 00:00:00 2001
From: Alexandr Kazachenko
Date: Wed, 8 Jan 2025 22:52:28 +0500
Subject: [PATCH 14/15] feat(swap): partial approve (#5256)
* feat(swap): add settings option for partial approve
* feat(swap): add sell amount to regular approve tx
* feat(swap): add sell amount to permit value
* chore: fix build
* fix: cache permit taking amount into account
* feat(swap): take partial approves into account for sc wallets
* fix: ignore account agnostic permit in hooks details
* fix: take permit amount into account when caching
* fix: skip partial permits in widgets besides swap
* chore: fix permit hook description
* chore: fix conditions
* fix: disable partial approve for Hooks store
* fix: support partial approve it classic eth flow
* fix: do not use infinite approvals in swap when partial approve mode
* chore: fix circular dependency
* chore: add a dot
* chore: fix tooltips
* chore: adjust approve tooltip
* fix: display hook details only in Hooks store confirm modal
* feat(partial-approvals): partial approve v2 (#5269)
* refactor: expose UndelinedLinkStyledButton component
* chore: removed unused React import
* refactor: export useOpenSettingsTab hook
* feat: expose needsApproval flag for all token types, not just permittable
* feat: add PartialApprovalBanner
* feat: update settings name Partial Approve to Minimal approve and tooltip
* fix: fix typo and padding on settings link
* fix: reworked minimal approvals title and tooltip
* fix: lint issues
* fix: cosmos build
* fix: fix grammar
* fix: do not show approval banner when selling native
* chore: move partial approval banner after TWAP warning
* feat: simplify text
* feat: remove isApprovalNeeded prop
* feat: remove banner from swap warnings
* feat: add banner to top of Swap widget
* chore: remove settings from cosmos
---------
Co-authored-by: Leandro
---
.../containers/OrderHooksDetails/index.tsx | 41 +++++++++-
.../TradeApprove/TradeApproveButton.tsx | 5 +-
.../TradeApprove/useTradeApproveCallback.ts | 9 ++-
.../src/common/hooks/useApproveCallback.ts | 15 ++--
.../src/common/pure/ApproveButton/index.tsx | 1 -
.../common/pure/OrderProgressBarV2/index.tsx | 15 +++-
.../common/pure/OrderProgressBarV2/styled.ts | 12 +--
.../src/common/utils/parsePermitData.ts | 15 ++++
.../src/legacy/state/user/hooks.tsx | 19 +++++
.../src/legacy/state/user/reducer.ts | 7 +-
.../src/lib/hooks/useApproval.ts | 74 +------------------
.../operations/bundle/buildApproveTx.ts | 5 +-
.../permit/hooks/useGeneratePermitHook.ts | 10 ++-
.../permit/hooks/useGetCachedPermit.ts | 5 +-
.../modules/permit/state/permitCacheAtom.ts | 31 ++++++--
.../src/modules/permit/types.ts | 4 +-
.../src/modules/permit/utils/handlePermit.ts | 3 +-
.../ConfirmSwapModalSetup/index.tsx | 8 +-
.../modules/swap/containers/EthFlow/index.tsx | 8 +-
.../swap/containers/SwapWidget/index.tsx | 10 ++-
.../swap/hooks/useHandleSwapOrEthFlow.ts | 8 +-
.../swap/hooks/useSwapButtonContext.ts | 4 +
.../modules/swap/hooks/useSwapFlowContext.ts | 6 +-
.../EthFlow/EthFlowModalContent/configs.ts | 4 +-
.../swap/pure/SwapButtons/index.cosmos.tsx | 1 +
.../modules/swap/pure/SwapButtons/index.tsx | 3 +-
.../pure/banners/PartialApprovalBanner.tsx | 31 ++++++++
.../trade/pure/TradeConfirmation/index.tsx | 18 ++++-
.../tradeFlow/hooks/useTradeFlowContext.ts | 8 +-
.../safeBundleFlow/safeBundleApprovalFlow.ts | 11 ++-
.../safeBundleFlow/safeBundleEthFlow.ts | 10 ++-
.../tradeFlow/services/swapFlow/index.ts | 1 +
.../tradeFlow/types/TradeFlowContext.ts | 1 +
.../containers/SettingsTab/index.tsx | 41 +++++++++-
.../pure/Row/RowSlippageContent/index.tsx | 7 +-
.../state/settingsTabState.ts | 8 +-
.../cowswap-frontend/src/pages/Swap/index.tsx | 5 +-
.../utils/orderUtils/getOrderPermitAmount.ts | 7 +-
libs/hook-dapp-lib/src/hookDappsRegistry.json | 2 +-
.../src/lib/generatePermitHook.ts | 14 +++-
libs/permit-utils/src/types.ts | 1 +
libs/ui/src/containers/InlineBanner/index.tsx | 2 +-
libs/ui/src/pure/LinkStyledButton/index.tsx | 9 +++
43 files changed, 350 insertions(+), 149 deletions(-)
create mode 100644 apps/cowswap-frontend/src/common/utils/parsePermitData.ts
create mode 100644 apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx
diff --git a/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx b/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx
index 3c8a623983..67e607b666 100644
--- a/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx
+++ b/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx
@@ -3,6 +3,8 @@ import { ReactElement, useEffect, useMemo, useState } from 'react'
import { latest } from '@cowprotocol/app-data'
import { CowHookDetails, HookToDappMatch, matchHooksToDappsRegistry } from '@cowprotocol/hook-dapp-lib'
import { InfoTooltip } from '@cowprotocol/ui'
+import { useWalletInfo } from '@cowprotocol/wallet'
+import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { ChevronDown, ChevronUp } from 'react-feather'
@@ -14,15 +16,27 @@ import { HookItem } from './HookItem'
import * as styledEl from './styled'
import { CircleCount } from './styled'
+import { parsePermitData } from '../../utils/parsePermitData'
+
interface OrderHooksDetailsProps {
appData: string | AppDataInfo
children: (content: ReactElement) => ReactElement
margin?: string
isTradeConfirmation?: boolean
+ slippageAdjustedSellAmount?: CurrencyAmount
+ isPartialApprove?: boolean
}
-export function OrderHooksDetails({ appData, children, margin, isTradeConfirmation }: OrderHooksDetailsProps) {
+export function OrderHooksDetails({
+ appData,
+ children,
+ margin,
+ isTradeConfirmation,
+ slippageAdjustedSellAmount,
+ isPartialApprove,
+}: OrderHooksDetailsProps) {
const [isOpen, setOpen] = useState(false)
+ const { account } = useWalletInfo()
const appDataDoc = useMemo(() => {
return typeof appData === 'string' ? decodeAppData(appData) : appData.doc
}, [appData])
@@ -41,9 +55,32 @@ export function OrderHooksDetails({ appData, children, margin, isTradeConfirmati
const metadata = appDataDoc.metadata as latest.Metadata
+ /**
+ * AppData might include a hook with account agnostic permit which is used to fetch a quote.
+ * This hook should be ignored.
+ * Moreover, any hook with a permit which has owner !== current account will be excluded.
+ * We also remove the permit from appData before order signing (see filterPermitSignerPermit).
+ */
+ const preHooks = account
+ ? metadata.hooks?.pre?.filter((hook) => {
+ try {
+ const permitHookData = parsePermitData(hook.callData)
+ const isOwnerMatched = permitHookData.owner.toLowerCase() === account.toLowerCase()
+
+ // If the hook is a partial approve, we need to check if the value is equal to the slippageAdjustedSellAmount
+ // Because there might be a hook with an "infinite" permit from other widget
+ return isPartialApprove && slippageAdjustedSellAmount
+ ? isOwnerMatched && permitHookData.value.eq(slippageAdjustedSellAmount.quotient.toString())
+ : isOwnerMatched
+ } catch {
+ return true
+ }
+ })
+ : metadata.hooks?.pre
+
const hasSomeFailedSimulation = isTradeConfirmation && Object.values(data || {}).some((hook) => !hook.status)
- const preHooksToDapp = matchHooksToDappsRegistry(metadata.hooks?.pre || [], preCustomHookDapps)
+ const preHooksToDapp = matchHooksToDappsRegistry(preHooks || [], preCustomHookDapps)
const postHooksToDapp = matchHooksToDappsRegistry(metadata.hooks?.post || [], postCustomHookDapps)
if (!preHooksToDapp.length && !postHooksToDapp.length) return null
diff --git a/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx b/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx
index 68e68ef2b3..3e31105e1d 100644
--- a/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx
+++ b/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx
@@ -14,15 +14,16 @@ export interface TradeApproveButtonProps {
amountToApprove: CurrencyAmount
children?: React.ReactNode
isDisabled?: boolean
+ isPartialApprove?: boolean
}
export function TradeApproveButton(props: TradeApproveButtonProps) {
- const { amountToApprove, children, isDisabled } = props
+ const { amountToApprove, children, isDisabled, isPartialApprove } = props
const currency = amountToApprove.currency
const { state: approvalState } = useApproveState(amountToApprove)
- const tradeApproveCallback = useTradeApproveCallback(amountToApprove)
+ const tradeApproveCallback = useTradeApproveCallback(amountToApprove, isPartialApprove)
const shouldZeroApprove = useShouldZeroApprove(amountToApprove)
const zeroApprove = useZeroApprove(amountToApprove.currency)
diff --git a/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts b/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts
index 3e5136344c..552a32d42b 100644
--- a/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts
+++ b/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts
@@ -19,13 +19,16 @@ export interface TradeApproveCallback {
(params?: TradeApproveCallbackParams): Promise
}
-export function useTradeApproveCallback(amountToApprove?: CurrencyAmount): TradeApproveCallback {
+export function useTradeApproveCallback(
+ amountToApprove?: CurrencyAmount,
+ isPartialApprove?: boolean,
+): TradeApproveCallback {
const updateTradeApproveState = useUpdateTradeApproveState()
const spender = useTradeSpenderAddress()
const currency = amountToApprove?.currency
const symbol = currency?.symbol
- const approveCallback = useApproveCallback(amountToApprove, spender)
+ const approveCallback = useApproveCallback(amountToApprove, spender, isPartialApprove)
return useCallback(
async ({ useModals = true }: TradeApproveCallbackParams = { useModals: true }) => {
@@ -58,6 +61,6 @@ export function useTradeApproveCallback(amountToApprove?: CurrencyAmount,
+ isPartialApprove?: boolean,
): Promise<{
approveAmount: BigNumber | string
gasLimit: BigNumber
}> {
+ const approveAmount =
+ isPartialApprove && amountToApprove ? BigNumber.from(amountToApprove.quotient.toString()) : MaxUint256
+
try {
return {
- approveAmount: MaxUint256,
- gasLimit: await tokenContract.estimateGas.approve(spender, MaxUint256),
+ approveAmount,
+ gasLimit: await tokenContract.estimateGas.approve(spender, approveAmount),
}
} catch {
// Fallback: Attempt to set an approval for the maximum wallet balance (instead of the MaxUint256).
@@ -45,7 +49,7 @@ export async function estimateApprove(
)
return {
- approveAmount: MaxUint256,
+ approveAmount,
gasLimit: GAS_LIMIT_DEFAULT,
}
}
@@ -55,6 +59,7 @@ export async function estimateApprove(
export function useApproveCallback(
amountToApprove?: CurrencyAmount,
spender?: string,
+ isPartialApprove?: boolean,
): (summary?: string) => Promise {
const { chainId } = useWalletInfo()
const currency = amountToApprove?.currency
@@ -68,7 +73,7 @@ export function useApproveCallback(
return
}
- const estimation = await estimateApprove(tokenContract, spender, amountToApprove)
+ const estimation = await estimateApprove(tokenContract, spender, amountToApprove, isPartialApprove)
return tokenContract
.approve(spender, estimation.approveAmount, {
gasLimit: calculateGasMargin(estimation.gasLimit),
@@ -81,5 +86,5 @@ export function useApproveCallback(
})
return response
})
- }, [chainId, token, tokenContract, amountToApprove, spender, addTransaction])
+ }, [chainId, token, tokenContract, amountToApprove, spender, addTransaction, isPartialApprove])
}
diff --git a/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx b/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx
index 13273df82e..68bda945b0 100644
--- a/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx
+++ b/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx
@@ -50,7 +50,6 @@ export function ApproveButton(props: ApproveButtonProps) {
content={
You must give the CoW Protocol smart contracts permission to use your .
- If you approve the default amount, you will only have to do this once per token.
}
>
diff --git a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx
index 81d1eaafc2..605eb3faf3 100644
--- a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx
+++ b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx
@@ -22,7 +22,16 @@ import { ExplorerDataType, getExplorerLink, getRandomInt, isSellOrder, shortenAd
import { OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk'
import { TokenLogo } from '@cowprotocol/tokens'
import { Command } from '@cowprotocol/types'
-import { Confetti, ExternalLink, InfoTooltip, ProductLogo, ProductVariant, TokenAmount, UI } from '@cowprotocol/ui'
+import {
+ Confetti,
+ ExternalLink,
+ InfoTooltip,
+ ProductLogo,
+ ProductVariant,
+ TokenAmount,
+ UI,
+ UnderlinedLinkStyledButton,
+} from '@cowprotocol/ui'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { AnimatePresence, motion } from 'framer-motion'
@@ -1123,14 +1132,14 @@ function ExpiredStep(props: OrderProgressBarV2Props) {
The good news
Unlike on other exchanges, you won't be charged for this! Feel free to{' '}
- {
props.navigateToNewOrder?.()
trackNewOrderClick()
}}
>
place a new order
- {' '}
+ {' '}
without worry.
diff --git a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts
index 198fe338ff..cd1a6a3de0 100644
--- a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts
+++ b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts
@@ -1,6 +1,6 @@
import IMAGE_STAR_SHINE from '@cowprotocol/assets/cow-swap/star-shine.svg'
import { SingleLetterLogoWrapper } from '@cowprotocol/tokens'
-import { ButtonPrimary, Font, LinkStyledButton, Media, UI } from '@cowprotocol/ui'
+import { ButtonPrimary, Font, Media, UI } from '@cowprotocol/ui'
import styled, { css, keyframes } from 'styled-components/macro'
@@ -66,6 +66,7 @@ export const StepsContainer = styled.div<{ $height: number; $minHeight?: string;
padding: 0;
// implement a gradient to hide the bottom of the steps container using white to opacity white using pseudo element
+
&::after {
content: ${({ bottomGradient }) => (bottomGradient ? '""' : 'none')};
position: absolute;
@@ -143,15 +144,6 @@ export const CancelButton = styled(CancelButtonOriginal)`
}
`
-export const Button = styled(LinkStyledButton)`
- font-size: 14px;
- text-decoration: underline;
-
- &:hover {
- text-decoration: none;
- }
-`
-
export const ProgressImageWrapper = styled.div<{ bgColor?: string; padding?: string; height?: string; gap?: string }>`
width: 100%;
height: ${({ height }) => height || '246px'};
diff --git a/apps/cowswap-frontend/src/common/utils/parsePermitData.ts b/apps/cowswap-frontend/src/common/utils/parsePermitData.ts
new file mode 100644
index 0000000000..6ba16f7c1a
--- /dev/null
+++ b/apps/cowswap-frontend/src/common/utils/parsePermitData.ts
@@ -0,0 +1,15 @@
+import { Erc20__factory } from '@cowprotocol/abis'
+import type { BigNumber } from '@ethersproject/bignumber'
+
+const erc20Interface = Erc20__factory.createInterface()
+
+export interface PermitParameters {
+ owner: string
+ spender: string
+ value: BigNumber
+ deadline: BigNumber
+}
+
+export function parsePermitData(callData: string): PermitParameters {
+ return erc20Interface.decodeFunctionData('permit', callData) as unknown as PermitParameters
+}
diff --git a/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx b/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx
index 2f86b41a32..24aeb1e878 100644
--- a/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx
+++ b/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx
@@ -8,8 +8,11 @@ import { Currency } from '@uniswap/sdk-core'
import { shallowEqual } from 'react-redux'
+import { useIsHooksTradeType } from 'modules/trade/hooks/useIsHooksTradeType'
+
import {
updateHooksEnabled,
+ updatePartialApprove,
updateRecipientToggleVisible,
updateUserDarkMode,
updateUserDeadline,
@@ -118,6 +121,22 @@ export function useUserTransactionTTL(): [number, (slippage: number) => void] {
return [deadline, setUserDeadline]
}
+export function usePartialApprove(): [boolean, (value: boolean) => void] {
+ const dispatch = useAppDispatch()
+ const isHookTradeType = useIsHooksTradeType()
+ const partialApprove = useAppSelector((state) => state.user.partialApprove)
+
+ const setPartialApprove = useCallback(
+ (partialApprove: boolean) => {
+ dispatch(updatePartialApprove({ partialApprove }))
+ },
+ [dispatch],
+ )
+
+ // Partial approve is disabled for Hooks store
+ return [isHookTradeType ? false : partialApprove, setPartialApprove]
+}
+
export function useSelectedWallet(): string | undefined {
return useAppSelector(({ user: { selectedWallet } }) => selectedWallet)
}
diff --git a/apps/cowswap-frontend/src/legacy/state/user/reducer.ts b/apps/cowswap-frontend/src/legacy/state/user/reducer.ts
index a9470d45b3..395811fa84 100644
--- a/apps/cowswap-frontend/src/legacy/state/user/reducer.ts
+++ b/apps/cowswap-frontend/src/legacy/state/user/reducer.ts
@@ -18,6 +18,7 @@ export interface UserState {
// TODO: mod, shouldn't be here
recipientToggleVisible: boolean
hooksEnabled: boolean
+ partialApprove: boolean
// deadline set by user in minutes, used in all txns
userDeadline: number
@@ -27,9 +28,9 @@ export const initialState: UserState = {
selectedWallet: undefined,
matchesDarkMode: false,
userDarkMode: null,
- // TODO: mod, shouldn't be here
recipientToggleVisible: false,
hooksEnabled: false,
+ partialApprove: false,
userLocale: null,
userDeadline: DEFAULT_DEADLINE_FROM_NOW,
}
@@ -56,6 +57,9 @@ const userSlice = createSlice({
updateUserDeadline(state, action) {
state.userDeadline = action.payload.userDeadline
},
+ updatePartialApprove(state, action) {
+ state.partialApprove = action.payload.partialApprove
+ },
updateRecipientToggleVisible(state, action) {
state.recipientToggleVisible = action.payload.recipientToggleVisible
},
@@ -70,5 +74,6 @@ export const {
updateUserDeadline,
updateUserLocale,
updateRecipientToggleVisible,
+ updatePartialApprove,
} = userSlice.actions
export default userSlice.reducer
diff --git a/apps/cowswap-frontend/src/lib/hooks/useApproval.ts b/apps/cowswap-frontend/src/lib/hooks/useApproval.ts
index 285b4afb2a..bfe49fa6c2 100644
--- a/apps/cowswap-frontend/src/lib/hooks/useApproval.ts
+++ b/apps/cowswap-frontend/src/lib/hooks/useApproval.ts
@@ -1,9 +1,7 @@
-import { useCallback, useMemo } from 'react'
+import { useMemo } from 'react'
-import { calculateGasMargin, getIsNativeToken } from '@cowprotocol/common-utils'
+import { getIsNativeToken } from '@cowprotocol/common-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
-import { MaxUint256 } from '@ethersproject/constants'
-import { TransactionResponse } from '@ethersproject/providers'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Nullish } from 'types'
@@ -11,7 +9,6 @@ import { Nullish } from 'types'
import { useTokenAllowance } from 'legacy/hooks/useTokenAllowance'
import { ApprovalState } from 'common/hooks/useApproveState'
-import { useTokenContract } from 'common/hooks/useContract'
export interface ApprovalStateForSpenderResult {
approvalState: ApprovalState
@@ -22,7 +19,7 @@ function toApprovalState(
amountToApprove: Nullish>,
spender: string | undefined,
currentAllowance?: CurrencyAmount,
- pendingApproval?: boolean
+ pendingApproval?: boolean,
): ApprovalState {
// Unknown amount or spender
if (!amountToApprove || !spender) {
@@ -50,7 +47,7 @@ function toApprovalState(
export function useApprovalStateForSpender(
amountToApprove: Nullish>,
spender: string | undefined,
- useIsPendingApproval: (token?: Token, spender?: string) => boolean
+ useIsPendingApproval: (token?: Token, spender?: string) => boolean,
): ApprovalStateForSpenderResult {
const { account } = useWalletInfo()
const currency = amountToApprove?.currency
@@ -64,66 +61,3 @@ export function useApprovalStateForSpender(
return { approvalState, currentAllowance }
}, [amountToApprove, currentAllowance, pendingApproval, spender])
}
-
-export function useApproval(
- amountToApprove: CurrencyAmount | undefined,
- spender: string | undefined,
- useIsPendingApproval: (token?: Token, spender?: string) => boolean
-): [
- ApprovalState,
- () => Promise<{ response: TransactionResponse; tokenAddress: string; spenderAddress: string } | undefined>
-] {
- const { chainId } = useWalletInfo()
- const currency = amountToApprove?.currency
- const token = currency && !getIsNativeToken(currency) ? currency : undefined
-
- // check the current approval status
- const approvalState = useApprovalStateForSpender(amountToApprove, spender, useIsPendingApproval).approvalState
-
- const tokenContract = useTokenContract(token?.address)
-
- const approve = useCallback(async () => {
- function logFailure(error: Error | string): undefined {
- console.warn(`${token?.symbol || 'Token'} approval failed:`, error)
- return
- }
-
- // Bail early if there is an issue.
- if (approvalState !== ApprovalState.NOT_APPROVED) {
- return logFailure('approve was called unnecessarily')
- } else if (!chainId) {
- return logFailure('no chainId')
- } else if (!token) {
- return logFailure('no token')
- } else if (!tokenContract) {
- return logFailure('tokenContract is null')
- } else if (!amountToApprove) {
- return logFailure('missing amount to approve')
- } else if (!spender) {
- return logFailure('no spender')
- }
-
- let useExact = false
- const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => {
- // general fallback for tokens which restrict approval amounts
- useExact = true
- return tokenContract.estimateGas.approve(spender, amountToApprove.quotient.toString())
- })
-
- return tokenContract
- .approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, {
- gasLimit: calculateGasMargin(estimatedGas),
- })
- .then((response) => ({
- response,
- tokenAddress: token.address,
- spenderAddress: spender,
- }))
- .catch((error: Error) => {
- logFailure(error)
- throw error
- })
- }, [approvalState, token, tokenContract, amountToApprove, spender, chainId])
-
- return [approvalState, approve]
-}
diff --git a/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts b/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts
index abd22db931..51aba39eea 100644
--- a/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts
+++ b/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts
@@ -7,15 +7,16 @@ export type BuildApproveTxParams = {
erc20Contract: Erc20
spender: string
amountToApprove: CurrencyAmount
+ isPartialApprove?: boolean
}
/**
* Builds the approval tx, without sending it
*/
export async function buildApproveTx(params: BuildApproveTxParams) {
- const { erc20Contract, spender, amountToApprove } = params
+ const { erc20Contract, spender, amountToApprove, isPartialApprove } = params
- const estimatedAmount = await estimateApprove(erc20Contract, spender, amountToApprove)
+ const estimatedAmount = await estimateApprove(erc20Contract, spender, amountToApprove, isPartialApprove)
return erc20Contract.populateTransaction.approve(spender, estimatedAmount.approveAmount)
}
diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts
index 86d6b6bf65..2fbf6ba422 100644
--- a/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts
+++ b/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts
@@ -19,7 +19,7 @@ import { GeneratePermitHook, GeneratePermitHookParams } from '../types'
/**
* Hook that returns callback to generate permit hook data
*/
-export function useGeneratePermitHook(): GeneratePermitHook {
+export function useGeneratePermitHook(isPartialApprove?: boolean): GeneratePermitHook {
const { chainId } = useWalletInfo()
const storePermit = useSetAtom(storePermitCacheAtom)
const getCachedPermit = useGetCachedPermit()
@@ -44,14 +44,15 @@ export function useGeneratePermitHook(): GeneratePermitHook {
const eip2162Utils = getPermitUtilsInstance(chainId, provider, account)
const spender = customSpender || COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId]
+ const amount = isPartialApprove ? params.amount : undefined
// Always get the nonce for the real account, to know whether the cache should be invalidated
// Static account should never need to pre-check the nonce as it'll never change once cached
const nonce = account ? await eip2162Utils.getTokenNonce(inputToken.address, account) : undefined
- const permitParams = { chainId, tokenAddress: inputToken.address, account, nonce }
+ const permitParams = { chainId, tokenAddress: inputToken.address, account, nonce, amount }
- const cachedPermit = await getCachedPermit(inputToken.address, spender)
+ const cachedPermit = await getCachedPermit(inputToken.address, spender, amount)
if (cachedPermit) {
return cachedPermit
@@ -66,12 +67,13 @@ export function useGeneratePermitHook(): GeneratePermitHook {
eip2162Utils,
account,
nonce,
+ amount,
})
hookData && storePermit({ ...permitParams, hookData, spender })
return hookData
},
- [provider, chainId, getCachedPermit, storePermit],
+ [provider, chainId, getCachedPermit, storePermit, isPartialApprove],
)
}
diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts
index 4f0ba0dc7a..4476e8c7c4 100644
--- a/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts
+++ b/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts
@@ -13,13 +13,14 @@ import { getPermitCacheAtom } from '../state/permitCacheAtom'
export function useGetCachedPermit(): (
tokenAddress: Nullish,
customSpender?: string,
+ amount?: bigint,
) => Promise {
const { chainId, account } = useWalletInfo()
const provider = useWalletProvider()
const getCachedPermit = useSetAtom(getPermitCacheAtom)
return useCallback(
- async (tokenAddress: Nullish, customSpender?: string) => {
+ async (tokenAddress: Nullish, customSpender?: string, amount?: bigint) => {
if (!provider || !account || !tokenAddress) {
return
}
@@ -32,7 +33,7 @@ export function useGetCachedPermit(): (
// Static account should never need to pre-check the nonce as it'll never change once cached
const nonce = account ? await eip2162Utils.getTokenNonce(tokenAddress, account) : undefined
- const permitParams = { chainId, tokenAddress, account, nonce, spender }
+ const permitParams = { chainId, tokenAddress, account, nonce, spender, amount }
return getCachedPermit(permitParams)
} catch (e) {
diff --git a/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts b/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts
index e09aa938f4..1ab0198f52 100644
--- a/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts
+++ b/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts
@@ -1,6 +1,12 @@
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
+import { MaxUint256 } from '@ethersproject/constants'
+
+import { TradeType, tradeTypeAtom } from 'modules/trade'
+
+import { Routes } from 'common/constants/routes'
+
import {
CachedPermitData,
GetPermitCacheParams,
@@ -14,14 +20,14 @@ import {
* Should never change once it has been created.
* Used exclusively for quote requests
*/
-export const staticPermitCacheAtom = atomWithStorage('staticPermitCache:v3', {})
+export const staticPermitCacheAtom = atomWithStorage('staticPermitCache:v4', {})
/**
* Atom that stores permit data for user permit requests.
* Should be updated whenever the permit nonce is updated.
* Used exclusively for order requests
*/
-export const userPermitCacheAtom = atomWithStorage('userPermitCache:v1', {})
+export const userPermitCacheAtom = atomWithStorage('userPermitCache:v2', {})
/**
* Atom to add/update permit cache data
@@ -36,6 +42,7 @@ export const storePermitCacheAtom = atom(null, (get, set, params: StorePermitCac
const dataToCache: CachedPermitData = {
hookData: params.hookData,
nonce: params.nonce,
+ amount: params.amount?.toString(),
}
set(atomToUpdate, (permitCache) => ({ ...permitCache, [key]: JSON.stringify(dataToCache) }))
@@ -53,6 +60,10 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
const atomToUpdate = params.account ? userPermitCacheAtom : staticPermitCacheAtom
const permitCache = get(atomToUpdate)
+ const tradeType = get(tradeTypeAtom)
+
+ const isSwap = tradeType?.tradeType === TradeType.SWAP && tradeType.route === Routes.SWAP
+
const key = buildKey(params)
const cachedData = permitCache[key]
@@ -61,7 +72,7 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
}
try {
- const { hookData, nonce: storedNonce }: CachedPermitData = JSON.parse(cachedData)
+ const { hookData, nonce: storedNonce, amount }: CachedPermitData = JSON.parse(cachedData)
if (params.account !== undefined) {
// User type permit cache, check the nonce
@@ -76,6 +87,16 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
return undefined
}
+
+ // Only Swap might create partial amount permits
+ // Because of that, we skip cached permits with partial amount in other widgets
+ if (!isSwap && amount && amount !== MaxUint256.toString()) {
+ return undefined
+ }
+
+ if (params.amount && params.amount.toString() !== amount) {
+ return undefined
+ }
}
// Cache hit for both static and user permit types
@@ -91,8 +112,8 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
}
})
-function buildKey({ chainId, tokenAddress, account, spender }: PermitCacheKeyParams) {
- const base = `${chainId}-${tokenAddress.toLowerCase()}-${spender.toLowerCase()}`
+function buildKey({ chainId, tokenAddress, account, spender, amount }: PermitCacheKeyParams) {
+ const base = `${chainId}-${tokenAddress.toLowerCase()}-${spender.toLowerCase()}-${amount ? amount.toString() : ''}`
return account ? `${base}-${account.toLowerCase()}` : base
}
diff --git a/apps/cowswap-frontend/src/modules/permit/types.ts b/apps/cowswap-frontend/src/modules/permit/types.ts
index c1b9cd215d..2a42f1c941 100644
--- a/apps/cowswap-frontend/src/modules/permit/types.ts
+++ b/apps/cowswap-frontend/src/modules/permit/types.ts
@@ -14,7 +14,7 @@ export type AddPermitTokenParams = {
permitInfo: PermitInfo
}
-export type GeneratePermitHookParams = Pick & {
+export type GeneratePermitHookParams = Pick & {
customSpender?: string
}
@@ -33,6 +33,7 @@ export type PermitCache = Record
export type CachedPermitData = {
hookData: PermitHookData
nonce: number | undefined
+ amount: string | undefined
}
export type PermitCacheKeyParams = {
@@ -41,6 +42,7 @@ export type PermitCacheKeyParams = {
account: string | undefined
nonce: number | undefined
spender: string
+ amount: bigint | undefined
}
export type StorePermitCacheParams = PermitCacheKeyParams & { hookData: PermitHookData }
diff --git a/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts b/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts
index 1a13cb033c..1be97e5ee9 100644
--- a/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts
+++ b/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts
@@ -22,7 +22,7 @@ import { HandlePermitParams } from '../types'
* Returns the updated appData
*/
export async function handlePermit(params: HandlePermitParams): Promise {
- const { permitInfo, inputToken, account, appData, typedHooks, generatePermitHook } = params
+ const { permitInfo, inputToken, account, appData, typedHooks, generatePermitHook, amount } = params
if (isSupportedPermitInfo(permitInfo) && !getIsNativeToken(inputToken)) {
// permitInfo will only be set if there's NOT enough allowance
@@ -31,6 +31,7 @@ export async function handlePermit(params: HandlePermitParams): Promise
{(restContent) => (
<>
diff --git a/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx
index a8b9760bd0..150a7eea22 100644
--- a/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx
@@ -9,6 +9,7 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { useSingleActivityDescriptor } from 'legacy/hooks/useRecentActivity'
import { WrapUnwrapCallback } from 'legacy/hooks/useWrapCallback'
+import { usePartialApprove } from 'legacy/state/user/hooks'
import { getDerivedEthFlowState } from 'modules/swap/containers/EthFlow/utils/getDerivedEthFlowState'
import { EthFlowModalContent } from 'modules/swap/pure/EthFlow/EthFlowModalContent'
@@ -25,7 +26,6 @@ import { useEthFlowActions } from './hooks/useEthFlowActions'
import useRemainingNativeTxsAndCosts from './hooks/useRemainingNativeTxsAndCosts'
import { useSetupEthFlow } from './hooks/useSetupEthFlow'
-
export interface EthFlowProps {
nativeInput?: CurrencyAmount
hasEnoughWrappedBalanceForSwap: boolean
@@ -45,10 +45,12 @@ export function EthFlowModal({
const native = useNativeCurrency()
const wrapped = useWrappedToken()
const { state: approvalState } = useApproveState(nativeInput || null)
+ const [isPartialApprove] = usePartialApprove()
const ethFlowContext = useAtomValue(ethFlowContextAtom)
const approveCallback = useTradeApproveCallback(
- (nativeInput && currencyAmountToTokenAmount(nativeInput)) || undefined
+ (nativeInput && currencyAmountToTokenAmount(nativeInput)) || undefined,
+ isPartialApprove,
)
const ethFlowActions = useEthFlowActions({
wrap: wrapCallback,
@@ -86,7 +88,7 @@ export function EthFlowModal({
approvalState,
approveActivity,
wrapActivity,
- onDismiss
+ onDismiss,
})
return (
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 a49a039628..d4fff58b3d 100644
--- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx
@@ -14,7 +14,12 @@ import { NetworkAlert } from 'legacy/components/NetworkAlert/NetworkAlert'
import { useModalIsOpen } from 'legacy/state/application/hooks'
import { ApplicationModal } from 'legacy/state/application/reducer'
import { Field } from 'legacy/state/types'
-import { useHooksEnabledManager, useRecipientToggleManager, useUserTransactionTTL } from 'legacy/state/user/hooks'
+import {
+ useHooksEnabledManager,
+ usePartialApprove,
+ useRecipientToggleManager,
+ useUserTransactionTTL,
+} from 'legacy/state/user/hooks'
import { useCurrencyAmountBalanceCombined } from 'modules/combinedBalances'
import { useInjectedWidgetParams } from 'modules/injectedWidget'
@@ -85,6 +90,7 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) {
const recipientToggleState = useRecipientToggleManager()
const hooksEnabledState = useHooksEnabledManager()
const deadlineState = useUserTransactionTTL()
+ const partialApproveState = usePartialApprove()
const isHookTradeType = useIsHooksTradeType()
const isTradePriceUpdating = useTradePricesUpdate()
@@ -238,6 +244,8 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) {
recipientToggleState={recipientToggleState}
hooksEnabledState={hooksEnabledState}
deadlineState={deadlineState}
+ // Partial approve is disabled for Hooks store
+ partialApproveState={isHookTradeType ? undefined : partialApproveState}
/>
),
diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts
index f550702e03..275a2798c3 100644
--- a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts
+++ b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts
@@ -1,7 +1,7 @@
import { useCallback } from 'react'
import { Field } from 'legacy/state/types'
-import { useUserTransactionTTL } from 'legacy/state/user/hooks'
+import { usePartialApprove, useUserTransactionTTL } from 'legacy/state/user/hooks'
import { TradeWidgetActions, useTradePriceImpact } from 'modules/trade'
import { logTradeFlow } from 'modules/trade/utils/logger'
@@ -25,7 +25,11 @@ export function useHandleSwapOrEthFlow(actions: TradeWidgetActions) {
const { onUserInput, onChangeRecipient } = actions
const [deadline] = useUserTransactionTTL()
- const { callback: handleSwap, contextIsReady } = useHandleSwap(useSafeMemoObject({ deadline }), actions)
+ const [isPartialApprove] = usePartialApprove()
+ const { callback: handleSwap, contextIsReady } = useHandleSwap(
+ useSafeMemoObject({ deadline, isPartialApprove }),
+ actions,
+ )
const callback = useCallback(async () => {
if (!swapFlowContext) return
diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts
index 91fe6ec473..19906be3f4 100644
--- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts
+++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts
@@ -14,6 +14,7 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { useToggleWalletModal } from 'legacy/state/application/hooks'
import { useGetQuoteAndStatus, useIsBestQuoteLoading } from 'legacy/state/price/hooks'
import { Field } from 'legacy/state/types'
+import { usePartialApprove } from 'legacy/state/user/hooks'
import { useCurrencyAmountBalanceCombined } from 'modules/combinedBalances'
import { useInjectedWidgetParams } from 'modules/injectedWidget'
@@ -63,6 +64,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
const tradeConfirmActions = useTradeConfirmActions()
const { standaloneMode } = useInjectedWidgetParams()
const isHooksStore = useIsHooksTradeType()
+ const [isPartialApprove] = usePartialApprove()
const currencyIn = currencies[Field.INPUT]
const currencyOut = currencies[Field.OUTPUT]
@@ -143,6 +145,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
onCurrencySelection,
widgetStandaloneMode: standaloneMode,
quoteDeadlineParams,
+ isPartialApprove,
}),
[
swapButtonState,
@@ -159,6 +162,7 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
onCurrencySelection,
standaloneMode,
quoteDeadlineParams,
+ isPartialApprove,
],
)
}
diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts
index afed5d89d7..bc70dfb18f 100644
--- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts
+++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts
@@ -1,4 +1,4 @@
-import { useUserTransactionTTL } from 'legacy/state/user/hooks'
+import { usePartialApprove, useUserTransactionTTL } from 'legacy/state/user/hooks'
import { useTradeFlowContext } from 'modules/tradeFlow'
@@ -6,5 +6,7 @@ import { useSafeMemoObject } from 'common/hooks/useSafeMemo'
export function useSwapFlowContext() {
const [deadline] = useUserTransactionTTL()
- return useTradeFlowContext(useSafeMemoObject({ deadline }))
+ const [isPartialApprove] = usePartialApprove()
+
+ return useTradeFlowContext(useSafeMemoObject({ deadline, isPartialApprove }))
}
diff --git a/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts b/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts
index b5648ccb99..e599b3e3f3 100644
--- a/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts
+++ b/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts
@@ -68,9 +68,7 @@ export const ethFlowConfigs: {
[EthFlowState.ApproveNeeded]: ({ wrappedSymbol }) => ({
title: `Approve ${wrappedSymbol}`,
buttonText: `Approve ${wrappedSymbol}`,
- descriptions: [
- `It is required to do a one-time approval of ${wrappedSymbol} via an on-chain ERC20 Approve transaction.`,
- ],
+ descriptions: [`It is required to do an approval of ${wrappedSymbol} via an on-chain ERC20 Approve transaction.`],
}),
[EthFlowState.SwapReady]: ({ wrappedSymbol }) => ({
title: `Continue swap with ${wrappedSymbol}`,
diff --git a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx
index 110dd701f1..c64663d107 100644
--- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx
@@ -26,6 +26,7 @@ const swapButtonsContext: SwapButtonsContext = {
openSwapConfirm: () => void 0,
toggleWalletModal: () => void 0,
hasEnoughWrappedBalanceForSwap: true,
+ isPartialApprove: false,
quoteDeadlineParams: {
validFor: 0,
quoteValidTo: 0,
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 28c5618757..a0dd7e1e41 100644
--- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx
@@ -40,6 +40,7 @@ export interface SwapButtonsContext {
onCurrencySelection: (field: Field, currency: Currency) => void
widgetStandaloneMode?: boolean
quoteDeadlineParams: QuoteDeadlineParams
+ isPartialApprove: boolean
}
const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext) => JSX.Element } = {
@@ -126,7 +127,7 @@ const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext
{props.inputAmount && (
-
+ Swap
diff --git a/apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx b/apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx
new file mode 100644
index 0000000000..caddfb37b5
--- /dev/null
+++ b/apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx
@@ -0,0 +1,31 @@
+import ICON_TOKENS from '@cowprotocol/assets/svg/tokens.svg'
+import { Command } from '@cowprotocol/types'
+import { BannerOrientation, ClosableBanner, InlineBanner, UnderlinedLinkStyledButton } from '@cowprotocol/ui'
+
+import styled from 'styled-components/macro'
+
+const BANNER_STORAGE_KEY = 'partialPermitBannerKey:v0'
+
+type PartialApprovalBannerProps = {
+ openSettings: Command
+}
+
+export function PartialApprovalBanner({ openSettings }: PartialApprovalBannerProps) {
+ return ClosableBanner(BANNER_STORAGE_KEY, (onClose) => (
+
+
+ NEW: You can now choose to do minimal token approvals in the settings.
+
+
+ ))
+}
+
+const Link = styled(UnderlinedLinkStyledButton)`
+ padding: 0;
+`
diff --git a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx
index 979c49913d..6edebb05e2 100644
--- a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx
+++ b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx
@@ -9,6 +9,7 @@ import {
CustomRecipientWarningBanner,
LongLoadText,
} from '@cowprotocol/ui'
+import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Trans } from '@lingui/macro'
import ms from 'ms.macro'
@@ -49,6 +50,9 @@ export interface TradeConfirmationProps {
recipient?: string | null
buttonText?: React.ReactNode
children?: (restContent: ReactElement) => ReactElement
+ slippageAdjustedSellAmount?: CurrencyAmount
+ isPartialApprove?: boolean
+ displayHookDetails?: boolean
}
export function TradeConfirmation(props: TradeConfirmationProps) {
@@ -76,6 +80,9 @@ export function TradeConfirmation(props: TradeConfirmationProps) {
recipient,
isPriceStatic,
appData,
+ isPartialApprove,
+ slippageAdjustedSellAmount,
+ displayHookDetails,
} = frozenProps || props
/**
@@ -126,15 +133,20 @@ export function TradeConfirmation(props: TradeConfirmationProps) {
onConfirm()
}
- const hookDetailsElement = (
+ const hookDetailsElement = displayHookDetails ? (
<>
{appData && (
-
+
{(hookChildren) => hookChildren}
)}
>
- )
+ ) : null
return (
e.key === 'Escape' && onDismiss()}>
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts
index fafa6e7594..945cf69e0f 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts
@@ -28,9 +28,10 @@ import { TradeFlowContext } from '../types/TradeFlowContext'
export interface TradeFlowParams {
deadline: number
+ isPartialApprove?: boolean
}
-export function useTradeFlowContext({ deadline }: TradeFlowParams): TradeFlowContext | null {
+export function useTradeFlowContext({ deadline, isPartialApprove }: TradeFlowParams): TradeFlowContext | null {
const { chainId, account } = useWalletInfo()
const provider = useWalletProvider()
const { allowsOffchainSigning } = useWalletDetails()
@@ -49,7 +50,7 @@ export function useTradeFlowContext({ deadline }: TradeFlowParams): TradeFlowCon
const networkFee = receiveAmountInfo?.costs.networkFee.amountInSellCurrency
const permitInfo = usePermitInfo(sellCurrency, tradeType)
- const generatePermitHook = useGeneratePermitHook()
+ const generatePermitHook = useGeneratePermitHook(isPartialApprove)
const getCachedPermit = useGetCachedPermit()
const closeModals = useCloseModals()
const dispatch = useDispatch()
@@ -122,6 +123,7 @@ export function useTradeFlowContext({ deadline }: TradeFlowParams): TradeFlowCon
deadline,
orderKind,
uiOrderType,
+ isPartialApprove,
]
: null,
([
@@ -152,6 +154,7 @@ export function useTradeFlowContext({ deadline }: TradeFlowParams): TradeFlowCon
deadline,
orderKind,
uiOrderType,
+ isPartialApprove,
]) => {
return {
context: {
@@ -162,6 +165,7 @@ export function useTradeFlowContext({ deadline }: TradeFlowParams): TradeFlowCon
},
flags: {
allowsOffchainSigning,
+ isPartialApprove,
},
callbacks: {
closeModals,
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
index cae474c566..eafae28aff 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
@@ -33,7 +33,15 @@ export async function safeBundleApprovalFlow(
return false
}
- const { context, callbacks, orderParams, swapFlowAnalyticsContext, tradeConfirmActions, typedHooks } = tradeContext
+ const {
+ context,
+ callbacks,
+ orderParams,
+ swapFlowAnalyticsContext,
+ tradeConfirmActions,
+ typedHooks,
+ flags: { isPartialApprove },
+ } = tradeContext
const { spender, settlementContract, safeAppsSdk, erc20Contract } = safeBundleContext
@@ -52,6 +60,7 @@ export async function safeBundleApprovalFlow(
erc20Contract,
spender,
amountToApprove: context.inputAmount,
+ isPartialApprove,
})
orderParams.appData = await removePermitHookFromAppData(orderParams.appData, typedHooks)
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
index 0247b09d84..c9b09bd002 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
@@ -35,7 +35,14 @@ export async function safeBundleEthFlow(
return false
}
- const { context, callbacks, swapFlowAnalyticsContext, tradeConfirmActions, typedHooks } = tradeContext
+ const {
+ context,
+ callbacks,
+ swapFlowAnalyticsContext,
+ tradeConfirmActions,
+ typedHooks,
+ flags: { isPartialApprove },
+ } = tradeContext
const { spender, settlementContract, safeAppsSdk, needsApproval, wrappedNativeContract } = safeBundleContext
@@ -73,6 +80,7 @@ export async function safeBundleEthFlow(
erc20Contract: wrappedNativeContract as unknown as Erc20,
spender,
amountToApprove: inputAmount,
+ isPartialApprove,
})
txs.push({
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts
index d26e22b9d4..1a8589d56c 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts
@@ -59,6 +59,7 @@ export async function swapFlow(
inputToken: inputCurrency,
permitInfo,
generatePermitHook,
+ amount: BigInt(inputAmount.quotient.toString()),
})
if (callDataContainsPermitSigner(orderParams.appData.fullAppData)) {
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts b/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts
index 16f6dc07b8..c129b37df6 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts
@@ -26,6 +26,7 @@ export interface TradeFlowContext {
}
flags: {
allowsOffchainSigning: boolean
+ isPartialApprove?: boolean
}
callbacks: {
closeModals: Command
diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx
index fc7273935d..7090446fdb 100644
--- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx
+++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx
@@ -27,12 +27,20 @@ interface SettingsTabProps {
className?: string
recipientToggleState: StatefulValue
hooksEnabledState?: StatefulValue
+ partialApproveState?: StatefulValue
deadlineState: StatefulValue
}
-export function SettingsTab({ className, recipientToggleState, hooksEnabledState, deadlineState }: SettingsTabProps) {
+export function SettingsTab({
+ className,
+ recipientToggleState,
+ hooksEnabledState,
+ deadlineState,
+ partialApproveState,
+}: SettingsTabProps) {
const menuButtonRef = useRef(null)
+ const [isPartialApprove, setPartialApprove] = partialApproveState || [null, null]
const [recipientToggleVisible, toggleRecipientVisibilityAux] = recipientToggleState
const toggleRecipientVisibility = useCallback(
(value?: boolean) => {
@@ -104,7 +112,7 @@ export function SettingsTab({ className, recipientToggleState, hooksEnabledState
Experimental:
{' '}
- Add DeFI interactions before and after your trade
+ Add DeFI interactions before and after your trade.
}
/>
@@ -112,6 +120,35 @@ export function SettingsTab({ className, recipientToggleState, hooksEnabledState
)}
+
+ {isPartialApprove !== null && setPartialApprove && (
+
+
+
+ Minimal Approvals
+
+
+ By default, token approvals & permits are for an unlimited amount, which ensures you don't pay extra for subsequent trades.
+
+
+ When this setting is enabled, approvals & permits will be for the minimum amount instead of unlimited.
+ This incurs additional costs on every trade.
+
+
+ Existing approvals must be revoked manually before you can re-approve.
+
+ }
+ />
+
+ setPartialApprove(!isPartialApprove)}
+ />
+
+ )}
diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx
index 741bc17006..4a9c0ec9d9 100644
--- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx
+++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx
@@ -1,4 +1,3 @@
-import { useSetAtom } from 'jotai'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { Command } from '@cowprotocol/types'
@@ -10,7 +9,7 @@ import styled from 'styled-components/macro'
import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips'
-import { settingsTabStateAtom } from '../../../state/settingsTabState'
+import { useOpenSettingsTab } from '../../../state/settingsTabState'
import { RowStyleProps, StyledInfoIcon, StyledRowBetween, TextWrapper, TransactionText } from '../styled'
const DefaultSlippage = styled.span`
@@ -65,9 +64,7 @@ export function RowSlippageContent(props: RowSlippageContentProps) {
isSmartSlippageLoading,
} = props
- const setSettingTabState = useSetAtom(settingsTabStateAtom)
-
- const openSettings = () => setSettingTabState({ open: true })
+ const openSettings = useOpenSettingsTab()
const tooltipContent =
slippageTooltip ||
diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts
index 659fb6321f..61b3e2561a 100644
--- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts
+++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts
@@ -1,3 +1,9 @@
-import { atom } from 'jotai'
+import { atom, useSetAtom } from 'jotai'
export const settingsTabStateAtom = atom({ open: false })
+
+export function useOpenSettingsTab() {
+ const setSettingTabState = useSetAtom(settingsTabStateAtom)
+
+ return () => setSettingTabState({ open: true })
+}
diff --git a/apps/cowswap-frontend/src/pages/Swap/index.tsx b/apps/cowswap-frontend/src/pages/Swap/index.tsx
index d672663c10..c90c727003 100644
--- a/apps/cowswap-frontend/src/pages/Swap/index.tsx
+++ b/apps/cowswap-frontend/src/pages/Swap/index.tsx
@@ -5,13 +5,16 @@ import { useWalletInfo } from '@cowprotocol/wallet'
import { Navigate, useLocation, useParams } from 'react-router-dom'
import { SwapUpdaters, SwapWidget } from 'modules/swap'
+import { PartialApprovalBanner } from 'modules/swap/pure/banners/PartialApprovalBanner'
import { getDefaultTradeRawState } from 'modules/trade/types/TradeRawState'
import { parameterizeTradeRoute } from 'modules/trade/utils/parameterizeTradeRoute'
+import { useOpenSettingsTab } from 'modules/tradeWidgetAddons/state/settingsTabState'
import { Routes } from 'common/constants/routes'
export function SwapPage() {
const params = useParams()
+ const openSettings = useOpenSettingsTab()
if (!params.chainId) {
return
@@ -20,7 +23,7 @@ export function SwapPage() {
return (
<>
-
+ } />
>
)
}
diff --git a/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts
index d579add099..ab4c3d7c8f 100644
--- a/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts
+++ b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts
@@ -1,11 +1,10 @@
-import { Erc20__factory } from '@cowprotocol/abis'
import type { LatestAppDataDocVersion } from '@cowprotocol/app-data'
import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk'
import { BigNumber } from '@ethersproject/bignumber'
-import { ParsedOrder } from './parseOrder'
+import { parsePermitData } from 'common/utils/parsePermitData'
-const erc20Interface = Erc20__factory.createInterface()
+import { ParsedOrder } from './parseOrder'
export function getOrderPermitAmount(chainId: SupportedChainId, order: ParsedOrder): BigNumber | null {
if (!order.fullAppData) return null
@@ -20,7 +19,7 @@ export function getOrderPermitAmount(chainId: SupportedChainId, order: ParsedOrd
const permitData = preHooks
.map((hook) => {
try {
- return erc20Interface.decodeFunctionData('permit', hook.callData)
+ return parsePermitData(hook.callData)
} catch {
return null
}
diff --git a/libs/hook-dapp-lib/src/hookDappsRegistry.json b/libs/hook-dapp-lib/src/hookDappsRegistry.json
index 4ce8c5e3e2..7f973f7768 100644
--- a/libs/hook-dapp-lib/src/hookDappsRegistry.json
+++ b/libs/hook-dapp-lib/src/hookDappsRegistry.json
@@ -24,7 +24,7 @@
"PERMIT_TOKEN": {
"type": "INTERNAL",
"name": "Permit a token",
- "descriptionShort": "Infinite permit an address to spend one token on your behalf.",
+ "descriptionShort": "Permit an address to spend one token on your behalf.",
"description": "This hook allows you to permit an address to spend your tokens on your behalf. This is useful for allowing a smart contract to spend your tokens without needing to approve each transaction.",
"image": "https://raw.githubusercontent.com/cowprotocol/cowswap/refs/heads/develop/apps/cowswap-frontend/src/modules/hooksStore/dapps/PermitHookApp/icon.png",
"version": "v0.1.0",
diff --git a/libs/permit-utils/src/lib/generatePermitHook.ts b/libs/permit-utils/src/lib/generatePermitHook.ts
index e9f2e32675..f121a67d83 100644
--- a/libs/permit-utils/src/lib/generatePermitHook.ts
+++ b/libs/permit-utils/src/lib/generatePermitHook.ts
@@ -37,7 +37,17 @@ export async function generatePermitHook(params: PermitHookParams): Promise {
- const { inputToken, spender, chainId, permitInfo, provider, account, eip2162Utils, nonce: preFetchedNonce } = params
+ const {
+ inputToken,
+ spender,
+ chainId,
+ permitInfo,
+ provider,
+ account,
+ eip2162Utils,
+ nonce: preFetchedNonce,
+ amount,
+ } = params
const tokenAddress = inputToken.address
// TODO: remove the need for `name` from input token. Should come from permitInfo instead
@@ -58,7 +68,7 @@ async function generatePermitHookRaw(params: PermitHookParams): Promise
Date: Thu, 9 Jan 2025 10:44:00 +0000
Subject: [PATCH 15/15] Revert "feat(swap): partial approve (#5256)"
This reverts commit f080ffdb098612e729f3a3f829410ce78697979f.
---
.../containers/OrderHooksDetails/index.tsx | 41 +---------
.../TradeApprove/TradeApproveButton.tsx | 5 +-
.../TradeApprove/useTradeApproveCallback.ts | 9 +--
.../src/common/hooks/useApproveCallback.ts | 15 ++--
.../src/common/pure/ApproveButton/index.tsx | 1 +
.../common/pure/OrderProgressBarV2/index.tsx | 15 +---
.../common/pure/OrderProgressBarV2/styled.ts | 12 ++-
.../src/common/utils/parsePermitData.ts | 15 ----
.../src/legacy/state/user/hooks.tsx | 19 -----
.../src/legacy/state/user/reducer.ts | 7 +-
.../src/lib/hooks/useApproval.ts | 74 ++++++++++++++++++-
.../operations/bundle/buildApproveTx.ts | 5 +-
.../permit/hooks/useGeneratePermitHook.ts | 10 +--
.../permit/hooks/useGetCachedPermit.ts | 5 +-
.../modules/permit/state/permitCacheAtom.ts | 31 ++------
.../src/modules/permit/types.ts | 4 +-
.../src/modules/permit/utils/handlePermit.ts | 3 +-
.../ConfirmSwapModalSetup/index.tsx | 8 +-
.../modules/swap/containers/EthFlow/index.tsx | 8 +-
.../swap/containers/SwapWidget/index.tsx | 10 +--
.../swap/hooks/useHandleSwapOrEthFlow.ts | 8 +-
.../swap/hooks/useSwapButtonContext.ts | 4 -
.../modules/swap/hooks/useSwapFlowContext.ts | 6 +-
.../EthFlow/EthFlowModalContent/configs.ts | 4 +-
.../swap/pure/SwapButtons/index.cosmos.tsx | 1 -
.../modules/swap/pure/SwapButtons/index.tsx | 3 +-
.../pure/banners/PartialApprovalBanner.tsx | 31 --------
.../trade/pure/TradeConfirmation/index.tsx | 18 +----
.../tradeFlow/hooks/useTradeFlowContext.ts | 8 +-
.../safeBundleFlow/safeBundleApprovalFlow.ts | 11 +--
.../safeBundleFlow/safeBundleEthFlow.ts | 10 +--
.../tradeFlow/services/swapFlow/index.ts | 1 -
.../tradeFlow/types/TradeFlowContext.ts | 1 -
.../containers/SettingsTab/index.tsx | 41 +---------
.../pure/Row/RowSlippageContent/index.tsx | 7 +-
.../state/settingsTabState.ts | 8 +-
.../cowswap-frontend/src/pages/Swap/index.tsx | 5 +-
.../utils/orderUtils/getOrderPermitAmount.ts | 7 +-
libs/hook-dapp-lib/src/hookDappsRegistry.json | 2 +-
.../src/lib/generatePermitHook.ts | 14 +---
libs/permit-utils/src/types.ts | 1 -
libs/ui/src/containers/InlineBanner/index.tsx | 2 +-
libs/ui/src/pure/LinkStyledButton/index.tsx | 9 ---
43 files changed, 149 insertions(+), 350 deletions(-)
delete mode 100644 apps/cowswap-frontend/src/common/utils/parsePermitData.ts
delete mode 100644 apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx
diff --git a/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx b/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx
index 67e607b666..3c8a623983 100644
--- a/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx
+++ b/apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx
@@ -3,8 +3,6 @@ import { ReactElement, useEffect, useMemo, useState } from 'react'
import { latest } from '@cowprotocol/app-data'
import { CowHookDetails, HookToDappMatch, matchHooksToDappsRegistry } from '@cowprotocol/hook-dapp-lib'
import { InfoTooltip } from '@cowprotocol/ui'
-import { useWalletInfo } from '@cowprotocol/wallet'
-import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { ChevronDown, ChevronUp } from 'react-feather'
@@ -16,27 +14,15 @@ import { HookItem } from './HookItem'
import * as styledEl from './styled'
import { CircleCount } from './styled'
-import { parsePermitData } from '../../utils/parsePermitData'
-
interface OrderHooksDetailsProps {
appData: string | AppDataInfo
children: (content: ReactElement) => ReactElement
margin?: string
isTradeConfirmation?: boolean
- slippageAdjustedSellAmount?: CurrencyAmount
- isPartialApprove?: boolean
}
-export function OrderHooksDetails({
- appData,
- children,
- margin,
- isTradeConfirmation,
- slippageAdjustedSellAmount,
- isPartialApprove,
-}: OrderHooksDetailsProps) {
+export function OrderHooksDetails({ appData, children, margin, isTradeConfirmation }: OrderHooksDetailsProps) {
const [isOpen, setOpen] = useState(false)
- const { account } = useWalletInfo()
const appDataDoc = useMemo(() => {
return typeof appData === 'string' ? decodeAppData(appData) : appData.doc
}, [appData])
@@ -55,32 +41,9 @@ export function OrderHooksDetails({
const metadata = appDataDoc.metadata as latest.Metadata
- /**
- * AppData might include a hook with account agnostic permit which is used to fetch a quote.
- * This hook should be ignored.
- * Moreover, any hook with a permit which has owner !== current account will be excluded.
- * We also remove the permit from appData before order signing (see filterPermitSignerPermit).
- */
- const preHooks = account
- ? metadata.hooks?.pre?.filter((hook) => {
- try {
- const permitHookData = parsePermitData(hook.callData)
- const isOwnerMatched = permitHookData.owner.toLowerCase() === account.toLowerCase()
-
- // If the hook is a partial approve, we need to check if the value is equal to the slippageAdjustedSellAmount
- // Because there might be a hook with an "infinite" permit from other widget
- return isPartialApprove && slippageAdjustedSellAmount
- ? isOwnerMatched && permitHookData.value.eq(slippageAdjustedSellAmount.quotient.toString())
- : isOwnerMatched
- } catch {
- return true
- }
- })
- : metadata.hooks?.pre
-
const hasSomeFailedSimulation = isTradeConfirmation && Object.values(data || {}).some((hook) => !hook.status)
- const preHooksToDapp = matchHooksToDappsRegistry(preHooks || [], preCustomHookDapps)
+ const preHooksToDapp = matchHooksToDappsRegistry(metadata.hooks?.pre || [], preCustomHookDapps)
const postHooksToDapp = matchHooksToDappsRegistry(metadata.hooks?.post || [], postCustomHookDapps)
if (!preHooksToDapp.length && !postHooksToDapp.length) return null
diff --git a/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx b/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx
index 3e31105e1d..68e68ef2b3 100644
--- a/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx
+++ b/apps/cowswap-frontend/src/common/containers/TradeApprove/TradeApproveButton.tsx
@@ -14,16 +14,15 @@ export interface TradeApproveButtonProps {
amountToApprove: CurrencyAmount
children?: React.ReactNode
isDisabled?: boolean
- isPartialApprove?: boolean
}
export function TradeApproveButton(props: TradeApproveButtonProps) {
- const { amountToApprove, children, isDisabled, isPartialApprove } = props
+ const { amountToApprove, children, isDisabled } = props
const currency = amountToApprove.currency
const { state: approvalState } = useApproveState(amountToApprove)
- const tradeApproveCallback = useTradeApproveCallback(amountToApprove, isPartialApprove)
+ const tradeApproveCallback = useTradeApproveCallback(amountToApprove)
const shouldZeroApprove = useShouldZeroApprove(amountToApprove)
const zeroApprove = useZeroApprove(amountToApprove.currency)
diff --git a/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts b/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts
index 552a32d42b..3e5136344c 100644
--- a/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts
+++ b/apps/cowswap-frontend/src/common/containers/TradeApprove/useTradeApproveCallback.ts
@@ -19,16 +19,13 @@ export interface TradeApproveCallback {
(params?: TradeApproveCallbackParams): Promise
}
-export function useTradeApproveCallback(
- amountToApprove?: CurrencyAmount,
- isPartialApprove?: boolean,
-): TradeApproveCallback {
+export function useTradeApproveCallback(amountToApprove?: CurrencyAmount): TradeApproveCallback {
const updateTradeApproveState = useUpdateTradeApproveState()
const spender = useTradeSpenderAddress()
const currency = amountToApprove?.currency
const symbol = currency?.symbol
- const approveCallback = useApproveCallback(amountToApprove, spender, isPartialApprove)
+ const approveCallback = useApproveCallback(amountToApprove, spender)
return useCallback(
async ({ useModals = true }: TradeApproveCallbackParams = { useModals: true }) => {
@@ -61,6 +58,6 @@ export function useTradeApproveCallback(
return undefined
})
},
- [symbol, approveCallback, updateTradeApproveState, currency],
+ [symbol, approveCallback, updateTradeApproveState, currency]
)
}
diff --git a/apps/cowswap-frontend/src/common/hooks/useApproveCallback.ts b/apps/cowswap-frontend/src/common/hooks/useApproveCallback.ts
index 8a07a9ef95..6fce1a2444 100644
--- a/apps/cowswap-frontend/src/common/hooks/useApproveCallback.ts
+++ b/apps/cowswap-frontend/src/common/hooks/useApproveCallback.ts
@@ -18,18 +18,14 @@ export async function estimateApprove(
tokenContract: Erc20,
spender: string,
amountToApprove: CurrencyAmount,
- isPartialApprove?: boolean,
): Promise<{
approveAmount: BigNumber | string
gasLimit: BigNumber
}> {
- const approveAmount =
- isPartialApprove && amountToApprove ? BigNumber.from(amountToApprove.quotient.toString()) : MaxUint256
-
try {
return {
- approveAmount,
- gasLimit: await tokenContract.estimateGas.approve(spender, approveAmount),
+ approveAmount: MaxUint256,
+ gasLimit: await tokenContract.estimateGas.approve(spender, MaxUint256),
}
} catch {
// Fallback: Attempt to set an approval for the maximum wallet balance (instead of the MaxUint256).
@@ -49,7 +45,7 @@ export async function estimateApprove(
)
return {
- approveAmount,
+ approveAmount: MaxUint256,
gasLimit: GAS_LIMIT_DEFAULT,
}
}
@@ -59,7 +55,6 @@ export async function estimateApprove(
export function useApproveCallback(
amountToApprove?: CurrencyAmount,
spender?: string,
- isPartialApprove?: boolean,
): (summary?: string) => Promise {
const { chainId } = useWalletInfo()
const currency = amountToApprove?.currency
@@ -73,7 +68,7 @@ export function useApproveCallback(
return
}
- const estimation = await estimateApprove(tokenContract, spender, amountToApprove, isPartialApprove)
+ const estimation = await estimateApprove(tokenContract, spender, amountToApprove)
return tokenContract
.approve(spender, estimation.approveAmount, {
gasLimit: calculateGasMargin(estimation.gasLimit),
@@ -86,5 +81,5 @@ export function useApproveCallback(
})
return response
})
- }, [chainId, token, tokenContract, amountToApprove, spender, addTransaction, isPartialApprove])
+ }, [chainId, token, tokenContract, amountToApprove, spender, addTransaction])
}
diff --git a/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx b/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx
index 68bda945b0..13273df82e 100644
--- a/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx
+++ b/apps/cowswap-frontend/src/common/pure/ApproveButton/index.tsx
@@ -50,6 +50,7 @@ export function ApproveButton(props: ApproveButtonProps) {
content={
You must give the CoW Protocol smart contracts permission to use your .
+ If you approve the default amount, you will only have to do this once per token.
}
>
diff --git a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx
index 605eb3faf3..81d1eaafc2 100644
--- a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx
+++ b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/index.tsx
@@ -22,16 +22,7 @@ import { ExplorerDataType, getExplorerLink, getRandomInt, isSellOrder, shortenAd
import { OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk'
import { TokenLogo } from '@cowprotocol/tokens'
import { Command } from '@cowprotocol/types'
-import {
- Confetti,
- ExternalLink,
- InfoTooltip,
- ProductLogo,
- ProductVariant,
- TokenAmount,
- UI,
- UnderlinedLinkStyledButton,
-} from '@cowprotocol/ui'
+import { Confetti, ExternalLink, InfoTooltip, ProductLogo, ProductVariant, TokenAmount, UI } from '@cowprotocol/ui'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { AnimatePresence, motion } from 'framer-motion'
@@ -1132,14 +1123,14 @@ function ExpiredStep(props: OrderProgressBarV2Props) {
The good news
Unlike on other exchanges, you won't be charged for this! Feel free to{' '}
- {
props.navigateToNewOrder?.()
trackNewOrderClick()
}}
>
place a new order
- {' '}
+ {' '}
without worry.
diff --git a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts
index cd1a6a3de0..198fe338ff 100644
--- a/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts
+++ b/apps/cowswap-frontend/src/common/pure/OrderProgressBarV2/styled.ts
@@ -1,6 +1,6 @@
import IMAGE_STAR_SHINE from '@cowprotocol/assets/cow-swap/star-shine.svg'
import { SingleLetterLogoWrapper } from '@cowprotocol/tokens'
-import { ButtonPrimary, Font, Media, UI } from '@cowprotocol/ui'
+import { ButtonPrimary, Font, LinkStyledButton, Media, UI } from '@cowprotocol/ui'
import styled, { css, keyframes } from 'styled-components/macro'
@@ -66,7 +66,6 @@ export const StepsContainer = styled.div<{ $height: number; $minHeight?: string;
padding: 0;
// implement a gradient to hide the bottom of the steps container using white to opacity white using pseudo element
-
&::after {
content: ${({ bottomGradient }) => (bottomGradient ? '""' : 'none')};
position: absolute;
@@ -144,6 +143,15 @@ export const CancelButton = styled(CancelButtonOriginal)`
}
`
+export const Button = styled(LinkStyledButton)`
+ font-size: 14px;
+ text-decoration: underline;
+
+ &:hover {
+ text-decoration: none;
+ }
+`
+
export const ProgressImageWrapper = styled.div<{ bgColor?: string; padding?: string; height?: string; gap?: string }>`
width: 100%;
height: ${({ height }) => height || '246px'};
diff --git a/apps/cowswap-frontend/src/common/utils/parsePermitData.ts b/apps/cowswap-frontend/src/common/utils/parsePermitData.ts
deleted file mode 100644
index 6ba16f7c1a..0000000000
--- a/apps/cowswap-frontend/src/common/utils/parsePermitData.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Erc20__factory } from '@cowprotocol/abis'
-import type { BigNumber } from '@ethersproject/bignumber'
-
-const erc20Interface = Erc20__factory.createInterface()
-
-export interface PermitParameters {
- owner: string
- spender: string
- value: BigNumber
- deadline: BigNumber
-}
-
-export function parsePermitData(callData: string): PermitParameters {
- return erc20Interface.decodeFunctionData('permit', callData) as unknown as PermitParameters
-}
diff --git a/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx b/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx
index 24aeb1e878..2f86b41a32 100644
--- a/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx
+++ b/apps/cowswap-frontend/src/legacy/state/user/hooks.tsx
@@ -8,11 +8,8 @@ import { Currency } from '@uniswap/sdk-core'
import { shallowEqual } from 'react-redux'
-import { useIsHooksTradeType } from 'modules/trade/hooks/useIsHooksTradeType'
-
import {
updateHooksEnabled,
- updatePartialApprove,
updateRecipientToggleVisible,
updateUserDarkMode,
updateUserDeadline,
@@ -121,22 +118,6 @@ export function useUserTransactionTTL(): [number, (slippage: number) => void] {
return [deadline, setUserDeadline]
}
-export function usePartialApprove(): [boolean, (value: boolean) => void] {
- const dispatch = useAppDispatch()
- const isHookTradeType = useIsHooksTradeType()
- const partialApprove = useAppSelector((state) => state.user.partialApprove)
-
- const setPartialApprove = useCallback(
- (partialApprove: boolean) => {
- dispatch(updatePartialApprove({ partialApprove }))
- },
- [dispatch],
- )
-
- // Partial approve is disabled for Hooks store
- return [isHookTradeType ? false : partialApprove, setPartialApprove]
-}
-
export function useSelectedWallet(): string | undefined {
return useAppSelector(({ user: { selectedWallet } }) => selectedWallet)
}
diff --git a/apps/cowswap-frontend/src/legacy/state/user/reducer.ts b/apps/cowswap-frontend/src/legacy/state/user/reducer.ts
index 395811fa84..a9470d45b3 100644
--- a/apps/cowswap-frontend/src/legacy/state/user/reducer.ts
+++ b/apps/cowswap-frontend/src/legacy/state/user/reducer.ts
@@ -18,7 +18,6 @@ export interface UserState {
// TODO: mod, shouldn't be here
recipientToggleVisible: boolean
hooksEnabled: boolean
- partialApprove: boolean
// deadline set by user in minutes, used in all txns
userDeadline: number
@@ -28,9 +27,9 @@ export const initialState: UserState = {
selectedWallet: undefined,
matchesDarkMode: false,
userDarkMode: null,
+ // TODO: mod, shouldn't be here
recipientToggleVisible: false,
hooksEnabled: false,
- partialApprove: false,
userLocale: null,
userDeadline: DEFAULT_DEADLINE_FROM_NOW,
}
@@ -57,9 +56,6 @@ const userSlice = createSlice({
updateUserDeadline(state, action) {
state.userDeadline = action.payload.userDeadline
},
- updatePartialApprove(state, action) {
- state.partialApprove = action.payload.partialApprove
- },
updateRecipientToggleVisible(state, action) {
state.recipientToggleVisible = action.payload.recipientToggleVisible
},
@@ -74,6 +70,5 @@ export const {
updateUserDeadline,
updateUserLocale,
updateRecipientToggleVisible,
- updatePartialApprove,
} = userSlice.actions
export default userSlice.reducer
diff --git a/apps/cowswap-frontend/src/lib/hooks/useApproval.ts b/apps/cowswap-frontend/src/lib/hooks/useApproval.ts
index bfe49fa6c2..285b4afb2a 100644
--- a/apps/cowswap-frontend/src/lib/hooks/useApproval.ts
+++ b/apps/cowswap-frontend/src/lib/hooks/useApproval.ts
@@ -1,7 +1,9 @@
-import { useMemo } from 'react'
+import { useCallback, useMemo } from 'react'
-import { getIsNativeToken } from '@cowprotocol/common-utils'
+import { calculateGasMargin, getIsNativeToken } from '@cowprotocol/common-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
+import { MaxUint256 } from '@ethersproject/constants'
+import { TransactionResponse } from '@ethersproject/providers'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Nullish } from 'types'
@@ -9,6 +11,7 @@ import { Nullish } from 'types'
import { useTokenAllowance } from 'legacy/hooks/useTokenAllowance'
import { ApprovalState } from 'common/hooks/useApproveState'
+import { useTokenContract } from 'common/hooks/useContract'
export interface ApprovalStateForSpenderResult {
approvalState: ApprovalState
@@ -19,7 +22,7 @@ function toApprovalState(
amountToApprove: Nullish>,
spender: string | undefined,
currentAllowance?: CurrencyAmount,
- pendingApproval?: boolean,
+ pendingApproval?: boolean
): ApprovalState {
// Unknown amount or spender
if (!amountToApprove || !spender) {
@@ -47,7 +50,7 @@ function toApprovalState(
export function useApprovalStateForSpender(
amountToApprove: Nullish>,
spender: string | undefined,
- useIsPendingApproval: (token?: Token, spender?: string) => boolean,
+ useIsPendingApproval: (token?: Token, spender?: string) => boolean
): ApprovalStateForSpenderResult {
const { account } = useWalletInfo()
const currency = amountToApprove?.currency
@@ -61,3 +64,66 @@ export function useApprovalStateForSpender(
return { approvalState, currentAllowance }
}, [amountToApprove, currentAllowance, pendingApproval, spender])
}
+
+export function useApproval(
+ amountToApprove: CurrencyAmount | undefined,
+ spender: string | undefined,
+ useIsPendingApproval: (token?: Token, spender?: string) => boolean
+): [
+ ApprovalState,
+ () => Promise<{ response: TransactionResponse; tokenAddress: string; spenderAddress: string } | undefined>
+] {
+ const { chainId } = useWalletInfo()
+ const currency = amountToApprove?.currency
+ const token = currency && !getIsNativeToken(currency) ? currency : undefined
+
+ // check the current approval status
+ const approvalState = useApprovalStateForSpender(amountToApprove, spender, useIsPendingApproval).approvalState
+
+ const tokenContract = useTokenContract(token?.address)
+
+ const approve = useCallback(async () => {
+ function logFailure(error: Error | string): undefined {
+ console.warn(`${token?.symbol || 'Token'} approval failed:`, error)
+ return
+ }
+
+ // Bail early if there is an issue.
+ if (approvalState !== ApprovalState.NOT_APPROVED) {
+ return logFailure('approve was called unnecessarily')
+ } else if (!chainId) {
+ return logFailure('no chainId')
+ } else if (!token) {
+ return logFailure('no token')
+ } else if (!tokenContract) {
+ return logFailure('tokenContract is null')
+ } else if (!amountToApprove) {
+ return logFailure('missing amount to approve')
+ } else if (!spender) {
+ return logFailure('no spender')
+ }
+
+ let useExact = false
+ const estimatedGas = await tokenContract.estimateGas.approve(spender, MaxUint256).catch(() => {
+ // general fallback for tokens which restrict approval amounts
+ useExact = true
+ return tokenContract.estimateGas.approve(spender, amountToApprove.quotient.toString())
+ })
+
+ return tokenContract
+ .approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, {
+ gasLimit: calculateGasMargin(estimatedGas),
+ })
+ .then((response) => ({
+ response,
+ tokenAddress: token.address,
+ spenderAddress: spender,
+ }))
+ .catch((error: Error) => {
+ logFailure(error)
+ throw error
+ })
+ }, [approvalState, token, tokenContract, amountToApprove, spender, chainId])
+
+ return [approvalState, approve]
+}
diff --git a/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts b/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts
index 51aba39eea..abd22db931 100644
--- a/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts
+++ b/apps/cowswap-frontend/src/modules/operations/bundle/buildApproveTx.ts
@@ -7,16 +7,15 @@ export type BuildApproveTxParams = {
erc20Contract: Erc20
spender: string
amountToApprove: CurrencyAmount
- isPartialApprove?: boolean
}
/**
* Builds the approval tx, without sending it
*/
export async function buildApproveTx(params: BuildApproveTxParams) {
- const { erc20Contract, spender, amountToApprove, isPartialApprove } = params
+ const { erc20Contract, spender, amountToApprove } = params
- const estimatedAmount = await estimateApprove(erc20Contract, spender, amountToApprove, isPartialApprove)
+ const estimatedAmount = await estimateApprove(erc20Contract, spender, amountToApprove)
return erc20Contract.populateTransaction.approve(spender, estimatedAmount.approveAmount)
}
diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts
index 2fbf6ba422..86d6b6bf65 100644
--- a/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts
+++ b/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts
@@ -19,7 +19,7 @@ import { GeneratePermitHook, GeneratePermitHookParams } from '../types'
/**
* Hook that returns callback to generate permit hook data
*/
-export function useGeneratePermitHook(isPartialApprove?: boolean): GeneratePermitHook {
+export function useGeneratePermitHook(): GeneratePermitHook {
const { chainId } = useWalletInfo()
const storePermit = useSetAtom(storePermitCacheAtom)
const getCachedPermit = useGetCachedPermit()
@@ -44,15 +44,14 @@ export function useGeneratePermitHook(isPartialApprove?: boolean): GeneratePermi
const eip2162Utils = getPermitUtilsInstance(chainId, provider, account)
const spender = customSpender || COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId]
- const amount = isPartialApprove ? params.amount : undefined
// Always get the nonce for the real account, to know whether the cache should be invalidated
// Static account should never need to pre-check the nonce as it'll never change once cached
const nonce = account ? await eip2162Utils.getTokenNonce(inputToken.address, account) : undefined
- const permitParams = { chainId, tokenAddress: inputToken.address, account, nonce, amount }
+ const permitParams = { chainId, tokenAddress: inputToken.address, account, nonce }
- const cachedPermit = await getCachedPermit(inputToken.address, spender, amount)
+ const cachedPermit = await getCachedPermit(inputToken.address, spender)
if (cachedPermit) {
return cachedPermit
@@ -67,13 +66,12 @@ export function useGeneratePermitHook(isPartialApprove?: boolean): GeneratePermi
eip2162Utils,
account,
nonce,
- amount,
})
hookData && storePermit({ ...permitParams, hookData, spender })
return hookData
},
- [provider, chainId, getCachedPermit, storePermit, isPartialApprove],
+ [provider, chainId, getCachedPermit, storePermit],
)
}
diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts
index 4476e8c7c4..4f0ba0dc7a 100644
--- a/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts
+++ b/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts
@@ -13,14 +13,13 @@ import { getPermitCacheAtom } from '../state/permitCacheAtom'
export function useGetCachedPermit(): (
tokenAddress: Nullish,
customSpender?: string,
- amount?: bigint,
) => Promise {
const { chainId, account } = useWalletInfo()
const provider = useWalletProvider()
const getCachedPermit = useSetAtom(getPermitCacheAtom)
return useCallback(
- async (tokenAddress: Nullish, customSpender?: string, amount?: bigint) => {
+ async (tokenAddress: Nullish, customSpender?: string) => {
if (!provider || !account || !tokenAddress) {
return
}
@@ -33,7 +32,7 @@ export function useGetCachedPermit(): (
// Static account should never need to pre-check the nonce as it'll never change once cached
const nonce = account ? await eip2162Utils.getTokenNonce(tokenAddress, account) : undefined
- const permitParams = { chainId, tokenAddress, account, nonce, spender, amount }
+ const permitParams = { chainId, tokenAddress, account, nonce, spender }
return getCachedPermit(permitParams)
} catch (e) {
diff --git a/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts b/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts
index 1ab0198f52..e09aa938f4 100644
--- a/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts
+++ b/apps/cowswap-frontend/src/modules/permit/state/permitCacheAtom.ts
@@ -1,12 +1,6 @@
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
-import { MaxUint256 } from '@ethersproject/constants'
-
-import { TradeType, tradeTypeAtom } from 'modules/trade'
-
-import { Routes } from 'common/constants/routes'
-
import {
CachedPermitData,
GetPermitCacheParams,
@@ -20,14 +14,14 @@ import {
* Should never change once it has been created.
* Used exclusively for quote requests
*/
-export const staticPermitCacheAtom = atomWithStorage('staticPermitCache:v4', {})
+export const staticPermitCacheAtom = atomWithStorage('staticPermitCache:v3', {})
/**
* Atom that stores permit data for user permit requests.
* Should be updated whenever the permit nonce is updated.
* Used exclusively for order requests
*/
-export const userPermitCacheAtom = atomWithStorage('userPermitCache:v2', {})
+export const userPermitCacheAtom = atomWithStorage('userPermitCache:v1', {})
/**
* Atom to add/update permit cache data
@@ -42,7 +36,6 @@ export const storePermitCacheAtom = atom(null, (get, set, params: StorePermitCac
const dataToCache: CachedPermitData = {
hookData: params.hookData,
nonce: params.nonce,
- amount: params.amount?.toString(),
}
set(atomToUpdate, (permitCache) => ({ ...permitCache, [key]: JSON.stringify(dataToCache) }))
@@ -60,10 +53,6 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
const atomToUpdate = params.account ? userPermitCacheAtom : staticPermitCacheAtom
const permitCache = get(atomToUpdate)
- const tradeType = get(tradeTypeAtom)
-
- const isSwap = tradeType?.tradeType === TradeType.SWAP && tradeType.route === Routes.SWAP
-
const key = buildKey(params)
const cachedData = permitCache[key]
@@ -72,7 +61,7 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
}
try {
- const { hookData, nonce: storedNonce, amount }: CachedPermitData = JSON.parse(cachedData)
+ const { hookData, nonce: storedNonce }: CachedPermitData = JSON.parse(cachedData)
if (params.account !== undefined) {
// User type permit cache, check the nonce
@@ -87,16 +76,6 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
return undefined
}
-
- // Only Swap might create partial amount permits
- // Because of that, we skip cached permits with partial amount in other widgets
- if (!isSwap && amount && amount !== MaxUint256.toString()) {
- return undefined
- }
-
- if (params.amount && params.amount.toString() !== amount) {
- return undefined
- }
}
// Cache hit for both static and user permit types
@@ -112,8 +91,8 @@ export const getPermitCacheAtom = atom(null, (get, set, params: GetPermitCachePa
}
})
-function buildKey({ chainId, tokenAddress, account, spender, amount }: PermitCacheKeyParams) {
- const base = `${chainId}-${tokenAddress.toLowerCase()}-${spender.toLowerCase()}-${amount ? amount.toString() : ''}`
+function buildKey({ chainId, tokenAddress, account, spender }: PermitCacheKeyParams) {
+ const base = `${chainId}-${tokenAddress.toLowerCase()}-${spender.toLowerCase()}`
return account ? `${base}-${account.toLowerCase()}` : base
}
diff --git a/apps/cowswap-frontend/src/modules/permit/types.ts b/apps/cowswap-frontend/src/modules/permit/types.ts
index 2a42f1c941..c1b9cd215d 100644
--- a/apps/cowswap-frontend/src/modules/permit/types.ts
+++ b/apps/cowswap-frontend/src/modules/permit/types.ts
@@ -14,7 +14,7 @@ export type AddPermitTokenParams = {
permitInfo: PermitInfo
}
-export type GeneratePermitHookParams = Pick & {
+export type GeneratePermitHookParams = Pick & {
customSpender?: string
}
@@ -33,7 +33,6 @@ export type PermitCache = Record
export type CachedPermitData = {
hookData: PermitHookData
nonce: number | undefined
- amount: string | undefined
}
export type PermitCacheKeyParams = {
@@ -42,7 +41,6 @@ export type PermitCacheKeyParams = {
account: string | undefined
nonce: number | undefined
spender: string
- amount: bigint | undefined
}
export type StorePermitCacheParams = PermitCacheKeyParams & { hookData: PermitHookData }
diff --git a/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts b/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts
index 1be97e5ee9..1a13cb033c 100644
--- a/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts
+++ b/apps/cowswap-frontend/src/modules/permit/utils/handlePermit.ts
@@ -22,7 +22,7 @@ import { HandlePermitParams } from '../types'
* Returns the updated appData
*/
export async function handlePermit(params: HandlePermitParams): Promise {
- const { permitInfo, inputToken, account, appData, typedHooks, generatePermitHook, amount } = params
+ const { permitInfo, inputToken, account, appData, typedHooks, generatePermitHook } = params
if (isSupportedPermitInfo(permitInfo) && !getIsNativeToken(inputToken)) {
// permitInfo will only be set if there's NOT enough allowance
@@ -31,7 +31,6 @@ export async function handlePermit(params: HandlePermitParams): Promise
{(restContent) => (
<>
diff --git a/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx b/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx
index 150a7eea22..a8b9760bd0 100644
--- a/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/containers/EthFlow/index.tsx
@@ -9,7 +9,6 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { useSingleActivityDescriptor } from 'legacy/hooks/useRecentActivity'
import { WrapUnwrapCallback } from 'legacy/hooks/useWrapCallback'
-import { usePartialApprove } from 'legacy/state/user/hooks'
import { getDerivedEthFlowState } from 'modules/swap/containers/EthFlow/utils/getDerivedEthFlowState'
import { EthFlowModalContent } from 'modules/swap/pure/EthFlow/EthFlowModalContent'
@@ -26,6 +25,7 @@ import { useEthFlowActions } from './hooks/useEthFlowActions'
import useRemainingNativeTxsAndCosts from './hooks/useRemainingNativeTxsAndCosts'
import { useSetupEthFlow } from './hooks/useSetupEthFlow'
+
export interface EthFlowProps {
nativeInput?: CurrencyAmount
hasEnoughWrappedBalanceForSwap: boolean
@@ -45,12 +45,10 @@ export function EthFlowModal({
const native = useNativeCurrency()
const wrapped = useWrappedToken()
const { state: approvalState } = useApproveState(nativeInput || null)
- const [isPartialApprove] = usePartialApprove()
const ethFlowContext = useAtomValue(ethFlowContextAtom)
const approveCallback = useTradeApproveCallback(
- (nativeInput && currencyAmountToTokenAmount(nativeInput)) || undefined,
- isPartialApprove,
+ (nativeInput && currencyAmountToTokenAmount(nativeInput)) || undefined
)
const ethFlowActions = useEthFlowActions({
wrap: wrapCallback,
@@ -88,7 +86,7 @@ export function EthFlowModal({
approvalState,
approveActivity,
wrapActivity,
- onDismiss,
+ onDismiss
})
return (
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 d4fff58b3d..a49a039628 100644
--- a/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx
@@ -14,12 +14,7 @@ import { NetworkAlert } from 'legacy/components/NetworkAlert/NetworkAlert'
import { useModalIsOpen } from 'legacy/state/application/hooks'
import { ApplicationModal } from 'legacy/state/application/reducer'
import { Field } from 'legacy/state/types'
-import {
- useHooksEnabledManager,
- usePartialApprove,
- useRecipientToggleManager,
- useUserTransactionTTL,
-} from 'legacy/state/user/hooks'
+import { useHooksEnabledManager, useRecipientToggleManager, useUserTransactionTTL } from 'legacy/state/user/hooks'
import { useCurrencyAmountBalanceCombined } from 'modules/combinedBalances'
import { useInjectedWidgetParams } from 'modules/injectedWidget'
@@ -90,7 +85,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) {
const recipientToggleState = useRecipientToggleManager()
const hooksEnabledState = useHooksEnabledManager()
const deadlineState = useUserTransactionTTL()
- const partialApproveState = usePartialApprove()
const isHookTradeType = useIsHooksTradeType()
const isTradePriceUpdating = useTradePricesUpdate()
@@ -244,8 +238,6 @@ export function SwapWidget({ topContent, bottomContent }: SwapWidgetProps) {
recipientToggleState={recipientToggleState}
hooksEnabledState={hooksEnabledState}
deadlineState={deadlineState}
- // Partial approve is disabled for Hooks store
- partialApproveState={isHookTradeType ? undefined : partialApproveState}
/>
),
diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts
index 275a2798c3..f550702e03 100644
--- a/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts
+++ b/apps/cowswap-frontend/src/modules/swap/hooks/useHandleSwapOrEthFlow.ts
@@ -1,7 +1,7 @@
import { useCallback } from 'react'
import { Field } from 'legacy/state/types'
-import { usePartialApprove, useUserTransactionTTL } from 'legacy/state/user/hooks'
+import { useUserTransactionTTL } from 'legacy/state/user/hooks'
import { TradeWidgetActions, useTradePriceImpact } from 'modules/trade'
import { logTradeFlow } from 'modules/trade/utils/logger'
@@ -25,11 +25,7 @@ export function useHandleSwapOrEthFlow(actions: TradeWidgetActions) {
const { onUserInput, onChangeRecipient } = actions
const [deadline] = useUserTransactionTTL()
- const [isPartialApprove] = usePartialApprove()
- const { callback: handleSwap, contextIsReady } = useHandleSwap(
- useSafeMemoObject({ deadline, isPartialApprove }),
- actions,
- )
+ const { callback: handleSwap, contextIsReady } = useHandleSwap(useSafeMemoObject({ deadline }), actions)
const callback = useCallback(async () => {
if (!swapFlowContext) return
diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts
index 19906be3f4..91fe6ec473 100644
--- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts
+++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts
@@ -14,7 +14,6 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { useToggleWalletModal } from 'legacy/state/application/hooks'
import { useGetQuoteAndStatus, useIsBestQuoteLoading } from 'legacy/state/price/hooks'
import { Field } from 'legacy/state/types'
-import { usePartialApprove } from 'legacy/state/user/hooks'
import { useCurrencyAmountBalanceCombined } from 'modules/combinedBalances'
import { useInjectedWidgetParams } from 'modules/injectedWidget'
@@ -64,7 +63,6 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
const tradeConfirmActions = useTradeConfirmActions()
const { standaloneMode } = useInjectedWidgetParams()
const isHooksStore = useIsHooksTradeType()
- const [isPartialApprove] = usePartialApprove()
const currencyIn = currencies[Field.INPUT]
const currencyOut = currencies[Field.OUTPUT]
@@ -145,7 +143,6 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
onCurrencySelection,
widgetStandaloneMode: standaloneMode,
quoteDeadlineParams,
- isPartialApprove,
}),
[
swapButtonState,
@@ -162,7 +159,6 @@ export function useSwapButtonContext(input: SwapButtonInput, actions: TradeWidge
onCurrencySelection,
standaloneMode,
quoteDeadlineParams,
- isPartialApprove,
],
)
}
diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts
index bc70dfb18f..afed5d89d7 100644
--- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts
+++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts
@@ -1,4 +1,4 @@
-import { usePartialApprove, useUserTransactionTTL } from 'legacy/state/user/hooks'
+import { useUserTransactionTTL } from 'legacy/state/user/hooks'
import { useTradeFlowContext } from 'modules/tradeFlow'
@@ -6,7 +6,5 @@ import { useSafeMemoObject } from 'common/hooks/useSafeMemo'
export function useSwapFlowContext() {
const [deadline] = useUserTransactionTTL()
- const [isPartialApprove] = usePartialApprove()
-
- return useTradeFlowContext(useSafeMemoObject({ deadline, isPartialApprove }))
+ return useTradeFlowContext(useSafeMemoObject({ deadline }))
}
diff --git a/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts b/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts
index e599b3e3f3..b5648ccb99 100644
--- a/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts
+++ b/apps/cowswap-frontend/src/modules/swap/pure/EthFlow/EthFlowModalContent/configs.ts
@@ -68,7 +68,9 @@ export const ethFlowConfigs: {
[EthFlowState.ApproveNeeded]: ({ wrappedSymbol }) => ({
title: `Approve ${wrappedSymbol}`,
buttonText: `Approve ${wrappedSymbol}`,
- descriptions: [`It is required to do an approval of ${wrappedSymbol} via an on-chain ERC20 Approve transaction.`],
+ descriptions: [
+ `It is required to do a one-time approval of ${wrappedSymbol} via an on-chain ERC20 Approve transaction.`,
+ ],
}),
[EthFlowState.SwapReady]: ({ wrappedSymbol }) => ({
title: `Continue swap with ${wrappedSymbol}`,
diff --git a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx
index c64663d107..110dd701f1 100644
--- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.cosmos.tsx
@@ -26,7 +26,6 @@ const swapButtonsContext: SwapButtonsContext = {
openSwapConfirm: () => void 0,
toggleWalletModal: () => void 0,
hasEnoughWrappedBalanceForSwap: true,
- isPartialApprove: false,
quoteDeadlineParams: {
validFor: 0,
quoteValidTo: 0,
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 a0dd7e1e41..28c5618757 100644
--- a/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx
+++ b/apps/cowswap-frontend/src/modules/swap/pure/SwapButtons/index.tsx
@@ -40,7 +40,6 @@ export interface SwapButtonsContext {
onCurrencySelection: (field: Field, currency: Currency) => void
widgetStandaloneMode?: boolean
quoteDeadlineParams: QuoteDeadlineParams
- isPartialApprove: boolean
}
const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext) => JSX.Element } = {
@@ -127,7 +126,7 @@ const swapButtonStateMap: { [key in SwapButtonState]: (props: SwapButtonsContext
{props.inputAmount && (
-
+ Swap
diff --git a/apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx b/apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx
deleted file mode 100644
index caddfb37b5..0000000000
--- a/apps/cowswap-frontend/src/modules/swap/pure/banners/PartialApprovalBanner.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import ICON_TOKENS from '@cowprotocol/assets/svg/tokens.svg'
-import { Command } from '@cowprotocol/types'
-import { BannerOrientation, ClosableBanner, InlineBanner, UnderlinedLinkStyledButton } from '@cowprotocol/ui'
-
-import styled from 'styled-components/macro'
-
-const BANNER_STORAGE_KEY = 'partialPermitBannerKey:v0'
-
-type PartialApprovalBannerProps = {
- openSettings: Command
-}
-
-export function PartialApprovalBanner({ openSettings }: PartialApprovalBannerProps) {
- return ClosableBanner(BANNER_STORAGE_KEY, (onClose) => (
-
-
- NEW: You can now choose to do minimal token approvals in the settings.
-
-
- ))
-}
-
-const Link = styled(UnderlinedLinkStyledButton)`
- padding: 0;
-`
diff --git a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx
index 6edebb05e2..979c49913d 100644
--- a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx
+++ b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx
@@ -9,7 +9,6 @@ import {
CustomRecipientWarningBanner,
LongLoadText,
} from '@cowprotocol/ui'
-import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Trans } from '@lingui/macro'
import ms from 'ms.macro'
@@ -50,9 +49,6 @@ export interface TradeConfirmationProps {
recipient?: string | null
buttonText?: React.ReactNode
children?: (restContent: ReactElement) => ReactElement
- slippageAdjustedSellAmount?: CurrencyAmount
- isPartialApprove?: boolean
- displayHookDetails?: boolean
}
export function TradeConfirmation(props: TradeConfirmationProps) {
@@ -80,9 +76,6 @@ export function TradeConfirmation(props: TradeConfirmationProps) {
recipient,
isPriceStatic,
appData,
- isPartialApprove,
- slippageAdjustedSellAmount,
- displayHookDetails,
} = frozenProps || props
/**
@@ -133,20 +126,15 @@ export function TradeConfirmation(props: TradeConfirmationProps) {
onConfirm()
}
- const hookDetailsElement = displayHookDetails ? (
+ const hookDetailsElement = (
<>
{appData && (
-
+
{(hookChildren) => hookChildren}
)}
>
- ) : null
+ )
return (
e.key === 'Escape' && onDismiss()}>
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts
index 945cf69e0f..fafa6e7594 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts
@@ -28,10 +28,9 @@ import { TradeFlowContext } from '../types/TradeFlowContext'
export interface TradeFlowParams {
deadline: number
- isPartialApprove?: boolean
}
-export function useTradeFlowContext({ deadline, isPartialApprove }: TradeFlowParams): TradeFlowContext | null {
+export function useTradeFlowContext({ deadline }: TradeFlowParams): TradeFlowContext | null {
const { chainId, account } = useWalletInfo()
const provider = useWalletProvider()
const { allowsOffchainSigning } = useWalletDetails()
@@ -50,7 +49,7 @@ export function useTradeFlowContext({ deadline, isPartialApprove }: TradeFlowPar
const networkFee = receiveAmountInfo?.costs.networkFee.amountInSellCurrency
const permitInfo = usePermitInfo(sellCurrency, tradeType)
- const generatePermitHook = useGeneratePermitHook(isPartialApprove)
+ const generatePermitHook = useGeneratePermitHook()
const getCachedPermit = useGetCachedPermit()
const closeModals = useCloseModals()
const dispatch = useDispatch()
@@ -123,7 +122,6 @@ export function useTradeFlowContext({ deadline, isPartialApprove }: TradeFlowPar
deadline,
orderKind,
uiOrderType,
- isPartialApprove,
]
: null,
([
@@ -154,7 +152,6 @@ export function useTradeFlowContext({ deadline, isPartialApprove }: TradeFlowPar
deadline,
orderKind,
uiOrderType,
- isPartialApprove,
]) => {
return {
context: {
@@ -165,7 +162,6 @@ export function useTradeFlowContext({ deadline, isPartialApprove }: TradeFlowPar
},
flags: {
allowsOffchainSigning,
- isPartialApprove,
},
callbacks: {
closeModals,
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
index eafae28aff..cae474c566 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
@@ -33,15 +33,7 @@ export async function safeBundleApprovalFlow(
return false
}
- const {
- context,
- callbacks,
- orderParams,
- swapFlowAnalyticsContext,
- tradeConfirmActions,
- typedHooks,
- flags: { isPartialApprove },
- } = tradeContext
+ const { context, callbacks, orderParams, swapFlowAnalyticsContext, tradeConfirmActions, typedHooks } = tradeContext
const { spender, settlementContract, safeAppsSdk, erc20Contract } = safeBundleContext
@@ -60,7 +52,6 @@ export async function safeBundleApprovalFlow(
erc20Contract,
spender,
amountToApprove: context.inputAmount,
- isPartialApprove,
})
orderParams.appData = await removePermitHookFromAppData(orderParams.appData, typedHooks)
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
index c9b09bd002..0247b09d84 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
@@ -35,14 +35,7 @@ export async function safeBundleEthFlow(
return false
}
- const {
- context,
- callbacks,
- swapFlowAnalyticsContext,
- tradeConfirmActions,
- typedHooks,
- flags: { isPartialApprove },
- } = tradeContext
+ const { context, callbacks, swapFlowAnalyticsContext, tradeConfirmActions, typedHooks } = tradeContext
const { spender, settlementContract, safeAppsSdk, needsApproval, wrappedNativeContract } = safeBundleContext
@@ -80,7 +73,6 @@ export async function safeBundleEthFlow(
erc20Contract: wrappedNativeContract as unknown as Erc20,
spender,
amountToApprove: inputAmount,
- isPartialApprove,
})
txs.push({
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts
index 1a8589d56c..d26e22b9d4 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/swapFlow/index.ts
@@ -59,7 +59,6 @@ export async function swapFlow(
inputToken: inputCurrency,
permitInfo,
generatePermitHook,
- amount: BigInt(inputAmount.quotient.toString()),
})
if (callDataContainsPermitSigner(orderParams.appData.fullAppData)) {
diff --git a/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts b/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts
index c129b37df6..16f6dc07b8 100644
--- a/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts
+++ b/apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts
@@ -26,7 +26,6 @@ export interface TradeFlowContext {
}
flags: {
allowsOffchainSigning: boolean
- isPartialApprove?: boolean
}
callbacks: {
closeModals: Command
diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx
index 7090446fdb..fc7273935d 100644
--- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx
+++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/SettingsTab/index.tsx
@@ -27,20 +27,12 @@ interface SettingsTabProps {
className?: string
recipientToggleState: StatefulValue
hooksEnabledState?: StatefulValue
- partialApproveState?: StatefulValue
deadlineState: StatefulValue
}
-export function SettingsTab({
- className,
- recipientToggleState,
- hooksEnabledState,
- deadlineState,
- partialApproveState,
-}: SettingsTabProps) {
+export function SettingsTab({ className, recipientToggleState, hooksEnabledState, deadlineState }: SettingsTabProps) {
const menuButtonRef = useRef(null)
- const [isPartialApprove, setPartialApprove] = partialApproveState || [null, null]
const [recipientToggleVisible, toggleRecipientVisibilityAux] = recipientToggleState
const toggleRecipientVisibility = useCallback(
(value?: boolean) => {
@@ -112,7 +104,7 @@ export function SettingsTab({
Experimental:
{' '}
- Add DeFI interactions before and after your trade.
+ Add DeFI interactions before and after your trade
}
/>
@@ -120,35 +112,6 @@ export function SettingsTab({
)}
-
- {isPartialApprove !== null && setPartialApprove && (
-
-
-
- Minimal Approvals
-
-
- By default, token approvals & permits are for an unlimited amount, which ensures you don't pay extra for subsequent trades.
-
-
- When this setting is enabled, approvals & permits will be for the minimum amount instead of unlimited.
- This incurs additional costs on every trade.
-
-
- Existing approvals must be revoked manually before you can re-approve.
-
- }
- />
-
- setPartialApprove(!isPartialApprove)}
- />
-
- )}
diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx
index 4a9c0ec9d9..741bc17006 100644
--- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx
+++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/pure/Row/RowSlippageContent/index.tsx
@@ -1,3 +1,4 @@
+import { useSetAtom } from 'jotai'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { Command } from '@cowprotocol/types'
@@ -9,7 +10,7 @@ import styled from 'styled-components/macro'
import { getNativeSlippageTooltip, getNonNativeSlippageTooltip } from 'common/utils/tradeSettingsTooltips'
-import { useOpenSettingsTab } from '../../../state/settingsTabState'
+import { settingsTabStateAtom } from '../../../state/settingsTabState'
import { RowStyleProps, StyledInfoIcon, StyledRowBetween, TextWrapper, TransactionText } from '../styled'
const DefaultSlippage = styled.span`
@@ -64,7 +65,9 @@ export function RowSlippageContent(props: RowSlippageContentProps) {
isSmartSlippageLoading,
} = props
- const openSettings = useOpenSettingsTab()
+ const setSettingTabState = useSetAtom(settingsTabStateAtom)
+
+ const openSettings = () => setSettingTabState({ open: true })
const tooltipContent =
slippageTooltip ||
diff --git a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts
index 61b3e2561a..659fb6321f 100644
--- a/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts
+++ b/apps/cowswap-frontend/src/modules/tradeWidgetAddons/state/settingsTabState.ts
@@ -1,9 +1,3 @@
-import { atom, useSetAtom } from 'jotai'
+import { atom } from 'jotai'
export const settingsTabStateAtom = atom({ open: false })
-
-export function useOpenSettingsTab() {
- const setSettingTabState = useSetAtom(settingsTabStateAtom)
-
- return () => setSettingTabState({ open: true })
-}
diff --git a/apps/cowswap-frontend/src/pages/Swap/index.tsx b/apps/cowswap-frontend/src/pages/Swap/index.tsx
index c90c727003..d672663c10 100644
--- a/apps/cowswap-frontend/src/pages/Swap/index.tsx
+++ b/apps/cowswap-frontend/src/pages/Swap/index.tsx
@@ -5,16 +5,13 @@ import { useWalletInfo } from '@cowprotocol/wallet'
import { Navigate, useLocation, useParams } from 'react-router-dom'
import { SwapUpdaters, SwapWidget } from 'modules/swap'
-import { PartialApprovalBanner } from 'modules/swap/pure/banners/PartialApprovalBanner'
import { getDefaultTradeRawState } from 'modules/trade/types/TradeRawState'
import { parameterizeTradeRoute } from 'modules/trade/utils/parameterizeTradeRoute'
-import { useOpenSettingsTab } from 'modules/tradeWidgetAddons/state/settingsTabState'
import { Routes } from 'common/constants/routes'
export function SwapPage() {
const params = useParams()
- const openSettings = useOpenSettingsTab()
if (!params.chainId) {
return
@@ -23,7 +20,7 @@ export function SwapPage() {
return (
<>
- } />
+
>
)
}
diff --git a/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts
index ab4c3d7c8f..d579add099 100644
--- a/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts
+++ b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts
@@ -1,11 +1,12 @@
+import { Erc20__factory } from '@cowprotocol/abis'
import type { LatestAppDataDocVersion } from '@cowprotocol/app-data'
import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk'
import { BigNumber } from '@ethersproject/bignumber'
-import { parsePermitData } from 'common/utils/parsePermitData'
-
import { ParsedOrder } from './parseOrder'
+const erc20Interface = Erc20__factory.createInterface()
+
export function getOrderPermitAmount(chainId: SupportedChainId, order: ParsedOrder): BigNumber | null {
if (!order.fullAppData) return null
@@ -19,7 +20,7 @@ export function getOrderPermitAmount(chainId: SupportedChainId, order: ParsedOrd
const permitData = preHooks
.map((hook) => {
try {
- return parsePermitData(hook.callData)
+ return erc20Interface.decodeFunctionData('permit', hook.callData)
} catch {
return null
}
diff --git a/libs/hook-dapp-lib/src/hookDappsRegistry.json b/libs/hook-dapp-lib/src/hookDappsRegistry.json
index 7f973f7768..4ce8c5e3e2 100644
--- a/libs/hook-dapp-lib/src/hookDappsRegistry.json
+++ b/libs/hook-dapp-lib/src/hookDappsRegistry.json
@@ -24,7 +24,7 @@
"PERMIT_TOKEN": {
"type": "INTERNAL",
"name": "Permit a token",
- "descriptionShort": "Permit an address to spend one token on your behalf.",
+ "descriptionShort": "Infinite permit an address to spend one token on your behalf.",
"description": "This hook allows you to permit an address to spend your tokens on your behalf. This is useful for allowing a smart contract to spend your tokens without needing to approve each transaction.",
"image": "https://raw.githubusercontent.com/cowprotocol/cowswap/refs/heads/develop/apps/cowswap-frontend/src/modules/hooksStore/dapps/PermitHookApp/icon.png",
"version": "v0.1.0",
diff --git a/libs/permit-utils/src/lib/generatePermitHook.ts b/libs/permit-utils/src/lib/generatePermitHook.ts
index f121a67d83..e9f2e32675 100644
--- a/libs/permit-utils/src/lib/generatePermitHook.ts
+++ b/libs/permit-utils/src/lib/generatePermitHook.ts
@@ -37,17 +37,7 @@ export async function generatePermitHook(params: PermitHookParams): Promise {
- const {
- inputToken,
- spender,
- chainId,
- permitInfo,
- provider,
- account,
- eip2162Utils,
- nonce: preFetchedNonce,
- amount,
- } = params
+ const { inputToken, spender, chainId, permitInfo, provider, account, eip2162Utils, nonce: preFetchedNonce } = params
const tokenAddress = inputToken.address
// TODO: remove the need for `name` from input token. Should come from permitInfo instead
@@ -68,7 +58,7 @@ async function generatePermitHookRaw(params: PermitHookParams): Promise