diff --git a/.changeset/lazy-bridges-wait.md b/.changeset/lazy-bridges-wait.md new file mode 100644 index 000000000..6c7c7138c --- /dev/null +++ b/.changeset/lazy-bridges-wait.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +⚡️ defer lifi sdk initialization diff --git a/.changeset/light-chains-fly.md b/.changeset/light-chains-fly.md new file mode 100644 index 000000000..5055d0adf --- /dev/null +++ b/.changeset/light-chains-fly.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +⚡️ remove startup-blocking viem/chains import diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index bfab39a6e..ce040842c 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -14,8 +14,6 @@ import { channel, checkForUpdateAsync, fetchUpdateAsync, reloadAsync } from "exp import { ToastProvider } from "@tamagui/toast"; -import { optimism } from "@account-kit/infra"; -import { createConfig, EVM } from "@lifi/sdk"; import { ErrorBoundary, feedbackIntegration, @@ -28,12 +26,9 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; import { reconnect } from "@wagmi/core"; import { use as configI18n } from "i18next"; -import { anvil } from "viem/chains"; import { WagmiProvider } from "wagmi"; -import alchemyAPIKey from "@exactly/common/alchemyAPIKey"; import domain from "@exactly/common/domain"; -import chain from "@exactly/common/generated/chain"; import BDOGroteskDemiBold from "../assets/fonts/BDOGrotesk-DemiBold.otf"; import BDOGroteskRegular from "../assets/fonts/BDOGrotesk-Regular.otf"; @@ -45,7 +40,6 @@ import release from "../generated/release"; import en from "../i18n/en.json"; import es from "../i18n/es.json"; import e2e from "../utils/e2e"; -import publicClient from "../utils/publicClient"; import queryClient, { persister } from "../utils/queryClient"; import reportError from "../utils/reportError"; import exaConfig from "../utils/wagmi/exa"; @@ -124,17 +118,6 @@ const useServerFonts = typeof window === "undefined" ? useFonts : () => undefine const useServerAssets = typeof window === "undefined" ? useAssets : () => undefined; const useLayoutEffect = typeof window === "undefined" ? () => undefined : useClientLayoutEffect; const devtools = !!JSON.parse(process.env.EXPO_PUBLIC_DEVTOOLS ?? String(Platform.OS === "web" && __DEV__)); -if (!chain.testnet && chain.id !== anvil.id && typeof window !== "undefined") { - createConfig({ - integrator: "exa_app", - apiKey: "4bdb54aa-4f28-4c61-992a-a2fdc87b0a0b.251e33ad-ef5e-40cb-9b0f-52d634b99e8f", - providers: [EVM({ getWalletClient: () => Promise.resolve(publicClient) })], - rpcUrls: { - [optimism.id]: [`${optimism.rpcUrls.alchemy?.http[0]}/${alchemyAPIKey}`], - [chain.id]: [publicClient.transport.alchemyRpcUrl], - }, - }); -} export default wrap(function RootLayout() { const navigationContainer = useNavigationContainerRef(); diff --git a/src/components/activity/PendingProposals.tsx b/src/components/activity/PendingProposals.tsx index 0b8298a6b..44fc4e3b7 100644 --- a/src/components/activity/PendingProposals.tsx +++ b/src/components/activity/PendingProposals.tsx @@ -16,9 +16,6 @@ import { } from "@tamagui/lucide-icons"; import { XStack, YStack } from "tamagui"; -import { extractChain, type Chain } from "viem"; -import * as chains from "viem/chains"; - import chain from "@exactly/common/generated/chain"; import ProposalType, { decodeBorrowAtMaturity, @@ -30,6 +27,7 @@ import ProposalType, { import shortenHex from "@exactly/common/shortenHex"; import { presentArticle } from "../../utils/intercom"; +import queryClient from "../../utils/queryClient"; import reportError from "../../utils/reportError"; import useAsset from "../../utils/useAsset"; import usePendingOperations from "../../utils/usePendingOperations"; @@ -37,7 +35,7 @@ import SafeView from "../shared/SafeView"; import Text from "../shared/Text"; import View from "../shared/View"; -import type { RouteFrom } from "../../utils/lifi"; +import type { BridgeSources, RouteFrom } from "../../utils/lifi"; import type { MutationState } from "@tanstack/react-query"; import type { TFunction } from "i18next"; @@ -300,10 +298,11 @@ function ProposalItem({ proposal }: { proposal: Proposal }) { function MutationItem({ mutation }: { mutation: MutationState & { id: number } }) { const { t } = useTranslation(); - const { name: sourceChainName } = extractChain({ - chains: Object.values(chains) as unknown as readonly [Chain, ...Chain[]], - id: mutation.variables?.chainId ?? 0, - }); + const chainId = mutation.variables?.chainId ?? 0; + const bridgeSources = queryClient.getQueriesData({ queryKey: ["bridge", "sources"] }); + const sourceChainName = + bridgeSources.flatMap(([, data]) => data?.chains ?? []).find((c) => c.id === chainId)?.name ?? + t("Chain {{id}}", { id: chainId }); // TODO map values to other supported mutations return ( diff --git a/src/components/add-funds/Bridge.tsx b/src/components/add-funds/Bridge.tsx index c72523e99..73359daa8 100644 --- a/src/components/add-funds/Bridge.tsx +++ b/src/components/add-funds/Bridge.tsx @@ -11,6 +11,7 @@ import { ScrollView, Spinner, Square, XStack, YStack } from "tamagui"; import { useMutation, useQuery } from "@tanstack/react-query"; import { switchChain, waitForTransactionReceipt } from "@wagmi/core"; import { + defineChain, encodeFunctionData, erc20Abi, formatUnits, @@ -37,7 +38,7 @@ import openBrowser from "../../utils/openBrowser"; import queryClient from "../../utils/queryClient"; import reportError from "../../utils/reportError"; import useAccount from "../../utils/useAccount"; -import ownerConfig from "../../utils/wagmi/owner"; +import ownerConfig, { addChains } from "../../utils/wagmi/owner"; import AssetLogo from "../shared/AssetLogo"; import GradientScrollView from "../shared/GradientScrollView"; import SafeView from "../shared/SafeView"; @@ -99,6 +100,22 @@ export default function Bridge() { refetchIntervalInBackground: true, }); + useEffect(() => { + if (!bridge?.chains) return; + addChains( + bridge.chains + .filter((c): c is typeof c & { metamask: { rpcUrls: [string, ...string[]] } } => Boolean(c.metamask.rpcUrls[0])) + .map(({ id, name, metamask }) => + defineChain({ + id, + name, + nativeCurrency: metamask.nativeCurrency, + rpcUrls: { default: { http: [metamask.rpcUrls[0]] } }, + }), + ), + ); + }, [bridge?.chains]); + const chains = bridge?.chains; const ownerAssetsByChain = bridge?.ownerAssetsByChain; const usdByToken = bridge?.usdByToken; diff --git a/src/utils/lifi.ts b/src/utils/lifi.ts index bbd25d55f..1dfd9d11c 100644 --- a/src/utils/lifi.ts +++ b/src/utils/lifi.ts @@ -1,6 +1,9 @@ +import { optimism } from "@account-kit/infra"; import { ChainType, config, + createConfig as createLifiConfig, + EVM, getChains, getQuote, getToken, @@ -12,15 +15,29 @@ import { type TokenAmount, } from "@lifi/sdk"; import { parse } from "valibot"; -import { encodeFunctionData, formatUnits } from "viem"; +import { encodeFunctionData, formatUnits, type Address } from "viem"; import { anvil } from "viem/chains"; +import alchemyAPIKey from "@exactly/common/alchemyAPIKey"; import chain, { mockSwapperAbi, swapperAddress } from "@exactly/common/generated/chain"; import { Address as AddressSchema, Hex } from "@exactly/common/validation"; import publicClient from "./publicClient"; -import type { Address } from "viem"; +let configured = false; +function ensureConfig() { + if (configured || chain.testnet || chain.id === anvil.id) return; + createLifiConfig({ + integrator: "exa_app", + apiKey: "4bdb54aa-4f28-4c61-992a-a2fdc87b0a0b.251e33ad-ef5e-40cb-9b0f-52d634b99e8f", + providers: [EVM({ getWalletClient: () => Promise.resolve(publicClient) })], + rpcUrls: { + [optimism.id]: [`${optimism.rpcUrls.alchemy?.http[0]}/${alchemyAPIKey}`], + [chain.id]: [publicClient.transport.alchemyRpcUrl], + }, + }); + configured = true; +} export async function getRoute( fromToken: Hex, @@ -30,6 +47,7 @@ export async function getRoute( receiver: Hex, denyExchanges?: Record, ) { + ensureConfig(); if (chain.testnet || chain.id === anvil.id) { const fromAmount = await publicClient.readContract({ abi: mockSwapperAbi, @@ -90,6 +108,7 @@ export async function getRoute( } async function getAllTokens(): Promise { + ensureConfig(); if (chain.testnet || chain.id === anvil.id) return []; const response = await getTokens({ chains: [chain.id] }); const exa = await getToken(chain.id, "0x1e925De1c68ef83bD98eE3E130eF14a50309C01B"); @@ -152,6 +171,7 @@ const allowList = new Set([ ]); export async function getAllowTokens() { + ensureConfig(); if (chain.testnet || chain.id === anvil.id) return []; const exa = await getToken(chain.id, "0x1e925De1c68ef83bD98eE3E130eF14a50309C01B"); const { tokens } = await getTokens({ chains: [chain.id] }); @@ -192,6 +212,7 @@ export async function getRouteFrom({ toChainId?: number; toTokenAddress: string; }): Promise { + ensureConfig(); config.set({ integrator: "exa_app", userId: fromAddress }); const { estimate, transactionRequest, tool } = await getQuote({ fee: 0.0025, @@ -242,6 +263,7 @@ export type BridgeSources = { }; export async function getBridgeSources(account?: string, protocolSymbols: string[] = []): Promise { + ensureConfig(); if (!account) throw new Error("account is required"); const bridgeTokenSymbols = new Set(protocolSymbols); if (bridgeTokenSymbols.size === 0) throw new Error("protocol symbols is required"); diff --git a/src/utils/wagmi/owner.ts b/src/utils/wagmi/owner.ts index ccc13f1a7..da816e652 100644 --- a/src/utils/wagmi/owner.ts +++ b/src/utils/wagmi/owner.ts @@ -2,8 +2,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; import { sdk } from "@farcaster/miniapp-sdk"; import { farcasterMiniApp as miniAppConnector } from "@farcaster/miniapp-wagmi-connector"; -import { http } from "viem"; -import * as chains from "viem/chains"; +import { createClient, http, type Chain } from "viem"; import { createConfig, createStorage, custom, injected } from "wagmi"; import chain from "@exactly/common/generated/chain"; @@ -11,16 +10,23 @@ import chain from "@exactly/common/generated/chain"; import publicClient from "../publicClient"; const config = createConfig({ - chains: [chain, ...Object.values(chains)], + chains: [chain], connectors: [miniAppConnector(), injected()], - transports: { - ...Object.fromEntries(Object.values(chains).map((c) => [c.id, http()])), - [chain.id]: custom(publicClient), + client({ chain: c }) { + return createClient({ chain: c, transport: c.id === chain.id ? custom(publicClient) : http() }); }, storage: createStorage({ key: "wagmi.owner", storage: AsyncStorage }), }); export default config; +export function addChains(newChains: readonly Chain[]) { + const current = config.chains; + const ids = new Set(current.map((c) => c.id)); + const toAdd = newChains.filter((c) => !ids.has(c.id)); + if (toAdd.length === 0) return; + config._internal.chains.setState([...current, ...toAdd]); +} + export async function getConnector() { const miniApp = await sdk.isInMiniApp(); const connector = miniApp