diff --git a/.env b/.env index 579463ead85..012099c765a 100644 --- a/.env +++ b/.env @@ -189,7 +189,6 @@ VITE_NEAR_NODE_URL=https://rpc.mainnet.near.org VITE_NEAR_NODE_URL_FALLBACK_1=https://near.lava.build VITE_NEAR_NODE_URL_FALLBACK_2=https://rpc.fastnear.com VITE_FASTNEAR_API_URL=https://api.fastnear.com -VITE_ALCHEMY_POLYGON_URL=https://polygon-mainnet.g.alchemy.com/v2/anoTMcIc2hbPUxri37h4DeuUwg2p5_xZ # midgard VITE_THORCHAIN_MIDGARD_URL=https://api.thorchain.shapeshift.com/midgard/v2 @@ -205,15 +204,9 @@ VITE_COINCAP_API_KEY=dab646843b251ce2d28864982989c335e1f0d32fa14e4ecc6b40cd057ec VITE_EXCHANGERATEHOST_BASE_URL=https://api.exchangerate.host VITE_EXCHANGERATEHOST_API_KEY=8f7515ffddef9d3e449b45f93108ca4d -# Alchemy API key - to be used either with Alchemy SDK or directly with the REST endpoints -VITE_ALCHEMY_API_KEY=anoTMcIc2hbPUxri37h4DeuUwg2p5_xZ - -# Moralis API key - to be used either with Alchemy SDK or directly with the REST endpoints +# Moralis API key VITE_MORALIS_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6ImY1ZmVjMTg4LTc0YzUtNDk0ZC05ZmFjLTZmYzQ5MTAyZTVhOCIsIm9yZ0lkIjoiNDYzNTU5IiwidXNlcklkIjoiNDc2OTA5IiwidHlwZUlkIjoiODE0NWUwYjEtYjEwNi00NzQyLTg2NDAtNzk1NmU4ZGQ5ZGFkIiwidHlwZSI6IlBST0pFQ1QiLCJpYXQiOjE3NTQ0NzEyNTksImV4cCI6NDkxMDIzMTI1OX0.Aa-ELM_vZR0i7Z1nQfh0rsx6ES21DHNEbNsjL28AMMw -# Alchemy Solana endpoint for custom token import -VITE_ALCHEMY_SOLANA_BASE_URL=https://solana-mainnet.g.alchemy.com/v2 - # boardroom VITE_BOARDROOM_API_BASE_URL=https://api.boardroom.info/v1/protocols/shapeshift/ VITE_BOARDROOM_APP_BASE_URL=https://boardroom.io/shapeshift/ @@ -259,6 +252,9 @@ VITE_WALLET_CONNECT_RELAY_URL=wss://relay.walletconnect.com # Portals VITE_PORTALS_BASE_URL=https://api.proxy.shapeshift.com/api/v1/portals +# Proxy API +VITE_PROXY_API_BASE_URL=https://api.proxy.shapeshift.com + VITE_SNAP_ID=npm:@shapeshiftoss/metamask-snaps VITE_SNAP_VERSION=1.0.9 # VITE_SNAP_ID=local:http://localhost:9000 diff --git a/.env.development b/.env.development index 5666ec50e22..0da3372f524 100644 --- a/.env.development +++ b/.env.development @@ -116,6 +116,9 @@ VITE_NOTIFICATIONS_SERVER_URL=https://dev-api.notifications-service.shapeshift.c # VITE_USER_SERVER_URL=/user-api # VITE_NOTIFICATIONS_SERVER_URL=/notifications-api +# Proxy API +VITE_PROXY_API_BASE_URL=https://dev-api.proxy.shapeshift.com + VITE_FEATURE_WC_DIRECT_CONNECTION=true VITE_FEATURE_CETUS_SWAP=true VITE_FEATURE_MANTLE=true diff --git a/headers/csps/alchemy.ts b/headers/csps/alchemy.ts deleted file mode 100644 index 1c4c8397fa4..00000000000 --- a/headers/csps/alchemy.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Csp } from '../types' - -export const csp: Csp = { - 'connect-src': ['https://*.g.alchemy.com/v2/'], -} diff --git a/headers/csps/chains/ethereum.ts b/headers/csps/chains/ethereum.ts index f627bdf50bf..8e960034175 100644 --- a/headers/csps/chains/ethereum.ts +++ b/headers/csps/chains/ethereum.ts @@ -11,7 +11,6 @@ export const csp: Csp = { env.VITE_ETHEREUM_NODE_URL, env.VITE_UNCHAINED_ETHEREUM_HTTP_URL, env.VITE_UNCHAINED_ETHEREUM_WS_URL, - env.VITE_ALCHEMY_POLYGON_URL, ...FALLBACK_RPC_URLS.ethereum, ], } diff --git a/headers/csps/index.ts b/headers/csps/index.ts index 93be4936c67..a1a1aeeb97b 100644 --- a/headers/csps/index.ts +++ b/headers/csps/index.ts @@ -1,6 +1,5 @@ import { csp as across } from './across' import { csp as agenticChat } from './agenticChat' -import { csp as alchemy } from './alchemy' import { csp as trustwallet } from './assetService/trustwallet' import { csp as base } from './base' import { csp as chainflip } from './chainflip' @@ -108,7 +107,6 @@ export const csps = [ base, agenticChat, hypelab, - alchemy, moralis, chainflip, chatwoot, diff --git a/package.json b/package.json index c928567fd82..673b7c62a60 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,6 @@ "@walletconnect/utils": "^2.20.2", "@xstate/react": "5.0.5", "ai": "^6.0.39", - "alchemy-sdk": "^3.4.1", "axios": "^1.13.5", "axios-cache-interceptor": "^1.11.1", "bech32": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7776c0e47c..082a07b85af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -303,9 +303,6 @@ importers: ai: specifier: ^6.0.39 version: 6.0.105(zod@3.25.76) - alchemy-sdk: - specifier: ^3.4.1 - version: 3.6.5(bufferutil@4.1.0)(utf-8-validate@6.0.6) axios: specifier: ^1.13.5 version: 1.13.6(debug@4.4.3) @@ -4227,9 +4224,6 @@ packages: '@ethersproject/units@5.7.0': resolution: {integrity: sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==} - '@ethersproject/units@5.8.0': - resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} - '@ethersproject/wallet@5.7.0': resolution: {integrity: sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==} @@ -9253,9 +9247,6 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - alchemy-sdk@3.6.5: - resolution: {integrity: sha512-vikvJvExqPoifnOtnIPoANwS2C46Nv44XsEWJz8kd5hrnZrS320GmhKWGyKSgupd8cvudAWv1+76iSr0pjy8DA==} - algo-msgpack-with-bigint@2.1.1: resolution: {integrity: sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ==} engines: {node: '>= 10'} @@ -10590,10 +10581,6 @@ packages: d3-voronoi@1.1.4: resolution: {integrity: sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==} - d@1.0.2: - resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} - engines: {node: '>=0.12'} - damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -11126,26 +11113,15 @@ packages: es-toolkit@1.44.0: resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} - es5-ext@0.10.64: - resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} - engines: {node: '>=0.10'} - es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - es6-iterator@2.0.3: - resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} - es6-promise@4.2.8: resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} es6-promisify@5.0.0: resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - es6-symbol@3.1.4: - resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} - engines: {node: '>=0.12'} - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -11371,10 +11347,6 @@ packages: deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true - esniff@2.0.1: - resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} - engines: {node: '>=0.10'} - espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -11556,9 +11528,6 @@ packages: ev-emitter@2.1.2: resolution: {integrity: sha512-jQ5Ql18hdCQ4qS+RCrbLfz1n+Pags27q5TwMKvZyhp5hh2UULUYZUy1keqj6k6SYsdqIYjnmz7xyyEY0V67B8Q==} - event-emitter@0.3.5: - resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} - eventemitter2@5.0.1: resolution: {integrity: sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==} @@ -11624,9 +11593,6 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - ext@1.7.0: - resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -13684,9 +13650,6 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - next-tick@1.1.0: - resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -15656,9 +15619,6 @@ packages: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} - sturdy-websocket@0.2.1: - resolution: {integrity: sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg==} - style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -16103,9 +16063,6 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type@2.7.3: - resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} - typecast@0.0.1: resolution: {integrity: sha512-L2f5OCLKsJdCjSyN0d5O6CkNxhiC8EQ2XlXnHpWZVNfF+mj2OTaXhAVnP0/7SY/sxO1DHZpOFMpIuGlFUZEGNA==} @@ -17041,10 +16998,6 @@ packages: websocket-stream@5.5.2: resolution: {integrity: sha512-8z49MKIHbGk3C4HtuHWDtYX8mYej1wWabjthC/RupM9ngeukU4IWoM46dgth1UOS/T4/IqgEdCDJuMe2039OQQ==} - websocket@1.0.35: - resolution: {integrity: sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==} - engines: {node: '>=4.0.0'} - whatwg-fetch@2.0.4: resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} @@ -17289,11 +17242,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yaeti@0.0.6: - resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} - engines: {node: '>=0.10.32'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -20376,12 +20324,6 @@ snapshots: '@ethersproject/constants': 5.7.0 '@ethersproject/logger': 5.7.0 - '@ethersproject/units@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/constants': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/wallet@5.7.0': dependencies: '@ethersproject/abstract-provider': 5.7.0 @@ -34515,30 +34457,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alchemy-sdk@3.6.5(bufferutil@4.1.0)(utf-8-validate@6.0.6): - dependencies: - '@ethersproject/abi': 5.8.0 - '@ethersproject/abstract-provider': 5.8.0 - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/contracts': 5.8.0 - '@ethersproject/hash': 5.8.0 - '@ethersproject/networks': 5.8.0 - '@ethersproject/providers': 5.8.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - '@ethersproject/units': 5.8.0 - '@ethersproject/wallet': 5.8.0 - '@ethersproject/web': 5.8.0 - '@solana/web3.js': 1.98.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - axios: 1.13.6(debug@4.4.3) - sturdy-websocket: 0.2.1 - websocket: 1.0.35 - transitivePeerDependencies: - - bufferutil - - debug - - encoding - - supports-color - - utf-8-validate - algo-msgpack-with-bigint@2.1.1: {} algosdk@1.24.1: @@ -36143,11 +36061,6 @@ snapshots: d3-voronoi@1.1.4: {} - d@1.0.2: - dependencies: - es5-ext: 0.10.64 - type: 2.7.3 - damerau-levenshtein@1.0.8: {} dargs@7.0.0: {} @@ -36768,33 +36681,15 @@ snapshots: es-toolkit@1.44.0: {} - es5-ext@0.10.64: - dependencies: - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - esniff: 2.0.1 - next-tick: 1.1.0 - es6-error@4.1.1: optional: true - es6-iterator@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-symbol: 3.1.4 - es6-promise@4.2.8: {} es6-promisify@5.0.0: dependencies: es6-promise: 4.2.8 - es6-symbol@3.1.4: - dependencies: - d: 1.0.2 - ext: 1.7.0 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -37183,13 +37078,6 @@ snapshots: transitivePeerDependencies: - supports-color - esniff@2.0.1: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - event-emitter: 0.3.5 - type: 2.7.3 - espree@10.4.0: dependencies: acorn: 8.16.0 @@ -37610,11 +37498,6 @@ snapshots: ev-emitter@2.1.2: {} - event-emitter@0.3.5: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - eventemitter2@5.0.1: {} eventemitter2@6.4.9: {} @@ -37702,10 +37585,6 @@ snapshots: exsolve@1.0.8: {} - ext@1.7.0: - dependencies: - type: 2.7.3 - extend@3.0.2: {} extension-port-stream@2.1.1: @@ -40349,8 +40228,6 @@ snapshots: netmask@2.0.2: {} - next-tick@1.1.0: {} - nice-try@1.0.5: {} no-case@3.0.4: @@ -42877,8 +42754,6 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 - sturdy-websocket@0.2.1: {} - style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -43359,8 +43234,6 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type@2.7.3: {} - typecast@0.0.1: {} typed-array-buffer@1.0.3: @@ -44733,17 +44606,6 @@ snapshots: - bufferutil - utf-8-validate - websocket@1.0.35: - dependencies: - bufferutil: 4.1.0 - debug: 2.6.9 - es5-ext: 0.10.64 - typedarray-to-buffer: 3.1.5 - utf-8-validate: 5.0.10 - yaeti: 0.0.6 - transitivePeerDependencies: - - supports-color - whatwg-fetch@2.0.4: {} whatwg-fetch@3.6.20: {} @@ -45027,8 +44889,6 @@ snapshots: y18n@5.0.8: {} - yaeti@0.0.6: {} - yallist@3.1.1: {} yallist@4.0.0: {} diff --git a/src/components/Layout/Header/GlobalSearch/AssetSearchResults.tsx b/src/components/Layout/Header/GlobalSearch/AssetSearchResults.tsx index e9c273f494e..605f35e69e7 100644 --- a/src/components/Layout/Header/GlobalSearch/AssetSearchResults.tsx +++ b/src/components/Layout/Header/GlobalSearch/AssetSearchResults.tsx @@ -1,4 +1,4 @@ -import { List } from '@chakra-ui/react' +import { Flex, List, Spinner } from '@chakra-ui/react' import type { Asset } from '@shapeshiftoss/types' import { memo, useMemo } from 'react' @@ -9,17 +9,25 @@ import { SearchEmpty } from '@/components/StakingVaults/SearchEmpty' export type AssetSearchResultsProps = { results: Asset[] searchQuery: string - isSearching: boolean + isLoading: boolean onClickResult: (item: Asset) => void } export const AssetSearchResults = memo( - ({ results, searchQuery, isSearching, onClickResult }: AssetSearchResultsProps) => { + ({ results, searchQuery, isLoading, onClickResult }: AssetSearchResultsProps) => { const noResults = useMemo(() => { return !results.length }, [results.length]) - if (isSearching && noResults) { + if (isLoading && noResults) { + return ( + + + + ) + } + + if (!isLoading && noResults && searchQuery) { return } diff --git a/src/components/Layout/Header/GlobalSearch/GlobalSearchModal.tsx b/src/components/Layout/Header/GlobalSearch/GlobalSearchModal.tsx index 148eeed7369..a7279f4b03e 100644 --- a/src/components/Layout/Header/GlobalSearch/GlobalSearchModal.tsx +++ b/src/components/Layout/Header/GlobalSearch/GlobalSearchModal.tsx @@ -9,9 +9,8 @@ import { useUpdateEffect, } from '@chakra-ui/react' import { captureException, setContext } from '@sentry/react' -import { solanaChainId, toAssetId } from '@shapeshiftoss/caip' -import type { Asset, KnownChainIds } from '@shapeshiftoss/types' -import { getAssetNamespaceFromChainId, makeAsset } from '@shapeshiftoss/utils' +import type { Asset } from '@shapeshiftoss/types' +import { makeAsset } from '@shapeshiftoss/utils' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import MultiRef from 'react-multi-ref' import { useNavigate } from 'react-router-dom' @@ -22,8 +21,7 @@ import { AssetSearchResults } from './AssetSearchResults' import { GlobalFilter } from '@/components/StakingVaults/GlobalFilter' import { useGetCustomTokensQuery } from '@/components/TradeAssetSearch/hooks/useGetCustomTokensQuery' import { useModalRegistration } from '@/context/ModalStackProvider' -import { ALCHEMY_SDK_SUPPORTED_CHAIN_IDS } from '@/lib/alchemySdkInstance' -import { isSome } from '@/lib/utils' +import { CUSTOM_TOKEN_IMPORT_SUPPORTED_CHAIN_IDS } from '@/lib/customTokenImportSupportedChainIds' import { assets as assetsSlice } from '@/state/slices/assetsSlice/assetsSlice' import { selectAssets, selectAssetsBySearchQuery } from '@/state/slices/selectors' import { tradeInput } from '@/state/slices/tradeInputSlice/tradeInputSlice' @@ -47,9 +45,7 @@ export const GlobalSearchModal = memo( const navigate = useNavigate() const dispatch = useAppDispatch() const searchFilter = useMemo(() => ({ searchQuery, limit: 10 }), [searchQuery]) - const results = useAppSelector(state => selectAssetsBySearchQuery(state, searchFilter)) - const assetResults = results - const resultsCount = results.length + const searchAssets = useAppSelector(state => selectAssetsBySearchQuery(state, searchFilter)) const isMac = useMemo(() => /Mac/.test(navigator.userAgent), []) const handleClose = useCallback(() => { setSearchQuery('') @@ -61,61 +57,35 @@ export const GlobalSearchModal = memo( onClose: handleClose, }) - const customTokenSupportedChainIds = useMemo(() => { - // Solana _is_ supported by Alchemy, but not by the SDK - return [...ALCHEMY_SDK_SUPPORTED_CHAIN_IDS, solanaChainId] - }, []) - const { data: customTokens, isLoading: isLoadingCustomTokens } = useGetCustomTokensQuery({ contractAddress: searchQuery, - chainIds: customTokenSupportedChainIds, + chainIds: CUSTOM_TOKEN_IMPORT_SUPPORTED_CHAIN_IDS, }) const customAssets = useMemo(() => { if (!customTokens?.length) return [] - // Do not move me to a regular useSelector(), as this is reactive on the *whole* assets set and would make this component extremely reactive for no reason const assetsById = selectAssets(store.getState()) - const assets = customTokens - .map(metaData => { - if (!metaData) return null - const { name, symbol, decimals, logo, chainId, contractAddress } = metaData - - if (!name || !symbol || !decimals) return null - - const assetId = toAssetId({ - chainId, - assetNamespace: getAssetNamespaceFromChainId(chainId as KnownChainIds), - assetReference: contractAddress, - }) - - const minimalAsset = { - assetId, - name, - symbol, - precision: decimals, - icon: logo ?? undefined, - } - - return makeAsset(assetsById, minimalAsset) - }) - .filter(isSome) - - return assets + return customTokens + .filter(token => !assetsById[token.assetId]) + .map(token => makeAsset(assetsById, token)) }, [customTokens]) useEffect(() => { customAssets.forEach(asset => { - // Do not move me to a regular useSelector(), as this is reactive on the *whole* assets set and would make this component extremely reactive for no reason - const assetsById = selectAssets(store.getState()) - - if (!assetsById[asset.assetId]) { - dispatch(assetsSlice.actions.upsertAsset(asset)) - } + dispatch(assetsSlice.actions.upsertAsset(asset)) }) }, [customAssets, dispatch]) + const results = useMemo(() => { + if (!customAssets.length) return searchAssets + const existingIds = new Set(searchAssets.map(a => a.assetId)) + return searchAssets.concat(customAssets.filter(a => !existingIds.has(a.assetId))) + }, [searchAssets, customAssets]) + + const resultsCount = results.length + useEffect(() => { if (!searchQuery) setActiveIndex(0) }, [searchQuery]) @@ -203,11 +173,6 @@ export const GlobalSearchModal = memo( setActiveIndex(0) }, [searchQuery]) - const isSearching = useMemo( - () => searchQuery.length > 0 || isLoadingCustomTokens, - [searchQuery.length, isLoadingCustomTokens], - ) - return ( @@ -230,9 +195,9 @@ export const GlobalSearchModal = memo( diff --git a/src/components/TradeAssetSearch/components/SearchTermAssetList.tsx b/src/components/TradeAssetSearch/components/SearchTermAssetList.tsx index 86543a8928e..47ffadd1efe 100644 --- a/src/components/TradeAssetSearch/components/SearchTermAssetList.tsx +++ b/src/components/TradeAssetSearch/components/SearchTermAssetList.tsx @@ -1,9 +1,8 @@ import { Box, useMediaQuery } from '@chakra-ui/react' import type { AssetId, ChainId } from '@shapeshiftoss/caip' -import { fromAssetId, isNft, solanaChainId, toAssetId } from '@shapeshiftoss/caip' -import type { Asset, KnownChainIds } from '@shapeshiftoss/types' -import type { MinimalAsset } from '@shapeshiftoss/utils' -import { bnOrZero, getAssetNamespaceFromChainId, makeAsset } from '@shapeshiftoss/utils' +import { fromAssetId, isNft } from '@shapeshiftoss/caip' +import type { Asset } from '@shapeshiftoss/types' +import { bnOrZero, makeAsset } from '@shapeshiftoss/utils' import { orderBy } from 'lodash' import { useMemo } from 'react' @@ -12,8 +11,8 @@ import { useGetCustomTokensQuery } from '../hooks/useGetCustomTokensQuery' import { AssetList } from '@/components/AssetSearch/components/AssetList' import { Text } from '@/components/Text' -import { ALCHEMY_SDK_SUPPORTED_CHAIN_IDS } from '@/lib/alchemySdkInstance' import { searchAssets } from '@/lib/assetSearch' +import { CUSTOM_TOKEN_IMPORT_SUPPORTED_CHAIN_IDS } from '@/lib/customTokenImportSupportedChainIds' import { isSome } from '@/lib/utils' import { isContractAddress } from '@/lib/utils/isContractAddress' import { @@ -22,8 +21,8 @@ import { selectRelatedAssetIdsByAssetIdInclusive, selectWalletConnectedChainIds, } from '@/state/slices/common-selectors' -import { selectPrimaryAssets } from '@/state/slices/selectors' -import { useAppSelector } from '@/state/store' +import { selectAssets } from '@/state/slices/selectors' +import { store, useAppSelector } from '@/state/store' import { breakpoints } from '@/theme/theme' export type SearchTermAssetListProps = { @@ -53,23 +52,17 @@ export const SearchTermAssetList = ({ ) const relatedAssetIdsById = useAppSelector(selectRelatedAssetIdsByAssetIdInclusive) const portfolioUserCurrencyBalances = useAppSelector(selectPortfolioUserCurrencyBalances) - const assetsById = useAppSelector(selectPrimaryAssets) const walletConnectedChainIds = useAppSelector(selectWalletConnectedChainIds) - const customTokenSupportedChainIds = useMemo(() => { - // Solana _is_ supported by Alchemy, but not by the SDK - return [...ALCHEMY_SDK_SUPPORTED_CHAIN_IDS, solanaChainId] - }, []) - const chainIds = useMemo(() => { if (activeChainId === 'All') { - return customTokenSupportedChainIds - } else if (customTokenSupportedChainIds.includes(activeChainId)) { + return CUSTOM_TOKEN_IMPORT_SUPPORTED_CHAIN_IDS + } else if (CUSTOM_TOKEN_IMPORT_SUPPORTED_CHAIN_IDS.includes(activeChainId)) { return [activeChainId] } else { return [] } - }, [activeChainId, customTokenSupportedChainIds]) + }, [activeChainId]) const { data: customTokens, isLoading: isLoadingCustomTokens } = useGetCustomTokensQuery({ contractAddress: searchString, @@ -114,32 +107,14 @@ export const SearchTermAssetList = ({ }, [assetsForChain]) const customAssets: Asset[] = useMemo(() => { - return (customTokens ?? []) - .map(metaData => { - if (!metaData) return null - const { name, symbol, decimals, logo } = metaData - // If we can't get all the information we need to create an Asset, don't allow the custom token - if (!name || !symbol || !decimals) return null - const assetId = toAssetId({ - chainId: metaData.chainId, - assetNamespace: getAssetNamespaceFromChainId(metaData.chainId as KnownChainIds), - assetReference: metaData.contractAddress, - }) - - // Skip if we already have this asset - if (assetIdMap[assetId] !== undefined) return null - - const minimalAsset: MinimalAsset = { - assetId, - name, - symbol, - precision: decimals, - icon: logo ?? undefined, - } - return makeAsset(assetsById, minimalAsset) - }) - .filter(isSome) - }, [assetIdMap, assetsById, customTokens]) + if (!customTokens?.length) return [] + + const assetsById = selectAssets(store.getState()) + + return customTokens + .filter(token => !assetsById[token.assetId]) + .map(token => makeAsset(assetsById, token)) + }, [customTokens]) const searchTermAssets = useMemo(() => { const filteredAssets: Asset[] = (() => { diff --git a/src/components/TradeAssetSearch/hooks/useGetCustomTokensQuery.tsx b/src/components/TradeAssetSearch/hooks/useGetCustomTokensQuery.tsx index 49e305afff9..dbefed5352b 100644 --- a/src/components/TradeAssetSearch/hooks/useGetCustomTokensQuery.tsx +++ b/src/components/TradeAssetSearch/hooks/useGetCustomTokensQuery.tsx @@ -1,24 +1,24 @@ -import { Metaplex } from '@metaplex-foundation/js' import type { ChainId } from '@shapeshiftoss/caip' -import { solanaChainId } from '@shapeshiftoss/caip' -import { isEvmChainId } from '@shapeshiftoss/chain-adapters' -import { Connection, PublicKey } from '@solana/web3.js' +import { toAssetId } from '@shapeshiftoss/caip' +import type { KnownChainIds } from '@shapeshiftoss/types' +import type { MinimalAsset } from '@shapeshiftoss/utils' +import { getAssetNamespaceFromChainId } from '@shapeshiftoss/utils' import type { UseQueryResult } from '@tanstack/react-query' import { skipToken, useQueries } from '@tanstack/react-query' -import type { TokenMetadataResponse } from 'alchemy-sdk' +import axios, { isAxiosError } from 'axios' import { useCallback, useMemo } from 'react' import { isAddress } from 'viem' import { getConfig } from '@/config' import { useFeatureFlag } from '@/hooks/useFeatureFlag/useFeatureFlag' -import { getAlchemyInstanceByChainId } from '@/lib/alchemySdkInstance' import { isSolanaAddress } from '@/lib/utils/isSolanaAddress' import { mergeQueryOutputs } from '@/react-queries/helpers' -type TokenMetadata = TokenMetadataResponse & { - chainId: ChainId - contractAddress: string - price: string +type TokenMetadataResponse = { + name: string + symbol: string + decimals: number | null + logo: string | null } type UseGetCustomTokensQueryProps = { @@ -37,38 +37,43 @@ const getCustomTokenQueryKey = (contractAddress: string, chainId: ChainId): Cust export const useGetCustomTokensQuery = ({ contractAddress, chainIds, -}: UseGetCustomTokensQueryProps): UseQueryResult<(TokenMetadata | undefined)[], Error[]> => { +}: UseGetCustomTokensQueryProps): UseQueryResult => { const customTokenImportEnabled = useFeatureFlag('CustomTokenImport') - const getEvmTokenMetadata = useCallback( - async (chainId: ChainId) => { - const alchemy = getAlchemyInstanceByChainId(chainId) - const tokenMetadataResponse = await alchemy.core.getTokenMetadata(contractAddress) - return { ...tokenMetadataResponse, chainId, contractAddress, price: '0' } - }, - [contractAddress], - ) + const getCustomToken = useCallback( + async (chainId: ChainId): Promise => { + try { + const { data } = await axios.get( + `${getConfig().VITE_PROXY_API_BASE_URL}/api/v1/tokens/metadata`, + { + params: { + chainId, + tokenAddress: contractAddress, + }, + }, + ) - const getSolanaTokenMetadata = useCallback( - async (mintAddress: string): Promise => { - const solanaRpcUrl = `${getConfig().VITE_ALCHEMY_SOLANA_BASE_URL}/${ - getConfig().VITE_ALCHEMY_API_KEY - }` - const connection = new Connection(solanaRpcUrl) - const metaplex = Metaplex.make(connection) - const metadata = await metaplex.nfts().findByMint({ mintAddress: new PublicKey(mintAddress) }) - - return { - name: metadata.name, - symbol: metadata.symbol, - decimals: metadata.mint.currency.decimals, - chainId: solanaChainId, - contractAddress: mintAddress, - price: '0', - logo: metadata.json?.image ?? '', + if (data.decimals === null) return undefined + + const assetId = toAssetId({ + chainId, + assetNamespace: getAssetNamespaceFromChainId(chainId as KnownChainIds), + assetReference: contractAddress, + }) + + return { + assetId, + name: data.name, + symbol: data.symbol, + precision: data.decimals, + icon: data.logo ?? undefined, + } + } catch (err) { + if (isAxiosError(err) && (err.response?.status ?? 0) >= 500) throw err + return undefined } }, - [], + [contractAddress], ) const isValidEvmAddress = useMemo( @@ -80,26 +85,15 @@ export const useGetCustomTokensQuery = ({ const getQueryFn = useCallback( (chainId: ChainId) => () => { - if (isValidSolanaAddress && chainId === solanaChainId) { - return getSolanaTokenMetadata(contractAddress) - } else if (isValidEvmAddress && isEvmChainId(chainId)) { - return getEvmTokenMetadata(chainId) - } else { - return skipToken - } + if (!isValidEvmAddress && !isValidSolanaAddress) return skipToken + return getCustomToken(chainId) }, - [ - contractAddress, - getEvmTokenMetadata, - getSolanaTokenMetadata, - isValidEvmAddress, - isValidSolanaAddress, - ], + [getCustomToken, isValidEvmAddress, isValidSolanaAddress], ) - const isTokenMetadata = ( - result: TokenMetadata | typeof skipToken | undefined, - ): result is TokenMetadata => result !== undefined && result !== skipToken + const isMinimalAsset = ( + result: MinimalAsset | typeof skipToken | undefined, + ): result is MinimalAsset => result !== undefined && result !== skipToken const customTokenQueries = useQueries({ queries: chainIds.map(chainId => ({ @@ -108,7 +102,7 @@ export const useGetCustomTokensQuery = ({ enabled: customTokenImportEnabled, staleTime: Infinity, })), - combine: queries => mergeQueryOutputs(queries, results => results.filter(isTokenMetadata)), + combine: queries => mergeQueryOutputs(queries, results => results.filter(isMinimalAsset)), }) return customTokenQueries diff --git a/src/config.ts b/src/config.ts index 225db766027..4b991116a7f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -95,7 +95,6 @@ const validators = { VITE_NEAR_NODE_URL_FALLBACK_1: url({ default: '' }), VITE_NEAR_NODE_URL_FALLBACK_2: url({ default: '' }), VITE_FASTNEAR_API_URL: url(), - VITE_ALCHEMY_POLYGON_URL: url(), VITE_KEEPKEY_VERSIONS_URL: url(), VITE_KEEPKEY_LATEST_RELEASE_URL: url(), VITE_WALLET_MIGRATION_URL: url(), @@ -212,9 +211,7 @@ const validators = { VITE_FEATURE_PORTALS_SWAPPER: bool({ default: false }), VITE_FEATURE_ONE_INCH: bool({ default: false }), VITE_SENTRY_DSN_URL: url(), - VITE_ALCHEMY_API_KEY: str(), VITE_MORALIS_API_KEY: str(), - VITE_ALCHEMY_SOLANA_BASE_URL: url(), VITE_CHATWOOT_TOKEN: str(), VITE_CHATWOOT_URL: str(), VITE_FEATURE_CHATWOOT: bool({ default: false }), @@ -241,6 +238,7 @@ const validators = { VITE_FEATURE_RUNEPOOL_WITHDRAW: bool({ default: false }), VITE_FEATURE_MARKETS: bool({ default: false }), VITE_PORTALS_BASE_URL: url(), + VITE_PROXY_API_BASE_URL: url({ default: 'https://api.proxy.shapeshift.com' }), VITE_ZERION_BASE_URL: url(), VITE_FEATURE_PHANTOM_WALLET: bool({ default: false }), VITE_FEATURE_FOX_PAGE_FOX_SECTION: bool({ default: true }), diff --git a/src/index.tsx b/src/index.tsx index 566c59eadc7..983145e6e70 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -112,13 +112,7 @@ if (shouldEnableSentry) { [500, 599], // Only server errors, not client errors ], - denyUrls: [ - 'alchemy.com', - 'snapshot.org', - 'coingecko.com', - 'coincap.io', - 'coinmarketcap.com', - ], + denyUrls: ['snapshot.org', 'coingecko.com', 'coincap.io', 'coinmarketcap.com'], }), browserApiErrorsIntegration(), breadcrumbsIntegration(), diff --git a/src/lib/alchemySdkInstance.ts b/src/lib/alchemySdkInstance.ts deleted file mode 100644 index 726b94c718f..00000000000 --- a/src/lib/alchemySdkInstance.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { ChainId } from '@shapeshiftoss/caip' -import { - arbitrumChainId, - baseChainId, - ethChainId, - optimismChainId, - polygonChainId, -} from '@shapeshiftoss/caip' -import { Alchemy, Network } from 'alchemy-sdk' - -import { getConfig } from '@/config' - -const alchemyInstanceMap: Map = new Map() - -export const ALCHEMY_SDK_SUPPORTED_CHAIN_IDS = [ - ethChainId, - polygonChainId, - optimismChainId, - arbitrumChainId, - baseChainId, -] as const - -export const getAlchemyInstanceByChainId = (chainId: ChainId): Alchemy => { - // Note, make sure to not unify this guy and `instance` below. - // This is a set, not an array, calling .set() will not automagically update `maybeInstance` to the new reference - // This should probably be an Array for dev QoL but cba to change it as part of this eslint PR - const maybeInstance = alchemyInstanceMap.get(chainId) - if (maybeInstance) return maybeInstance - - const apiKey = (() => { - switch (chainId) { - case polygonChainId: - case ethChainId: - case optimismChainId: - case arbitrumChainId: - case baseChainId: - return getConfig().VITE_ALCHEMY_API_KEY - default: - return undefined - } - })() - - const network = (() => { - switch (chainId) { - case polygonChainId: - return Network.MATIC_MAINNET - case ethChainId: - return Network.ETH_MAINNET - case optimismChainId: - return Network.OPT_MAINNET - case arbitrumChainId: - return Network.ARB_MAINNET - case baseChainId: - return Network.BASE_MAINNET - default: - return undefined - } - })() - - if (!apiKey || !network) throw new Error(`Cannot get Alchemy Instance for chainId: ${chainId}`) - - const config = { - apiKey, - network, - } - - alchemyInstanceMap.set(chainId, new Alchemy(config)) - - const instance = alchemyInstanceMap.get(chainId) - - if (!instance) throw new Error(`Cannot get Alchemy Instance for chainId: ${chainId}`) - - return instance -} diff --git a/src/lib/assetSearch/deduplicateAssets.test.ts b/src/lib/assetSearch/deduplicateAssets.test.ts index 5e39e5e9b83..1e41863d15c 100644 --- a/src/lib/assetSearch/deduplicateAssets.test.ts +++ b/src/lib/assetSearch/deduplicateAssets.test.ts @@ -87,10 +87,10 @@ describe('deduplicateAssets', () => { it('prefers primary AXLUSDC over non-primary when both have exact match', () => { // Create AXLUSDC assets with their own family and a primary - const axlusdcFamily = 'eip155:1/erc20:axlusdc-family' + const axlusdcFamily = 'eip155:1/erc20:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' const axlusdcPrimary = { ...AXLUSDC_OPTIMISM, - assetId: 'eip155:1/erc20:axlusdc-primary' as const, + assetId: 'eip155:1/erc20:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as const, relatedAssetKey: axlusdcFamily, isPrimary: true, } @@ -109,10 +109,10 @@ describe('deduplicateAssets', () => { it('returns primary even when non-primary exact match comes first in array', () => { // Create AXLUSDC assets with primary coming second in array - const axlusdcFamily = 'eip155:1/erc20:axlusdc-family' + const axlusdcFamily = 'eip155:1/erc20:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' const axlusdcPrimary = { ...AXLUSDC_OPTIMISM, - assetId: 'eip155:1/erc20:axlusdc-primary' as const, + assetId: 'eip155:1/erc20:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as const, relatedAssetKey: axlusdcFamily, isPrimary: true, } @@ -130,13 +130,13 @@ describe('deduplicateAssets', () => { it('shows both AXLUSDC and AXLUSDT groups when searching "axlusd"', () => { // Create separate families for AXLUSDC and AXLUSDT - const axlusdcFamily = 'eip155:1/erc20:axlusdc-family' - const axlusdtFamily = 'eip155:1/erc20:axlusdt-family' + const axlusdcFamily = 'eip155:1/erc20:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + const axlusdtFamily = 'eip155:1/erc20:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' const assets = [ { ...AXLUSDC_OPTIMISM, relatedAssetKey: axlusdcFamily, isPrimary: false }, { ...AXLUSDC_OPTIMISM, - assetId: 'eip155:1/erc20:axlusdc-primary' as const, + assetId: 'eip155:1/erc20:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as const, relatedAssetKey: axlusdcFamily, isPrimary: true, }, @@ -149,7 +149,7 @@ describe('deduplicateAssets', () => { }, { ...AXLUSDC_ARBITRUM, - assetId: 'eip155:1/erc20:axlusdt-primary' as const, + assetId: 'eip155:1/erc20:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' as const, symbol: 'AXLUSDT', name: 'Axelar USDT', relatedAssetKey: axlusdtFamily, @@ -207,6 +207,16 @@ describe('deduplicateAssets', () => { expect(result[0].isPrimary).toBe(true) }) + it('shows each chain variant independently when searching by contract address', () => { + // AXLUSDC_OPTIMISM and AXLUSDC_ARBITRUM share address 0xeb466342c4d449bc9f53a865d5cb90586f405215 + const assets = [AXLUSDC_OPTIMISM, AXLUSDC_ARBITRUM] + + const result = deduplicateAssets(assets, '0xeb466342c4d449bc9f53a865d5cb90586f405215') + + expect(result).toHaveLength(2) + expect(result.map(a => a.chainId).sort()).toEqual(['eip155:10', 'eip155:42161'].sort()) + }) + describe('name search grouping', () => { it('groups "Axelar Bridged" name search results by family', () => { // AXLUSDC and AXLUSDT are in different families (USDC and USDT families) diff --git a/src/lib/assetSearch/deduplicateAssets.ts b/src/lib/assetSearch/deduplicateAssets.ts index 07c7e9a9efe..57fdf3347fb 100644 --- a/src/lib/assetSearch/deduplicateAssets.ts +++ b/src/lib/assetSearch/deduplicateAssets.ts @@ -1,3 +1,6 @@ +import { fromAssetId } from '@shapeshiftoss/caip' + +import { isContractAddress } from '../utils/isContractAddress' import type { SearchableAsset } from './types' import { isExactMatch } from './utils' @@ -26,7 +29,13 @@ export const deduplicateAssets = ( : false for (const asset of assets) { - const familyKey = asset.relatedAssetKey ?? asset.assetId + // When the search is a valid contract address matching this asset's assetReference, + // use assetId as the family key so each chain variant is shown independently rather than + // collapsed into its family via relatedAssetKey. + const matchedByAddress = + isContractAddress(searchLower) && + fromAssetId(asset.assetId).assetReference.toLowerCase() === searchLower + const familyKey = matchedByAddress ? asset.assetId : asset.relatedAssetKey ?? asset.assetId const existing = familyToAsset.get(familyKey) const isExact = hasExactSymbolMatch && isExactMatch(searchLower, asset.symbol) diff --git a/src/lib/customTokenImportSupportedChainIds.ts b/src/lib/customTokenImportSupportedChainIds.ts new file mode 100644 index 00000000000..3664c292743 --- /dev/null +++ b/src/lib/customTokenImportSupportedChainIds.ts @@ -0,0 +1,18 @@ +import type { ChainId } from '@shapeshiftoss/caip' +import { + arbitrumChainId, + baseChainId, + ethChainId, + optimismChainId, + polygonChainId, + solanaChainId, +} from '@shapeshiftoss/caip' + +export const CUSTOM_TOKEN_IMPORT_SUPPORTED_CHAIN_IDS: ChainId[] = [ + ethChainId, + polygonChainId, + optimismChainId, + arbitrumChainId, + baseChainId, + solanaChainId, +] diff --git a/src/state/slices/common-selectors.ts b/src/state/slices/common-selectors.ts index aecf987d99f..66fca10c4fe 100644 --- a/src/state/slices/common-selectors.ts +++ b/src/state/slices/common-selectors.ts @@ -595,11 +595,13 @@ export const selectAssetsBySearchQuery = createCachedSelector( const sortedAssets = useAllAssets ? allAssets : primaryAssets - // Filter out spam tokens (low market cap) but keep assets with no market data - const filteredAssets = sortedAssets.filter(asset => { - const marketCap = bnOrZero(marketDataUsd[asset.assetId]?.marketCap) - return marketCap.isZero() || marketCap.gte(MINIMUM_MARKET_CAP_THRESHOLD) - }) + // Filter out spam tokens (low market cap) but keep assets with no market data, unless user is searching by contract address + const filteredAssets = isContractAddressSearch + ? sortedAssets + : sortedAssets.filter(asset => { + const marketCap = bnOrZero(marketDataUsd[asset.assetId]?.marketCap) + return marketCap.isZero() || marketCap.gte(MINIMUM_MARKET_CAP_THRESHOLD) + }) const matchedAssets = searchAssets(searchQuery, filteredAssets) const deduplicated = deduplicateAssets(matchedAssets, searchQuery) diff --git a/src/utils/sentry/httpclient.ts b/src/utils/sentry/httpclient.ts index 4dde3b8378a..394a3f42fbf 100644 --- a/src/utils/sentry/httpclient.ts +++ b/src/utils/sentry/httpclient.ts @@ -45,7 +45,7 @@ interface HttpClientOptions { * This array can contain strings, not regular expressions. * If omitted, no filtering by requests blacklist will be applied. * - * Example: ['snapshot.org', 'alchemy'] + * Example: ['snapshot.org'] */ denyUrls?: string[] } diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index df73f5b8306..c82708f9c12 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -83,14 +83,11 @@ interface ImportMetaEnv { readonly VITE_KEEPKEY_VERSIONS_URL: string readonly VITE_KEEPKEY_LATEST_RELEASE_URL: string readonly VITE_COWSWAP_BASE_URL: string - readonly VITE_ALCHEMY_POLYGON_URL: string readonly VITE_TOKEMAK_STATS_URL: string readonly VITE_COINCAP_API_KEY: string readonly VITE_EXCHANGERATEHOST_BASE_URL: string readonly VITE_EXCHANGERATEHOST_API_KEY: string - readonly VITE_ALCHEMY_API_KEY: string readonly VITE_MORALIS_API_KEY: string - readonly VITE_ALCHEMY_SOLANA_BASE_URL: string readonly VITE_BOARDROOM_API_BASE_URL: string readonly VITE_BOARDROOM_APP_BASE_URL: string readonly VITE_SNAPSHOT_BASE_URL: string @@ -114,6 +111,7 @@ interface ImportMetaEnv { readonly VITE_WALLET_CONNECT_WALLET_PROJECT_ID: string readonly VITE_WALLET_CONNECT_RELAY_URL: string readonly VITE_PORTALS_BASE_URL: string + readonly VITE_PROXY_API_BASE_URL: string readonly VITE_SNAP_ID: string readonly VITE_SNAP_VERSION: string readonly VITE_EXPERIMENTAL_CUSTOM_SEND_NONCE: string