Skip to content

Conversation

@mastilver
Copy link
Contributor

@mastilver mastilver commented Sep 18, 2025

Add two new function isLayerZero and getLayerZeroDetails.

  • getLayerZeroDetails expose eid and if possible the chain id.

Summary by CodeRabbit

  • New Features
    • Onramp orders now include an optional LayerZero destination EID for improved cross-chain visibility.
    • SDK can resolve a chain ID from a LayerZero EID to support cross-chain routing and integrations.
  • Tests
    • Added tests validating EID-to-chain ID resolution.
    • Updated gateway tests to include an example order with a LayerZero destination EID and adjusted expectations.
  • Chores
    • Package version bumped.

@vercel
Copy link

vercel bot commented Sep 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
bob-docs Ready Ready Preview Comment Sep 22, 2025 2:55pm

Copy link
Contributor

@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.

Summary of Changes

Hello @mastilver, 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 Gateway SDK by providing explicit support for orders that utilize LayerZero for cross-chain routing. It equips the OnrampOrder object with new methods to identify LayerZero-enabled orders and to extract crucial details such as the destination Endpoint ID and its corresponding blockchain chain ID. This is achieved through a new internal service that queries and caches LayerZero's public metadata API, ensuring efficient and accurate retrieval of cross-chain routing information.

Highlights

  • LayerZero Integration: Introduced new functionality to detect if an order is routed via LayerZero and to retrieve its specific LayerZero details.
  • New API Methods: Added isLayerZero() and getLayerZeroDetails() methods to the OnrampOrder object, allowing programmatic access to LayerZero routing information.
  • LayerZero Metadata API: Implemented a mechanism to fetch LayerZero deployment metadata from a public API, mapping LayerZero Endpoint IDs (EIDs) to native chain IDs.
  • Caching for Efficiency: Incorporated an internal caching layer for LayerZero EID to chain ID mappings to optimize performance and reduce redundant API calls.
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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds an optional layerzeroDstEid?: number to onramp order responses, implements EID→chainId lookup on the LayerZero client and gateway, and updates tests to include the new field and a test asserting EID 30111 maps to the optimism chain id.

Changes

Cohort / File(s) Summary of changes
Gateway types & tests
sdk/src/gateway/types.ts, sdk/test/gateway.test.ts
Added optional layerzeroDstEid?: number to OnrampOrderResponse. Updated mocked gateway test data to include an 8th onramp order with layerzeroDstEid: 30111 and adjusted assertions to expect 8 orders.
LayerZero client, gateway & tests
sdk/src/gateway/layerzero.ts, sdk/test/layerzero.test.ts
Added `LayerZeroClient.getChainId(eid: string): Promise<number
Package version
sdk/package.json
Bumped package version from 4.2.154.2.16.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Gateway as LayerZeroGatewayClient
  participant Client as LayerZeroClient
  participant Deploy as Deployments (chain configs)

  Caller->>Gateway: getChainIdForEid(eid)
  Gateway->>Client: getChainId(eid)
  Client->>Deploy: scan deployments for matching eid
  alt match found
    Deploy-->>Client: nativeChainId
    Client-->>Gateway: nativeChainId
    Gateway-->>Caller: nativeChainId
  else no match
    Deploy-->>Client: not found
    Client-->>Gateway: null
    Gateway-->>Caller: null
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • gregdhill
  • danielsimao
  • nakul1010

Poem

A little rabbit hops where EIDs hide,
I sniff the map and follow the tide.
Eight carrots now, one marked 30111,
I find its chain and dance—oh heaven! 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(gateway-v4): expose layerzero" is concise and accurately describes the main intent of the changeset — exposing LayerZero functionality in the gateway v4 surface (new LayerZero client/gateway methods and an added layerzeroDstEid field with test updates). It follows conventional commit style and is specific enough for a reviewer scanning PR history to understand the primary change. The title is not vague or off-topic relative to the provided diffs and objectives.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf82c3a and 4663a3d.

⛔ Files ignored due to path filters (1)
  • sdk/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • sdk/package.json (1 hunks)
  • sdk/src/gateway/layerzero.ts (2 hunks)
  • sdk/src/gateway/types.ts (1 hunks)
  • sdk/test/layerzero.test.ts (2 hunks)

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.

Copy link
Contributor

@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 introduces functionality to expose LayerZero details for onramp orders. It adds two new methods, isLayerZero and getLayerZeroDetails, to the OnrampOrder object. The implementation includes fetching and caching LayerZero chain metadata.

My review focuses on improving code clarity, correctness, and testing practices. I've identified a typo, a suggestion for more modern syntax, a hardcoded URL, and some confusing logic in the data transformation. Additionally, a new test case is making a live network request, which should be mocked to ensure test stability and determinism. Addressing these points will enhance the maintainability and robustness of the new functionality.

Comment on lines 436 to 437
assert.strictEqual(orders[7].isLayerZero(), true);
assert.deepEqual(await orders[7].getLayerZeroDetails(), { dstEid: 30111, chainId: optimism.id });
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This test makes a live network request to https://metadata.layerzero-api.com to fetch LayerZero details. Unit and integration tests should be deterministic and not rely on external services. Please use nock to mock this API call and provide a stable, predictable response for the test.

For example:

nock('https://metadata.layerzero-api.com')
  .get('/v1/metadata/deployments')
  .reply(200, {
    'optimism': {
      deployments: [{ eid: '30111' }],
      chainDetails: { nativeChainId: optimism.id }
    }
  });

`Failed to get layerzero deployments`
);

if (!response.ok) {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

It's a good practice to define URLs as constants rather than hardcoding them. This improves maintainability and makes it easier to manage for different environments. Please extract this URL to a constant at the top of the file.

Copy link
Contributor

@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: 0

♻️ Duplicate comments (3)
sdk/src/gateway/client.ts (2)

99-111: Fix type alias typo: Deplotments → Deployments.

Pure rename for clarity and future grepping.

-// subset of: https://github.com/LayerZero-Labs/devtools/blob/aa43d67ec0ca5a1668b9174a3f3a032e6ce8693d/packages/metadata-tools/src/types.ts#L41
-type LayerZeroDeplotmentsMetadata = Record<
+// subset of: https://github.com/LayerZero-Labs/devtools/blob/aa43d67ec0ca5a1668b9174a3f3a032e6ce8693d/packages/metadata-tools/src/types.ts#L41
+type LayerZeroDeploymentsMetadata = Record<
     string,
     {
         deployments?: {
             eid: string;
         }[];
         chainDetails?: {
             nativeChainId?: number;
         };
     }
 >;

1328-1369: Tighten mapping build and avoid hardcoded URL.

  • Replace ineffective .filter(Boolean) with a predicate on the tuple’s second element.
  • Use a constant for the metadata URL (cleaner config).
  • Avoid truthiness check that would reject a valid 0 chainId; check for null/undefined instead.
-    private async getLayerZeroChainId(eid: number): Promise<number | null> {
+    private async getLayerZeroChainId(eid: number): Promise<number | null> {
         // Only calls the API once and cache it for future calls
         if (!this.layerZeroEidToChainIdMapP) {
             this.layerZeroEidToChainIdMapP = (async () => {
                 const response = await this.safeFetch(
-                    'https://metadata.layerzero-api.com/v1/metadata/deployments',
+                    GatewayApiClient.LAYERZERO_DEPLOYMENTS_URL,
                     {
                         headers: {
                             'Content-Type': 'application/json',
                             Accept: 'application/json',
                         },
                     },
                     `Failed to get layerzero deployments`
                 );
 
                 if (!response.ok) {
                     return new Map();
                 }
 
-                const data: LayerZeroDeplotmentsMetadata = await response.json();
+                const data: LayerZeroDeploymentsMetadata = await response.json();
 
-                return new Map(
-                    Object.values(data)
-                        .map((x) => x.deployments?.map((d) => [d.eid, x.chainDetails?.nativeChainId] as const) ?? [])
-                        .flat()
-                        .filter(Boolean)
-                );
+                const entries = Object.values(data).flatMap((x) =>
+                    (x.deployments ?? []).map((d) => [d.eid, x.chainDetails?.nativeChainId] as const)
+                );
+                // Keep only entries that have a defined chainId
+                const filtered = entries.filter(
+                    (pair): pair is readonly [string, number] => pair[1] !== undefined && pair[1] !== null
+                );
+                return new Map(filtered);
             })();
         }
 
         const map = await this.layerZeroEidToChainIdMapP;
 
-        const chainId = map.get(eid.toString());
+        const chainId = map.get(eid.toString());
 
-        // make sure it's not null, undefined, or 0
-        if (chainId) {
-            return chainId;
-        }
+        // Return null if missing; allow 0 if ever used
+        if (chainId !== undefined && chainId !== null) return chainId;
 
         return null;
     }

Add this constant (or similar) near the other base URLs or as a static in the class:

// Place near other API base URLs
export const LAYERZERO_DEPLOYMENTS_URL = 'https://metadata.layerzero-api.com/v1/metadata/deployments';
// ...then reference it as LAYERZERO_DEPLOYMENTS_URL inside getLayerZeroChainId

or

// Inside GatewayApiClient
private static readonly LAYERZERO_DEPLOYMENTS_URL =
  'https://metadata.layerzero-api.com/v1/metadata/deployments';
sdk/test/gateway.test.ts (1)

436-438: Mock the LayerZero metadata endpoint; avoid live network in tests.

This test currently hits https://metadata.layerzero-api.com at runtime. Please stub it with nock to keep tests deterministic.

         const gatewaySDK = new GatewaySDK(bob.id);
+        // Stub LayerZero deployments metadata used by getLayerZeroDetails()
+        nock('https://metadata.layerzero-api.com')
+            .get('/v1/metadata/deployments')
+            .reply(200, {
+                optimism: {
+                    deployments: [{ eid: '30111' }],
+                    chainDetails: { nativeChainId: optimism.id },
+                },
+            });
         const orders = await gatewaySDK.getOnrampOrders(zeroAddress);
🧹 Nitpick comments (3)
sdk/src/gateway/types.ts (2)

349-352: Type addition looks good; add brief field docs for consumers.

Optional JSDoc to make API self‑describing.

 export interface LayerZeroDetails {
-    dstEid: number;
-    chainId: number | null;
+    /** @description Destination Endpoint ID (LayerZero EID) */
+    dstEid: number;
+    /** @description Native EVM chainId for the destination chain, or null if unknown */
+    chainId: number | null;
 }

371-375: Normalize “LayerZero” capitalization and clarify null case.

-    /** @description Check if the order is routed through layerzero */
+    /** @description Check if the order is routed through LayerZero */
     isLayerZero(): boolean;
-    /** @description Get the layerzero details */
-    getLayerZeroDetails(): Promise<LayerZeroDetails | null>;
+    /** @description Get the LayerZero details (returns null when not routed via LayerZero) */
+    getLayerZeroDetails(): Promise<LayerZeroDetails | null>;
sdk/src/gateway/client.ts (1)

1099-1101: Use strict comparison.

Make the intent explicit; only undefined is possible from typing.

-                isLayerZero() {
-                    return order.layerzeroDstEid != undefined;
-                },
+                isLayerZero() {
+                    return order.layerzeroDstEid !== undefined;
+                },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85aaa26 and f4563ad.

📒 Files selected for processing (3)
  • sdk/src/gateway/client.ts (5 hunks)
  • sdk/src/gateway/types.ts (3 hunks)
  • sdk/test/gateway.test.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
sdk/test/gateway.test.ts (1)
sdk/src/gateway/tokens.ts (1)
  • SYMBOL_LOOKUP (376-376)
🪛 GitHub Check: Tests
sdk/src/gateway/client.ts

[failure] 1067-1067:
Unexpected aliasing of 'this' to local variable

🪛 GitHub Actions: SDK
sdk/src/gateway/client.ts

[warning] 1-1: ESLint: 'AddressType' is defined but never used. (no-unused-vars)


[warning] 20-20: ESLint: 'getAddressInfo' is defined but never used. (no-unused-vars)


[error] 1067-1067: ESLint: Unexpected aliasing of 'this' to local variable. (no-this-alias). Command: npm run lint

🔇 Additional comments (4)
sdk/src/gateway/types.ts (1)

321-322: Capitalize “LayerZero” in JSDoc and clarify wording.

Small polish to match brand capitalization and be clearer.

[ suggest_nitpick_refactor ]

-    /** layerzero dst eid if the order being routed through layerzero */
+    /** @description LayerZero destination EID if the order is routed through LayerZero */
sdk/test/gateway.test.ts (1)

410-424: LGTM: adds LayerZero order fixture and length assertion.

The added mock order and count change look consistent with the new API surface.

sdk/src/gateway/client.ts (2)

140-142: Good: single-flight cache for LayerZero mapping.

This Promise cache avoids redundant fetches across calls.


1066-1115: Fix lint error: no-this-alias (pipeline blocker)

Replace const that = this; with a bound function reference and update its usage in getLayerZeroDetails; re-run the verification.

-            const that = this;
+            const getLayerZeroChainId = this.getLayerZeroChainId.bind(this);
@@
                 isLayerZero() {
                     return order.layerzeroDstEid != undefined;
                 },
                 async getLayerZeroDetails() {
                     const eid = order.layerzeroDstEid;
 
                     if (!eid) {
                         return null;
                     }
 
-                    const chainId = await that.getLayerZeroChainId(eid);
+                    const chainId = await getLayerZeroChainId(eid);
 
                     return {
                         dstEid: eid,
                         chainId,
                     };
                 },

rg results you ran:

  • sdk/src/gateway/client.ts:1066 — const that = this; (actual this-alias to fix)
  • other matches are const x = this.y property copies (fine): sdk/src/inscription.ts:72, sdk/src/gateway/client.ts:1029, 1133, 1134

Run the verification script again after applying the patch.

Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
sdk/src/gateway/client.ts (1)

1334-1342: Extract the LayerZero metadata URL to a constant.

This mirrors the earlier feedback about avoiding hardcoded URLs and improves maintainability across environments.

-                const response = await this.safeFetch(
-                    'https://metadata.layerzero-api.com/v1/metadata/deployments',
+                const response = await this.safeFetch(
+                    LAYERZERO_DEPLOYMENTS_METADATA_URL,
                     {

Add this near the other base URLs:

export const LAYERZERO_DEPLOYMENTS_METADATA_URL =
  'https://metadata.layerzero-api.com/v1/metadata/deployments';
🧹 Nitpick comments (3)
sdk/src/gateway/client.ts (3)

140-142: Narrow the cache type; store only valid chainIds.

Storing null/undefined in the Map complicates usage. Build the Map with only valid numbers and type it accordingly.

-    private layerZeroEidToChainIdMapP: Promise<Map<string, number | null | undefined>> | null = null;
+    private layerZeroEidToChainIdMapP: Promise<ReadonlyMap<string, number>> | null = null;

1066-1068: Avoid this-alias; use arrow functions and add resilient error handling.

Use arrow functions to capture the class this without disabling the lint rule. Also make getLayerZeroDetails robust if the metadata fetch fails.

-            // eslint-disable-next-line @typescript-eslint/no-this-alias
-            const that = this;
+            // Capture class `this` via arrow functions below; no alias needed.

...
-                isLayerZero() {
-                    return order.layerzeroDstEid != undefined;
-                },
-                async getLayerZeroDetails() {
-                    const eid = order.layerzeroDstEid;
-
-                    if (!eid) {
-                        return null;
-                    }
-
-                    const chainId = await that.getLayerZeroChainId(eid);
-
-                    return {
-                        dstEid: eid,
-                        chainId,
-                    };
-                },
+                isLayerZero: () => order.layerzeroDstEid != undefined,
+                getLayerZeroDetails: async () => {
+                    const eid = order.layerzeroDstEid;
+                    if (!eid) return null;
+                    try {
+                        const chainId = await this.getLayerZeroChainId(eid);
+                        return { dstEid: eid, chainId };
+                    } catch {
+                        // Gracefully degrade if metadata lookup fails
+                        return { dstEid: eid, chainId: null };
+                    }
+                },

Also applies to: 1100-1116


1350-1367: Tighten mapping and checks; filter invalid entries and simplify consumers.

Filter out entries lacking a nativeChainId and keep only positive numbers. This lets you drop nullable types and avoid truthiness checks on 0.

-                return new Map(
-                    Object.values(data)
-                        .map((x) => x.deployments?.map((d) => [d.eid, x.chainDetails?.nativeChainId] as const) ?? [])
-                        .flat()
-                );
+                const pairs = Object.values(data).flatMap((x) =>
+                    (x.deployments ?? []).map((d) => [String(d.eid), x.chainDetails?.nativeChainId] as const)
+                );
+                const filtered = pairs.filter(([, chainId]) => typeof chainId === 'number' && chainId > 0);
+                return new Map(filtered);
             })();
         }
 
         const map = await this.layerZeroEidToChainIdMapP;
 
-        const chainId = map.get(eid.toString());
-
-        // make sure it's not null, undefined, or 0
-        if (chainId) {
-            return chainId;
-        }
-
-        return null;
+        const chainId = map.get(String(eid));
+        return typeof chainId === 'number' && chainId > 0 ? chainId : null;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4563ad and cc8df45.

📒 Files selected for processing (1)
  • sdk/src/gateway/client.ts (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (1)
sdk/src/gateway/client.ts (1)

99-111: Typo fixed and type alias looks good.

Name is corrected to LayerZeroDeploymentsMetadata and the shape matches the referenced subset.

});
}

async getChainId(eid: string): Promise<number | null> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

added getChainId

Copy link
Contributor

@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: 0

🧹 Nitpick comments (9)
sdk/src/gateway/layerzero-client.ts (9)

1-6: Consider making eid required for better type safety.

The LayerZeroChainInfo type has optional eid and nativeChainId fields, but based on the LayerZero v2 documentation, every supported chain should have a valid endpoint ID. Making eid required would prevent potential null pointer errors downstream.

 export type LayerZeroChainInfo = {
     name: string;
-    eid?: string;
+    eid: string;
     oftAddress: string;
     nativeChainId?: number;
 };

32-35: Add input validation for chainKey parameter.

The function should validate that chainKey is not empty to prevent unnecessary API calls and provide better error handling.

 async getEidForChain(chainKey: string) {
+    if (!chainKey?.trim()) {
+        return null;
+    }
     const data = await this.getChainDeployments();
     return data[chainKey]?.deployments?.find((item) => item.version === 2)?.eid || null;
 }

54-57: Add input validation for chainKey parameter.

Similar to getEidForChain, this function should validate the input parameter.

 async getOftAddressForChain(chainKey: string): Promise<string | null> {
+    if (!chainKey?.trim()) {
+        return null;
+    }
     const deployments = await this.getWbtcDeployments();
     return deployments[chainKey]?.address || null;
 }

59-81: Handle potential missing chain information gracefully.

Based on LayerZero documentation, endpoint IDs are unique identifiers for each chain endpoint. The current implementation may create entries with undefined eid values, which could cause issues downstream.

 async getSupportedChainsInfo(): Promise<Array<LayerZeroChainInfo>> {
     const chains = await this.getChainDeployments();
     const chainLookup = Object.fromEntries(
         Object.entries(chains).map(([_, chainData]) => [
             chainData.chainKey,
             {
                 eid: chainData.deployments?.find((item) => item.version === 2)?.eid,
                 nativeChainId: chainData.chainDetails?.nativeChainId,
             },
         ])
     );

     const deployments = await this.getWbtcDeployments();
-    return Object.entries(deployments).map(([chainKey, deployment]) => {
+    return Object.entries(deployments).map(([chainKey, deployment]) => {
         const chainInfo = chainLookup[chainKey];
+        const eid = chainInfo?.eid;
+        if (!eid) {
+            throw new Error(`Missing EID for chain: ${chainKey}`);
+        }
         return {
             name: chainKey,
-            eid: chainInfo?.eid,
+            eid,
             oftAddress: deployment.address,
             nativeChainId: chainInfo?.nativeChainId,
         };
-    });
+    }).filter(chain => chain.eid); // Additional safety filter
 }

83-93: Validate EID parameter format.

LayerZero uses numeric IDs (e.g., 30101 for Ethereum, 30168 for Solana). The function should validate that the EID parameter is a valid numeric string.

 async getChainId(eid: string): Promise<number | null> {
+    if (!eid?.trim() || !/^\d+$/.test(eid)) {
+        return null;
+    }
+    
     const chains = await this.getChainDeployments();

     const chainId = Object.values(chains).find((chain) => chain.deployments?.some((deployment) => deployment.eid === eid))?.chainDetails?.nativeChainId;

     if (chainId) {
         return chainId;
     }

     return null;
 }

95-102: Improve error handling and type safety.

The current error handling only throws generic status text, which may not be helpful for debugging. Also, the return type casting is unnecessary and potentially unsafe.

 private async getJson<T>(url: string): Promise<T> {
     const response = await fetch(url);
     if (!response.ok) {
-        throw new Error(response.statusText);
+        throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
     }
-    return (await response.json()) as Promise<T>;
+    return await response.json() as T;
 }

11-13: Consider making the base path configurable.

The hardcoded LayerZero API endpoint should be configurable to support different environments (mainnet, testnet) or allow for API endpoint changes.

 export class LayerZeroClient {
     private basePath: string;

-    constructor() {
-        this.basePath = 'https://metadata.layerzero-api.com/v1/metadata';
+    constructor(basePath?: string) {
+        this.basePath = basePath ?? 'https://metadata.layerzero-api.com/v1/metadata';
     }

37-47: Add error handling for malformed API response.

The WBTC deployments endpoint structure could change, and the current code assumes a specific nested structure. Adding validation would prevent runtime errors.

 private async getWbtcDeployments() {
     const params = new URLSearchParams({
         symbols: 'WBTC',
     });

     const data = await this.getJson<{
         WBTC: [{ deployments: { [chainKey: string]: { address: string; }; }; }];
     }>(`${this.basePath}/experiment/ofts/list?${params.toString()}`);

+    if (!data.WBTC?.[0]?.deployments) {
+        throw new Error('Invalid WBTC deployments response structure');
+    }
+    
     return data.WBTC[0].deployments;
 }

8-102: Consider adding caching to reduce API calls.

The LayerZero metadata is relatively static and doesn't change frequently. Adding a simple caching mechanism would improve performance and reduce API load, especially since multiple methods call the same endpoints.

Add a simple cache with TTL:

export class LayerZeroClient {
    private basePath: string;
    private cache = new Map<string, { data: any; expires: number }>();
    private cacheTtl = 5 * 60 * 1000; // 5 minutes

    private isCacheValid(key: string): boolean {
        const cached = this.cache.get(key);
        return cached !== undefined && cached.expires > Date.now();
    }

    private getCached<T>(key: string): T | null {
        if (this.isCacheValid(key)) {
            return this.cache.get(key)!.data as T;
        }
        return null;
    }

    private setCached(key: string, data: any): void {
        this.cache.set(key, { data, expires: Date.now() + this.cacheTtl });
    }

    private async getJson<T>(url: string): Promise<T> {
        const cached = this.getCached<T>(url);
        if (cached) {
            return cached;
        }

        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
        }
        
        const data = await response.json() as T;
        this.setCached(url, data);
        return data;
    }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc8df45 and 562f83a.

📒 Files selected for processing (3)
  • sdk/src/gateway/client.ts (4 hunks)
  • sdk/src/gateway/layerzero-client.ts (1 hunks)
  • sdk/src/gateway/layerzero.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/src/gateway/client.ts
🔇 Additional comments (1)
sdk/src/gateway/layerzero.ts (1)

16-16: LGTM! Clean separation of concerns.

Moving LayerZero types and client logic to a separate module improves maintainability and reduces code duplication. The import clearly indicates the external dependency.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
sdk/src/gateway/layerzero.ts (1)

44-44: Consolidate LayerZeroClient usage (single shared instance or add caching)

LayerZeroClient makes HTTP metadata calls (getChainDeployments / getWbtcDeployments / getEidForChain / getOftAddressForChain). The code currently creates both a class member and a local instance, causing redundant network requests — replace the local new LayerZeroClient() with the existing this.l0Client or add memoization/singleton behavior in LayerZeroClient.
Locations: sdk/src/gateway/layerzero.ts — private l0Client (line 44), constructor this.l0Client = new LayerZeroClient() (line 51), local const layerZeroClient = new LayerZeroClient() (≈line 218).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 562f83a and 2b41035.

📒 Files selected for processing (3)
  • sdk/src/gateway/client.ts (4 hunks)
  • sdk/src/gateway/layerzero-client.ts (1 hunks)
  • sdk/src/gateway/layerzero.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • sdk/src/gateway/layerzero-client.ts
  • sdk/src/gateway/client.ts
🔇 Additional comments (1)
sdk/src/gateway/layerzero.ts (1)

16-16: Good refactoring to prevent circular import dependencies.

The import statement correctly brings in LayerZeroChainInfo and LayerZeroClient from the new dedicated module, which helps maintain better code organization and prevents the circular import issues mentioned in the previous review comments.

Copy link
Contributor

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/src/gateway/layerzero.ts (1)

235-243: bytes32 bug: pad ‘to’ to 32 bytes or ABI encoding will fail.

quoteSend/send expect bytes32 for to. padHex(addr) doesn’t set size; Viem will error for non‑32‑byte hex. Pad to 32 bytes.

Apply this diff:

-            const sendParam: SendParam = {
+            const sendParam: SendParam = {
                 dstEid: parseInt(dstEid!, 10),
-                to: padHex(params.toUserAddress as Hex) as Hex,
+                to: padHex(params.toUserAddress as Hex, { size: 32 }) as Hex,
                 amountLD: BigInt(0), // will be added inside the strategy
                 minAmountLD: BigInt(0), // will be added inside the strategyzz
                 extraOptions: extraOptions,
                 composeMsg: '0x' as Hex,
                 oftCmd: '0x' as Hex,
             };
🧹 Nitpick comments (4)
sdk/test/gateway.test.ts (3)

410-418: Good addition: covers L0 EID propagation on onramp order.

As a small hardening, also assert the type to prevent regressions where EIDs become strings.

Apply this diff to extend the assertion:

         {
             ...mockOrder,
             baseTokenAddress: SYMBOL_LOOKUP[bob.id]['wbtc'],
             satoshis: 1000,
             fee: 0,
             status: true,
             layerzeroDstEid: 30111,
         },

Add near the existing checks:

+        assert.strictEqual(typeof orders[7].layerzeroDstEid, 'number');

423-423: Assert the 8th entry is the L0 case for clarity.

Length change is correct. Consider also asserting a distinguishing field (e.g., baseTokenAddress is wBTC) to make the intent explicit.

Example:

 assert.lengthOf(orders, 8);
+assert.strictEqual(orders[7].baseTokenAddress, SYMBOL_LOOKUP[bob.id]['wbtc']);

436-437: Also validate L0 details end-to-end (optional).

If getLayerZeroDetails/isLayerZero were added in this PR, add assertions here to cover them too.

I can wire a small test that, given layerzeroDstEid: 30111, asserts isLayerZero === true and getLayerZeroDetails().chainId === optimism.id (mocking the L0 API).

sdk/src/gateway/layerzero.ts (1)

112-124: Avoid truthy check; return explicitly when chainId is 0/undefined-safe.

Use an explicit type check to future‑proof.

Apply this diff:

-        if (chainId) {
-            return chainId;
-        }
-
-        return null;
+        return typeof chainId === 'number' ? chainId : null;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b41035 and bf82c3a.

📒 Files selected for processing (4)
  • sdk/src/gateway/layerzero.ts (2 hunks)
  • sdk/src/gateway/types.ts (1 hunks)
  • sdk/test/gateway.test.ts (2 hunks)
  • sdk/test/layerzero.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/src/gateway/types.ts
🧰 Additional context used
🧬 Code graph analysis (3)
sdk/src/gateway/layerzero.ts (1)
sdk/src/gateway/client.ts (1)
  • chainId (169-171)
sdk/test/gateway.test.ts (1)
sdk/src/gateway/tokens.ts (1)
  • SYMBOL_LOOKUP (376-376)
sdk/test/layerzero.test.ts (1)
sdk/src/gateway/layerzero.ts (1)
  • LayerZeroClient (37-133)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (2)
sdk/test/layerzero.test.ts (1)

14-70: Tests rely on live LayerZero metadata; mock these endpoints.

This suite hits https://metadata.layerzero-api.com and asserts exact EIDs/OFT addresses, which is brittle and can flake when metadata changes or the service blips. Please stub with nock and use fixed fixtures.

Proposed setup (add once per suite):

import nock from 'nock';
import { afterEach, beforeAll } from 'vitest';

beforeAll(() => {
  nock.disableNetConnect();
});
afterEach(() => {
  nock.cleanAll();
  nock.enableNetConnect();
});

// Example stubs used by "should get chains"
nock('https://metadata.layerzero-api.com')
  .get('/v1/metadata')
  .reply(200, {
    ethereum: { chainKey: 'ethereum', deployments: [{ version: 2, eid: '30101' }], chainDetails: { nativeChainId: 1 } },
    bob: { chainKey: 'bob', deployments: [{ version: 2, eid: '30279' }], chainDetails: { nativeChainId: 60808 } },
    base: { chainKey: 'base', deployments: [{ version: 2, eid: '30184' }], chainDetails: { nativeChainId: 8453 } },
    optimism: { chainKey: 'optimism', deployments: [{ version: 2, eid: '30111' }], chainDetails: { nativeChainId: 10 } },
    // ...include only chains you assert on
  });

nock('https://metadata.layerzero-api.com')
  .get('/v1/metadata/experiment/ofts/list')
  .query({ symbols: 'WBTC' })
  .reply(200, {
    WBTC: [{
      deployments: {
        ethereum: { address: '0x0555e30da8f98308edb960aa94c0db47230d2b9c' },
        bob: { address: '0x0555e30da8f98308edb960aa94c0db47230d2b9c' },
        base: { address: '0x0555e30da8f98308edb960aa94c0db47230d2b9c' },
        optimism: { address: '0xc3f854b2970f8727d28527ece33176fac67fef48' },
      }
    }]
  });

Optionally relax assertions to structure/containment instead of exact values if you don’t want to maintain fixtures.

sdk/src/gateway/layerzero.ts (1)

179-187: Thin delegator looks fine.

getChainIdForEid correctly proxies to LayerZeroClient. No issues.

Comment on lines +155 to +161
it('should get chain id for eid', async () => {
const client = new LayerZeroClient();

const optimismEid = '30111';

assert.equal(await client.getChainId(optimismEid), optimism.id);
}, 120000);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mock the metadata call in this test; don’t hit the network.

getChainId('30111') currently fetches live data. Stub /v1/metadata with just the optimism entry.

Apply this diff inside the test:

 it('should get chain id for eid', async () => {
-    const client = new LayerZeroClient();
-
-    const optimismEid = '30111';
-
-    assert.equal(await client.getChainId(optimismEid), optimism.id);
+    const client = new LayerZeroClient();
+    const optimismEid = '30111';
+    nock('https://metadata.layerzero-api.com')
+      .get('/v1/metadata')
+      .reply(200, {
+        optimism: {
+          chainKey: 'optimism',
+          deployments: [{ version: 2, eid: optimismEid }],
+          chainDetails: { nativeChainId: optimism.id },
+        },
+      });
+    assert.equal(await client.getChainId(optimismEid), optimism.id);
 }, 120000);
📝 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
it('should get chain id for eid', async () => {
const client = new LayerZeroClient();
const optimismEid = '30111';
assert.equal(await client.getChainId(optimismEid), optimism.id);
}, 120000);
it('should get chain id for eid', async () => {
const client = new LayerZeroClient();
const optimismEid = '30111';
nock('https://metadata.layerzero-api.com')
.get('/v1/metadata')
.reply(200, {
optimism: {
chainKey: 'optimism',
deployments: [{ version: 2, eid: optimismEid }],
chainDetails: { nativeChainId: optimism.id },
},
});
assert.equal(await client.getChainId(optimismEid), optimism.id);
}, 120000);
🤖 Prompt for AI Agents
In sdk/test/layerzero.test.ts around lines 155 to 161, the test currently calls
client.getChainId('30111') which performs a live /v1/metadata network request;
replace that with a mocked response: before invoking getChainId, stub the GET
/v1/metadata endpoint to return a minimal JSON payload containing only the
optimism entry mapping eid '30111' to the expected chain id, then call
getChainId and assert the result; ensure the stub is scoped to this test (and
cleaned up/restored after) so no real network calls occur.

@mastilver mastilver merged commit 9a943c9 into master Sep 22, 2025
7 of 9 checks passed
@mastilver mastilver deleted the feat/gateway-v4-layerzero branch September 22, 2025 14:56
@coderabbitai coderabbitai bot mentioned this pull request Oct 21, 2025
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.

4 participants