feat: add BOB Gateway swapper (BTC ↔ BOB)#12275
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (17)
💤 Files with no reviewable changes (11)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughIntroduces the Bob Gateway swapper supporting BTC ↔ BOB chain token swaps through quote retrieval, order creation, unsigned transaction building, and order status polling. Includes environment variables, SDK dependency, type contracts, quote/rate/execution APIs, CSP headers, feature flag, client wiring, and server-side API integration with disabled swapper gating. ChangesBob Gateway swapper integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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
|
BOB which is in the middle of refactoring their API to just take a regular raw EVM address for payouts. No UUID needed, which is great because it fits into our swapper params perfectly. They confirmed that they work with Gnosis safes, no problem. So a simple parameter swap and then a bunch of testing to make sure the fees go in, and we should be good. |
The affiliate recipient isn't a secret, so drop the VITE_BOB_GATEWAY_AFFILIATE_ID env var and source it from treasury.ts like the rest of the swappers. Gateway pays affiliate fees as basis-point cuts of the swap output at settlement on BOB chain, so add DAO_TREASURY_BOB (same DAO EOA as Monad/HyperEVM) and wire getBobGatewayAffiliates to getTreasuryAddressFromChainId(bobChainId). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- collapse chain name/id structures into a single chainIdToBobGatewayChainName map (matching the debridge/across pattern) and drop telos/swell/soneium/ optimism, which are no longer in gateway's route set (telos/swell were caip additions solely for gateway, so remove them there too) - plumb affiliateBps from the quote/rate input instead of a hardcoded const - use the shared getDefaultSlippageDecimalPercentageForSwapper instead of a duplicated local default - replace generated instanceOf*OneOf* guards with semantic 'key' in checks - support tokenSwap (EVM↔EVM and same-chain) orders: accept the tokenSwap createOrderV2 variant in the evm execution path, drop the same-chain route guard, and pick rate dummy addresses per side instead of assuming evm↔btc - flatten utils/helpers/helpers.ts to utils/helpers.ts, drop the redundant BTC_TOKEN_ADDRESS alias of zeroAddress and the getBobGatewayClient apiKey || undefined normalization (sdk treats '' as absent) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Declare the bob gateway metadata types in BobGatewaySwapper/types.ts like the other swappers, removing the @gobob/bob-sdk import from the shared types.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BOB denominates quote fees across multiple assets (e.g. the BTC inclusion fee in the output token, affiliate/solver fees in USDT). Resolve each fee's chain + token address to an AssetId and aggregate per asset instead of only counting output-token-denominated fees. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Publishable key for affiliate attribution and rate limiting, committed inline like the other swapper keys (bebop, chainflip, etc.). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Create the gateway order during getTradeQuote so quotes are executable (including via the public api) and estimate real network fees from the order's tx data instead of a hardcoded gas limit - UTXO deposit fees for onramps, eth_estimateGas for the EVM gateway call. Fee estimation throws on an unsupported chain or invalid quote rather than returning zero fees. Collapse the two bob metadata types into a single XOR BobGatewayMetadata (evmTx | utxoTx) and share quote/order/fee helpers between getTradeRate and getTradeQuote. Fix checkTradeStatus to surface buyTxHash only on success and flag refunds via the status message instead of mislabeling a refund hash as buyTxHash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Associate the broadcast sell tx with its BOB Gateway order via the v2 register-tx endpoint so the watchtower can track the order and progress its status. Registers once per swap during status polling, retrying until the tx propagates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d funds
- add rate-time network fee estimation, since there's no order/tx to estimate
against yet: default gas limits for EVM tokenSwap (350k) / offramp (550k)
× live gas price, and dynamic sats/vByte × default onramp tx vsize (200) for
the BTC onramp. quote path still computes the exact fee.
- pay affiliate fees to the BOB treasury via getTreasuryAddressFromChainId
instead of a hardcoded address
- surface INSUFFICIENT_CONFIRMED_FUNDS as a dedicated user-facing error
("Funds still confirming. Try again once confirmed.") instead of a generic
quote failure
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BOB Gateway validates the balance of the assigned sender address at order creation, but utxo account funds are spread across many addresses (the default sendAddress is the next unused receive address, which never holds funds). - select the richest utxo address as the order sender at quote time - constrain tx building and fee estimation inputs to the validated sender - surface an actionable FundsFragmented error directing the user to consolidate funds when no single address can cover the sell amount Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rates route already gated swappers via a hardcoded allowlist, but the quote route accepted any registered swapper name. Share the allowlist and enforce it for quotes so new swappers (e.g. BOB Gateway) are not exposed via the public api until fully validated in the web app. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Threshold the funds fragmented check on amount + actual getFeeData fee for the from-constrained send instead of the sell amount alone, surfacing the actionable error before order creation for borderline fee shortfalls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
New Features
Chores