Skip to content

Conversation

@gregdhill
Copy link
Contributor

@gregdhill gregdhill commented Sep 18, 2025

Summary by CodeRabbit

  • New Features

    • Added a structured "supported chains" info endpoint returning chain name, oft address, optional eid and native chain ID.
  • Bug Fixes

    • Improved origin-chain validation by comparing numeric chain IDs to avoid naming mismatches and clearer errors when client isn’t on the origin chain.
    • Removed a strict address-type runtime guard for offramp creation so requests no longer fail for certain address cases.
  • Refactor

    • Adjusted onramp order shapes: raw order details remain in responses while public order objects expose normalized V4 details.
  • Chores

    • Bumped SDK version to 4.2.11.

Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
@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 18, 2025 0:39am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

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

Updates onramp response typing and OnrampOrder shape (introduces OnrampOrderResponse and separates raw vs V4 orderDetails), removes a Taproot address runtime guard for offramp creation, changes LayerZero origin-chain validation to compare numeric chain IDs, and bumps SDK version 4.2.10 → 4.2.11.

Changes

Cohort / File(s) Summary of edits
Version bump
sdk/package.json
Bumped "version" from 4.2.10 to 4.2.11.
Onramp response & public type
sdk/src/gateway/types.ts
Added/changed OnrampOrderResponse.orderDetails from OrderDetailsOrderDetailsRaw; OnrampOrder now omits orderDetails from the response shape and reintroduces an internal orderDetails?: OrderDetails (V4).
Client typing & runtime guard removal
sdk/src/gateway/client.ts
Import OnrampOrderResponse; annotate getOnrampOrders payload as OnrampOrderResponse[]; removed runtime validation that rejected missing/P2TR toUserAddress in createOfframpOrder.
LayerZero chain info & ID-based validation
sdk/src/gateway/layerzero.ts
Introduced LayerZeroChainInfo and getSupportedChainsInfo(); refactored chain deployment/WBTC address fetches; in offramp quote flow validate origin using numeric fromChain vs publicClient.chain?.id and throw "Public client must be origin chain" on mismatch.
Tests updated
sdk/test/gateway.test.ts, sdk/test/layerzero.test.ts
Removed test asserting P2TR rejection; tests switched to use getSupportedChainsInfo() and assert chain info fields (eid, oftAddress) from returned objects.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant LZ as LayerZeroGatewayClient
  participant LC as LayerZeroClient
  participant API as MetadataAPI
  participant PC as publicClient

  Caller->>LZ: getSupportedChainsInfo()
  LZ->>LC: getSupportedChainsInfo()
  LC->>API: fetch chain deployments & WBTC deployments
  API-->>LC: chain deployments + WBTC addresses
  LC-->>LZ: array of LayerZeroChainInfo{name,eid,oftAddress,nativeChainId}
  LZ-->>Caller: supported chains info
Loading
sequenceDiagram
  autonumber
  actor Caller
  participant LZ as LayerZeroGatewayClient
  participant PC as publicClient

  Caller->>LZ: executeQuote(params)
  LZ->>LZ: maybeFromChainId = params.params.fromChain
  alt fromChain is number
    LZ->>PC: read publicClient.chain?.id
    alt id matches fromChain
      LZ-->>Caller: proceed with quote
    else id mismatch
      LZ-->>Caller: throw "Public client must be origin chain"
    end
  else not a number
    LZ-->>Caller: proceed without ID check
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • danielsimao
  • slavastartsev
  • nakul1010

Poem

A rabbit hops through types and chains,
I tuck raw details into tidy lanes.
IDs now steer the origin call,
Taproot’s guard has softly stalled.
A tiny bump — version sings 🥕✨

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 succinctly and accurately describes a primary change in the PR: the LayerZero (L0) client now validates the origin chain by checking the numeric fromChain ID against the public client chain and throwing if they differ. While the PR also includes refactors (getSupportedChainsInfo), type updates, and test adjustments, the title's focus on adding the fromChain ID check is clear and relevant to the main functional change.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/from-check

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.

Summary of Changes

Hello @gregdhill, 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 enhances the robustness of the LayerZero client by implementing a more precise validation mechanism for the fromChain identifier. The change shifts from name-based comparison to numerical chain ID comparison, mitigating potential inconsistencies arising from differing naming conventions. Additionally, it includes minor but important type definition updates within the SDK to ensure accurate representation and handling of onramp order details.

Highlights

  • Improved Chain ID Validation: The LayerZero client now validates the fromChain ID using the numerical chain ID instead of the chain name, which can vary between different systems like L0 and viem.
  • Type Definition Refinement: Adjustments were made to the OnrampOrderResponse and OnrampOrder types to correctly handle orderDetails, ensuring better type consistency.
  • SDK Version Update: The SDK package version has been incremented from 4.2.10 to 4.2.11.
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
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 improves the robustness of the LayerZero client by checking the fromChain ID instead of its name, which can be inconsistent. It also includes several type corrections, notably for OnrampOrderResponse and OnrampOrder to better reflect the data structures from the API and after processing. While the changes are generally good, there is an issue with the updated OnrampOrder type definition that needs to be addressed.

Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
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

Caution

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

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

160-168: Pad to bytes32 for to field in sendParam.

to is ABI-encoded as bytes32. Pad the EVM address to 32 bytes to avoid encoding errors; also fix the comment typo.

Apply:

-                to: padHex(params.toUserAddress as Hex) as Hex,
+                to: padHex(params.toUserAddress as Hex, { size: 32 }) as Hex,
-                minAmountLD: BigInt(0), // will be added inside the strategyzz
+                minAmountLD: BigInt(0), // will be added inside the strategy
sdk/src/gateway/client.ts (1)

1021-1085: OnrampOrder type hides orderDetails, but the returned object still exposes it.

This leaks a field intentionally omitted from the public type and can cause consumer reliance on a non-API property.

Apply:

-            const orderDetails = order.orderDetails
-                ? convertOrderDetailsRawToOrderDetails(order.orderDetails)
-                : undefined;
-
             return {
                 ...order,
-                orderDetails,
                 gasRefill: order.satsToConvertToEth,
                 baseToken: ADDRESS_LOOKUP[chainId][order.baseTokenAddress],
                 outputToken: ADDRESS_LOOKUP[chainId][order.outputTokenAddress!],

If you need orderDetails internally later, keep it local-only (don’t include it in the returned shape).

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

182-186: Zero buffer should be allowed.

params.l0FeeBuffer ? ... treats 0 as falsy and forces default 5%. Use explicit undefined check.

Apply:

-            const buffer = params.l0FeeBuffer ? BigInt(params.l0FeeBuffer) : BigInt(500); // 5% buffer
+            const buffer = params.l0FeeBuffer !== undefined ? BigInt(params.l0FeeBuffer) : 500n; // 5% buffer

318-321: Good: chain-id based origin validation; add a guard for missing chain.

If publicClient.chain is unset, fail fast; keep id comparison for numbers.

Apply:

-            const maybeFromChainId = executeQuoteParams.params.fromChain;
-            if (typeof maybeFromChainId === 'number' && publicClient.chain?.id !== maybeFromChainId) {
-                // avoid matching on name since L0 and viem may have different naming conventions
-                throw new Error(`Public client must be origin chain`);
-            }
+            const maybeFromChainId = executeQuoteParams.params.fromChain;
+            if (typeof maybeFromChainId === 'number') {
+                if (publicClient.chain?.id === undefined) {
+                    throw new Error('Public client must be configured with a chain.');
+                }
+                if (publicClient.chain.id !== maybeFromChainId) {
+                    // avoid matching on name since L0 and viem may have different naming conventions
+                    throw new Error('Public client must be origin chain');
+                }
+            }
sdk/src/gateway/types.ts (1)

318-318: Good shift to transport/raw typing for orderDetails; please document and confirm normalization path

Using OrderDetailsRaw at the IO boundary is sensible. Add a brief JSDoc note that this is the raw/transport shape (string-encoded quantities) and confirm that client code reliably converts it to OrderDetails before exposure/usage.

Can you confirm getOnrampOrders (or equivalent) performs the OrderDetailsRaw -> OrderDetails normalization and that no public return types expose the raw shape?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d4b45f and 15d27b2.

📒 Files selected for processing (4)
  • sdk/package.json (1 hunks)
  • sdk/src/gateway/client.ts (2 hunks)
  • sdk/src/gateway/layerzero.ts (1 hunks)
  • sdk/src/gateway/types.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-17T11:18:53.195Z
Learnt from: slavastartsev
PR: bob-collective/bob#634
File: sdk/src/gateway/client.ts:136-140
Timestamp: 2025-06-17T11:18:53.195Z
Learning: The codebase has a requirement not to introduce discriminated unions in the Gateway SDK client methods, including the getQuote method return types in sdk/src/gateway/client.ts.

Applied to files:

  • sdk/src/gateway/layerzero.ts
🧬 Code graph analysis (1)
sdk/src/gateway/client.ts (1)
sdk/src/gateway/types.ts (1)
  • OnrampOrderResponse (285-319)
⏰ 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 (3)
sdk/package.json (1)

3-3: Version bump looks good.

No concerns from me for 4.2.11.

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

46-46: Importing OnrampOrderResponse is correct.

Aligns payload typing with API response.

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

346-354: Avoid intersect-then-omit; simplify OnrampOrder and treat removal of orderDetails as a breaking change.

Verification: sdk/package.json → 4.2.11; ripgrep reported "unrecognized file type: tsx" and returned no matches — search inconclusive, do not assume consumers are unaffected. Either keep orderDetails (deprecated) for one minor release or bump the major version.

Apply this diff to simplify the type and avoid the risky merge:

-export type OnrampOrder = Omit<
-    OnrampOrderResponse & {
-        /** @description The gas refill in satoshis */
-        gasRefill: number;
-        /** @description V4 order details */
-        orderDetails?: OrderDetails;
-    },
-    'satsToConvertToEth' | 'orderDetails'
-> & {
+export type OnrampOrder = Omit<OnrampOrderResponse, 'satsToConvertToEth' | 'orderDetails'> & {
+    /** @description The gas refill in satoshis */
+    gasRefill: number;
+} & {

Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
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)

211-214: Pad to to bytes32 explicitly for ABI (bytes32 to).

Without { size: 32 }, padHex may not produce a 32‑byte value; ABI encoders often require exact size. Also fix comment typo.

Apply:

-                to: padHex(params.toUserAddress as Hex) as Hex,
-                amountLD: BigInt(0), // will be added inside the strategy
-                minAmountLD: BigInt(0), // will be added inside the strategyzz
+                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 strategy
♻️ Duplicate comments (1)
sdk/src/gateway/types.ts (1)

345-351: Correct property override pattern for OnrampOrder.

This fixes the earlier issue with Omit<A & { prop }, 'prop'>. New form is canonical.

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

41-56: Fix tuple typing for deployments; use arrays.

deployments?: [ { ... } ] is a 1‑element tuple, not “array of”. This narrows types and can break when multiple deployments are returned.

Apply:

-        return this.getJson<{
-            [chainKey: string]: {
-                deployments?: [
-                    {
-                        version: number;
-                        eid: string;
-                    },
-                ];
-                chainKey: string;
-                chainDetails?: {
-                    nativeChainId: number;
-                };
-            };
-        }>(`${this.basePath}`);
+        return this.getJson<{
+            [chainKey: string]: {
+                deployments?: Array<{
+                    version: number;
+                    eid: string;
+                }>;
+                chainKey: string;
+                chainDetails?: {
+                    nativeChainId: number;
+                };
+            };
+        }>(`${this.basePath}`);

63-74: Harden typing and null‑guard WBTC deployments.

Same tuple issue here; also guard for missing data to avoid Cannot read properties of undefined on WBTC[0].

Apply:

-        const data = await this.getJson<{
-            WBTC: [{ deployments: { [chainKey: string]: { address: string } } }];
-        }>(`${this.basePath}/experiment/ofts/list?${params.toString()}`);
-
-        return data.WBTC[0].deployments;
+        const data = await this.getJson<{
+            WBTC: Array<{ deployments: Record<string, { address: string }> }>;
+        }>(`${this.basePath}/experiment/ofts/list?${params.toString()}`);
+
+        const first = data.WBTC?.[0];
+        if (!first) {
+            throw new Error('WBTC deployments missing from LayerZero metadata API');
+        }
+        return first.deployments;

85-96: Remove unused _ binding; it triggers static‑analysis warning.

Use elision to avoid “defined but never used”.

Apply:

-        const chainLookup = Object.fromEntries(
-            Object.entries(chains).map(([_, chainData]) => [
+        const chainLookup = Object.fromEntries(
+            Object.entries(chains).map(([, chainData]) => [
                 chainData.chainKey,
                 {
                     eid: chainData.deployments?.find((item) => item.version === 2)?.eid,
                     nativeChainId: chainData.chainDetails?.nativeChainId,
                 },
             ])
         );

97-107: OK, but consider memoizing metadata to cut HTTP calls.

Optional: cache results inside LayerZeroClient to reuse across calls in a process.


109-115: Type assertion nit: cast JSON to T, not Promise<T>.

await response.json() yields a value, not a Promise. Current cast is misleading.

Apply:

-        return (await response.json()) as Promise<T>;
+        return (await response.json()) as T;

308-309: Reuse the existing client instance.

Avoid extra network state and duplicate caches by reusing this.l0Client.

Apply:

-            const layerZeroClient = new LayerZeroClient();
+            const layerZeroClient = this.l0Client;
sdk/test/layerzero.test.ts (3)

16-19: Network‑dependent test; consider mocking to reduce flakiness.

Calls live metadata API; CI can fail on network/hot updates. Consider stubbing LayerZeroClient.getSupportedChainsInfo with fixtures.

Do you want a quick vitest mock setup + fixture generator?


38-52: Hard‑coding EIDs may be brittle.

EIDs can change or new versions could alter the mapping. If the intent is smoke‑testing structure, assert presence/type rather than exact values; keep exact checks in a snapshot that you update intentionally.


53-69: Address checks OK; consider casing normalizer.

Comparisons are case‑sensitive; if API casing changes, this breaks. Normalizing to lowercase could help.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a00674b and 58b5cac.

📒 Files selected for processing (4)
  • sdk/src/gateway/layerzero.ts (4 hunks)
  • sdk/src/gateway/types.ts (2 hunks)
  • sdk/test/gateway.test.ts (0 hunks)
  • sdk/test/layerzero.test.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • sdk/test/gateway.test.ts
🧰 Additional context used
🪛 GitHub Check: Tests
sdk/src/gateway/layerzero.ts

[warning] 88-88:
'_' is defined but never used

⏰ 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 (8)
sdk/src/gateway/layerzero.ts (6)

27-32: Type addition looks good.

LayerZeroChainInfo shape is clear and matches usage across the file.


58-61: Getter logic is fine.

Version filter and nullable fallback are appropriate.


75-79: Supported chains via deployments keys: LGTM.


80-84: OFT address fetch: LGTM.

Null fallback is appropriate.


144-147: Gateway wrapper passthrough: LGTM.

Keeps API parity while introducing the richer info method.


155-167: Deprecation docs are clear.

Good to keep shims while tests migrate.

sdk/test/layerzero.test.ts (1)

19-34: Subset assertion is fine; verify containSubset is available.

Ensure chai‑subset (or equivalent) is registered with Vitest; otherwise switch to a stable assertion.

Would you like me to refactor this to assert.includeMembers with a curated minimal set?

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

318-319: Raw vs enriched orderDetails split: LGTM.

Clear separation avoids accidental misuse of raw types downstream.

Comment on lines 366 to 371
// we're quoting on the origin chain, so public client must be configured correctly
if (publicClient.chain?.name.toLowerCase() !== fromChain) {
const maybeFromChainId = executeQuoteParams.params.fromChain;
if (typeof maybeFromChainId === 'number' && publicClient.chain?.id !== maybeFromChainId) {
// avoid matching on name since L0 and viem may have different naming conventions
throw new Error(`Public client must be origin chain`);
}
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

Strengthen origin‑chain check when fromChain is a string.

Currently only enforced for numeric IDs. If a string slug is provided, publicClient could point to the wrong chain and the tx would be simulated/submitted on the wrong network.

Apply:

-            const maybeFromChainId = executeQuoteParams.params.fromChain;
-            if (typeof maybeFromChainId === 'number' && publicClient.chain?.id !== maybeFromChainId) {
-                // avoid matching on name since L0 and viem may have different naming conventions
-                throw new Error(`Public client must be origin chain`);
-            }
+            const fromParam = executeQuoteParams.params.fromChain;
+            if (typeof fromParam === 'number') {
+                if (publicClient.chain?.id !== fromParam) {
+                    throw new Error(`Public client must be origin chain`);
+                }
+            } else {
+                // Resolve slug -> nativeChainId via metadata and verify by numeric id to avoid name mismatches.
+                const info = await this.l0Client.getSupportedChainsInfo();
+                const nativeId = info.find((c) => c.name === fromParam)?.nativeChainId;
+                if (nativeId && publicClient.chain?.id !== nativeId) {
+                    throw new Error(`Public client must be origin chain`);
+                }
+            }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In sdk/src/gateway/layerzero.ts around lines 366 to 371, the origin-chain guard
only checks numeric IDs; extend it to also validate when
executeQuoteParams.params.fromChain is a string by performing a case-insensitive
comparison against the publicClient.chain's identifying fields (e.g., chain.name
and chain.network or chain.slug if available) and throw the same error if none
match; keep the existing numeric ID check, ensure null/undefined
publicClient.chain is handled safely, and normalize strings before comparing.

@gregdhill gregdhill merged commit 49ac04a into master Sep 18, 2025
8 checks passed
@gregdhill gregdhill deleted the chore/from-check branch September 18, 2025 12:55
@coderabbitai coderabbitai bot mentioned this pull request Oct 9, 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.

3 participants