Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ VITE_FEATURE_TON=true
VITE_FEATURE_EARN_TAB=true
VITE_FEATURE_ACROSS_SWAP=true
VITE_FEATURE_DEBRIDGE_SWAP=true
VITE_FEATURE_BOB_GATEWAY_SWAP=false
VITE_BOB_GATEWAY_AFFILIATE_ID=
VITE_FEATURE_USERBACK=true
VITE_FEATURE_AGENTIC_CHAT=false
VITE_FEATURE_MM_NATIVE_MULTICHAIN=false
Expand Down
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ VITE_FEATURE_HEMI=true
VITE_FEATURE_SONIC=true
VITE_FEATURE_UNICHAIN=true
VITE_FEATURE_BOB=true
VITE_FEATURE_BOB_GATEWAY_SWAP=true
VITE_BOB_GATEWAY_AFFILIATE_ID=
VITE_FEATURE_MODE=true
VITE_FEATURE_SONEIUM=true
VITE_FEATURE_TON=true
Expand Down
5 changes: 5 additions & 0 deletions headers/csps/defi/swappers/BobGateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Csp } from '../../../types'

export const csp: Csp = {
'connect-src': ['https://gateway-api-mainnet.gobob.xyz'],
}
2 changes: 2 additions & 0 deletions headers/csps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import { csp as safe } from './defi/safe'
import { csp as zeroX } from './defi/swappers/0x'
import { csp as avnu } from './defi/swappers/Avnu'
import { csp as bebop } from './defi/swappers/Bebop'
import { csp as bobGateway } from './defi/swappers/BobGateway'
import { csp as butterSwap } from './defi/swappers/ButterSwap'
import { csp as cowSwap } from './defi/swappers/CowSwap'
import { csp as nearIntents } from './defi/swappers/NearIntents'
Expand Down Expand Up @@ -184,6 +185,7 @@ export const csps = [
safe,
zeroX,
avnu,
bobGateway,
bebop,
cowSwap,
nearIntents,
Expand Down
1 change: 1 addition & 0 deletions packages/public-api/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export const getServerConfig = (): SwapperConfig => ({
VITE_ACROSS_API_URL: env.ACROSS_API_URL,
VITE_ACROSS_INTEGRATOR_ID: env.ACROSS_INTEGRATOR_ID,
VITE_DEBRIDGE_API_URL: env.DEBRIDGE_API_URL,
VITE_BOB_GATEWAY_AFFILIATE_ID: env.BOB_GATEWAY_AFFILIATE_ID,
})
1 change: 1 addition & 0 deletions packages/public-api/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const envSchema = z.object({
CHAINFLIP_API_KEY: z.string().default(''),
BEBOP_API_KEY: z.string().default(''),
NEAR_INTENTS_API_KEY: z.string().default(''),
BOB_GATEWAY_AFFILIATE_ID: z.string().default(''),
TENDERLY_API_KEY: z.string().default(''),
TENDERLY_ACCOUNT_SLUG: z.string().default(''),
TENDERLY_PROJECT_SLUG: z.string().default(''),
Expand Down
1 change: 1 addition & 0 deletions packages/swapper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@coral-xyz/anchor": "0.29.0",
"@cowprotocol/app-data": "^2.3.0",
"@defuse-protocol/one-click-sdk-typescript": "^0.1.1-0.2",
"@gobob/bob-sdk": "5.3.2",
"@mysten/sui": "^1.45.2",
"@shapeshiftoss/bitcoinjs-lib": "7.0.0-shapeshift.0",
"@shapeshiftoss/caip": "workspace:^",
Expand Down
9 changes: 9 additions & 0 deletions packages/swapper/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { avnuSwapper } from './swappers/AvnuSwapper/AvnuSwapper'
import { avnuApi } from './swappers/AvnuSwapper/endpoints'
import { bebopSwapper } from './swappers/BebopSwapper/BebopSwapper'
import { bebopApi } from './swappers/BebopSwapper/endpoints'
import { bobGatewaySwapper } from './swappers/BobGatewaySwapper/BobGatewaySwapper'
import { bobGatewayApi } from './swappers/BobGatewaySwapper/endpoints'
import { butterSwap } from './swappers/ButterSwap/ButterSwap'
import { butterSwapApi } from './swappers/ButterSwap/endpoints'
import { cetusSwapper } from './swappers/CetusSwapper/CetusSwapper'
Expand Down Expand Up @@ -116,6 +118,10 @@ export const swappers: Record<SwapperName, (SwapperApi & Swapper) | undefined> =
...debridgeSwapper,
...debridgeApi,
},
[SwapperName.BobGateway]: {
...bobGatewaySwapper,
...bobGatewayApi,
},
[SwapperName.Test]: undefined,
}

Expand All @@ -135,6 +141,7 @@ const DEFAULT_AVNU_SLIPPAGE_DECIMAL_PERCENTAGE = '0.02'
const DEFAULT_STONFI_SLIPPAGE_DECIMAL_PERCENTAGE = '0.01'
// deBridge API off-chain simulation overestimates output on some chains (e.g. SEI ~2.4%), so auto slippage (1%) is insufficient
const DEFAULT_DEBRIDGE_SLIPPAGE_DECIMAL_PERCENTAGE = '0.03'
const DEFAULT_BOB_GATEWAY_SLIPPAGE_DECIMAL_PERCENTAGE = '0.005'

export const getDefaultSlippageDecimalPercentageForSwapper = (
swapperName: SwapperName | undefined,
Expand Down Expand Up @@ -175,6 +182,8 @@ export const getDefaultSlippageDecimalPercentageForSwapper = (
return DEFAULT_AVNU_SLIPPAGE_DECIMAL_PERCENTAGE
case SwapperName.Stonfi:
return DEFAULT_STONFI_SLIPPAGE_DECIMAL_PERCENTAGE
case SwapperName.BobGateway:
return DEFAULT_BOB_GATEWAY_SLIPPAGE_DECIMAL_PERCENTAGE
default:
return assertUnreachable(swapperName)
}
Expand Down
1 change: 1 addition & 0 deletions packages/swapper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './cowswap-utils'
export * from './safe-utils'
export * from './swapper'
export * from './swappers/ArbitrumBridgeSwapper'
export * from './swappers/BobGatewaySwapper'
export * from './swappers/AvnuSwapper'
export * from './swappers/BebopSwapper'
export * from './swappers/CetusSwapper'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Swapper } from '../../types'
import { executeEvmTransaction } from '../../utils'

export const bobGatewaySwapper: Swapper = {
executeEvmTransaction,
}
163 changes: 163 additions & 0 deletions packages/swapper/src/swappers/BobGatewaySwapper/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { Configuration, V1Api } from '@gobob/bob-sdk'
import { evm } from '@shapeshiftoss/chain-adapters'
import { TxStatus } from '@shapeshiftoss/unchained-client'

import type { SwapperApi, UtxoFeeData } from '../../types'
import { getExecutableTradeStep, isExecutableTradeQuote } from '../../utils'
import { getTradeQuote } from './swapperApi/getTradeQuote'
import { getTradeRate } from './swapperApi/getTradeRate'
import { BOB_GATEWAY_BASE_URL } from './utils/constants'
import { mapBobGatewayOrderStatusToTxStatus } from './utils/helpers/helpers'

export const bobGatewayApi: SwapperApi = {
getTradeQuote,
getTradeRate,

getUnsignedUtxoTransaction: ({
stepIndex,
tradeQuote,
xpub,
accountType,
assertGetUtxoChainAdapter,
}) => {
if (!isExecutableTradeQuote(tradeQuote))
throw new Error('[BobGateway] unable to execute a trade rate')

const step = getExecutableTradeStep(tradeQuote, stepIndex)
const { accountNumber, bobSpecific, sellAsset } = step

if (!bobSpecific?.depositAddress)
throw new Error('[BobGateway] missing depositAddress in step metadata')
if (!bobSpecific?.orderId) throw new Error('[BobGateway] missing orderId in step metadata')

const adapter = assertGetUtxoChainAdapter(sellAsset.chainId)

return adapter.buildSendApiTransaction({
value: step.sellAmountIncludingProtocolFeesCryptoBaseUnit,
xpub,
to: bobSpecific.depositAddress,
accountNumber,
skipToAddressValidation: true,
chainSpecific: {
accountType,
satoshiPerByte: (step.feeData.chainSpecific as UtxoFeeData).satsPerByte,
},
})
},

getUtxoTransactionFees: async ({ stepIndex, tradeQuote, xpub, assertGetUtxoChainAdapter }) => {
if (!isExecutableTradeQuote(tradeQuote))
throw new Error('[BobGateway] unable to execute a trade rate')

const step = getExecutableTradeStep(tradeQuote, stepIndex)
const { bobSpecific, sellAsset } = step

if (!bobSpecific?.depositAddress)
throw new Error('[BobGateway] missing depositAddress in step metadata')

const adapter = assertGetUtxoChainAdapter(sellAsset.chainId)
const { fast } = await adapter.getFeeData({
to: bobSpecific.depositAddress,
value: step.sellAmountIncludingProtocolFeesCryptoBaseUnit,
chainSpecific: { pubkey: xpub },
sendMax: false,
})

return fast.txFee
},

getUnsignedEvmTransaction: async ({
from,
stepIndex,
tradeQuote,
assertGetEvmChainAdapter,
supportsEIP1559,
}) => {
if (!isExecutableTradeQuote(tradeQuote))
throw new Error('[BobGateway] unable to execute a trade rate')

const step = getExecutableTradeStep(tradeQuote, stepIndex)
const { accountNumber, bobSpecific, sellAsset } = step

if (!bobSpecific?.evmTx) throw new Error('[BobGateway] missing evmTx in step metadata')
if (!bobSpecific?.orderId) throw new Error('[BobGateway] missing orderId in step metadata')

const adapter = assertGetEvmChainAdapter(sellAsset.chainId)
const { to, data, value } = bobSpecific.evmTx

const feeData = await evm.getFees({
adapter,
data: data || '0x',
to,
value,
from,
supportsEIP1559,
})

return adapter.buildCustomApiTx({
accountNumber,
from,
to,
value,
data: data || '0x',
...feeData,
})
},

getEvmTransactionFees: async ({
from,
stepIndex,
tradeQuote,
supportsEIP1559,
assertGetEvmChainAdapter,
}) => {
if (!isExecutableTradeQuote(tradeQuote))
throw new Error('[BobGateway] unable to execute a trade rate')

const step = getExecutableTradeStep(tradeQuote, stepIndex)
const { bobSpecific, sellAsset } = step

if (!bobSpecific?.evmTx) throw new Error('[BobGateway] missing evmTx in step metadata')

const adapter = assertGetEvmChainAdapter(sellAsset.chainId)
const { to, data, value } = bobSpecific.evmTx

const { networkFeeCryptoBaseUnit } = await evm.getFees({
adapter,
data: data || '0x',
to,
value,
from,
supportsEIP1559,
})

return networkFeeCryptoBaseUnit
},

checkTradeStatus: async ({ swap }) => {
const orderId = swap?.metadata.bobSpecific?.orderId
if (!orderId) throw new Error('[BobGateway] orderId is required for status check')

const api = new V1Api(new Configuration({ basePath: BOB_GATEWAY_BASE_URL }))

let orderInfo
try {
orderInfo = await api.getOrder({ id: orderId })
} catch {
return {
buyTxHash: undefined,
status: TxStatus.Unknown,
message: 'Waiting for deposit...',
}
}
Comment on lines +143 to +152
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.


const status = mapBobGatewayOrderStatusToTxStatus(orderInfo.status)
const buyTxHash = orderInfo.dstInfo.txHash ?? undefined

return {
buyTxHash,
status,
message: undefined,
}
},
}
3 changes: 3 additions & 0 deletions packages/swapper/src/swappers/BobGatewaySwapper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { bobGatewayApi } from './endpoints'
export { bobGatewaySwapper } from './BobGatewaySwapper'
export * from './utils/constants'
Loading
Loading