Skip to content

✨ app: provision alchemy rpc urls across lifi chains#833

Open
cruzdanilo wants to merge 1 commit intomainfrom
lifi
Open

✨ app: provision alchemy rpc urls across lifi chains#833
cruzdanilo wants to merge 1 commit intomainfrom
lifi

Conversation

@cruzdanilo
Copy link
Member

@cruzdanilo cruzdanilo commented Feb 23, 2026


Open with Devin

Greptile Summary

replaces hardcoded optimism rpc url with dynamic provisioning across all lifi-supported evm chains by iterating through @account-kit/infra exports. adds preloadChains: false and async chain loading via config.loading to defer chain configuration until after the initial lifi setup.

  • improves multi-chain support by automatically provisioning alchemy rpc urls for all chains in the @account-kit/infra package
  • changes from synchronous to asynchronous chain loading pattern with config.loading promise
  • critical issue: race condition at line 122 where configured = true is set before config.loading completes, which may cause subsequent function calls to proceed before chains are fully loaded

Confidence Score: 2/5

  • this pr has a critical race condition that could cause runtime issues in production
  • the race condition at line 122 where configured is set to true before the async config.loading promise completes could cause functions like getRoute, getAllTokens, and others to execute before chains are fully configured via config.setChains. this may lead to unpredictable behavior when lifi operations are called quickly after initialization.
  • src/utils/lifi.ts requires attention for the race condition in ensureConfig function

Important Files Changed

Filename Overview
src/utils/lifi.ts dynamically provisions alchemy rpc urls across all lifi-supported chains by iterating through @account-kit/infra exports and adds async chain loading with preloadChains: false
.changeset/bright-llamas-provision.md adds patch changeset documenting the lifi chain provisioning feature

Last reviewed commit: a3f82fa

Summary by CodeRabbit

Release Notes

  • Bug Fixes & Improvements
    • Improved RPC endpoint provisioning across multiple blockchain networks with optimized Alchemy integration for more stable connections.
    • Implemented automatic discovery of available blockchain networks, enabling the app to dynamically support new chains without requiring application updates.

@changeset-bot
Copy link

changeset-bot bot commented Feb 23, 2026

🦋 Changeset detected

Latest commit: a3f82fa

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Walkthrough

Adds a new changeset announcing a patch release for dynamic Alchemy RPC URL provisioning. Refactors RPC configuration to dynamically build RPC URL mappings from infra exports and adds asynchronous chain loading to fetch available EVM chains at runtime.

Changes

Cohort / File(s) Summary
Changeset
.changeset/bright-llamas-provision.md
New changeset file announcing patch release for @exactly/mobile with provisioning of Alchemy RPC URLs across LiFi chains.
LiFi Configuration
src/utils/lifi.ts
Replaces named optimism import with wildcard infra import; refactors RPC URL configuration to dynamically scan infra exports and build rpcUrls map from Alchemy endpoints; adds preloadChains flag set to false; introduces async config.loading logic to fetch and update available EVM chains with metamask rpcUrls handling and error reporting.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Config as ensureConfig
    participant Infra as Infra Exports
    participant LiFi as LiFi Config
    participant Loader as Chain Loader
    participant MetaMask as MetaMask

    App->>Config: Call ensureConfig()
    Config->>Infra: Scan for id & rpcUrls.alchemy.http
    Infra-->>Config: Return RPC URLs map
    Config->>LiFi: Update rpcUrls & preloadChains
    Config->>Loader: Trigger config.loading (async)
    Loader->>Loader: Fetch available EVM chains
    Loader->>MetaMask: Reset rpcUrls
    Loader->>LiFi: Update config.chains
    Loader-->>Config: Complete (errors reported)
    Config-->>App: Return configured state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: provisioning Alchemy RPC URLs across LiFi chains, which is the primary objective reflected in the changeset and src/utils/lifi.ts modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lifi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @cruzdanilo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's ability to interact with various blockchain networks by implementing a dynamic system for provisioning Alchemy RPC URLs across all supported LiFi chains. This change improves the flexibility and scalability of the LiFi integration, ensuring that the application can automatically configure RPC endpoints for new or existing chains without manual updates, leading to a more robust and maintainable system.

Highlights

  • Dynamic RPC URL Provisioning: Implemented a dynamic mechanism to provision Alchemy RPC URLs for all LiFi chains defined within the @account-kit/infra module, replacing previous hardcoded configurations.
  • Refactored Chain Imports: Updated the import of chain configurations from @account-kit/infra to use a namespace import, facilitating broader and more flexible access to chain properties.
  • Enhanced LiFi Configuration: Modified the LiFi configuration process to asynchronously fetch and set available chains, ensuring that RPC URLs are correctly configured for all supported chains.
Changelog
  • .changeset/bright-llamas-provision.md
    • Added a new changeset file documenting the provisioning of Alchemy RPC URLs across LiFi chains.
  • src/utils/lifi.ts
    • Updated the import of chain configurations from @account-kit/infra to use a namespace import.
    • Modified the lifiTokensOptions query to reflect the updated import path for optimism.id.
    • Refactored the ensureConfig function to dynamically generate and provision Alchemy RPC URLs for all detected LiFi chains, replacing hardcoded RPC configurations.
    • Introduced preloadChains: false and added logic to asynchronously fetch and configure available chains, improving the robustness of chain setup.
Activity
  • No human activity has occurred on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

.catch((error: unknown) => {
reportError(error);
});
configured = true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

race condition: configured is set before config.loading completes. subsequent calls to functions like getRoute (line 135) or getAllTokens (line 196) won't re-run ensureConfig, but chains may not be fully loaded yet, potentially causing issues if they rely on config.setChains being complete.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +112 to 124
config.loading = getChains({ chainTypes: [ChainType.EVM] })
.then((availableChains) => {
const rpcs = config.get().rpcUrls as Partial<Record<number, readonly string[]>>;
config.setChains(
availableChains.map((c) => (rpcs[c.id]?.length ? { ...c, metamask: { ...c.metamask, rpcUrls: [] } } : c)),
);
})
.catch((error: unknown) => {
reportError(error);
});
configured = true;
queryClient.prefetchQuery(lifiTokensOptions).catch(reportError);
queryClient.prefetchQuery(lifiChainsOptions).catch(reportError);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Duplicate getChains call — config.loading and lifiChainsOptions both fetch the same data

The new config.loading at src/utils/lifi.ts:112 calls getChains({ chainTypes: [ChainType.EVM] }), and then src/utils/lifi.ts:124 prefetches lifiChainsOptions which also calls getChains({ chainTypes: [ChainType.EVM] }) (defined at src/utils/lifi.ts:37). The lifi SDK internally awaits config.loading before API operations, so the lifiChainsOptions prefetch will first wait for the config.loading chain fetch to complete, then make a second identical API call.

This is functionally correct but results in two sequential getChains calls on initialization — the first to configure chain RPC overrides, the second to populate the query cache. Consider reusing the chains fetched by config.loading to populate the query cache directly, avoiding the redundant network request.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

.then((availableChains) => {
const rpcs = config.get().rpcUrls as Partial<Record<number, readonly string[]>>;
config.setChains(
availableChains.map((c) => (rpcs[c.id]?.length ? { ...c, metamask: { ...c.metamask, rpcUrls: [] } } : c)),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The code spreads c.metamask without checking if it exists. If c.metamask is undefined, this creates a new, incomplete metamask object, leading to incorrect LiFi SDK configuration.
Severity: MEDIUM

Suggested Fix

Conditionally update the metamask property only if it already exists on the chain object c. For example, change the logic to rpcs[c.id]?.length && c.metamask ? { ...c, metamask: { ...c.metamask, rpcUrls: [] } } : c.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/utils/lifi.ts#L116

Potential issue: When configuring chains for the LiFi SDK, the code maps over
`availableChains`. If a chain has a configured RPC, it attempts to modify the chain's
`metamask` property by spreading it: `{ ...c, metamask: { ...c.metamask, rpcUrls: [] }
}`. The `metamask` property is optional on a chain object `c`. If `c.metamask` is
`undefined`, the spread results in a new, incomplete object `{ rpcUrls: [] }`. This
creates a malformed `metamask` configuration, as it's missing other required properties
like `chainId` and `chainName`, which can cause downstream errors within the LiFi SDK.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the LiFi configuration to dynamically provision Alchemy RPC URLs for all supported EVM chains from @account-kit/infra, which is a good improvement over hardcoding them. However, a high-severity security vulnerability was identified: a hardcoded LiFi API key in src/utils/lifi.ts that must be externalized to an environment variable to prevent secret exposure. Additionally, two potential runtime errors were found in the new logic: a missing null check when iterating through infra chains and an unguarded spread of an optional property when modifying chain configurations, both of which could lead to crashes. The rest of the changes, including @lifi/sdk dependency modifications, do not appear to introduce new security risks and include beneficial validation enhancements.

if (configured || chain.testnet || chain.id === anvil.id) return;
const rpcUrls = {
...Object.values(infra).reduce<Record<number, string[]>>((result, item) => {
if (typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The check typeof item !== "object" is not sufficient to guard against null, because typeof null is 'object'. If item is null, the expression !("id" in item) will throw a TypeError. You should add a check for item === null.

Suggested change
if (typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;
if (typeof item !== "object" || item === null || !("id" in item) || !("rpcUrls" in item)) return result;

.then((availableChains) => {
const rpcs = config.get().rpcUrls as Partial<Record<number, readonly string[]>>;
config.setChains(
availableChains.map((c) => (rpcs[c.id]?.length ? { ...c, metamask: { ...c.metamask, rpcUrls: [] } } : c)),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The metamask property on ExtendedChain (the type of c) is optional. If c.metamask is undefined, the spread operator ...c.metamask will throw a TypeError. You should handle this case, for example by providing a default empty object.

Suggested change
availableChains.map((c) => (rpcs[c.id]?.length ? { ...c, metamask: { ...c.metamask, rpcUrls: [] } } : c)),
availableChains.map((c) => (rpcs[c.id]?.length ? { ...c, metamask: { ...(c.metamask ?? {}), rpcUrls: [] } } : c)),

@sentry
Copy link

sentry bot commented Feb 23, 2026

Codecov Report

❌ Patch coverage is 6.25000% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.82%. Comparing base (3b2b810) to head (a3f82fa).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/utils/lifi.ts 6.25% 15 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #833      +/-   ##
==========================================
- Coverage   68.95%   68.82%   -0.13%     
==========================================
  Files         211      211              
  Lines        7444     7459      +15     
  Branches     2385     2388       +3     
==========================================
+ Hits         5133     5134       +1     
- Misses       2096     2109      +13     
- Partials      215      216       +1     
Flag Coverage Δ
e2e 68.81% <6.25%> (+17.82%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b2b810 and a3f82fa.

📒 Files selected for processing (2)
  • .changeset/bright-llamas-provision.md
  • src/utils/lifi.ts

Comment on lines +94 to +104
const rpcUrls = {
...Object.values(infra).reduce<Record<number, string[]>>((result, item) => {
if (typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;
const candidate = item as { id: number; rpcUrls: { alchemy?: { http?: string[] } } };
const alchemyRpcUrl = candidate.rpcUrls.alchemy?.http?.[0];
if (!alchemyRpcUrl) return result;
result[candidate.id] = [`${alchemyRpcUrl}/${alchemyAPIKey}`];
return result;
}, {}),
[chain.id]: [publicClient.transport.alchemyRpcUrl],
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

rpcUrls is used in a single place — consider inlining per project guidelines.

The rpcUrls variable is only consumed in the createLifiConfig call on line 110. As per coding guidelines, single-use values should be kept inline rather than extracted into a named variable.

♻️ Proposed inline refactor
-  const rpcUrls = {
-    ...Object.values(infra).reduce<Record<number, string[]>>((result, item) => {
-      if (typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;
-      const candidate = item as { id: number; rpcUrls: { alchemy?: { http?: string[] } } };
-      const alchemyRpcUrl = candidate.rpcUrls.alchemy?.http?.[0];
-      if (!alchemyRpcUrl) return result;
-      result[candidate.id] = [`${alchemyRpcUrl}/${alchemyAPIKey}`];
-      return result;
-    }, {}),
-    [chain.id]: [publicClient.transport.alchemyRpcUrl],
-  };
   createLifiConfig({
     integrator: "exa_app",
     apiKey: "4bdb54aa-4f28-4c61-992a-a2fdc87b0a0b.251e33ad-ef5e-40cb-9b0f-52d634b99e8f",
     preloadChains: false,
     providers: [EVM({ getWalletClient: () => Promise.resolve(publicClient) })],
-    rpcUrls,
+    rpcUrls: {
+      ...Object.values(infra).reduce<Record<number, string[]>>((result, item) => {
+        if (typeof item !== "object" || !item || !("id" in item) || !("rpcUrls" in item)) return result;
+        const candidate = item as { id: number; rpcUrls: { alchemy?: { http?: string[] } } };
+        const alchemyRpcUrl = candidate.rpcUrls.alchemy?.http?.[0];
+        if (!alchemyRpcUrl) return result;
+        result[candidate.id] = [`${alchemyRpcUrl}/${alchemyAPIKey}`];
+        return result;
+      }, {}),
+      [chain.id]: [publicClient.transport.alchemyRpcUrl],
+    },
   });

Note: the !item guard is included to prevent a TypeError when "id" in item is evaluated against a null value (see below).

As per coding guidelines: "Do not extract a value into a variable or logic into a function unless it is used in two or more places; keep single-use values and functions inline."

if (configured || chain.testnet || chain.id === anvil.id) return;
const rpcUrls = {
...Object.values(infra).reduce<Record<number, string[]>>((result, item) => {
if (typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

null slips past the typeof item !== "object" guard.

typeof null === "object" in JavaScript, so null values are not filtered by the typeof check. The subsequent "id" in item call on null throws a TypeError at runtime. While @account-kit/infra is unlikely to export null today, this is fragile.

🛡️ Proposed fix
-      if (typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;
+      if (!item || typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;
if (!item || typeof item !== "object" || !("id" in item) || !("rpcUrls" in item)) return result;

result[candidate.id] = [`${alchemyRpcUrl}/${alchemyAPIKey}`];
return result;
}, {}),
[chain.id]: [publicClient.transport.alchemyRpcUrl],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the lifi.ts file around line 103
fd -e ts -path "*utils/lifi*" --exclude "*.test.*"

Repository: exactly/exa

Length of output: 223


🏁 Script executed:

# Find publicClient definition
fd -e ts "publicClient" --exclude "*.test.*" --exclude node_modules

Repository: exactly/exa

Length of output: 109


🏁 Script executed:

# Search for how alchemyRpcUrl is defined or used
rg "alchemyRpcUrl" --type ts

Repository: exactly/exa

Length of output: 357


🏁 Script executed:

# Read the lifi.ts file to see full context around line 103
cat -n src/utils/lifi.ts | head -120 | tail -30

Repository: exactly/exa

Length of output: 1596


🏁 Script executed:

# Read publicClient.ts to understand the transport type
cat -n src/utils/publicClient.ts

Repository: exactly/exa

Length of output: 487


🏁 Script executed:

# Check if there's any guard or conditional before line 103
cat -n src/utils/lifi.ts | sed -n '90,110p'

Repository: exactly/exa

Length of output: 1132


🏁 Script executed:

# Search for alchemy transport typing and alchemyRpcUrl property
rg "alchemyRpcUrl" -B 2 -A 2 --type-list

Repository: exactly/exa

Length of output: 444


🏁 Script executed:

# Check what `@account-kit/infra` exports and how alchemy transport is typed
rg "@account-kit/infra" src/utils/publicClient.ts

Repository: exactly/exa

Length of output: 130


🏁 Script executed:

# Check how alchemyAPIKey is imported and what its default value is
rg "alchemyAPIKey" src/utils/lifi.ts -B 2 -A 2

Repository: exactly/exa

Length of output: 527


🏁 Script executed:

# Find the alchemyAPIKey definition/export
fd -e ts -e js "alchemyAPIKey" --exclude node_modules

Repository: exactly/exa

Length of output: 78


🏁 Script executed:

# Read the alchemyAPIKey definition
cat -n common/alchemyAPIKey.ts

Repository: exactly/exa

Length of output: 1036


🏁 Script executed:

# Also check for type definitions in publicClient.ts to see if alchemyRpcUrl is properly typed
rg "transport" src/utils/publicClient.ts -B 2 -A 2

Repository: exactly/exa

Length of output: 198


🏁 Script executed:

# Check if line 103 execution is guarded for anvil chain
cat -n src/utils/lifi.ts | sed -n '92,104p'

Repository: exactly/exa

Length of output: 795


Add a guard to ensure publicClient.transport.alchemyRpcUrl is defined before using it.

If the alchemy transport doesn't expose the alchemyRpcUrl property or it's undefined, the code will add an undefined value to the RPC URLs array, silently breaking LiFi's RPC configuration for the active chain. Consider adding a check like:

[chain.id]: publicClient.transport.alchemyRpcUrl ? [publicClient.transport.alchemyRpcUrl] : [],

Or retrieve the URL from the same infra object used above to maintain consistency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant