From aa8d8203ccd038b46ce9aceef594ff9f0df00710 Mon Sep 17 00:00:00 2001 From: Konstantin Barabanov Date: Sat, 27 Dec 2025 13:07:31 +0300 Subject: [PATCH 01/23] refactor(optimization): @1inch/permit-signed-approvals-utils@1.5.1 supports esm --- apps/cowswap-frontend/vite.config.mts | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cowswap-frontend/vite.config.mts b/apps/cowswap-frontend/vite.config.mts index 1a638c5c6f..ef150a647e 100644 --- a/apps/cowswap-frontend/vite.config.mts +++ b/apps/cowswap-frontend/vite.config.mts @@ -156,7 +156,7 @@ export default defineConfig(({ mode }) => { if (id.includes('@sentry')) return '@sentry' if (id.includes('@uniswap')) return '@uniswap' if (id.includes('crypto-es/lib')) return 'crypto-es' - if (id.includes('web3/dist')) return 'web3' + if (id.includes('web3/dist')) return 'web3' // was used by @1inch if (id.includes('lottie-react')) return 'lottie-react' }, }, diff --git a/package.json b/package.json index 66ce5fcde7..22a76c7ed7 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "@babel/traverse": "^7.23.2" }, "dependencies": { - "@1inch/permit-signed-approvals-utils": "^1.4.10", + "@1inch/permit-signed-approvals-utils": "1.5.1", "@apollo/client": "^3.1.5", "@babel/runtime": "^7.27.0", "@coinbase/wallet-sdk": "^3.3.0", From a23e6912b32a3698adf2b0a1df35c61b685c59de Mon Sep 17 00:00:00 2001 From: Konstantin Barabanov Date: Sat, 27 Dec 2025 13:08:11 +0300 Subject: [PATCH 02/23] chore: fix vite.config types --- apps/cowswap-frontend/vite.config.mts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cowswap-frontend/vite.config.mts b/apps/cowswap-frontend/vite.config.mts index ef150a647e..5135d76014 100644 --- a/apps/cowswap-frontend/vite.config.mts +++ b/apps/cowswap-frontend/vite.config.mts @@ -18,7 +18,7 @@ import { robotsPlugin } from '../../tools/vite-plugins/robotsPlugin' // eslint-disable-next-line no-restricted-imports import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types' -import type { Plugin } from 'vite' +import type { PluginOption } from 'vite' const allNodeDeps = Object.keys(stdLibBrowser).map((key) => key.replace('node:', '')) as ModuleNameWithoutNodePrefix[] @@ -32,7 +32,7 @@ const analyzeBundleTemplate: TemplateType = (process.env.ANALYZE_BUNDLE_TEMPLATE export default defineConfig(({ mode }) => { const isProduction = mode === 'production' - const plugins = [ + const plugins: PluginOption[] = [ nodePolyfills({ exclude: allNodeDeps.filter((dep) => !nodeDepsToInclude.includes(dep)), globals: { @@ -78,7 +78,7 @@ export default defineConfig(({ mode }) => { gzipSize: true, brotliSize: true, filename: 'analyse.html', // will be saved in project's root - }) as Plugin, + }) as PluginOption, ) } From 85a9cc7e5a1530c73e0fdfcc0632f043787d2ccf Mon Sep 17 00:00:00 2001 From: Konstantin Barabanov Date: Sat, 27 Dec 2025 13:10:37 +0300 Subject: [PATCH 03/23] refactor(optimization): dedupe bn.js --- apps/cowswap-frontend/vite.config.mts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/cowswap-frontend/vite.config.mts b/apps/cowswap-frontend/vite.config.mts index 5135d76014..d249abd9c4 100644 --- a/apps/cowswap-frontend/vite.config.mts +++ b/apps/cowswap-frontend/vite.config.mts @@ -135,6 +135,9 @@ export default defineConfig(({ mode }) => { alias: { 'node-fetch': 'isomorphic-fetch', }, + dedupe: [ + 'bn.js', // 240kb -> 16kb (gzip) // v5 is compatible with v4 + ], }, build: { @@ -158,6 +161,7 @@ export default defineConfig(({ mode }) => { if (id.includes('crypto-es/lib')) return 'crypto-es' if (id.includes('web3/dist')) return 'web3' // was used by @1inch if (id.includes('lottie-react')) return 'lottie-react' + if (id.includes('bn.js')) return 'bn' }, }, }, From f0cccee88f7e74fe45dee87340d9becbf09bbfa0 Mon Sep 17 00:00:00 2001 From: Konstantin Barabanov Date: Sat, 27 Dec 2025 13:47:51 +0300 Subject: [PATCH 04/23] refactor(optimization): dynamic import @1inch/permit-signed-approvals-utils methods and hardcode used consts added eslint restrict rules --- .../hooks/useDoesOrderHaveValidPermit.test.ts | 6 +- .../utils/checkPermitNonceAndAmount.ts | 3 +- .../ordersTable/utils/extractPermitData.ts | 8 +-- .../permit/hooks/useGeneratePermitHook.ts | 4 +- .../permit/hooks/useGetCachedPermit.ts | 2 +- .../orderUtils/getOrderPermitAmount.test.ts | 16 ++--- .../utils/orderUtils/isPermitValidForOrder.ts | 16 ++--- apps/cowswap-frontend/vite.config.mts | 1 - eslint.config.js | 20 ++++-- libs/permit-utils/README.md | 6 +- .../src/consts/1inchPermitUtils.ts | 68 +++++++++++++++++++ .../src/imports/1inchPermitUtils.ts | 10 +++ libs/permit-utils/src/index.ts | 2 + .../src/lib/checkIsCallDataAValidPermit.ts | 17 ++--- .../src/lib/getPermitUtilsInstance.ts | 15 ++-- .../src/lib/getTokenPermitInfo.ts | 11 +-- .../src/utils/PermitProviderConnector.ts | 6 +- .../src/utils/buildPermitCallData.ts | 7 +- 18 files changed, 147 insertions(+), 71 deletions(-) create mode 100644 libs/permit-utils/src/consts/1inchPermitUtils.ts create mode 100644 libs/permit-utils/src/imports/1inchPermitUtils.ts diff --git a/apps/cowswap-frontend/src/modules/ordersTable/hooks/useDoesOrderHaveValidPermit.test.ts b/apps/cowswap-frontend/src/modules/ordersTable/hooks/useDoesOrderHaveValidPermit.test.ts index 93ebd5a05e..ee6fb9c971 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/hooks/useDoesOrderHaveValidPermit.test.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/hooks/useDoesOrderHaveValidPermit.test.ts @@ -1,11 +1,11 @@ import { Erc20__factory } from '@cowprotocol/abis' import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk' +import { oneInchPermitUtilsConsts } from '@cowprotocol/permit-utils' import { useWalletInfo } from '@cowprotocol/wallet' import { useWalletProvider } from '@cowprotocol/wallet-provider' import { BigNumber as EthersBigNumber } from '@ethersproject/bignumber' import type { Web3Provider } from '@ethersproject/providers' -import { EIP_2612_PERMIT_SELECTOR } from '@1inch/permit-signed-approvals-utils' import { renderHook } from '@testing-library/react' import useSWR from 'swr' @@ -14,8 +14,6 @@ import { Order } from 'legacy/state/orders/actions' import { usePermitInfo } from 'modules/permit' import { TradeType } from 'modules/trade' - - import { isPending } from 'common/hooks/useCategorizeRecentActivity' import { getOrderPermitIfExists } from 'common/utils/doesOrderHavePermit' @@ -79,7 +77,7 @@ function createEip2612PermitCallData(owner: string, spender: string, value: Ethe '0x0000000000000000000000000000000000000000000000000000000000000000', // s ]) // Replace standard permit selector (first 10 chars: 0x + 4 bytes) with EIP_2612_PERMIT_SELECTOR - return EIP_2612_PERMIT_SELECTOR + permitData.slice(10) + return oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR + permitData.slice(10) } describe('useDoesOrderHaveValidPermit', () => { diff --git a/apps/cowswap-frontend/src/modules/ordersTable/utils/checkPermitNonceAndAmount.ts b/apps/cowswap-frontend/src/modules/ordersTable/utils/checkPermitNonceAndAmount.ts index 0d219a1468..0727266335 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/utils/checkPermitNonceAndAmount.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/utils/checkPermitNonceAndAmount.ts @@ -5,7 +5,6 @@ import { GenericOrder } from 'common/types' import { extractPermitData } from './extractPermitData' - export async function checkPermitNonceAndAmount( account: string, chainId: number, @@ -15,7 +14,7 @@ export async function checkPermitNonceAndAmount( permitInfo: PermitInfo, ): Promise { try { - const eip2612Utils = getPermitUtilsInstance(chainId, provider, account) + const eip2612Utils = await getPermitUtilsInstance(chainId, provider, account) const sellTokenAddress = order.inputToken.address const { permitNonce, permitAmount, permitType } = extractPermitData(permitCallData) diff --git a/apps/cowswap-frontend/src/modules/ordersTable/utils/extractPermitData.ts b/apps/cowswap-frontend/src/modules/ordersTable/utils/extractPermitData.ts index 864ac6f969..271e2a30b2 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/utils/extractPermitData.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/utils/extractPermitData.ts @@ -1,8 +1,6 @@ -import { PermitType } from '@cowprotocol/permit-utils' +import { oneInchPermitUtilsConsts, PermitType } from '@cowprotocol/permit-utils' import { Interface } from '@ethersproject/abi' -import { DAI_PERMIT_SELECTOR, EIP_2612_PERMIT_SELECTOR } from '@1inch/permit-signed-approvals-utils' - import { MAX_APPROVE_AMOUNT } from 'modules/erc20Approve/constants' const EIP_2612_SIGNATURE = @@ -18,7 +16,7 @@ export interface PermitData { export function extractPermitData(callData: string): PermitData { try { - if (callData.startsWith(EIP_2612_PERMIT_SELECTOR)) { + if (callData.startsWith(oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR)) { const erc20Interface = new Interface([EIP_2612_SIGNATURE]) const decoded = erc20Interface.decodeFunctionData('permit', callData) @@ -29,7 +27,7 @@ export function extractPermitData(callData: string): PermitData { } } - if (callData.startsWith(DAI_PERMIT_SELECTOR)) { + if (callData.startsWith(oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR)) { const daiInterface = new Interface([DAI_SIGNATURE]) const decoded = daiInterface.decodeFunctionData('permit', callData) diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts index 837ed6dac1..f5e60cd2c2 100644 --- a/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts +++ b/apps/cowswap-frontend/src/modules/permit/hooks/useGeneratePermitHook.ts @@ -6,7 +6,7 @@ import { generatePermitHook, getPermitUtilsInstance, isSupportedPermitInfo, - PermitHookData + PermitHookData, } from '@cowprotocol/permit-utils' import { useWalletInfo } from '@cowprotocol/wallet' import { useWalletProvider } from '@cowprotocol/wallet-provider' @@ -54,7 +54,7 @@ export function useGeneratePermitHook(): GeneratePermitHook { return } - const eip2612Utils = getPermitUtilsInstance(chainId, provider, account) + const eip2612Utils = await getPermitUtilsInstance(chainId, provider, account) const spender = customSpender || COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId] // Always get the nonce for the real account, to know whether the cache should be invalidated diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts index 6d036a71e3..c25fdb2893 100644 --- a/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts +++ b/apps/cowswap-frontend/src/modules/permit/hooks/useGetCachedPermit.ts @@ -27,7 +27,7 @@ export function useGetCachedPermit(): ( const spender = customSpender || COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId] try { - const eip2612Utils = getPermitUtilsInstance(chainId, provider, account) + const eip2612Utils = await getPermitUtilsInstance(chainId, provider, account) // TODO: it might add unwanted node RPC requests // Always get the nonce for the real account, to know whether the cache should be invalidated diff --git a/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.test.ts b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.test.ts index fd064420c5..c40239446e 100644 --- a/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.test.ts +++ b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.test.ts @@ -1,14 +1,10 @@ import { Erc20__factory } from '@cowprotocol/abis' import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk' +import { oneInchPermitUtilsConsts } from '@cowprotocol/permit-utils' import { Interface } from '@ethersproject/abi' import { BigNumber as EthersBigNumber } from '@ethersproject/bignumber' import { MaxUint256 } from '@ethersproject/constants' -import { - DAI_EIP_2612_PERMIT_ABI, - DAI_PERMIT_SELECTOR, - EIP_2612_PERMIT_SELECTOR, -} from '@1inch/permit-signed-approvals-utils' import BigNumber from 'bignumber.js' import JSBI from 'jsbi' @@ -17,7 +13,7 @@ import { ParsedOrder } from './parseOrder' const erc20Interface = Erc20__factory.createInterface() // eslint-disable-next-line @typescript-eslint/no-explicit-any -const daiInterface = new Interface(DAI_EIP_2612_PERMIT_ABI as any) +const daiInterface = new Interface(oneInchPermitUtilsConsts.DAI_EIP_2612_PERMIT_ABI as any) // eslint-disable-next-line max-lines-per-function describe('getOrderPermitAmount', () => { @@ -85,7 +81,7 @@ describe('getOrderPermitAmount', () => { '0x0000000000000000000000000000000000000000000000000000000000000000', // s ]) // Replace standard permit selector (first 10 chars: 0x + 4 bytes) with EIP_2612_PERMIT_SELECTOR - return EIP_2612_PERMIT_SELECTOR + permitData.slice(10) + return oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR + permitData.slice(10) } function createDaiPermitCallData( @@ -106,7 +102,7 @@ describe('getOrderPermitAmount', () => { '0x0000000000000000000000000000000000000000000000000000000000000000', // s ]) // Replace standard permit selector (first 10 chars: 0x + 4 bytes) with DAI_PERMIT_SELECTOR - return DAI_PERMIT_SELECTOR + permitData.slice(10) + return oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR + permitData.slice(10) } describe('when order has no fullAppData', () => { @@ -260,7 +256,7 @@ describe('getOrderPermitAmount', () => { }) it('should continue to next hook when decoding fails', () => { - const invalidCallData = EIP_2612_PERMIT_SELECTOR + 'invalid' + const invalidCallData = oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR + 'invalid' const validCallData = createEip2612PermitCallData(ownerAddress, spenderAddress, permitValue, futureDeadline) const order = { ...baseOrder, @@ -386,7 +382,7 @@ describe('getOrderPermitAmount', () => { }) it('should continue to next hook when decoding fails', () => { - const invalidCallData = DAI_PERMIT_SELECTOR + 'invalid' + const invalidCallData = oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR + 'invalid' const validCallData = createDaiPermitCallData(ownerAddress, spenderAddress, 0, futureDeadline, true) const order = { ...baseOrder, diff --git a/apps/cowswap-frontend/src/utils/orderUtils/isPermitValidForOrder.ts b/apps/cowswap-frontend/src/utils/orderUtils/isPermitValidForOrder.ts index 1ee63eee22..8b0a258b58 100644 --- a/apps/cowswap-frontend/src/utils/orderUtils/isPermitValidForOrder.ts +++ b/apps/cowswap-frontend/src/utils/orderUtils/isPermitValidForOrder.ts @@ -1,20 +1,14 @@ import { Erc20__factory, type Erc20Interface } from '@cowprotocol/abis' import { areAddressesEqual } from '@cowprotocol/common-utils' import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk' +import { oneInchPermitUtilsConsts } from '@cowprotocol/permit-utils' import { Interface } from '@ethersproject/abi' import { BigNumber } from '@ethersproject/bignumber' import { MaxUint256 } from '@ethersproject/constants' -import { - DAI_EIP_2612_PERMIT_ABI, - DAI_PERMIT_SELECTOR, - EIP_2612_PERMIT_SELECTOR, -} from '@1inch/permit-signed-approvals-utils' - const erc20Interface = Erc20__factory.createInterface() -// TODO: Replace any with proper type definitions -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const daiInterface = new Interface(DAI_EIP_2612_PERMIT_ABI as any) as Erc20Interface + +const daiInterface = new Interface(oneInchPermitUtilsConsts.DAI_EIP_2612_PERMIT_ABI) as Erc20Interface export interface PermitValidationResult { isValid: boolean @@ -83,10 +77,10 @@ export function isPermitDecodedCalldataValid( const defaultSpenderAddress = (spenderAddress || COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId]).toLowerCase() const normalizedOwnerAddress = ownerAddress.toLowerCase() - if (callData.startsWith(EIP_2612_PERMIT_SELECTOR)) { + if (callData.startsWith(oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR)) { const result = validateEip2612Permit(callData, defaultSpenderAddress, normalizedOwnerAddress) if (result) return result - } else if (callData.startsWith(DAI_PERMIT_SELECTOR)) { + } else if (callData.startsWith(oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR)) { const result = validateDaiPermit(callData, defaultSpenderAddress, normalizedOwnerAddress) if (result) return result } diff --git a/apps/cowswap-frontend/vite.config.mts b/apps/cowswap-frontend/vite.config.mts index d249abd9c4..cb52f35155 100644 --- a/apps/cowswap-frontend/vite.config.mts +++ b/apps/cowswap-frontend/vite.config.mts @@ -154,7 +154,6 @@ export default defineConfig(({ mode }) => { return 'static/[name]-[hash][extname]' // Everything else with hash }, manualChunks(id) { - if (id.includes('@1inch')) return '@1inch' if (id.includes('@safe-global') || id.includes('viem')) return '@safe-global' if (id.includes('@sentry')) return '@sentry' if (id.includes('@uniswap')) return '@uniswap' diff --git a/eslint.config.js b/eslint.config.js index 3d18a9e039..4e6ae80ce2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -111,7 +111,7 @@ module.exports = [ 'jsx-a11y/accessible-emoji': 'off', 'no-async-promise-executor': 'off', 'no-constant-condition': 'off', - 'no-restricted-imports': [ + '@typescript-eslint/no-restricted-imports': [ 'error', { paths: [ @@ -123,6 +123,11 @@ module.exports = [ name: 'styled-components', message: 'Please import from styled-components/macro.', }, + { + name: '@1inch/permit-signed-approvals-utils', + message: 'Please import from @cowprotocol/permit-utils.', + allowTypeImports: true, + }, ], patterns: [ @@ -136,6 +141,13 @@ module.exports = [ ], }, ], + 'no-restricted-syntax': [ + 'error', + { + selector: 'ImportExpression[source.value="@1inch/permit-signed-approvals-utils"]', + message: 'Please import dynamically from @cowprotocol/permit-utils', + }, + ], 'no-unused-vars': 'off', 'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-vars': [ @@ -209,7 +221,7 @@ module.exports = [ { files: ['apps/cowswap-frontend/**/*.{ts,tsx,js,jsx}'], rules: { - 'no-restricted-imports': [ + '@typescript-eslint/no-restricted-imports': [ 'error', { paths: [ @@ -240,7 +252,7 @@ module.exports = [ { files: ['apps/cowswap-frontend/src/common/**/*.{ts,tsx,js,jsx}'], rules: { - 'no-restricted-imports': [ + '@typescript-eslint/no-restricted-imports': [ 'error', { paths: [ @@ -298,7 +310,7 @@ module.exports = [ 'react-hooks/exhaustive-deps': 'warn', 'react-hooks/purity': 'warn', '@next/next/no-img-element': 'warn', - 'no-restricted-imports': 'warn', + '@typescript-eslint/no-restricted-imports': 'warn', }, }, ...compat.config({ extends: ['plugin:@nx/typescript'] }).map((config) => ({ diff --git a/libs/permit-utils/README.md b/libs/permit-utils/README.md index 20c4317757..2b6eb12680 100644 --- a/libs/permit-utils/README.md +++ b/libs/permit-utils/README.md @@ -26,10 +26,10 @@ const permitInfo = await getTokenPermitInfo({ import { getPermitUtilsInstance } from "@cowprotocol/permit-utils" // Using a static account defined in the library -const staticEip2612PermitUtils = getPermitUtilsInstance(chainId, provider) +const staticEip2612PermitUtils = await getPermitUtilsInstance(chainId, provider) // Using a provided account address -const accountEip2612PermitUtils = getPermitUtilsInstance(chainId, provider, account) +const accountEip2612PermitUtils = await getPermitUtilsInstance(chainId, provider, account) ``` ### `generatePermitHook` @@ -93,7 +93,7 @@ if (!permitInfo) { } // Pass in an account address as we'll need the user to sign the actual permit -const eip2612Utils = getPermitUtilsInstance(chainId, provider, account) +const eip2612Utils = await getPermitUtilsInstance(chainId, provider, account) // Need to know what the current permit nonce is const nonce = await eip2612Utils.getTokenNonce(inputToken.address, account) diff --git a/libs/permit-utils/src/consts/1inchPermitUtils.ts b/libs/permit-utils/src/consts/1inchPermitUtils.ts new file mode 100644 index 0000000000..8c26ec9648 --- /dev/null +++ b/libs/permit-utils/src/consts/1inchPermitUtils.ts @@ -0,0 +1,68 @@ +/** + * @1inch/permit-signed-approvals-utils has a lot of extra deps and increases bundle size. + * + * At the same time it's repo is archived. + * + * Therefore we just copied needed constants, and when it's not possible, we should use import(...) + * + * You also can use `import type ... from '@1inch/permit-signed-approvals-utils'` + */ + +export const DAI_EIP_2612_PERMIT_ABI = [ + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'holder', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'allowed', + type: 'bool', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'permit', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export const EIP_2612_PERMIT_SELECTOR = '0xd505accf' + +export const DAI_PERMIT_SELECTOR = '0x8fcbaf0c' + +export const DAI_LIKE_PERMIT_TYPEHASH = '0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb' diff --git a/libs/permit-utils/src/imports/1inchPermitUtils.ts b/libs/permit-utils/src/imports/1inchPermitUtils.ts new file mode 100644 index 0000000000..0437c4bb3b --- /dev/null +++ b/libs/permit-utils/src/imports/1inchPermitUtils.ts @@ -0,0 +1,10 @@ +/** + * @1inch/permit-signed-approvals-utils has a lot of extra deps and increases bundle size, we should import in dynamically. + * + * It uses esm, but dymaic import breaks tree shaking, so we re-export here only used variables. + * + * You also can use `import type ... from '@1inch/permit-signed-approvals-utils'` + */ + +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +export { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils' diff --git a/libs/permit-utils/src/index.ts b/libs/permit-utils/src/index.ts index 62b87d93b2..68b1b0f983 100644 --- a/libs/permit-utils/src/index.ts +++ b/libs/permit-utils/src/index.ts @@ -6,4 +6,6 @@ export { getPermitUtilsInstance } from './lib/getPermitUtilsInstance' export { getTokenPermitInfo } from './lib/getTokenPermitInfo' export { isSupportedPermitInfo } from './utils/isSupportedPermitInfo' +export * as oneInchPermitUtilsConsts from './consts/1inchPermitUtils' + export type { GetTokenPermitIntoResult, PermitHookData, PermitHookParams, PermitInfo, PermitType } from './types' diff --git a/libs/permit-utils/src/lib/checkIsCallDataAValidPermit.ts b/libs/permit-utils/src/lib/checkIsCallDataAValidPermit.ts index b69564313c..56c2e98d9f 100644 --- a/libs/permit-utils/src/lib/checkIsCallDataAValidPermit.ts +++ b/libs/permit-utils/src/lib/checkIsCallDataAValidPermit.ts @@ -1,7 +1,8 @@ -import { DAI_PERMIT_SELECTOR, Eip2612PermitUtils, EIP_2612_PERMIT_SELECTOR } from '@1inch/permit-signed-approvals-utils' - +import { oneInchPermitUtilsConsts } from '..' import { PermitInfo } from '../types' +import type { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils' + export async function checkIsCallDataAValidPermit( owner: string, chainId: number, @@ -9,7 +10,7 @@ export async function checkIsCallDataAValidPermit( tokenAddress: string, _tokenName: string | undefined, callData: string, - { version, type, name }: PermitInfo + { version, type, name }: PermitInfo, ): Promise { // TODO: take name only from PermitInfo const tokenName = name || _tokenName @@ -27,19 +28,19 @@ export async function checkIsCallDataAValidPermit( let recoverPermitOwnerPromise: Promise | undefined = undefined // If pre-hook doesn't start with either selector, it's not a permit - if (callData.startsWith(EIP_2612_PERMIT_SELECTOR)) { + if (callData.startsWith(oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR)) { recoverPermitOwnerPromise = eip2612Utils.recoverPermitOwnerFromCallData({ ...params, // I don't know why this was removed, ok? // We added it back on buildPermitCallData.ts // But it looks like this is needed 🤷 // Check the test for this method https://github.com/1inch/permit-signed-approvals-utils/blob/master/src/eip-2612-permit.test.ts#L85-L106 - callData: callData.replace(EIP_2612_PERMIT_SELECTOR, '0x'), + callData: callData.replace(oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR, '0x'), }) - } else if (callData.startsWith(DAI_PERMIT_SELECTOR)) { + } else if (callData.startsWith(oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR)) { recoverPermitOwnerPromise = eip2612Utils.recoverDaiLikePermitOwnerFromCallData({ ...params, - callData: callData.replace(DAI_PERMIT_SELECTOR, '0x'), + callData: callData.replace(oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR, '0x'), }) } @@ -56,7 +57,7 @@ export async function checkIsCallDataAValidPermit( } catch (e) { console.debug( `[checkHasValidPendingPermit] Failed to check permit validity for owner ${owner} with callData ${callData}`, - e + e, ) return false } diff --git a/libs/permit-utils/src/lib/getPermitUtilsInstance.ts b/libs/permit-utils/src/lib/getPermitUtilsInstance.ts index fc533b1021..6389a06690 100644 --- a/libs/permit-utils/src/lib/getPermitUtilsInstance.ts +++ b/libs/permit-utils/src/lib/getPermitUtilsInstance.ts @@ -1,10 +1,10 @@ import type { JsonRpcProvider } from '@ethersproject/providers' -import { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils' - import { PERMIT_SIGNER } from '../const' import { PermitProviderConnector } from '../utils/PermitProviderConnector' +import type { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils' + /** * Cache by network. Here we don't care about the provider as a static account will be used for the signature */ @@ -14,11 +14,11 @@ const CHAIN_UTILS_CACHE = new Map() */ const PROVIDER_UTILS_CACHE = new Map() -export function getPermitUtilsInstance( +export async function getPermitUtilsInstance( chainId: number, provider: JsonRpcProvider, - account?: string | undefined -): Eip2612PermitUtils { + account?: string | undefined, +): Promise { const chainCache = CHAIN_UTILS_CACHE.get(chainId) if (!account && chainCache) { @@ -33,7 +33,8 @@ export function getPermitUtilsInstance( // TODO: allow to receive the signer as a parameter const web3ProviderConnector = new PermitProviderConnector(provider, account ? undefined : PERMIT_SIGNER) - const eip2612PermitUtils = new Eip2612PermitUtils(web3ProviderConnector, { enabledCheckSalt: true }) + const Eip2612PermitUtilsClass = await import('../imports/1inchPermitUtils').then((r) => r.Eip2612PermitUtils) + const eip2612PermitUtils = new Eip2612PermitUtilsClass(web3ProviderConnector, { enabledCheckSalt: true }) if (!account) { console.log(`[getPermitUtilsInstance] Set cached chain utils for chain ${chainId}`, eip2612PermitUtils) @@ -41,7 +42,7 @@ export function getPermitUtilsInstance( } else { console.log( `[getPermitUtilsInstance] Set cached provider utils for chain ${chainId}-${account}`, - eip2612PermitUtils + eip2612PermitUtils, ) PROVIDER_UTILS_CACHE.set(providerCacheKey, eip2612PermitUtils) } diff --git a/libs/permit-utils/src/lib/getTokenPermitInfo.ts b/libs/permit-utils/src/lib/getTokenPermitInfo.ts index 9f8e04db9a..b83d922c04 100644 --- a/libs/permit-utils/src/lib/getTokenPermitInfo.ts +++ b/libs/permit-utils/src/lib/getTokenPermitInfo.ts @@ -1,9 +1,8 @@ import type { JsonRpcProvider } from '@ethersproject/providers' -import { DAI_LIKE_PERMIT_TYPEHASH, Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils' - import { getPermitUtilsInstance } from './getPermitUtilsInstance' +import { oneInchPermitUtilsConsts } from '..' import { DEFAULT_MIN_GAS_LIMIT, DEFAULT_PERMIT_VALUE, PERMIT_SIGNER } from '../const' import { GetTokenPermitInfoParams, GetTokenPermitIntoResult, PermitInfo, PermitType } from '../types' import { buildDaiLikePermitCallData, buildEip2612PermitCallData } from '../utils/buildPermitCallData' @@ -12,6 +11,8 @@ import { getPermitDeadline } from '../utils/getPermitDeadline' import { getTokenName } from '../utils/getTokenName' import { getTokenPermitVersion } from '../utils/getTokenPermitVersion' +import type { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils' + const EIP_2612_PERMIT_PARAMS = { nonce: 0, deadline: getPermitDeadline(), @@ -51,7 +52,7 @@ export async function getTokenPermitInfo(params: GetTokenPermitInfoParams): Prom async function actuallyCheckTokenIsPermittable(params: GetTokenPermitInfoParams): Promise { const { spender, tokenAddress, chainId, provider, minGasLimit, amount } = params - const eip2612PermitUtils = getPermitUtilsInstance(chainId, provider) + const eip2612PermitUtils = await getPermitUtilsInstance(chainId, provider) const owner = PERMIT_SIGNER.address @@ -250,7 +251,7 @@ async function getEip2612CallData(params: BaseParams): Promise { async function isDaiLikeTypeHash(tokenAddress: string, eip2612PermitUtils: Eip2612PermitUtils): Promise { const permitTypeHash = await eip2612PermitUtils.getPermitTypeHash(tokenAddress) - return permitTypeHash === DAI_LIKE_PERMIT_TYPEHASH + return permitTypeHash === oneInchPermitUtilsConsts.DAI_LIKE_PERMIT_TYPEHASH } async function getDaiLikeCallData(params: BaseParams): Promise { @@ -258,7 +259,7 @@ async function getDaiLikeCallData(params: BaseParams): Promise { const permitTypeHash = await eip2612PermitUtils.getPermitTypeHash(tokenAddress) - if (permitTypeHash === DAI_LIKE_PERMIT_TYPEHASH) { + if (permitTypeHash === oneInchPermitUtilsConsts.DAI_LIKE_PERMIT_TYPEHASH) { return buildDaiLikePermitCallData({ eip2612Utils: eip2612PermitUtils, callDataParams: [ diff --git a/libs/permit-utils/src/utils/PermitProviderConnector.ts b/libs/permit-utils/src/utils/PermitProviderConnector.ts index f906b94ffa..84534234a0 100644 --- a/libs/permit-utils/src/utils/PermitProviderConnector.ts +++ b/libs/permit-utils/src/utils/PermitProviderConnector.ts @@ -4,10 +4,10 @@ import { BigNumber } from '@ethersproject/bignumber' import type { JsonRpcProvider } from '@ethersproject/providers' import { Wallet } from '@ethersproject/wallet' -import { AbiInput, AbiItem, EIP712TypedData, ProviderConnector } from '@1inch/permit-signed-approvals-utils' - import { getContract } from './getContract' +import type { AbiInput, AbiItem, EIP712TypedData, ProviderConnector } from '@1inch/permit-signed-approvals-utils' + export class PermitProviderConnector implements ProviderConnector { constructor( private provider: JsonRpcProvider, @@ -57,11 +57,9 @@ export class PermitProviderConnector implements ProviderConnector { const copy: Record = {} Object.keys(decodedValues).forEach((key) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const value = decodedValues[key] if (BigNumber.isBigNumber(value)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore copy[key] = value.toHexString() } else { diff --git a/libs/permit-utils/src/utils/buildPermitCallData.ts b/libs/permit-utils/src/utils/buildPermitCallData.ts index 2e608151b4..5f9f26a0b2 100644 --- a/libs/permit-utils/src/utils/buildPermitCallData.ts +++ b/libs/permit-utils/src/utils/buildPermitCallData.ts @@ -1,5 +1,4 @@ -import { DAI_PERMIT_SELECTOR, EIP_2612_PERMIT_SELECTOR } from '@1inch/permit-signed-approvals-utils' - +import { oneInchPermitUtilsConsts } from '..' import { BuildDaiLikePermitCallDataParams, BuildEip2612PermitCallDataParams } from '../types' export async function buildEip2612PermitCallData({ @@ -12,7 +11,7 @@ export async function buildEip2612PermitCallData({ // For some reason, the method above removes the permit selector prefix // https://github.com/1inch/permit-signed-approvals-utils/blob/master/src/eip-2612-permit.utils.ts#L92 // Adding it back - return callData.replace('0x', EIP_2612_PERMIT_SELECTOR) + return callData.replace('0x', oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR) } export async function buildDaiLikePermitCallData({ @@ -23,5 +22,5 @@ export async function buildDaiLikePermitCallData({ // Same as above, but for dai like // https://github.com/1inch/permit-signed-approvals-utils/blob/master/src/eip-2612-permit.utils.ts#L140 - return callData.replace('0x', DAI_PERMIT_SELECTOR) + return callData.replace('0x', oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR) } From eb48d9050ce0791e1129733cc357173f442934b5 Mon Sep 17 00:00:00 2001 From: Konstantin Barabanov Date: Sat, 27 Dec 2025 14:05:19 +0300 Subject: [PATCH 05/23] refactor(optimization): @safe-global new version migration (esm) dynamic import @safe-global/api-kit --- .../src/common/hooks/useSafeApiKit.ts | 18 ++++---- apps/cowswap-frontend/src/common/types.ts | 2 +- .../updaters/orders/PendingOrdersUpdater.ts | 6 ++- .../src/legacy/hooks/useGetSafeTxInfo.ts | 13 ++---- .../state/enhancedTransactions/actions.ts | 2 +- .../state/enhancedTransactions/reducer.ts | 6 +-- .../src/legacy/state/orders/actions.ts | 2 +- .../TradeApproveButton/TradeApproveButton.tsx | 2 +- .../erc20Approve/hooks/useApproveAndSwap.ts | 4 +- .../erc20Approve/hooks/useApproveCurrency.ts | 2 +- .../utils/getIsTradeApproveResult.ts | 2 +- .../services/safeBundleFlow/index.ts | 2 +- .../services/checkSafeTransaction.ts | 6 +-- .../pure/ReceiptModal/fields/SafeTxFields.tsx | 2 +- .../safeBundleFlow/safeBundleApprovalFlow.ts | 2 +- .../safeBundleFlow/safeBundleEthFlow.ts | 2 +- .../twap/services/cancelTwapOrderTxs.ts | 2 +- .../twap/services/createTwapOrderTxs.ts | 2 +- .../services/extensibleFallbackSetupTxs.ts | 2 +- .../twap/services/fetchTwapOrdersFromSafe.ts | 2 +- .../twap/updaters/TwapOrdersUpdater.tsx | 2 +- .../zeroApproval/hooks/useZeroApprove.ts | 4 +- apps/cowswap-frontend/vite.config.mts | 2 +- eslint.config.js | 9 ++++ libs/core/src/gnosisSafe/index.ts | 44 +++++++------------ libs/core/src/imports/safeApiKit.ts | 10 +++++ .../src/consts/1inchPermitUtils.ts | 2 +- .../src/imports/1inchPermitUtils.ts | 2 +- .../src/api/hooks/useSendBatchTransactions.ts | 2 +- libs/wallet/src/api/types.ts | 2 +- .../src/web3-react/hooks/useSafeAppsSdk.ts | 2 +- libs/wallet/src/web3-react/updater.ts | 9 ++-- package.json | 6 +-- 33 files changed, 88 insertions(+), 89 deletions(-) create mode 100644 libs/core/src/imports/safeApiKit.ts diff --git a/apps/cowswap-frontend/src/common/hooks/useSafeApiKit.ts b/apps/cowswap-frontend/src/common/hooks/useSafeApiKit.ts index 1458076111..7c6a8ddefa 100644 --- a/apps/cowswap-frontend/src/common/hooks/useSafeApiKit.ts +++ b/apps/cowswap-frontend/src/common/hooks/useSafeApiKit.ts @@ -2,22 +2,22 @@ import { useEffect, useState } from 'react' import { createSafeApiKitInstance } from '@cowprotocol/core' import { useIsSafeWallet, useWalletInfo } from '@cowprotocol/wallet' -import { useWalletProvider } from '@cowprotocol/wallet-provider' -import SafeApiKit from '@safe-global/api-kit' +import type SafeApiKit from '@safe-global/api-kit' export function useSafeApiKit(): SafeApiKit | null { const [safeApiClient, setSafeApiClient] = useState(null) const { chainId } = useWalletInfo() - const provider = useWalletProvider() const isSafeWallet = useIsSafeWallet() useEffect(() => { - if (provider && chainId && isSafeWallet) { - setSafeApiClient(createSafeApiKitInstance(chainId, provider)) - } else { - setSafeApiClient(null) - } - }, [chainId, isSafeWallet, provider]) + ;(async () => { + if (chainId && isSafeWallet) { + setSafeApiClient(await createSafeApiKitInstance(chainId)) + } else { + setSafeApiClient(null) + } + })() + }, [chainId, isSafeWallet]) return safeApiClient } diff --git a/apps/cowswap-frontend/src/common/types.ts b/apps/cowswap-frontend/src/common/types.ts index d9895ef6d8..bb6c67ab7a 100644 --- a/apps/cowswap-frontend/src/common/types.ts +++ b/apps/cowswap-frontend/src/common/types.ts @@ -24,7 +24,7 @@ export type SafeTransactionParams = { submissionDate: string executionDate: string | null isExecuted: boolean - nonce: number + nonce: string confirmationsRequired: number confirmations: number safeTxHash: string diff --git a/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts b/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts index ebe633eb6f..0334021094 100644 --- a/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts +++ b/apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts @@ -88,7 +88,11 @@ async function _updatePresignGnosisSafeTx( * If an order has a nonce lower than the current Safe nonce, it means that the proposed transaction was replaced by another one. * In this case, we should cancel the order. */ - const isOrderTxReplaced = !!(safeNonce && safeTransaction.nonce < safeNonce && !safeTransaction.isExecuted) + const isOrderTxReplaced = !!( + safeNonce && + BigInt(safeTransaction.nonce) < BigInt(safeNonce) && + !safeTransaction.isExecuted + ) if (CREATING_STATES.includes(order.status) && isOrderTxReplaced) { cancelOrdersBatch({ diff --git a/apps/cowswap-frontend/src/legacy/hooks/useGetSafeTxInfo.ts b/apps/cowswap-frontend/src/legacy/hooks/useGetSafeTxInfo.ts index ee97f82fcd..44c53e7b49 100644 --- a/apps/cowswap-frontend/src/legacy/hooks/useGetSafeTxInfo.ts +++ b/apps/cowswap-frontend/src/legacy/hooks/useGetSafeTxInfo.ts @@ -3,10 +3,8 @@ import { useCallback } from 'react' import { retry, RetryOptions } from '@cowprotocol/common-utils' import { getSafeTransaction } from '@cowprotocol/core' import { useWalletInfo } from '@cowprotocol/wallet' -import { useWalletProvider } from '@cowprotocol/wallet-provider' -import type { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' -import { t } from '@lingui/core/macro' import { RetryResult } from 'types' const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 3, minWait: 1000, maxWait: 3000 } @@ -14,20 +12,15 @@ const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 3, minWait: 1000, maxWait: 3000 export type GetSafeTxInfo = (hash: string) => RetryResult export function useGetSafeTxInfo(): GetSafeTxInfo { - const provider = useWalletProvider() const { chainId } = useWalletInfo() const getSafeInfo = useCallback( (hash) => { return retry(() => { - if (!provider) { - throw new Error(t`There is no provider to get Gnosis safe info`) - } - - return getSafeTransaction(chainId, hash, provider) + return getSafeTransaction(chainId, hash) }, DEFAULT_RETRY_OPTIONS) }, - [chainId, provider], + [chainId], ) return getSafeInfo diff --git a/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/actions.ts b/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/actions.ts index b14b10d523..9ded31c8c8 100644 --- a/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/actions.ts +++ b/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/actions.ts @@ -1,4 +1,4 @@ -import type { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' import { createAction } from '@reduxjs/toolkit' diff --git a/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/reducer.ts b/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/reducer.ts index 1cdabb1211..ef2d8f4c85 100644 --- a/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/reducer.ts +++ b/apps/cowswap-frontend/src/legacy/state/enhancedTransactions/reducer.ts @@ -1,5 +1,5 @@ import { OrderClass } from '@cowprotocol/cow-sdk' -import type { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' import { createReducer } from '@reduxjs/toolkit' @@ -22,7 +22,7 @@ export enum HashType { export interface EnhancedTransactionDetails { hash: string // The hash of the transaction, normally Ethereum one, but not necessarily hashType: HashType // Transaction hash: could be Ethereum tx, or for multisigs could be some kind of hash identifying the order (i.e. Gnosis Safe) - transactionHash?: string // Transaction hash. For EOA this field is immediately available, however, other wallets go through a process of offchain signing before the transactionHash is available + transactionHash: string | null // Transaction hash. For EOA this field is immediately available, however, other wallets go through a process of offchain signing before the transactionHash is available nonce: number // Params using for polling handling @@ -122,7 +122,7 @@ export default createReducer(initialState, (builder) => const txs = transactions[chainId] ?? {} txs[hash] = { hash, - transactionHash: hashType === HashType.ETHEREUM_TX ? hash : undefined, + transactionHash: hashType === HashType.ETHEREUM_TX ? hash : null, nonce, hashType, addedTime: now(), diff --git a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts index 8b45a33867..4ee736b1fc 100644 --- a/apps/cowswap-frontend/src/legacy/state/orders/actions.ts +++ b/apps/cowswap-frontend/src/legacy/state/orders/actions.ts @@ -1,6 +1,6 @@ import { EnrichedOrder, OrderClass, OrderCreation, SupportedChainId as ChainId, UID } from '@cowprotocol/cow-sdk' import { BigNumberish } from '@ethersproject/bignumber' -import type { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { createAction } from '@reduxjs/toolkit' diff --git a/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveButton/TradeApproveButton.tsx b/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveButton/TradeApproveButton.tsx index c31b9135e0..e5ac502235 100644 --- a/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveButton/TradeApproveButton.tsx +++ b/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveButton/TradeApproveButton.tsx @@ -26,7 +26,7 @@ export interface TradeApproveButtonProps { children?: ReactNode isDisabled?: boolean enablePartialApprove?: boolean - onApproveConfirm?: (txHash?: string) => void + onApproveConfirm?: (txHash: string | null) => void label?: string buttonSize?: ButtonSize useModals?: boolean diff --git a/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveAndSwap.ts b/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveAndSwap.ts index 8ff99d09b6..93e2c441c0 100644 --- a/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveAndSwap.ts +++ b/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveAndSwap.ts @@ -17,7 +17,7 @@ import { getIsTradeApproveResult } from '../utils/getIsTradeApproveResult' export interface ApproveAndSwapProps { amountToApprove: CurrencyAmount minAmountToSignForSwap?: CurrencyAmount - onApproveConfirm?: (transactionHash?: string) => void + onApproveConfirm?: (transactionHash: string | null) => void ignorePermit?: boolean useModals?: boolean } @@ -41,7 +41,7 @@ export function useApproveAndSwap({ if (isPermitSupported && onApproveConfirm) { const isPermitSigned = await generatePermitToTrade() if (isPermitSigned) { - onApproveConfirm() + onApproveConfirm(null) } return true diff --git a/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveCurrency.ts b/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveCurrency.ts index 9eb0427d61..aad7d76380 100644 --- a/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveCurrency.ts +++ b/apps/cowswap-frontend/src/modules/erc20Approve/hooks/useApproveCurrency.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react' import { Nullish } from '@cowprotocol/types' -import { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { GenerecTradeApproveResult, useTradeApproveCallback } from 'modules/erc20Approve' diff --git a/apps/cowswap-frontend/src/modules/erc20Approve/utils/getIsTradeApproveResult.ts b/apps/cowswap-frontend/src/modules/erc20Approve/utils/getIsTradeApproveResult.ts index f14d7ad661..1660429941 100644 --- a/apps/cowswap-frontend/src/modules/erc20Approve/utils/getIsTradeApproveResult.ts +++ b/apps/cowswap-frontend/src/modules/erc20Approve/utils/getIsTradeApproveResult.ts @@ -1,5 +1,5 @@ import { Nullish } from '@cowprotocol/types' -import { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' import { GenerecTradeApproveResult } from '../containers' diff --git a/apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts b/apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts index 167edb90c9..079ed80f11 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts @@ -1,7 +1,7 @@ import { SigningScheme } from '@cowprotocol/cow-sdk' import { Command, UiOrderType } from '@cowprotocol/types' import { MaxUint256 } from '@ethersproject/constants' -import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types' +import type { MetaTransactionData } from '@safe-global/types-kit' import { Percent } from '@uniswap/sdk-core' import { tradingSdk } from 'tradingSdk/tradingSdk' diff --git a/apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/services/checkSafeTransaction.ts b/apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/services/checkSafeTransaction.ts index 5014785016..de4a8a2cb7 100644 --- a/apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/services/checkSafeTransaction.ts +++ b/apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/services/checkSafeTransaction.ts @@ -23,18 +23,18 @@ export function checkSafeTransaction(transaction: EnhancedTransactionDetails, pa const { isExecuted, transactionHash } = safeTransaction const safeNonce = safeInfo?.nonce - if (typeof safeNonce === 'number' && safeNonce > safeTransaction.nonce && !isExecuted) { + if (typeof safeNonce === 'string' && BigInt(safeNonce) > BigInt(safeTransaction.nonce) && !isExecuted) { handleTransactionReplacement(transaction, params) return } // If the safe transaction is executed, but we don't have a tx receipt yet - if (isExecuted && !receipt) { + if (isExecuted && transactionHash && !receipt) { // Get the ethereum tx receipt console.log( '[FinalizeTxUpdater] Safe transaction is executed, but we have not fetched the receipt yet. Tx: ', - transactionHash + transactionHash, ) // Get the transaction receipt const { promise: receiptPromise } = getReceipt(transactionHash) diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/fields/SafeTxFields.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/fields/SafeTxFields.tsx index eee8138f39..ab48c2679b 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/fields/SafeTxFields.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/fields/SafeTxFields.tsx @@ -17,7 +17,7 @@ export interface SafeTxFieldsProps { chainId: SupportedChainId safeAddress: string safeTxHash: string - nonce: number + nonce: string confirmations: number confirmationsRequired: number } 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 5bda720892..cc9579c447 100644 --- a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts @@ -1,6 +1,6 @@ import { SigningScheme } from '@cowprotocol/cow-sdk' import { UiOrderType } from '@cowprotocol/types' -import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types' +import type { MetaTransactionData } from '@safe-global/types-kit' import { Percent } from '@uniswap/sdk-core' import { tradingSdk } from 'tradingSdk/tradingSdk' 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 8fb61a14fb..f93f9866b9 100644 --- a/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts +++ b/apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts @@ -2,7 +2,7 @@ import { Erc20 } from '@cowprotocol/abis' import { WRAPPED_NATIVE_CURRENCIES } from '@cowprotocol/common-const' import { SigningScheme, SupportedChainId } from '@cowprotocol/cow-sdk' import { UiOrderType } from '@cowprotocol/types' -import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types' +import type { MetaTransactionData } from '@safe-global/types-kit' import { Percent } from '@uniswap/sdk-core' import { tradingSdk } from 'tradingSdk/tradingSdk' diff --git a/apps/cowswap-frontend/src/modules/twap/services/cancelTwapOrderTxs.ts b/apps/cowswap-frontend/src/modules/twap/services/cancelTwapOrderTxs.ts index 1bf8ee4776..e9c04da0ab 100644 --- a/apps/cowswap-frontend/src/modules/twap/services/cancelTwapOrderTxs.ts +++ b/apps/cowswap-frontend/src/modules/twap/services/cancelTwapOrderTxs.ts @@ -3,7 +3,7 @@ import { USDC_LENS, WRAPPED_NATIVE_CURRENCIES } from '@cowprotocol/common-const' import { isZkSyncChain, SupportedChainId } from '@cowprotocol/cow-sdk' import { ContractsOrder } from '@cowprotocol/sdk-contracts-ts' import { BigNumber } from '@ethersproject/bignumber' -import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types' +import type { MetaTransactionData } from '@safe-global/types-kit' import { CurrencyAmount } from '@uniswap/sdk-core' import { toKeccak256 } from 'common/utils/toKeccak256' diff --git a/apps/cowswap-frontend/src/modules/twap/services/createTwapOrderTxs.ts b/apps/cowswap-frontend/src/modules/twap/services/createTwapOrderTxs.ts index dd88fa50d4..30c705d1bc 100644 --- a/apps/cowswap-frontend/src/modules/twap/services/createTwapOrderTxs.ts +++ b/apps/cowswap-frontend/src/modules/twap/services/createTwapOrderTxs.ts @@ -1,5 +1,5 @@ import { MaxUint256 } from '@ethersproject/constants' -import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types' +import type { MetaTransactionData } from '@safe-global/types-kit' import { TwapOrderCreationContext } from '../hooks/useTwapOrderCreationContext' import { ConditionalOrderParams, TWAPOrder } from '../types' diff --git a/apps/cowswap-frontend/src/modules/twap/services/extensibleFallbackSetupTxs.ts b/apps/cowswap-frontend/src/modules/twap/services/extensibleFallbackSetupTxs.ts index f96faae333..2a7bea2686 100644 --- a/apps/cowswap-frontend/src/modules/twap/services/extensibleFallbackSetupTxs.ts +++ b/apps/cowswap-frontend/src/modules/twap/services/extensibleFallbackSetupTxs.ts @@ -1,4 +1,4 @@ -import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types' +import type { MetaTransactionData } from '@safe-global/types-kit' import { COMPOSABLE_COW_ADDRESS, SAFE_EXTENSIBLE_HANDLER_ADDRESS } from 'modules/advancedOrders/const' diff --git a/apps/cowswap-frontend/src/modules/twap/services/fetchTwapOrdersFromSafe.ts b/apps/cowswap-frontend/src/modules/twap/services/fetchTwapOrdersFromSafe.ts index c8fa353c22..bc52840e27 100644 --- a/apps/cowswap-frontend/src/modules/twap/services/fetchTwapOrdersFromSafe.ts +++ b/apps/cowswap-frontend/src/modules/twap/services/fetchTwapOrdersFromSafe.ts @@ -3,7 +3,7 @@ import { delay, isTruthy } from '@cowprotocol/common-utils' import { SAFE_TRANSACTION_SERVICE_URL } from '@cowprotocol/core' import { SupportedChainId } from '@cowprotocol/cow-sdk' import type { AllTransactionsListResponse } from '@safe-global/api-kit' -import type { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' import ms from 'ms.macro' diff --git a/apps/cowswap-frontend/src/modules/twap/updaters/TwapOrdersUpdater.tsx b/apps/cowswap-frontend/src/modules/twap/updaters/TwapOrdersUpdater.tsx index 6f4cc57c42..34cfd3cf01 100644 --- a/apps/cowswap-frontend/src/modules/twap/updaters/TwapOrdersUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/twap/updaters/TwapOrdersUpdater.tsx @@ -107,7 +107,7 @@ export function TwapOrdersUpdater(props: { .filter((data) => { const { nonce, isExecuted } = data.safeData.safeTxParams - return !isExecuted && nonce < safeNonce + return !isExecuted && BigInt(nonce) < BigInt(safeNonce) }) .map((item) => item.id) })() diff --git a/apps/cowswap-frontend/src/modules/zeroApproval/hooks/useZeroApprove.ts b/apps/cowswap-frontend/src/modules/zeroApproval/hooks/useZeroApprove.ts index c9aea54d4e..597f321799 100644 --- a/apps/cowswap-frontend/src/modules/zeroApproval/hooks/useZeroApprove.ts +++ b/apps/cowswap-frontend/src/modules/zeroApproval/hooks/useZeroApprove.ts @@ -5,8 +5,8 @@ import { useTradeSpenderAddress } from '@cowprotocol/balances-and-allowances' import { Nullish } from '@cowprotocol/types' import { useIsSafeWallet, useIsWalletConnect } from '@cowprotocol/wallet' import { TransactionReceipt } from '@ethersproject/abstract-provider' -import SafeApiKit from '@safe-global/api-kit' -import type { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' +import type SafeApiKit from '@safe-global/api-kit' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { useApproveCallback } from 'modules/erc20Approve' diff --git a/apps/cowswap-frontend/vite.config.mts b/apps/cowswap-frontend/vite.config.mts index cb52f35155..bfd8f9ef6f 100644 --- a/apps/cowswap-frontend/vite.config.mts +++ b/apps/cowswap-frontend/vite.config.mts @@ -154,7 +154,7 @@ export default defineConfig(({ mode }) => { return 'static/[name]-[hash][extname]' // Everything else with hash }, manualChunks(id) { - if (id.includes('@safe-global') || id.includes('viem')) return '@safe-global' + if (id.includes('@safe-global/safe-apps-sdk')) return '@safe-global-safe-apps-sdk' // used by some deps if (id.includes('@sentry')) return '@sentry' if (id.includes('@uniswap')) return '@uniswap' if (id.includes('crypto-es/lib')) return 'crypto-es' diff --git a/eslint.config.js b/eslint.config.js index 4e6ae80ce2..f0347cc233 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -128,6 +128,11 @@ module.exports = [ message: 'Please import from @cowprotocol/permit-utils.', allowTypeImports: true, }, + { + name: '@safe-global/api-kit', + message: 'Please import from @cowprotocol/core.', + allowTypeImports: true, + }, ], patterns: [ @@ -147,6 +152,10 @@ module.exports = [ selector: 'ImportExpression[source.value="@1inch/permit-signed-approvals-utils"]', message: 'Please import dynamically from @cowprotocol/permit-utils', }, + { + selector: 'ImportExpression[source.value="@safe-global/api-kit"]', + message: 'Please import dynamically from @cowprotocol/core', + }, ], 'no-unused-vars': 'off', 'unused-imports/no-unused-imports': 'error', diff --git a/libs/core/src/gnosisSafe/index.ts b/libs/core/src/gnosisSafe/index.ts index ca4a46ea48..34e26b3dcc 100644 --- a/libs/core/src/gnosisSafe/index.ts +++ b/libs/core/src/gnosisSafe/index.ts @@ -1,12 +1,7 @@ import { CHAIN_INFO } from '@cowprotocol/common-const' import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { JsonRpcFetchFunc, Web3Provider } from '@ethersproject/providers' -import SafeApiKit, { SafeInfoResponse } from '@safe-global/api-kit' -import { EthersAdapter } from '@safe-global/protocol-kit' -import type { SafeMultisigTransactionResponse } from '@safe-global/safe-core-sdk-types' - -// eslint-disable-next-line no-restricted-imports -import { ethers } from 'ethers' +import type { SafeInfoResponse, default as SafeApiKitType } from '@safe-global/api-kit' +import type { SafeMultisigTransactionResponse } from '@safe-global/types-kit' export const SAFE_TRANSACTION_SERVICE_URL: Record = { [SupportedChainId.MAINNET]: 'https://safe-transaction-mainnet.safe.global', @@ -24,16 +19,16 @@ export const SAFE_TRANSACTION_SERVICE_URL: Record = { const SAFE_BASE_URL = 'https://app.safe.global' -const SAFE_TRANSACTION_SERVICE_CACHE: Partial> = {} +const SAFE_TRANSACTION_SERVICE_CACHE: Partial> = {} -function _getClient(chainId: number, library: Web3Provider): SafeApiKit | null { +async function _getClient(chainId: number): Promise { const cachedClient = SAFE_TRANSACTION_SERVICE_CACHE[chainId] if (cachedClient !== undefined) { return cachedClient } - const client = createSafeApiKitInstance(chainId, library) + const client = await createSafeApiKitInstance(chainId) // Add client to cache (or null if unknown network) SAFE_TRANSACTION_SERVICE_CACHE[chainId] = client @@ -41,27 +36,19 @@ function _getClient(chainId: number, library: Web3Provider): SafeApiKit | null { return client } -function _createSafeEthAdapter(library: Web3Provider): EthersAdapter { - const provider = new Web3Provider(library.send.bind(library) as JsonRpcFetchFunc) - - return new EthersAdapter({ - ethers, - signerOrProvider: provider.getSigner(0), - }) -} - -export function createSafeApiKitInstance(chainId: number, library: Web3Provider): SafeApiKit | null { +export async function createSafeApiKitInstance(chainId: number): Promise { const url = SAFE_TRANSACTION_SERVICE_URL[chainId as SupportedChainId] if (!url) { return null } - const ethAdapter = _createSafeEthAdapter(library) - return new SafeApiKit({ txServiceUrl: url, ethAdapter }) + const SafeApiKit = await import('../imports/safeApiKit').then((r) => r.default) + + return new SafeApiKit({ txServiceUrl: url, chainId: BigInt(chainId) }) } -function _getClientOrThrow(chainId: number, library: Web3Provider): SafeApiKit { - const client = _getClient(chainId, library) +async function _getClientOrThrow(chainId: number): Promise { + const client = await _getClient(chainId) if (!client) { throw new Error('Unsupported network for Gnosis Safe Transaction Service: ' + chainId) } @@ -81,21 +68,20 @@ export function getSafeAccountUrl(chainId: SupportedChainId, safeAddress: string return `${SAFE_BASE_URL}/${chainShortName}:${safeAddress}` } -export function getSafeTransaction( +export async function getSafeTransaction( chainId: number, safeTxHash: string, - library: Web3Provider, ): Promise { console.log('[api/gnosisSafe] getSafeTransaction', chainId, safeTxHash) - const client = _getClientOrThrow(chainId, library) + const client = await _getClientOrThrow(chainId) return client.getTransaction(safeTxHash) } -export function getSafeInfo(chainId: number, safeAddress: string, library: Web3Provider): Promise { +export async function getSafeInfo(chainId: number, safeAddress: string): Promise { console.log('[api/gnosisSafe] getSafeInfo', chainId, safeAddress) try { - const client = _getClientOrThrow(chainId, library) + const client = await _getClientOrThrow(chainId) return client.getSafeInfo(safeAddress) } catch (error) { diff --git a/libs/core/src/imports/safeApiKit.ts b/libs/core/src/imports/safeApiKit.ts new file mode 100644 index 0000000000..8f01e020f0 --- /dev/null +++ b/libs/core/src/imports/safeApiKit.ts @@ -0,0 +1,10 @@ +/** + * @safe-global/api-kit has a lot of extra variables and increases bundle size, we should import in dynamically. + * + * It uses esm, but dymaic import breaks tree shaking, so we re-export here only used variables. + * + * You also can use `import type ... from '@safe-global/api-kit'` + */ + +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +export { default } from '@safe-global/api-kit' diff --git a/libs/permit-utils/src/consts/1inchPermitUtils.ts b/libs/permit-utils/src/consts/1inchPermitUtils.ts index 8c26ec9648..03f835a73f 100644 --- a/libs/permit-utils/src/consts/1inchPermitUtils.ts +++ b/libs/permit-utils/src/consts/1inchPermitUtils.ts @@ -1,5 +1,5 @@ /** - * @1inch/permit-signed-approvals-utils has a lot of extra deps and increases bundle size. + * @1inch/permit-signed-approvals-utils has a lot of extra variables and increases bundle size. * * At the same time it's repo is archived. * diff --git a/libs/permit-utils/src/imports/1inchPermitUtils.ts b/libs/permit-utils/src/imports/1inchPermitUtils.ts index 0437c4bb3b..3ad2e81c80 100644 --- a/libs/permit-utils/src/imports/1inchPermitUtils.ts +++ b/libs/permit-utils/src/imports/1inchPermitUtils.ts @@ -1,5 +1,5 @@ /** - * @1inch/permit-signed-approvals-utils has a lot of extra deps and increases bundle size, we should import in dynamically. + * @1inch/permit-signed-approvals-utils has a lot of extra variables and increases bundle size, we should import in dynamically. * * It uses esm, but dymaic import breaks tree shaking, so we re-export here only used variables. * diff --git a/libs/wallet/src/api/hooks/useSendBatchTransactions.ts b/libs/wallet/src/api/hooks/useSendBatchTransactions.ts index 34b9a89c30..5b10ce3904 100644 --- a/libs/wallet/src/api/hooks/useSendBatchTransactions.ts +++ b/libs/wallet/src/api/hooks/useSendBatchTransactions.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react' import { useWalletProvider } from '@cowprotocol/wallet-provider' -import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types' +import type { MetaTransactionData } from '@safe-global/types-kit' import { useWalletCapabilities } from './useWalletCapabilities' diff --git a/libs/wallet/src/api/types.ts b/libs/wallet/src/api/types.ts index bedd0f7fce..4ad25b7ebe 100644 --- a/libs/wallet/src/api/types.ts +++ b/libs/wallet/src/api/types.ts @@ -1,5 +1,5 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { SafeInfoResponse } from '@safe-global/api-kit' +import type { SafeInfoResponse } from '@safe-global/api-kit' export enum ConnectionType { NETWORK = 'NETWORK', diff --git a/libs/wallet/src/web3-react/hooks/useSafeAppsSdk.ts b/libs/wallet/src/web3-react/hooks/useSafeAppsSdk.ts index 76d05ba214..32e2bc7a2d 100644 --- a/libs/wallet/src/web3-react/hooks/useSafeAppsSdk.ts +++ b/libs/wallet/src/web3-react/hooks/useSafeAppsSdk.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' -import SafeAppsSDK from '@safe-global/safe-apps-sdk' +import type SafeAppsSDK from '@safe-global/safe-apps-sdk' import { useWeb3React } from '@web3-react/core' import { GnosisSafe } from '@web3-react/gnosis-safe' diff --git a/libs/wallet/src/web3-react/updater.ts b/libs/wallet/src/web3-react/updater.ts index e8738dbd0b..11493a6742 100644 --- a/libs/wallet/src/web3-react/updater.ts +++ b/libs/wallet/src/web3-react/updater.ts @@ -71,7 +71,6 @@ function useWalletDetails(account?: string, standaloneMode?: boolean): WalletDet // TODO: Break down this large function into smaller functions // eslint-disable-next-line max-lines-per-function function useSafeInfo(walletInfo: WalletInfo): GnosisSafeInfo | undefined { - const { provider } = useWeb3React() const { account, chainId } = walletInfo const [safeInfo, setSafeInfo] = useState() const safeAppsSdk = useSafeAppsSdk() @@ -93,7 +92,7 @@ function useSafeInfo(walletInfo: WalletInfo): GnosisSafeInfo | undefined { chainId, threshold, owners, - nonce, + nonce: String(nonce), isReadOnly, } }) @@ -102,9 +101,9 @@ function useSafeInfo(walletInfo: WalletInfo): GnosisSafeInfo | undefined { setSafeInfo(undefined) } } else { - if (chainId && account && provider) { + if (chainId && account) { try { - const _safeInfo = await getSafeInfo(chainId, account, provider) + const _safeInfo = await getSafeInfo(chainId, account) const { address, threshold, owners, nonce } = _safeInfo setSafeInfo((prevSafeInfo) => ({ ...prevSafeInfo, @@ -147,7 +146,7 @@ function useSafeInfo(walletInfo: WalletInfo): GnosisSafeInfo | undefined { clearInterval(longSafeInfoInterval !== null ? longSafeInfoInterval : undefined) longSafeInfoInterval = null } - }, [setSafeInfo, chainId, account, provider, safeAppsSdk]) + }, [setSafeInfo, chainId, account, safeAppsSdk]) return safeInfo } diff --git a/package.json b/package.json index 22a76c7ed7..72d1393759 100644 --- a/package.json +++ b/package.json @@ -115,11 +115,9 @@ "@react-spring/web": "^9.6.1", "@reduxjs/toolkit": "^1.8.0", "@rjsf/core": "^4.2.2", - "@safe-global/api-kit": "^1.3.0", - "@safe-global/protocol-kit": "^1.2.0", + "@safe-global/api-kit": "^4.0.1", "@safe-global/safe-apps-sdk": "^9.1.0", - "@safe-global/safe-core-sdk-types": "^2.2.0", - "@safe-global/safe-ethers-lib": "^1.9.4", + "@safe-global/types-kit": "^3.0.0", "@sentry/react": "^7.80.0", "@sentry/tracing": "^7.80.0", "@sentry/webpack-plugin": "^2.10.0", From 2384228c1e2fe833f1fac52b86d8820342d240cf Mon Sep 17 00:00:00 2001 From: Konstantin Barabanov Date: Sat, 27 Dec 2025 14:13:23 +0300 Subject: [PATCH 06/23] refactor(optimization): update deps @ethereumjs/util (esm) @walletconnect/ethereum-provider @walletconnect/types @walletconnect/utils --- apps/cowswap-frontend/vite.config.mts | 1 + .../TrezorConnector/getAccountsList.ts | 18 ++++++++++++++---- package.json | 7 +++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/cowswap-frontend/vite.config.mts b/apps/cowswap-frontend/vite.config.mts index bfd8f9ef6f..c2e383d66b 100644 --- a/apps/cowswap-frontend/vite.config.mts +++ b/apps/cowswap-frontend/vite.config.mts @@ -161,6 +161,7 @@ export default defineConfig(({ mode }) => { if (id.includes('web3/dist')) return 'web3' // was used by @1inch if (id.includes('lottie-react')) return 'lottie-react' if (id.includes('bn.js')) return 'bn' + if (id.includes('@ethersproject')) return '@ethersproject' }, }, }, diff --git a/libs/wallet/src/web3-react/connectors/TrezorConnector/getAccountsList.ts b/libs/wallet/src/web3-react/connectors/TrezorConnector/getAccountsList.ts index f06d0c5c54..1325aad45d 100644 --- a/libs/wallet/src/web3-react/connectors/TrezorConnector/getAccountsList.ts +++ b/libs/wallet/src/web3-react/connectors/TrezorConnector/getAccountsList.ts @@ -1,4 +1,4 @@ -import { publicToAddress } from 'ethereumjs-util' +import { publicToAddress } from '@ethereumjs/util' import HDNode from 'hdkey' import { TREZOR_DERIVATION_PATH } from '../../../api/utils/getHwAccount' @@ -29,7 +29,11 @@ interface DerivedHDKeyInfo { class DerivedHDKeyInfoIterator { private index = 0 - constructor(private parentDerivedKeyInfo: DerivedHDKeyInfo, private offset = 0, private limit = 100) {} + constructor( + private parentDerivedKeyInfo: DerivedHDKeyInfo, + private offset = 0, + private limit = 100, + ) {} // TODO: Add proper return type annotation // eslint-disable-next-line @typescript-eslint/explicit-function-return-type next() { @@ -93,7 +97,7 @@ async function initialDerivedKeyInfoAsync(trezorConnect: TrezorConnect): Promise function calculateDerivedHDKeyInfos( parentDerivedKeyInfo: DerivedHDKeyInfo, offset: number, - limit: number + limit: number, ): DerivedHDKeyInfo[] { const derivedKeys: DerivedHDKeyInfo[] = [] const derivedKeyIterator = new DerivedHDKeyInfoIterator(parentDerivedKeyInfo, offset, limit) @@ -105,10 +109,16 @@ function calculateDerivedHDKeyInfos( return derivedKeys } +function uint8ArrayToHex(uint8arr: Uint8Array): string { + return Array.from(uint8arr) + .map((x) => x.toString(16).padStart(2, '0')) + .join('') +} + function addressOfHDKey(hdKey: HDNode): string { const shouldSanitizePublicKey = true const derivedPublicKey = hdKey.publicKey - const ethereumAddressUnprefixed = publicToAddress(derivedPublicKey, shouldSanitizePublicKey).toString('hex') + const ethereumAddressUnprefixed = uint8ArrayToHex(publicToAddress(derivedPublicKey, shouldSanitizePublicKey)) return '0x' + ethereumAddressUnprefixed.toLowerCase() } diff --git a/package.json b/package.json index 72d1393759..abb9a2bf78 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@cowprotocol/sdk-subgraph": "^0.2.6", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@ethereumjs/util": "^10.1.0", "@ethersproject/experimental": "^5.8.0", "@fortawesome/fontawesome-svg-core": "^6.7.1", "@fortawesome/free-regular-svg-icons": "^6.7.1", @@ -134,8 +135,9 @@ "@visx/glyph": "^3.3.0", "@visx/responsive": "^3.10.2", "@visx/shape": "^3.5.0", - "@walletconnect/ethereum-provider": "^2.18.0", - "@walletconnect/types": "2.17.3", + "@walletconnect/ethereum-provider": "^2.23.0", + "@walletconnect/types": "^2.23.0", + "@walletconnect/utils": "^2.23.0", "@web3-react/coinbase-wallet": "^8.2.3", "@web3-react/core": "^8.2.3", "@web3-react/eip1193": "^8.2.3", @@ -247,6 +249,7 @@ "viem": "^1.16.6", "wagmi": "^1.2", "web-vitals": "^2.1.4", + "web3": "1.8.1", "web3modal": "1.9.0" }, "devDependencies": { From 20f60c56a2c9581fca1e4117b73deaa7ba93f686 Mon Sep 17 00:00:00 2001 From: Konstantin Barabanov Date: Sat, 27 Dec 2025 14:42:52 +0300 Subject: [PATCH 07/23] refactor(optimization): dynamic import huge json files and framer-mortion and lottie-react --- .../orderProgressBar/pure/LottieContainer.tsx | 15 ++++- .../pure/RenderProgressTopSection.tsx | 64 ++++++++++--------- .../orderProgressBar/pure/StepComponent.tsx | 19 +++--- .../orderProgressBar/pure/TopSections.tsx | 17 ++++- .../pure/steps/FinishedStep.tsx | 18 ++++-- .../ordersTable/hooks/useNoOrdersAnimation.ts | 2 +- .../OrdersTableContainer/NoOrdersContent.tsx | 10 ++- apps/cowswap-frontend/vite.config.mts | 3 +- eslint.config.js | 12 +++- 9 files changed, 103 insertions(+), 57 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/LottieContainer.tsx b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/LottieContainer.tsx index 2e5b52e801..ab0a6f1014 100644 --- a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/LottieContainer.tsx +++ b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/LottieContainer.tsx @@ -1,8 +1,9 @@ -import { ReactNode, useRef, useEffect } from 'react' +import { ReactNode, useRef, useEffect, lazy, Suspense } from 'react' -import Lottie from 'lottie-react' import styled from 'styled-components/macro' +const Lottie = lazy(() => import('lottie-react')) + const LottieWrapper = styled.div` --size: 100%; width: var(--size); @@ -61,7 +62,15 @@ export function FullSizeLottie({ animationData, loop = true, autoplay = true }: return ( - + // TODO: what fallback should be used here? + + + ) } diff --git a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/RenderProgressTopSection.tsx b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/RenderProgressTopSection.tsx index 4142db8b04..929a2e3ce0 100644 --- a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/RenderProgressTopSection.tsx +++ b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/RenderProgressTopSection.tsx @@ -1,9 +1,8 @@ -import { ReactNode, useMemo } from 'react' +import { ReactNode, useMemo, lazy, Suspense } from 'react' import { getRandomInt } from '@cowprotocol/common-utils' import { useLingui } from '@lingui/react/macro' -import { AnimatePresence, motion } from 'framer-motion' import { FinishedStepContentSection } from './FinishedStepContentSection' import { ProgressSkeleton } from './ProgressSkeleton' @@ -27,6 +26,9 @@ interface ProgressContentProps { shouldShowSurplus: boolean } +const AnimatePresence = lazy(() => import('framer-motion').then((r) => ({ default: r.AnimatePresence }))) +const MotionDiv = lazy(() => import('framer-motion').then((r) => ({ default: r.motion.div }))) + function ProgressContent({ isLayoutReady, stepName, @@ -42,34 +44,36 @@ function ProgressContent({ } return ( - - - {stepName && ( - - )} - - + }> + + + {stepName && ( + + )} + + + ) } diff --git a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/StepComponent.tsx b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/StepComponent.tsx index 6b51a900df..cc0bc3c623 100644 --- a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/StepComponent.tsx +++ b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/StepComponent.tsx @@ -2,13 +2,13 @@ import React from 'react' import LOTTIE_RED_CROSS from '@cowprotocol/assets/lottie/red-cross.json' -import Lottie from 'lottie-react' - import * as styledEl from './styled' import { StepStatus } from '../constants' import { Description } from '../sharedStyled' +const Lottie = React.lazy(() => import('lottie-react')) + // TODO: Add proper return type annotation // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function StepComponent({ @@ -39,12 +39,15 @@ export function StepComponent({ $isCancelling={isCancelling} > {status === StepStatus.CANCELLING ? ( - + // TODO: what fallback should be used here? + + + ) : ( <> {index + 1} diff --git a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/TopSections.tsx b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/TopSections.tsx index 73c9c51e23..78a5c0b62b 100644 --- a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/TopSections.tsx +++ b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/TopSections.tsx @@ -4,13 +4,12 @@ import STEP_IMAGE_CANCELLED from '@cowprotocol/assets/cow-swap/progressbar-step- import STEP_IMAGE_EXPIRED from '@cowprotocol/assets/cow-swap/progressbar-step-expired.svg' import STEP_IMAGE_SOLVING from '@cowprotocol/assets/cow-swap/progressbar-step-solving.svg' import STEP_IMAGE_UNFILLABLE from '@cowprotocol/assets/cow-swap/progressbar-step-unfillable.svg' -import STEP_LOTTIE_EXECUTING from '@cowprotocol/assets/lottie/progressbar-step-executing.json' -import STEP_LOTTIE_NEXTBATCH from '@cowprotocol/assets/lottie/progressbar-step-nextbatch.json' import LOTTIE_TIME_EXPIRED_DARK from '@cowprotocol/assets/lottie/time-expired-dark.json' import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' import { t } from '@lingui/core/macro' import SVG from 'react-inlinesvg' +import useSWR from 'swr' import { NoSurplus, ShowSurplus } from './BenefitComponents' import { FullSizeLottie } from './LottieContainer' @@ -50,6 +49,13 @@ export function UnfillableTopSection(): ReactNode { // delayed, submissionFailed, solved export function DelayedSolvedSubmissionFailedTopSection(): ReactNode { + const { data: STEP_LOTTIE_NEXTBATCH } = useSWR( + ['progressbar-step-executing'], + () => import('@cowprotocol/assets/lottie/progressbar-step-nextbatch.json'), + ) + + if (!STEP_LOTTIE_NEXTBATCH) return null + return } @@ -83,6 +89,13 @@ export function SolvingTopSection({ countdown }: SolvingTopSectionProps): ReactN } export function ExecutingTopSection({ stepName }: BaseTopSectionProps): ReactNode { + const { data: STEP_LOTTIE_EXECUTING } = useSWR( + ['progressbar-step-executing'], + () => import('@cowprotocol/assets/lottie/progressbar-step-executing.json'), + ) + + if (!STEP_LOTTIE_EXECUTING) return null + return ( diff --git a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/steps/FinishedStep.tsx b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/steps/FinishedStep.tsx index 0f7e1d554f..5e85cc5dc9 100644 --- a/apps/cowswap-frontend/src/modules/orderProgressBar/pure/steps/FinishedStep.tsx +++ b/apps/cowswap-frontend/src/modules/orderProgressBar/pure/steps/FinishedStep.tsx @@ -12,7 +12,6 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { i18n } from '@lingui/core' import { Trans, useLingui } from '@lingui/react/macro' -import Lottie from 'lottie-react' import { PiCaretDown, PiCaretUp, PiTrophyFill } from 'react-icons/pi' import SVG from 'react-inlinesvg' @@ -32,15 +31,20 @@ import { getSurplusText, getTwitterShareUrl, getTwitterShareUrlForBenefit } from import { useWithConfetti } from '../../hooks/useWithConfetti' import { OrderProgressBarStepName } from '../../types' +const Lottie = React.lazy(() => import('lottie-react')) + function TransactionStatus({ isDarkMode }: { isDarkMode: boolean }): ReactNode { return ( - + // TODO: what fallback should be used here? + + + Transaction completed! ) diff --git a/apps/cowswap-frontend/src/modules/ordersTable/hooks/useNoOrdersAnimation.ts b/apps/cowswap-frontend/src/modules/ordersTable/hooks/useNoOrdersAnimation.ts index 341461b345..64789b2551 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/hooks/useNoOrdersAnimation.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/hooks/useNoOrdersAnimation.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { loadSurprisedCowAnimation } from '@cowprotocol/assets/lazy-loaders' -import type { LottieComponentProps } from 'lottie-react' +import { LottieComponentProps } from 'lottie-react' interface UseNoOrdersAnimationParams { emptyOrdersImage?: string | null diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/NoOrdersContent.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/NoOrdersContent.tsx index d749025d1a..bc88aac924 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/NoOrdersContent.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/NoOrdersContent.tsx @@ -1,10 +1,9 @@ -import { ReactNode, memo } from 'react' +import { ReactNode, memo, lazy, Suspense } from 'react' import { CowSwapSafeAppLink } from '@cowprotocol/ui' import { t } from '@lingui/core/macro' import { Trans, useLingui } from '@lingui/react/macro' -import Lottie from 'lottie-react' import * as styledEl from './OrdersTableContainer.styled' @@ -13,6 +12,8 @@ import { useNoOrdersAnimation } from '../../hooks/useNoOrdersAnimation' import { useOrdersTableState } from '../../hooks/useOrdersTableState' import { TabOrderTypes } from '../../types' +const Lottie = lazy(() => import('lottie-react')) + interface NoOrdersDescriptionProps { currentTab: OrderTabId orderType: TabOrderTypes | undefined @@ -103,7 +104,10 @@ export function NoOrdersContent({ {t`There ) : animationData ? ( - + // TODO: what fallback should be used here? + + + ) : (