Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
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/
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion headers/csps/chains/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,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,
'https://eth.llamarpc.com',
],
}
1 change: 1 addition & 0 deletions headers/csps/customTokenImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export const csp: Csp = {
// Common metadata sources
'https://arweave.net/',
'https://*.arweave.net/',
'https://api.proxy.shapeshift.com',
],
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
useUpdateEffect,
} from '@chakra-ui/react'
import { captureException, setContext } from '@sentry/react'
import { solanaChainId, toAssetId } from '@shapeshiftoss/caip'
import { toAssetId } from '@shapeshiftoss/caip'
import type { Asset, KnownChainIds } from '@shapeshiftoss/types'
import { getAssetNamespaceFromChainId, makeAsset } from '@shapeshiftoss/utils'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
Expand All @@ -22,7 +22,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 { CUSTOM_TOKEN_IMPORT_SUPPORTED_CHAIN_IDS } from '@/lib/customTokenImportSupportedChainIds'
import { isSome } from '@/lib/utils'
import { assets as assetsSlice } from '@/state/slices/assetsSlice/assetsSlice'
import { selectAssets, selectAssetsBySearchQuery } from '@/state/slices/selectors'
Expand Down Expand Up @@ -61,14 +61,9 @@ 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(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, useMediaQuery } from '@chakra-ui/react'
import type { AssetId, ChainId } from '@shapeshiftoss/caip'
import { fromAssetId, isNft, solanaChainId, toAssetId } from '@shapeshiftoss/caip'
import { fromAssetId, isNft, toAssetId } from '@shapeshiftoss/caip'
import type { Asset, KnownChainIds } from '@shapeshiftoss/types'
import type { MinimalAsset } from '@shapeshiftoss/utils'
import { bnOrZero, getAssetNamespaceFromChainId, makeAsset } from '@shapeshiftoss/utils'
Expand All @@ -12,8 +12,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 {
Expand Down Expand Up @@ -56,20 +56,15 @@ export const SearchTermAssetList = ({
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,
Expand Down
80 changes: 41 additions & 39 deletions src/components/TradeAssetSearch/hooks/useGetCustomTokensQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
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 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 & {
type TokenMetadata = {
name?: string
symbol?: string
decimals?: number
logo?: string
chainId: ChainId
contractAddress: string
price: string
Expand All @@ -40,35 +41,42 @@ export const useGetCustomTokensQuery = ({
}: UseGetCustomTokensQueryProps): UseQueryResult<(TokenMetadata | undefined)[], Error[]> => {
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 getTokenMetadata = useCallback(
async (chainId: ChainId): Promise<TokenMetadata | undefined> => {
try {
const { data } = await axios.get<{
chainId: ChainId
tokenAddress: string
name?: string
symbol?: string
decimals?: number
logo?: string
}>(`${getConfig().VITE_PROXY_API_BASE_URL}/api/v1/tokens/metadata`, {
params: {
chainId,
tokenAddress: contractAddress,
},
timeout: 10_000,
})

const getSolanaTokenMetadata = useCallback(
async (mintAddress: string): Promise<TokenMetadata> => {
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: data.name,
symbol: data.symbol,
decimals: data.decimals,
chainId: data.chainId,
contractAddress: data.tokenAddress,
price: '0',
logo: data.logo,
}
} catch (e) {
if (isAxiosError(e) && (e.response?.status === 404 || e.response?.status === 422)) {
return undefined
}

return {
name: metadata.name,
symbol: metadata.symbol,
decimals: metadata.mint.currency.decimals,
chainId: solanaChainId,
contractAddress: mintAddress,
price: '0',
logo: metadata.json?.image ?? '',
throw e
}
},
[],
[contractAddress],
)

const isValidEvmAddress = useMemo(
Expand All @@ -81,20 +89,14 @@ export const useGetCustomTokensQuery = ({
const getQueryFn = useCallback(
(chainId: ChainId) => () => {
if (isValidSolanaAddress && chainId === solanaChainId) {
return getSolanaTokenMetadata(contractAddress)
return getTokenMetadata(chainId)
} else if (isValidEvmAddress && isEvmChainId(chainId)) {
return getEvmTokenMetadata(chainId)
return getTokenMetadata(chainId)
} else {
return skipToken
}
},
[
contractAddress,
getEvmTokenMetadata,
getSolanaTokenMetadata,
isValidEvmAddress,
isValidSolanaAddress,
],
[getTokenMetadata, isValidEvmAddress, isValidSolanaAddress],
)

const isTokenMetadata = (
Expand Down
4 changes: 1 addition & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,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(),
Expand Down Expand Up @@ -214,9 +213,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 }),
Expand All @@ -243,6 +240,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 }),
Expand Down
74 changes: 0 additions & 74 deletions src/lib/alchemySdkInstance.ts

This file was deleted.

18 changes: 18 additions & 0 deletions src/lib/customTokenImportSupportedChainIds.ts
Original file line number Diff line number Diff line change
@@ -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,
]
4 changes: 1 addition & 3 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading