Skip to content

feat: add BOB Gateway swapper (BTC ↔ BOB)#12275

Open
twblack88 wants to merge 1 commit intodevelopfrom
bob-swapper
Open

feat: add BOB Gateway swapper (BTC ↔ BOB)#12275
twblack88 wants to merge 1 commit intodevelopfrom
bob-swapper

Conversation

@twblack88
Copy link
Copy Markdown
Contributor

@twblack88 twblack88 commented Apr 10, 2026

Description

Adds the BOB Gateway swapper, enabling native BTC ↔ BOB chain swaps via the BOB Gateway protocol. This is the first pass supporting BTC↔BOB routes. LayerZero cross-chain routes (other EVM chains via BOB) are tracked separately in SS-5639.

What's included:

  • BobGatewaySwapper package under packages/swapper/src/swappers/BobGatewaySwapper/
  • getTradeQuote and getTradeRate implementations using @gobob/bob-sdk
  • CSP headers for gateway-api-mainnet.gobob.xyz
  • SwapperIcon and bob-gateway-icon.png
  • Config/env wiring for BOB_GATEWAY_ENABLED feature flag
  • Slippage represented internally as decimal percentage, converted to BOB Gateway basis points at call-site

Issue (if applicable)

closes #12267

Risk

Medium — new swapper introducing a new on-chain deposit-to-address flow for BTC→BOB and BOB→BTC. Does not modify existing swappers. Gated behind a feature flag (BOB_GATEWAY_ENABLED).

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

  • BOB Gateway smart contracts (BTC bridge deposits)
  • BTC send transactions (deposit-address flow)
  • BOB EVM transactions

Testing

Engineering

  1. Set VITE_FEATURE_BOB_GATEWAY_ENABLED=true in .env.development
  2. Connect a wallet with BTC or BOB assets
  3. Navigate to the swap page and select BTC → WBTC (BOB) or BOB WBTC → BTC
  4. Verify quote is fetched from BOB Gateway API and order is created on confirmation

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

Screenshots (if applicable)

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Bob Gateway as a new swap provider, enabling cross-chain trades between Bitcoin and the BOB blockchain
    • Introduced a feature flag to control Bob Gateway availability (disabled by default)

@twblack88 twblack88 requested a review from a team as a code owner April 10, 2026 18:28
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

A new Bob Gateway cross-chain swapper is introduced, enabling Bitcoin and BOB chain token swaps through a third-party gateway API. This includes environment variables, a complete swapper implementation with trade quote/rate APIs, transaction builders for UTXO and EVM chains, status tracking, state management, UI components, and CSP headers.

Changes

Cohort / File(s) Summary
Environment & Configuration
.env, .env.development, src/config.ts, packages/public-api/src/env.ts, packages/public-api/src/config.ts
Added VITE_FEATURE_BOB_GATEWAY_SWAP (boolean flag, default false/true in dev) and VITE_BOB_GATEWAY_AFFILIATE_ID (affiliate identifier) environment variables and validators for feature enablement and API configuration.
Type Definitions
packages/swapper/src/types.ts
Extended SwapperConfig, SwapperName, TradeQuoteStep, and SwapperSpecificMetadata to include Bob Gateway–specific properties: affiliate ID config, swapper enum variant, and step/swap metadata with order and transaction details.
Package Dependencies
packages/swapper/package.json
Added @gobob/bob-sdk@5.3.2 dependency for Bob Gateway API integration.
Bob Gateway Swapper Core
packages/swapper/src/swappers/BobGatewaySwapper/BobGatewaySwapper.ts, packages/swapper/src/swappers/BobGatewaySwapper/endpoints.ts, packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.ts, packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeRate.ts
Implemented complete swapper with quote/rate retrieval, transaction building for UTXO deposits and EVM transactions, fee estimation via chain adapters, and order status tracking with Bob Gateway V1 API client integration.
Swapper Utilities & Integration
packages/swapper/src/swappers/BobGatewaySwapper/utils/constants.ts, packages/swapper/src/swappers/BobGatewaySwapper/utils/helpers/helpers.ts, packages/swapper/src/swappers/BobGatewaySwapper/index.ts, packages/swapper/src/constants.ts, packages/swapper/src/index.ts
Added Bob Gateway chain/token mappings, supported chain IDs, address placeholders, slippage conversion, validation helpers, status mapping, and registered swapper in the global swappers registry.
CSP Headers
headers/csps/defi/swappers/BobGateway.ts, headers/csps/index.ts
Added Content Security Policy header configuration permitting requests to Bob Gateway API endpoint (https://gateway-api-mainnet.gobob.xyz).
UI & Components
src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/SwapperIcon.tsx
Added Bob Gateway icon asset and UI mapping for swapper display.
State Management & Feature Flags
src/state/slices/preferencesSlice/preferencesSlice.ts, src/state/helpers.ts, src/test/mocks/store.ts
Integrated BobGatewaySwap feature flag into Redux state, enabled swapper when flag is active and cross-account trades are supported, updated mock store for tests.
Trade Execution & Metadata
src/lib/tradeExecution.ts, src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx
Wired Bob Gateway–specific metadata (bobSpecific containing order and transaction details) through trade execution and confirmation flows.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/UI
    participant Swapper as Bob Gateway Swapper
    participant BobAPI as Bob Gateway API
    participant UTXOAdapter as UTXO Chain Adapter
    participant EVMAdapter as EVM Chain Adapter

    Client->>Swapper: getTradeQuote(BTC→BOB)
    Swapper->>BobAPI: getQuote(chainIds, tokens, amount, slippage)
    BobAPI-->>Swapper: quoteResponse
    Swapper->>BobAPI: createOrder(quote details)
    BobAPI-->>Swapper: orderId, depositAddress
    Swapper->>UTXOAdapter: getFeeData(depositAddress, amount)
    UTXOAdapter-->>Swapper: UTXO fees
    Swapper-->>Client: TradeQuote (with orderId, depositAddress, fees)

    Client->>Swapper: getUnsignedUtxoTransaction(tradeQuote, stepIndex)
    Swapper->>UTXOAdapter: buildTransaction(depositAddress, amount, fees)
    UTXOAdapter-->>Swapper: unsigned tx
    Swapper-->>Client: transaction

    Client->>Swapper: checkTradeStatus(swap)
    Swapper->>BobAPI: getOrderStatus(orderId)
    BobAPI-->>Swapper: orderStatus
    Swapper-->>Client: TxStatus, buyTxHash
Loading
sequenceDiagram
    participant Client as Client/UI
    participant Swapper as Bob Gateway Swapper
    participant BobAPI as Bob Gateway API
    participant EVMAdapter as EVM Chain Adapter

    Client->>Swapper: getTradeQuote(BOB→BTC)
    Swapper->>BobAPI: getQuote(chainIds, tokens, amount, slippage)
    BobAPI-->>Swapper: quoteResponse
    Swapper->>BobAPI: createOrder(quote details)
    BobAPI-->>Swapper: orderId, evmTx{to, data, value, chain}
    Swapper->>EVMAdapter: getFees(chainId)
    EVMAdapter-->>Swapper: gas fees
    Swapper-->>Client: TradeQuote (with orderId, evmTx, fees)

    Client->>Swapper: getUnsignedEvmTransaction(tradeQuote, stepIndex)
    Swapper->>EVMAdapter: buildTransaction(to, data, value, gasData)
    EVMAdapter-->>Swapper: signed/unsigned tx
    Swapper-->>Client: transaction

    Client->>Swapper: checkTradeStatus(swap)
    Swapper->>BobAPI: getOrderStatus(orderId)
    BobAPI-->>Swapper: orderStatus
    Swapper-->>Client: TxStatus, buyTxHash
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 A gateway opens, swift and bright,
Bitcoin hops to BOB with all its might,
Chains unite in swaps so clean,
The smoothest bridge you've ever seen!
With quotes and orders, fees all set,
This gateway's the best trade yet! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding a new BOB Gateway swapper for BTC ↔ BOB swaps. It is specific, concise, and clearly highlights the primary feature being introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bob-swapper

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.

@twblack88
Copy link
Copy Markdown
Contributor Author

note that we are waiting on the UUID from the bob team for fees and gateway.

so, no rush here, just wanted to unblock other work.

Copy link
Copy Markdown
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: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/swapper/src/swappers/BobGatewaySwapper/endpoints.ts`:
- Around line 143-152: The current catch for api.getOrder indiscriminately
returns a Waiting for deposit state; change the error handling in the order
lookup so only a true "not found" / not-yet-indexed response maps to {
buyTxHash: undefined, status: TxStatus.Unknown, message: 'Waiting for
deposit...' } — detect this by inspecting the thrown error from api.getOrder
(e.g. error.response?.status === 404 or a NotFoundError class) and return the
waiting payload only in that case; for all other errors
(network/server/transport) either rethrow or return an explicit error
result/preserve the last known status instead of masking it as Waiting for
deposit. Ensure you reference the existing symbols orderInfo, api.getOrder,
buyTxHash, and TxStatus.Unknown when making the change.

In `@packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.ts`:
- Around line 202-208: The code calls utxoAdapter.getFeeData and assigns
networkFeeCryptoBaseUnit = fast.txFee but fails to persist the BTC fee
parameters required for execution; update the quote construction where fast is
obtained (the utxoAdapter.getFeeData call and the similar occurrence later) to
also set step.feeData.chainSpecific.satsPerByte (and any other chainSpecific fee
fields returned by fast) from fast.satsPerByte (or equivalent) so
getUnsignedUtxoTransaction() can read step.feeData.chainSpecific.satsPerByte at
execution time; ensure the same change is applied to the second getFeeData usage
mentioned so both quote paths include the chainSpecific fee params.
- Around line 150-162: The getTradeQuote function is performing a side-effect by
calling api.createOrder (see getTradeQuote and api.createOrder) which reserves
Bob Gateway orders during quoting; remove the createOrder call and any
orderResponse usage from getTradeQuote so the quote path is
stateless/deterministic, and instead invoke api.createOrder from the
execution/unsigned-tx path (e.g., in the swap execution or build/confirm flow
where orders are actually submitted) so order creation happens only at
confirmation; ensure error handling and mapping (TradeQuoteError.QueryFailed)
for createOrder are moved accordingly to the execution code.
- Around line 43-46: _wrap the entire async function body of _getTradeQuote in a
try-catch and ensure every awaited helper/async operation (not just SDK calls)
is converted to a Result using AsyncResultOf; for each AsyncResultOf check for
Err and return Err(...) (using the SwapErrorRight shape) instead of letting
exceptions propagate, and in the catch block convert the thrown error into an
Err(SwapErrorRight) return. Apply the same pattern to the other async blocks in
this file flagged by the review (the other promise/await sections around the
later trade quote logic) so no helper throws escape the Result contract._

In `@packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeRate.ts`:
- Around line 42-45: _getTradeRate currently can throw uncaught exceptions
instead of returning a Result; wrap the entire async body of _getTradeRate (and
the other marked blocks around lines ~60-74 and ~129-166) in a try-catch,
convert any unexpected errors into Err(makeSwapErrorRight(...)) so the function
always returns Result<TradeRate, SwapErrorRight>, and use Ok(...) for success;
also use the AsyncResultOf utility to convert awaited helper promises to Results
and handle their Err branches rather than letting them throw; reference symbols:
_getTradeRate, GetTradeRateInput, SwapperDeps, Result, TradeRate,
SwapErrorRight, makeSwapErrorRight, Ok, Err, AsyncResultOf.

In `@packages/swapper/src/swappers/BobGatewaySwapper/utils/constants.ts`:
- Around line 28-30: The current flat BOB_GATEWAY_SUPPORTED_CHAIN_IDS and
BobGatewaySupportedChainId must be replaced with the standard SupportedChainIds
shape (an object with sell and buy arrays) used by swappers; update the exported
constant (rename to BOB_GATEWAY_SUPPORTED_CHAIN_IDS or
BOB_GATEWAY_SUPPORTED_CHAINS as you prefer) to be { sell: [ ... ], buy: [ ... ]
} using btcChainId and bobChainId in the appropriate arrays, and change the
exported type to match SupportedChainIds (or derive a type alias from that
shape) so downstream filtering that expects .sell/.buy works correctly; ensure
any code importing BobGatewaySupportedChainId or the old constant is updated to
use the new object shape.
- Around line 49-50: The decimalSlippageToBobBps function can return "NaN" for
malformed slippage input; add input validation at the top of
decimalSlippageToBobBps to parse slippageDecimal, ensure it's a non-empty string
that parses to a finite number and is within an acceptable range (e.g., >= 0 and
<= 1 or whatever project limit you prefer), and use an early return/throw with a
clear error message (e.g., throw new Error(`Invalid slippageDecimal:
"${slippageDecimal}"`)) when validation fails so downstream code never receives
"NaN".

In `@src/config.ts`:
- Around line 282-283: The feature flag name in config is wrong: replace or
alias VITE_FEATURE_BOB_GATEWAY_SWAP with the rollout name
VITE_FEATURE_BOB_GATEWAY_ENABLED so the environment flag used in the PR
activates the feature; update the config entry that currently defines
VITE_FEATURE_BOB_GATEWAY_SWAP to validate VITE_FEATURE_BOB_GATEWAY_ENABLED (or
add a second boolean key that maps to the same setting) and ensure any code
reading the flag (references to VITE_FEATURE_BOB_GATEWAY_SWAP elsewhere) is
updated to read VITE_FEATURE_BOB_GATEWAY_ENABLED or both names are kept in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a3cec11b-7c8d-468a-8476-ad1cfc3c0534

📥 Commits

Reviewing files that changed from the base of the PR and between 51bf5c1 and 60bcb47.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/bob-gateway-icon.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • .env
  • .env.development
  • headers/csps/defi/swappers/BobGateway.ts
  • headers/csps/index.ts
  • packages/public-api/src/config.ts
  • packages/public-api/src/env.ts
  • packages/swapper/package.json
  • packages/swapper/src/constants.ts
  • packages/swapper/src/index.ts
  • packages/swapper/src/swappers/BobGatewaySwapper/BobGatewaySwapper.ts
  • packages/swapper/src/swappers/BobGatewaySwapper/endpoints.ts
  • packages/swapper/src/swappers/BobGatewaySwapper/index.ts
  • packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.ts
  • packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeRate.ts
  • packages/swapper/src/swappers/BobGatewaySwapper/utils/constants.ts
  • packages/swapper/src/swappers/BobGatewaySwapper/utils/helpers/helpers.ts
  • packages/swapper/src/types.ts
  • src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx
  • src/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/SwapperIcon.tsx
  • src/config.ts
  • src/lib/tradeExecution.ts
  • src/state/helpers.ts
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/test/mocks/store.ts

Comment on lines +143 to +152
let orderInfo
try {
orderInfo = await api.getOrder({ id: orderId })
} catch {
return {
buyTxHash: undefined,
status: TxStatus.Unknown,
message: 'Waiting for deposit...',
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't map every order lookup failure to “Waiting for deposit...”.

This masks real Bob Gateway/API failures as a user-actionable pending state. Only a true not-found/not-yet-indexed response should become "Waiting for deposit..."; transport/server errors should surface as an error or at least preserve the last known status.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swapper/src/swappers/BobGatewaySwapper/endpoints.ts` around lines
143 - 152, The current catch for api.getOrder indiscriminately returns a Waiting
for deposit state; change the error handling in the order lookup so only a true
"not found" / not-yet-indexed response maps to { buyTxHash: undefined, status:
TxStatus.Unknown, message: 'Waiting for deposit...' } — detect this by
inspecting the thrown error from api.getOrder (e.g. error.response?.status ===
404 or a NotFoundError class) and return the waiting payload only in that case;
for all other errors (network/server/transport) either rethrow or return an
explicit error result/preserve the last known status instead of masking it as
Waiting for deposit. Ensure you reference the existing symbols orderInfo,
api.getOrder, buyTxHash, and TxStatus.Unknown when making the change.

Comment on lines +43 to +46
const _getTradeQuote = async (
input: CommonTradeQuoteInput,
deps: SwapperDeps,
): Promise<Result<TradeQuote, SwapErrorRight>> => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unexpected throws can escape the Result contract.

Only the SDK calls are caught. If any helper here throws, _getTradeQuote() rejects instead of returning Err(...), which breaks the swapper API shape expected by callers.

As per coding guidelines, "Use Result<T, E> pattern for error handling in swappers and APIs; ALWAYS use Ok() and Err()" and "ALWAYS wrap async operations in try-catch blocks and use AsyncResultOf utility for converting promises to Results".

Also applies to: 90-106, 224-229

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.ts`
around lines 43 - 46, _wrap the entire async function body of _getTradeQuote in
a try-catch and ensure every awaited helper/async operation (not just SDK calls)
is converted to a Result using AsyncResultOf; for each AsyncResultOf check for
Err and return Err(...) (using the SwapErrorRight shape) instead of letting
exceptions propagate, and in the catch block convert the thrown error into an
Err(SwapErrorRight) return. Apply the same pattern to the other async blocks in
this file flagged by the review (the other promise/await sections around the
later trade quote logic) so no helper throws escape the Result contract._

Comment on lines +150 to +162
// Step 2: Create order to reserve liquidity and get deposit address / EVM tx data
let orderResponse
try {
orderResponse = await api.createOrder({ gatewayQuote: quoteResponse })
} catch (err) {
return Err(
makeSwapErrorRight({
message: '[BobGateway] failed to create order',
code: TradeQuoteError.QueryFailed,
cause: err,
}),
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

getTradeQuote() shouldn't create orders.

This makes the quote path stateful: every quote refresh can reserve a new Bob Gateway order before the user confirms. It also conflicts with the PR's own test flow, which says the order is created on confirmation. Move createOrder() to the execution/unsigned-tx path and keep quoting side-effect free.

Based on learnings, "Avoid side effects in swap logic; ensure swap methods are deterministic and stateless".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.ts`
around lines 150 - 162, The getTradeQuote function is performing a side-effect
by calling api.createOrder (see getTradeQuote and api.createOrder) which
reserves Bob Gateway orders during quoting; remove the createOrder call and any
orderResponse usage from getTradeQuote so the quote path is
stateless/deterministic, and instead invoke api.createOrder from the
execution/unsigned-tx path (e.g., in the swap execution or build/confirm flow
where orders are actually submitted) so order creation happens only at
confirmation; ensure error handling and mapping (TradeQuoteError.QueryFailed)
for createOrder are moved accordingly to the execution code.

Comment on lines +202 to +208
const { fast } = await utxoAdapter.getFeeData({
to: bobSpecific.depositAddress ?? '',
value: sellAmountIncludingProtocolFeesCryptoBaseUnit,
chainSpecific: { pubkey: sendAddress },
sendMax: false,
})
networkFeeCryptoBaseUnit = fast.txFee
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Persist the BTC fee params you use for execution.

For BTC→BOB you estimate the UTXO fee here, but the quote only stores networkFeeCryptoBaseUnit. getUnsignedUtxoTransaction() later reads step.feeData.chainSpecific.satsPerByte, so this path will fail at execution time because chainSpecific was never populated.

Also applies to: 248-251

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.ts`
around lines 202 - 208, The code calls utxoAdapter.getFeeData and assigns
networkFeeCryptoBaseUnit = fast.txFee but fails to persist the BTC fee
parameters required for execution; update the quote construction where fast is
obtained (the utxoAdapter.getFeeData call and the similar occurrence later) to
also set step.feeData.chainSpecific.satsPerByte (and any other chainSpecific fee
fields returned by fast) from fast.satsPerByte (or equivalent) so
getUnsignedUtxoTransaction() can read step.feeData.chainSpecific.satsPerByte at
execution time; ensure the same change is applied to the second getFeeData usage
mentioned so both quote paths include the chainSpecific fee params.

Comment on lines +42 to +45
const _getTradeRate = async (
input: GetTradeRateInput,
deps: SwapperDeps,
): Promise<Result<TradeRate, SwapErrorRight>> => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

getTradeRate() has the same uncaught-exception gap as quote.

This function promises Result<TradeRate, SwapErrorRight>, but helper failures here still throw. Wrap the full body and convert unexpected failures into Err(makeSwapErrorRight(...)) so callers never see a rejected promise from the rate path.

As per coding guidelines, "Use Result<T, E> pattern for error handling in swappers and APIs; ALWAYS use Ok() and Err()" and "ALWAYS wrap async operations in try-catch blocks and use AsyncResultOf utility for converting promises to Results".

Also applies to: 60-74, 129-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeRate.ts`
around lines 42 - 45, _getTradeRate currently can throw uncaught exceptions
instead of returning a Result; wrap the entire async body of _getTradeRate (and
the other marked blocks around lines ~60-74 and ~129-166) in a try-catch,
convert any unexpected errors into Err(makeSwapErrorRight(...)) so the function
always returns Result<TradeRate, SwapErrorRight>, and use Ok(...) for success;
also use the AsyncResultOf utility to convert awaited helper promises to Results
and handle their Err branches rather than letting them throw; reference symbols:
_getTradeRate, GetTradeRateInput, SwapperDeps, Result, TradeRate,
SwapErrorRight, makeSwapErrorRight, Ok, Err, AsyncResultOf.

Comment on lines +28 to +30
export const BOB_GATEWAY_SUPPORTED_CHAIN_IDS = [btcChainId, bobChainId] as const
export type BobGatewaySupportedChainId = (typeof BOB_GATEWAY_SUPPORTED_CHAIN_IDS)[number]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use SupportedChainIds shape (sell/buy) for swapper chain support.

Line 28 defines a flat list, but this path requires supported chain IDs to be split by sell and buy. This can break downstream filtering contracts that expect the standard structure.

♻️ Suggested refactor
+import type { SupportedChainIds } from '../../../types'
 
 // Supported chain IDs for PR 1 (BTC ↔ BOB). LayerZero cross-chain routes are SS-5639.
-export const BOB_GATEWAY_SUPPORTED_CHAIN_IDS = [btcChainId, bobChainId] as const
-export type BobGatewaySupportedChainId = (typeof BOB_GATEWAY_SUPPORTED_CHAIN_IDS)[number]
+export const BOB_GATEWAY_SUPPORTED_CHAIN_IDS: SupportedChainIds = {
+  sell: [btcChainId, bobChainId],
+  buy: [btcChainId, bobChainId],
+}
+export type BobGatewaySupportedChainId =
+  | (typeof BOB_GATEWAY_SUPPORTED_CHAIN_IDS.sell)[number]
+  | (typeof BOB_GATEWAY_SUPPORTED_CHAIN_IDS.buy)[number]
As per coding guidelines: `packages/swapper/src/swappers/*/utils/constants.ts`: “Define supported chain IDs for each swapper in utils/constants.ts with both 'sell' and 'buy' properties following the pattern: SupportedChainIds type”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swapper/src/swappers/BobGatewaySwapper/utils/constants.ts` around
lines 28 - 30, The current flat BOB_GATEWAY_SUPPORTED_CHAIN_IDS and
BobGatewaySupportedChainId must be replaced with the standard SupportedChainIds
shape (an object with sell and buy arrays) used by swappers; update the exported
constant (rename to BOB_GATEWAY_SUPPORTED_CHAIN_IDS or
BOB_GATEWAY_SUPPORTED_CHAINS as you prefer) to be { sell: [ ... ], buy: [ ... ]
} using btcChainId and bobChainId in the appropriate arrays, and change the
exported type to match SupportedChainIds (or derive a type alias from that
shape) so downstream filtering that expects .sell/.buy works correctly; ensure
any code importing BobGatewaySupportedChainId or the old constant is updated to
use the new object shape.

Comment on lines +49 to +50
export const decimalSlippageToBobBps = (slippageDecimal: string): string => {
return String(Math.round(parseFloat(slippageDecimal) * 10_000))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate slippage input before conversion to BPS.

Line 50 can return "NaN" for malformed values (e.g. empty/non-numeric input), which can propagate invalid parameters to quote/rate requests.

🛡️ Suggested hardening
 export const decimalSlippageToBobBps = (slippageDecimal: string): string => {
-  return String(Math.round(parseFloat(slippageDecimal) * 10_000))
+  const parsedSlippage = Number(slippageDecimal)
+  if (!Number.isFinite(parsedSlippage) || parsedSlippage < 0) return '0'
+  return String(Math.round(parsedSlippage * 10_000))
 }
As per coding guidelines: `**/*.{ts,tsx}`: “ALWAYS validate inputs before processing with clear validation error messages and use early returns for validation failures”.
📝 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
export const decimalSlippageToBobBps = (slippageDecimal: string): string => {
return String(Math.round(parseFloat(slippageDecimal) * 10_000))
export const decimalSlippageToBobBps = (slippageDecimal: string): string => {
const parsedSlippage = Number(slippageDecimal)
if (!Number.isFinite(parsedSlippage) || parsedSlippage < 0) return '0'
return String(Math.round(parsedSlippage * 10_000))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swapper/src/swappers/BobGatewaySwapper/utils/constants.ts` around
lines 49 - 50, The decimalSlippageToBobBps function can return "NaN" for
malformed slippage input; add input validation at the top of
decimalSlippageToBobBps to parse slippageDecimal, ensure it's a non-empty string
that parses to a finite number and is within an acceptable range (e.g., >= 0 and
<= 1 or whatever project limit you prefer), and use an early return/throw with a
clear error message (e.g., throw new Error(`Invalid slippageDecimal:
"${slippageDecimal}"`)) when validation fails so downstream code never receives
"NaN".

Comment thread src/config.ts
Comment on lines +282 to +283
VITE_FEATURE_BOB_GATEWAY_SWAP: bool({ default: false }),
VITE_BOB_GATEWAY_AFFILIATE_ID: str({ default: '' }),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Feature flag name doesn't match the rollout instructions.

The PR objective says to enable this via VITE_FEATURE_BOB_GATEWAY_ENABLED=true, but config only validates VITE_FEATURE_BOB_GATEWAY_SWAP. With the current wiring, following the documented setup leaves Bob Gateway disabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config.ts` around lines 282 - 283, The feature flag name in config is
wrong: replace or alias VITE_FEATURE_BOB_GATEWAY_SWAP with the rollout name
VITE_FEATURE_BOB_GATEWAY_ENABLED so the environment flag used in the PR
activates the feature; update the config entry that currently defines
VITE_FEATURE_BOB_GATEWAY_SWAP to validate VITE_FEATURE_BOB_GATEWAY_ENABLED (or
add a second boolean key that maps to the same setting) and ensure any code
reading the flag (references to VITE_FEATURE_BOB_GATEWAY_SWAP elsewhere) is
updated to read VITE_FEATURE_BOB_GATEWAY_ENABLED or both names are kept in sync.

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.

BOB swapper

1 participant