Skip to content

Conversation

@shoom3301
Copy link
Contributor

@shoom3301 shoom3301 commented Dec 9, 2025

Before this change, we were just taking the first proposed intermediate token from the bridge provider.
In case if bridge provider returned an array of only one intermediate token, then it will be taken by default.
Otherwise, we will apply the following logic:

Tokens are evaluated and ranked using a 4-tier priority system:

🥇 Priority 1 - HIGHEST: Stablecoins (USDC/USDT)

  • Pre-configured registry of USDC and USDT addresses across all supported chains
  • Best liquidity and price stability
  • Optimal for bridging with minimal slippage
  • Example: If USDC is available as an intermediate token, it will always be selected first

🥈 Priority 2 - HIGH: Correlated Tokens

  • Tokens provided via external sources (e.g., CMS API)
  • Known to have high liquidity or price correlation
  • Fetched dynamically based on the source chain
  • Example: WETH, DAI, or other major tokens with proven liquidity

🥉 Priority 3 - MEDIUM: Native Chain Tokens

  • Native blockchain currency (ETH, MATIC, AVAX, BNB, etc.)
  • Generally good liquidity but may have different bridging characteristics
  • Recognized by the special address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
  • Example: ETH on Ethereum mainnet, MATIC on Polygon

📊 Priority 4 - LOW: Other Tokens

  • Any other ERC-20 tokens
  • Used as fallback when no higher-priority options are available
  • May have lower liquidity or higher slippage

Summary by CodeRabbit

  • New Features

    • Implemented intelligent intermediate token selection for cross-chain swaps using a priority-based system that favors stablecoins, correlated tokens, and native chain tokens.
  • Documentation

    • Added comprehensive guide explaining the intermediate token selection process and priority tiers used in cross-chain swap operations.
  • Tests

    • Added extensive test suite covering intermediate token selection across multiple scenarios and edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 9, 2025

Warning

Rate limit exceeded

@shoom3301 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b321ddf and 4b54ea4.

📒 Files selected for processing (2)
  • packages/bridging/src/BridgingSdk/determineIntermediateToken.ts (1 hunks)
  • packages/bridging/src/BridgingSdk/tokenPriority.ts (1 hunks)

Walkthrough

Introduces a new intermediate token selection feature for the Bridging SDK. Implements a priority-based algorithm (HIGHEST → HIGH → MEDIUM → LOW) to select tokens from bridge provider options, adds token priority utilities, integrates into swap result flow, extends the swap settings interface with optional token correlation callback, and documents the selection mechanism.

Changes

Cohort / File(s) Summary
Intermediate Token Selection Core
packages/bridging/src/BridgingSdk/determineIntermediateToken.ts
New module implementing priority-based intermediate token selection with four-tier priority system (HIGHEST for USDC/USDT, HIGH for correlated tokens, MEDIUM for native tokens, LOW for others), async correlated token resolution with error handling and fallback.
Token Priority Utilities
packages/bridging/src/BridgingSdk/tokenPriority.ts
New utility module defining HIGH_PRIORITY_TOKENS registry per chain (USDC/USDT across major chains) and exposing three helper functions: isHighPriorityToken, isCorrelatedToken, and isNativeToken for case-insensitive address matching.
Integration & Swap Flow
packages/bridging/src/BridgingSdk/getIntermediateSwapResult.ts
Replaces heuristic first-token selection with async call to determineIntermediateToken, making token selection driven by priority logic instead of static ordering.
Test Suite
packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts
Comprehensive test coverage for intermediate token selection including token fixtures, priority rule validation across all four tiers, correlated token handling with failure scenarios, stable sorting behavior, and edge cases (empty inputs, single token, missing getCorrelatedTokens callback).
Type Extensions
packages/trading/src/types.ts
Adds optional getCorrelatedTokens method to SwapAdvancedSettings interface to enable dynamic token correlation lookup by chain.
Documentation
packages/bridging/src/README.md
Adds new top-level section "How It Works: Intermediate Token Selection" with two-step swap overview, four-tier priority explanation, example scenarios, and clarification that tokens are selected from provider-supported options.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • determineIntermediateToken.ts: Requires understanding of the four-tier priority system, async error handling for getCorrelatedTokens callback, and stable sorting behavior with memoization
  • tokenPriority.ts: Straightforward utility module; primary focus on verifying HIGH_PRIORITY_TOKENS registry completeness and accuracy across chains
  • determineIntermediateToken.test.ts: Dense test suite with multiple priority scenarios; review should validate edge case coverage and test fixture correctness

Suggested reviewers

  • cowdan
  • alfetopito

Poem

🐰 Hops through token tiers with care,
Stablecoins first, correlated fare,
Native chain tokens next in line,
Then others—priority's design!
Intermediate paths now clearly defined, ✨🌉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'feat(bridge): determine intermediate token' directly and clearly summarizes the main change—implementing intermediate token determination logic with priority-based selection.

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.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

📦 GitHub Packages Published

Last updated: Dec 9, 2025, 12:00:41 PM UTC

The following packages have been published to GitHub Packages with pre-release version pr-738-e89b3944:


Installation

These packages require authentication to install from GitHub Packages. First, create a .npmrc file:

# Create .npmrc file in your project root
echo "@cowprotocol:registry=https://npm.pkg.github.com" > .npmrc
echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> .npmrc

To get your GitHub token:

  1. Go to https://github.com/settings/tokens
  2. Click "Generate new token (classic)"
  3. Check only the "read:packages" scope
  4. Copy the token and replace YOUR_GITHUB_TOKEN in the .npmrc file

Then install any of the packages above, either by exact version (i.e. @cowprotocol/[email protected]) or more conveniently by using the tag (@cowprotocol/cow-sdk@pr-738):

# Yarn
yarn add npm:@cowprotocol/cow-sdk@pr-738

# pnpm
pnpm install npm:@cowprotocol/cow-sdk@pr-738

# NPM
npm install npm:@cowprotocol/cow-sdk@pr-738

Update to the latest version (only if you used the tag)

Every commit will publish a new package. To upgrade to the latest version, run:

# Yarn
yarn upgrade @cowprotocol/cow-sdk

# pnpm
pnpm update @cowprotocol/cow-sdk

# NPM
npm update @cowprotocol/cow-sdk

View Packages

You can view the published packages at: https://github.com/cowprotocol/cow-sdk/packages

@shoom3301 shoom3301 self-assigned this Dec 9, 2025
@shoom3301 shoom3301 requested a review from a team December 9, 2025 10:58
@shoom3301 shoom3301 marked this pull request as ready for review December 9, 2025 10:58
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

🧹 Nitpick comments (5)
packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts (2)

55-62: Minor: The two error handling tests are identical.

Both tests pass an empty array [] and expect BridgeProviderQuoteError. Consider removing one or differentiating them (e.g., testing with undefined tokens if the function signature changes, or testing with an array containing only undefined elements).

   describe('error handling', () => {
     it('should throw error when no intermediate tokens provided', async () => {
       await expect(determineIntermediateToken(SupportedChainId.MAINNET, [])).rejects.toThrow(BridgeProviderQuoteError)
     })
-
-    it('should throw error when intermediateTokens is empty array', async () => {
-      await expect(determineIntermediateToken(SupportedChainId.MAINNET, [])).rejects.toThrow(BridgeProviderQuoteError)
-    })
   })

184-204: Consider adding a test for the single-token optimization.

The implementation has an early return when only one token is provided (bypasses priority calculation). Adding an explicit test for this case would improve coverage:

it('should return the only token when single token provided', async () => {
  const result = await determineIntermediateToken(SupportedChainId.MAINNET, [randomToken])
  expect(result).toBe(randomToken)
})
packages/bridging/src/README.md (1)

86-108: Fix markdown linting issues in example scenarios.

The static analysis tool flags two issues:

  1. Emphasis (**Scenario X**) used instead of proper headings
  2. Fenced code blocks missing language specifiers
-**Scenario 1: Stablecoin Available**
-```
+#### Scenario 1: Stablecoin Available
+```text
 Candidates: [DAI, WETH, USDC]
 Selected: USDC (HIGHEST priority - stablecoin)

-Scenario 2: No Stablecoin
- +#### Scenario 2: No Stablecoin +text
Candidates: [RandomToken, WETH (correlated), NativeETH]
Selected: WETH (HIGH priority - correlated token)


-**Scenario 3: Only Native and Random Tokens**
-```
+#### Scenario 3: Only Native and Random Tokens
+```text
Candidates: [RandomToken1, NativeETH, RandomToken2]
Selected: NativeETH (MEDIUM priority - native currency)

-Scenario 4: All Low Priority
- +#### Scenario 4: All Low Priority +text
Candidates: [TokenA, TokenB, TokenC]
Selected: TokenA (first in list when all have same priority)

packages/bridging/src/BridgingSdk/tokenPriority.ts (1)

63-65: Minor optimization: Cache the lowercase native currency address.

NATIVE_CURRENCY_ADDRESS.toLowerCase() is called on every invocation, but this is a constant. Consider caching it:

+const NATIVE_ADDRESS_LOWER = NATIVE_CURRENCY_ADDRESS.toLowerCase()
+
 /**
  * Checks if a token is the native blockchain currency (ETH, MATIC, AVAX, etc.)
  */
 export function isNativeToken(tokenAddress: string): boolean {
-  return tokenAddress.toLowerCase() === NATIVE_CURRENCY_ADDRESS.toLowerCase()
+  return tokenAddress.toLowerCase() === NATIVE_ADDRESS_LOWER
 }
packages/bridging/src/BridgingSdk/determineIntermediateToken.ts (1)

61-68: Consider pre-computing indices for stable sort.

The indexOf() call in the comparator is O(n) per comparison. For small token lists this is fine, but you could pre-compute indices in the mapping phase for O(n log n) overall:

   // Calculate priority for each token
-  const tokensWithPriority = intermediateTokens.map((token) => {
+  const tokensWithPriority = intermediateTokens.map((token, index) => {
     if (isHighPriorityToken(token.chainId, token.address)) {
-      return { token, priority: TokenPriority.HIGHEST }
+      return { token, priority: TokenPriority.HIGHEST, index }
     }
     if (isCorrelatedToken(token.address, correlatedTokens)) {
-      return { token, priority: TokenPriority.HIGH }
+      return { token, priority: TokenPriority.HIGH, index }
     }
     if (isNativeToken(token.address)) {
-      return { token, priority: TokenPriority.MEDIUM }
+      return { token, priority: TokenPriority.MEDIUM, index }
     }

-    return { token, priority: TokenPriority.LOW }
+    return { token, priority: TokenPriority.LOW, index }
   })

   // Sort by priority (highest first), then by original order for stability
   tokensWithPriority.sort((a, b) => {
     if (a.priority !== b.priority) {
       return b.priority - a.priority // Higher priority first
     }
     // Maintain original order for tokens with same priority
-    return intermediateTokens.indexOf(a.token) - intermediateTokens.indexOf(b.token)
+    return a.index - b.index
   })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c17655c and b321ddf.

⛔ Files ignored due to path filters (1)
  • packages/contracts-ts/src/generated/packageVersion.ts is excluded by !**/generated/**
📒 Files selected for processing (6)
  • packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts (1 hunks)
  • packages/bridging/src/BridgingSdk/determineIntermediateToken.ts (1 hunks)
  • packages/bridging/src/BridgingSdk/getIntermediateSwapResult.ts (2 hunks)
  • packages/bridging/src/BridgingSdk/tokenPriority.ts (1 hunks)
  • packages/bridging/src/README.md (2 hunks)
  • packages/trading/src/types.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: anxolin
Repo: cowprotocol/cow-sdk PR: 251
File: src/bridging/BridgingSdk/getQuoteWithBridging.ts:34-35
Timestamp: 2025-03-19T22:06:52.139Z
Learning: The implementation in the Across bridge provider deliberately selects only the first intermediate token from the list (`intermediateTokens[0]`) as a simplification for the initial implementation, with potential for future enhancement.
Learnt from: anxolin
Repo: cowprotocol/cow-sdk PR: 267
File: src/bridging/BridgingSdk/getCrossChainOrder.ts:44-46
Timestamp: 2025-04-14T20:37:56.543Z
Learning: The decoding of bridge appData (using provider.decodeBridgeHook) in the getCrossChainOrder function was intentionally left as a TODO comment for implementation in a future PR.
Learnt from: shoom3301
Repo: cowprotocol/cow-sdk PR: 324
File: src/bridging/types.ts:384-385
Timestamp: 2025-05-30T05:56:12.625Z
Learning: The BridgingDepositParams interface in src/bridging/types.ts includes a bridgingId: string field along with other deposit parameters like inputTokenAddress, outputTokenAddress, inputAmount, outputAmount, owner, quoteTimestamp, fillDeadline, recipient, sourceChainId, and destinationChainId.
📚 Learning: 2025-05-30T05:56:12.625Z
Learnt from: shoom3301
Repo: cowprotocol/cow-sdk PR: 324
File: src/bridging/types.ts:384-385
Timestamp: 2025-05-30T05:56:12.625Z
Learning: The BridgingDepositParams interface in src/bridging/types.ts includes a bridgingId: string field along with other deposit parameters like inputTokenAddress, outputTokenAddress, inputAmount, outputAmount, owner, quoteTimestamp, fillDeadline, recipient, sourceChainId, and destinationChainId.

Applied to files:

  • packages/bridging/src/BridgingSdk/tokenPriority.ts
  • packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts
  • packages/bridging/src/BridgingSdk/determineIntermediateToken.ts
  • packages/bridging/src/BridgingSdk/getIntermediateSwapResult.ts
  • packages/bridging/src/README.md
📚 Learning: 2025-08-12T09:15:28.459Z
Learnt from: alfetopito
Repo: cowprotocol/cow-sdk PR: 391
File: src/trading/getEthFlowTransaction.ts:80-85
Timestamp: 2025-08-12T09:15:28.459Z
Learning: In the CoW SDK codebase, ETH_FLOW_ADDRESSES and BARN_ETH_FLOW_ADDRESSES are typed as Record<SupportedChainId, string>, which requires all SupportedChainId enum values to have corresponding string entries. TypeScript compilation will fail if any chainId is missing from these mappings, making runtime guards for missing addresses unnecessary in these specific cases.

Applied to files:

  • packages/bridging/src/BridgingSdk/tokenPriority.ts
📚 Learning: 2025-11-06T11:26:21.337Z
Learnt from: shoom3301
Repo: cowprotocol/cow-sdk PR: 642
File: packages/bridging/src/BridgingSdk/BridgingSdk.ts:124-144
Timestamp: 2025-11-06T11:26:21.337Z
Learning: In the cow-sdk bridging module (packages/bridging/), all bridge providers return ChainInfo objects that are singleton instances imported from cowprotocol/sdk-config. The chain objects (mainnet, arbitrumOne, base, optimism, polygon, avalanche, gnosisChain, etc.) are exported as constants from packages/config/src/chains/details/ and are shared across all providers. This singleton pattern makes object reference comparison (e.g., with Array.includes()) safe for deduplicating networks returned by multiple providers, as the same chain will always be represented by the same object reference.
<!--

Applied to files:

  • packages/bridging/src/BridgingSdk/tokenPriority.ts
  • packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts
  • packages/bridging/src/README.md
📚 Learning: 2025-03-19T22:06:52.139Z
Learnt from: anxolin
Repo: cowprotocol/cow-sdk PR: 251
File: src/bridging/BridgingSdk/getQuoteWithBridging.ts:34-35
Timestamp: 2025-03-19T22:06:52.139Z
Learning: The implementation in the Across bridge provider deliberately selects only the first intermediate token from the list (`intermediateTokens[0]`) as a simplification for the initial implementation, with potential for future enhancement.

Applied to files:

  • packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts
  • packages/bridging/src/BridgingSdk/determineIntermediateToken.ts
  • packages/bridging/src/BridgingSdk/getIntermediateSwapResult.ts
  • packages/bridging/src/README.md
📚 Learning: 2025-03-20T09:45:27.965Z
Learnt from: anxolin
Repo: cowprotocol/cow-sdk PR: 251
File: src/bridging/providers/across/AcrossBridgeProvider.ts:85-100
Timestamp: 2025-03-20T09:45:27.965Z
Learning: In the AcrossBridgeProvider's getIntermediateTokens method, not finding an intermediate token is expected behavior in some cases and should not be logged as a warning.

Applied to files:

  • packages/bridging/src/BridgingSdk/determineIntermediateToken.ts
  • packages/bridging/src/BridgingSdk/getIntermediateSwapResult.ts
🧬 Code graph analysis (4)
packages/bridging/src/BridgingSdk/tokenPriority.ts (1)
packages/config/src/constants/tokens.ts (1)
  • NATIVE_CURRENCY_ADDRESS (4-4)
packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts (4)
packages/config/src/types/tokens.ts (1)
  • TokenInfo (6-14)
packages/config/src/constants/tokens.ts (1)
  • NATIVE_CURRENCY_ADDRESS (4-4)
packages/bridging/src/BridgingSdk/determineIntermediateToken.ts (1)
  • determineIntermediateToken (28-77)
packages/bridging/src/errors.ts (1)
  • BridgeProviderQuoteError (13-21)
packages/bridging/src/BridgingSdk/determineIntermediateToken.ts (3)
packages/config/src/types/tokens.ts (1)
  • TokenInfo (6-14)
packages/bridging/src/errors.ts (1)
  • BridgeProviderQuoteError (13-21)
packages/bridging/src/BridgingSdk/tokenPriority.ts (3)
  • isHighPriorityToken (46-51)
  • isCorrelatedToken (56-58)
  • isNativeToken (63-65)
packages/bridging/src/BridgingSdk/getIntermediateSwapResult.ts (2)
packages/bridging/src/BridgingSdk/mock/bridgeRequestMocks.ts (1)
  • intermediateToken (25-25)
packages/bridging/src/BridgingSdk/determineIntermediateToken.ts (1)
  • determineIntermediateToken (28-77)
🪛 markdownlint-cli2 (0.18.1)
packages/bridging/src/README.md

86-86: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


87-87: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


92-92: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


104-104: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


105-105: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (8)
packages/trading/src/types.ts (1)

151-152: LGTM!

The new optional callback follows the existing pattern established by getSlippageSuggestion and integrates cleanly with the bridging module's determineIntermediateToken function.

packages/bridging/src/BridgingSdk/getIntermediateSwapResult.ts (1)

66-71: LGTM! Good enhancement over the previous first-token selection.

The integration with determineIntermediateToken properly passes the source chain ID and optional correlated tokens callback. Based on learnings, this replaces the previous deliberate simplification of selecting only the first token.

packages/bridging/src/BridgingSdk/determineIntermediateToken.test.ts (1)

142-155: Good edge case coverage.

The test correctly verifies that when getCorrelatedTokens fails, the function gracefully falls back to basic priority and returns the first token (since both randomToken and wethMainnet have LOW priority without the correlated tokens callback succeeding).

packages/bridging/src/README.md (1)

42-82: Well-documented priority system.

The documentation clearly explains the two-step cross-chain swap process and the 4-tier priority system. The descriptions align with the implementation in determineIntermediateToken.ts.

packages/bridging/src/BridgingSdk/tokenPriority.ts (2)

46-58: LGTM! Clean helper functions with consistent case handling.

All functions properly normalize addresses to lowercase before comparison, ensuring case-insensitive matching.


7-41: No changes needed—OPTIMISM is intentionally excluded from HIGH_PRIORITY_TOKENS.

OPTIMISM (chain ID 10) is defined in the AdditionalTargetChainId enum, not SupportedChainId. The HIGH_PRIORITY_TOKENS object is typed as Partial<Record<SupportedChainId, Set<string>>>, which means only chains in the SupportedChainId enum are supported. This is by design: AdditionalTargetChainId contains bridging-only chains that are not part of the primary supported chains, so they cannot have entries in HIGH_PRIORITY_TOKENS.

packages/bridging/src/BridgingSdk/determineIntermediateToken.ts (2)

8-13: LGTM!

The enum values enable straightforward numeric sorting, and keeping it unexported maintains good encapsulation.


47-59: The invariant holds across all bridge provider implementations—intermediate tokens are always sourced from sellTokenChainId.

Line 48 uses token.chainId because all bridge providers (NearIntentsBridgeProvider, AcrossBridgeProvider, BungeeBridgeProvider) explicitly filter or organize returned tokens by sellTokenChainId. For example, NearIntentsBridgeProvider filters tokens where token.chainId === sellTokenChainId, and AcrossBridgeProvider organizes its state by supportedTokensState[sellTokenChainId]. This ensures token.chainId always equals sourceChainId at this point. However, documenting this invariant or adding an explicit assertion would improve clarity.

Comment on lines +9 to +12
HIGHEST = 4, // USDC/USDT from hardcoded registry
HIGH = 3, // Tokens in CMS correlated tokens list
MEDIUM = 2, // Blockchain native token
LOW = 1, // Other tokens
Copy link
Contributor

Choose a reason for hiding this comment

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

This priority is inverted with the description. (Highest is 1)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I don't really see a problem, what do you mean?

Copy link
Contributor

Choose a reason for hiding this comment

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

In the readme, priority 1 is the highest:

image

In the enum, priority 4 is the highest.

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