feat: add BOB Gateway swapper (BTC ↔ BOB)#12275
Conversation
📝 WalkthroughWalkthroughA 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
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. |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/bob-gateway-icon.pngis excluded by!**/*.png
📒 Files selected for processing (24)
.env.env.developmentheaders/csps/defi/swappers/BobGateway.tsheaders/csps/index.tspackages/public-api/src/config.tspackages/public-api/src/env.tspackages/swapper/package.jsonpackages/swapper/src/constants.tspackages/swapper/src/index.tspackages/swapper/src/swappers/BobGatewaySwapper/BobGatewaySwapper.tspackages/swapper/src/swappers/BobGatewaySwapper/endpoints.tspackages/swapper/src/swappers/BobGatewaySwapper/index.tspackages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.tspackages/swapper/src/swappers/BobGatewaySwapper/swapperApi/getTradeRate.tspackages/swapper/src/swappers/BobGatewaySwapper/utils/constants.tspackages/swapper/src/swappers/BobGatewaySwapper/utils/helpers/helpers.tspackages/swapper/src/types.tssrc/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsxsrc/components/MultiHopTrade/components/TradeInput/components/SwapperIcon/SwapperIcon.tsxsrc/config.tssrc/lib/tradeExecution.tssrc/state/helpers.tssrc/state/slices/preferencesSlice/preferencesSlice.tssrc/test/mocks/store.ts
| let orderInfo | ||
| try { | ||
| orderInfo = await api.getOrder({ id: orderId }) | ||
| } catch { | ||
| return { | ||
| buyTxHash: undefined, | ||
| status: TxStatus.Unknown, | ||
| message: 'Waiting for deposit...', | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| const _getTradeQuote = async ( | ||
| input: CommonTradeQuoteInput, | ||
| deps: SwapperDeps, | ||
| ): Promise<Result<TradeQuote, SwapErrorRight>> => { |
There was a problem hiding this comment.
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._
| // 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, | ||
| }), | ||
| ) | ||
| } |
There was a problem hiding this comment.
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.
| const { fast } = await utxoAdapter.getFeeData({ | ||
| to: bobSpecific.depositAddress ?? '', | ||
| value: sellAmountIncludingProtocolFeesCryptoBaseUnit, | ||
| chainSpecific: { pubkey: sendAddress }, | ||
| sendMax: false, | ||
| }) | ||
| networkFeeCryptoBaseUnit = fast.txFee |
There was a problem hiding this comment.
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.
| const _getTradeRate = async ( | ||
| input: GetTradeRateInput, | ||
| deps: SwapperDeps, | ||
| ): Promise<Result<TradeRate, SwapErrorRight>> => { |
There was a problem hiding this comment.
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.
| export const BOB_GATEWAY_SUPPORTED_CHAIN_IDS = [btcChainId, bobChainId] as const | ||
| export type BobGatewaySupportedChainId = (typeof BOB_GATEWAY_SUPPORTED_CHAIN_IDS)[number] | ||
|
|
There was a problem hiding this comment.
🛠️ 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]🤖 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.
| export const decimalSlippageToBobBps = (slippageDecimal: string): string => { | ||
| return String(Math.round(parseFloat(slippageDecimal) * 10_000)) |
There was a problem hiding this comment.
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))
}📝 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.
| 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".
| VITE_FEATURE_BOB_GATEWAY_SWAP: bool({ default: false }), | ||
| VITE_BOB_GATEWAY_AFFILIATE_ID: str({ default: '' }), |
There was a problem hiding this comment.
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.
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:
BobGatewaySwapperpackage underpackages/swapper/src/swappers/BobGatewaySwapper/getTradeQuoteandgetTradeRateimplementations using@gobob/bob-sdkgateway-api-mainnet.gobob.xyzbob-gateway-icon.pngBOB_GATEWAY_ENABLEDfeature flagIssue (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).Testing
Engineering
VITE_FEATURE_BOB_GATEWAY_ENABLED=truein.env.developmentOperations
Screenshots (if applicable)
Summary by CodeRabbit
Release Notes