From 1ea9f7d0b8a138376a10de7287cf0ed2254a7760 Mon Sep 17 00:00:00 2001 From: tomiir Date: Tue, 21 Jan 2025 13:02:32 +0100 Subject: [PATCH] chore: add check for suported networks on blockchain api controller (#3682) --- .changeset/plenty-colts-rush.md | 23 ++ .../src/pages/library/multichain-basic.tsx | 19 +- packages/appkit/src/client.ts | 7 + .../controllers/BlockchainApiController.ts | 204 ++++++++++++++++-- .../core/src/controllers/OnRampController.ts | 2 +- .../core/src/controllers/SwapController.ts | 2 +- 6 files changed, 238 insertions(+), 19 deletions(-) create mode 100644 .changeset/plenty-colts-rush.md diff --git a/.changeset/plenty-colts-rush.md b/.changeset/plenty-colts-rush.md new file mode 100644 index 0000000000..926ff7d74b --- /dev/null +++ b/.changeset/plenty-colts-rush.md @@ -0,0 +1,23 @@ +--- +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit-scaffold-ui': patch +'@reown/appkit': patch +'@reown/appkit-common': patch +'@reown/appkit-core': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-experimental': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Prevents calls to Blockchain Api that would fail due to lack of support. Initialize supported list on AppKit initialization' diff --git a/apps/laboratory/src/pages/library/multichain-basic.tsx b/apps/laboratory/src/pages/library/multichain-basic.tsx index 757f7a1db2..fd406fcf5a 100644 --- a/apps/laboratory/src/pages/library/multichain-basic.tsx +++ b/apps/laboratory/src/pages/library/multichain-basic.tsx @@ -1,4 +1,4 @@ -import { mainnet } from '@reown/appkit/networks' +import { type AppKitNetwork, defineChain, mainnet } from '@reown/appkit/networks' import { createAppKit } from '@reown/appkit/react' import { AppKitButtons } from '../../components/AppKitButtons' @@ -7,10 +7,23 @@ import { UpaTests } from '../../components/UPA/UpaTests' import { ConstantsUtil } from '../../utils/ConstantsUtil' import { ThemeStore } from '../../utils/StoreUtil' -const networks = ConstantsUtil.AllNetworks +const networks = [ + ...ConstantsUtil.AllNetworks, + defineChain({ + id: '91b171bb158e2d3848fa23a9f1c25182', + name: 'Polkadot', + network: 'polkadot', + nativeCurrency: { name: 'Polkadot', symbol: 'DOT', decimals: 18 }, + rpcUrls: { + default: { http: ['https://rpc.polkadot.io'] } + }, + chainNamespace: 'polkadot', + caipNetworkId: 'polkadot:mainnet' + }) +] const modal = createAppKit({ - networks, + networks: networks as [AppKitNetwork, ...AppKitNetwork[]], defaultNetwork: mainnet, projectId: ConstantsUtil.ProjectId, metadata: ConstantsUtil.Metadata diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index 48c1724b9d..9b4a4e8595 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -766,10 +766,17 @@ export class AppKit { } } + private async initializeBlockchainApiController(options: AppKitOptions) { + await BlockchainApiController.getSupportedNetworks({ + projectId: options.projectId + }) + } + private initControllers(options: AppKitOptionsWithSdk) { this.initializeOptionsController(options) this.initializeChainController(options) this.initializeThemeController(options) + this.initializeBlockchainApiController(options) if (options.excludeWalletIds) { ApiController.initializeExcludedWalletRdns({ ids: options.excludeWalletIds }) diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 421c742aa5..1b021a5dc2 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -1,6 +1,6 @@ import { proxy } from 'valtio/vanilla' -import type { CaipAddress } from '@reown/appkit-common' +import type { CaipAddress, CaipNetworkId } from '@reown/appkit-common' import { ConstantsUtil } from '../utils/ConstantsUtil.js' import { CoreHelperUtil } from '../utils/CoreHelperUtil.js' @@ -116,6 +116,7 @@ const DEFAULT_OPTIONS = { export interface BlockchainApiControllerState { clientId: string | null api: FetchUtil + supportedChains: { http: CaipNetworkId[]; ws: CaipNetworkId[] } } // -- Helpers ------------------------------------------- // @@ -124,13 +125,51 @@ const baseUrl = CoreHelperUtil.getBlockchainApiUrl() // -- State --------------------------------------------- // const state = proxy({ clientId: null, - api: new FetchUtil({ baseUrl, clientId: null }) + api: new FetchUtil({ baseUrl, clientId: null }), + supportedChains: { http: [], ws: [] } }) // -- Controller ---------------------------------------- // export const BlockchainApiController = { state, - fetchIdentity({ address }: BlockchainApiIdentityRequest) { + + async isNetworkSupported(network?: CaipNetworkId) { + if (!network) { + return false + } + + try { + if (!state.supportedChains.http.length) { + await BlockchainApiController.getSupportedNetworks({ + projectId: OptionsController.state.projectId + }) + } + } catch (e) { + return false + } + + return state.supportedChains.http.includes(network) + }, + async getSupportedNetworks({ projectId }: { projectId: string }) { + const supportedChains = await state.api.get({ + path: 'v1/supported-chains', + params: { + projectId + } + }) + + state.supportedChains = supportedChains + + return supportedChains + }, + async fetchIdentity({ address }: BlockchainApiIdentityRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { avatar: '', name: '' } + } + return state.api.get({ path: `/v1/identity/${address}`, params: { @@ -142,7 +181,7 @@ export const BlockchainApiController = { }) }, - fetchTransactions({ + async fetchTransactions({ account, projectId, cursor, @@ -151,6 +190,13 @@ export const BlockchainApiController = { cache, chainId }: BlockchainApiTransactionsRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { data: [], next: undefined } + } + return state.api.get({ path: `/v1/account/${account}/history`, params: { @@ -164,7 +210,7 @@ export const BlockchainApiController = { }) }, - fetchSwapQuote({ + async fetchSwapQuote({ projectId, amount, userAddress, @@ -172,6 +218,13 @@ export const BlockchainApiController = { to, gasPrice }: BlockchainApiSwapQuoteRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { quotes: [] } + } + return state.api.get({ path: `/v1/convert/quotes`, headers: { @@ -188,7 +241,17 @@ export const BlockchainApiController = { }) }, - fetchSwapTokens({ projectId, chainId }: BlockchainApiSwapTokensRequest) { + async fetchSwapTokens({ + projectId, + chainId + }: BlockchainApiSwapTokensRequest): Promise { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { tokens: [] } + } + return state.api.get({ path: `/v1/convert/tokens`, params: { @@ -198,7 +261,14 @@ export const BlockchainApiController = { }) }, - fetchTokenPrice({ projectId, addresses }: BlockchainApiTokenPriceRequest) { + async fetchTokenPrice({ projectId, addresses }: BlockchainApiTokenPriceRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { fungibles: [] } + } + return state.api.post({ path: '/v1/fungible/price', body: { @@ -212,9 +282,20 @@ export const BlockchainApiController = { }) }, - fetchSwapAllowance({ projectId, tokenAddress, userAddress }: BlockchainApiSwapAllowanceRequest) { + async fetchSwapAllowance({ + projectId, + tokenAddress, + userAddress + }: BlockchainApiSwapAllowanceRequest) { const { sdkType, sdkVersion } = OptionsController.state + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { allowance: '0' } + } + return state.api.get({ path: `/v1/convert/allowance`, params: { @@ -230,9 +311,16 @@ export const BlockchainApiController = { }) }, - fetchGasPrice({ projectId, chainId }: BlockchainApiGasPriceRequest) { + async fetchGasPrice({ projectId, chainId }: BlockchainApiGasPriceRequest) { const { sdkType, sdkVersion } = OptionsController.state + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + throw new Error('Network not supported for Gas Price') + } + return state.api.get({ path: `/v1/convert/gas-price`, headers: { @@ -247,13 +335,20 @@ export const BlockchainApiController = { }) }, - generateSwapCalldata({ + async generateSwapCalldata({ amount, from, projectId, to, userAddress }: BlockchainApiGenerateSwapCalldataRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + throw new Error('Network not supported for Swaps') + } + return state.api.post({ path: '/v1/convert/build-transaction', headers: { @@ -272,7 +367,7 @@ export const BlockchainApiController = { }) }, - generateApproveCalldata({ + async generateApproveCalldata({ from, projectId, to, @@ -280,6 +375,13 @@ export const BlockchainApiController = { }: BlockchainApiGenerateApproveCalldataRequest) { const { sdkType, sdkVersion } = OptionsController.state + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + throw new Error('Network not supported for Swaps') + } + return state.api.get({ path: `/v1/convert/build-approve`, headers: { @@ -298,6 +400,12 @@ export const BlockchainApiController = { async getBalance(address: string, chainId?: string, forceUpdate?: string) { const { sdkType, sdkVersion } = OptionsController.state + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { balances: [] } + } return state.api.get({ path: `/v1/account/${address}/balance`, @@ -315,6 +423,13 @@ export const BlockchainApiController = { }, async lookupEnsName(name: string) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { addresses: {}, attributes: [] } + } + return state.api.get({ path: `/v1/profile/account/${name}`, params: { @@ -325,6 +440,13 @@ export const BlockchainApiController = { }, async reverseLookupEnsName({ address }: { address: string }) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return [] + } + return state.api.get({ path: `/v1/profile/reverse/${address}`, params: { @@ -336,6 +458,13 @@ export const BlockchainApiController = { }, async getEnsNameSuggestions(name: string) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { suggestions: [] } + } + return state.api.get({ path: `/v1/profile/suggestions/${name}`, params: { @@ -351,6 +480,13 @@ export const BlockchainApiController = { message, signature }: BlockchainApiRegisterNameParams) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { success: false } + } + return state.api.post({ path: `/v1/profile/account`, body: { coin_type: coinType, address, message, signature }, @@ -367,6 +503,13 @@ export const BlockchainApiController = { purchaseAmount, paymentAmount }: GenerateOnRampUrlArgs) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return '' + } + const response = await state.api.post<{ url: string }>({ path: `/v1/generators/onrampurl`, params: { @@ -386,6 +529,13 @@ export const BlockchainApiController = { }, async getOnrampOptions() { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { paymentCurrencies: [], purchaseCurrencies: [] } + } + try { const response = await state.api.get<{ paymentCurrencies: PaymentCurrency[] @@ -403,8 +553,20 @@ export const BlockchainApiController = { } }, - async getOnrampQuote({ purchaseCurrency, paymentCurrency, amount, network }: GetQuoteArgs) { + async getOnrampQuote({ + purchaseCurrency, + paymentCurrency, + amount, + network + }: GetQuoteArgs): Promise { try { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return null + } + const response = await state.api.post({ path: `/v1/onramp/quote`, params: { @@ -432,7 +594,14 @@ export const BlockchainApiController = { } }, - getSmartSessions(caipAddress: CaipAddress) { + async getSmartSessions(caipAddress: CaipAddress) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return [] + } + return state.api.get({ path: `/v1/sessions/${caipAddress}`, params: { @@ -440,7 +609,14 @@ export const BlockchainApiController = { } }) }, - revokeSmartSession(address: `0x${string}`, pci: string, signature: string) { + async revokeSmartSession(address: `0x${string}`, pci: string, signature: string) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ChainController.state.activeCaipNetwork?.caipNetworkId + ) + if (!isSupported) { + return { success: false } + } + return state.api.post({ path: `/v1/sessions/${address}/revoke`, params: { diff --git a/packages/core/src/controllers/OnRampController.ts b/packages/core/src/controllers/OnRampController.ts index 6f06461199..bed9f96a25 100644 --- a/packages/core/src/controllers/OnRampController.ts +++ b/packages/core/src/controllers/OnRampController.ts @@ -156,7 +156,7 @@ export const OnRampController = { network: state.purchaseCurrency?.symbol }) state.quotesLoading = false - state.purchaseAmount = Number(quote.purchaseAmount.amount) + state.purchaseAmount = Number(quote?.purchaseAmount.amount) return quote } catch (error) { diff --git a/packages/core/src/controllers/SwapController.ts b/packages/core/src/controllers/SwapController.ts index 54a3345071..0ec1100b08 100644 --- a/packages/core/src/controllers/SwapController.ts +++ b/packages/core/src/controllers/SwapController.ts @@ -389,7 +389,7 @@ export const SwapController = { projectId: OptionsController.state.projectId, addresses: [address] }) - const fungibles = response.fungibles || [] + const fungibles = response?.fungibles || [] const allTokens = [...(state.tokens || []), ...(state.myTokensWithBalance || [])] const symbol = allTokens?.find(token => token.address === address)?.symbol const price = fungibles.find(p => p.symbol.toLowerCase() === symbol?.toLowerCase())?.price || 0