• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cowprotocol / cow-sdk / #1878

25 Jun 2025 10:06AM UTC coverage: 73.99% (-0.2%) from 74.222%
#1878

push

shoom3301
fix(bridge): use gasLimit for hook depending on proxy deployment

613 of 875 branches covered (70.06%)

Branch coverage included in aggregate %.

15 of 26 new or added lines in 9 files covered. (57.69%)

2 existing lines in 2 files now uncovered.

1273 of 1674 relevant lines covered (76.05%)

18.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.19
/src/bridging/providers/bungee/BungeeBridgeProvider.ts
1
import { Signer } from 'ethers'
2
import { latest as latestAppData } from '@cowprotocol/app-data'
3
import { JsonRpcProvider } from '@ethersproject/providers'
4
import { OrderKind } from '@cowprotocol/contracts'
5

6
import {
7
  BridgeDeposit,
8
  BridgeHook,
9
  BridgeProvider,
10
  BridgeProviderInfo,
11
  BridgeQuoteResult,
12
  BridgeStatus,
13
  BridgeStatusResult,
14
  BridgingDepositParams,
15
  QuoteBridgeRequest,
16
} from '../../types'
17
import { RAW_PROVIDERS_FILES_PATH } from '../../const'
18
import { ChainId, ChainInfo, SupportedChainId, TargetChainId } from '../../../chains'
19
import { EvmCall, TokenInfo } from '../../../common'
20
import { mainnet } from '../../../chains/details/mainnet'
21
import { polygon } from '../../../chains/details/polygon'
22
import { arbitrumOne } from '../../../chains/details/arbitrum'
23
import { base } from '../../../chains/details/base'
24
import { optimism } from '../../../chains/details/optimism'
25
import { BungeeApi, BungeeApiOptions } from './BungeeApi'
26
import { toBridgeQuoteResult } from './util'
27
import { CowShedSdk, CowShedSdkOptions } from '../../../cow-shed'
28
import { createBungeeDepositCall } from './createBungeeDepositCall'
29
import { HOOK_DAPP_BRIDGE_PROVIDER_PREFIX } from './const/misc'
30
import { BungeeBridgeName, BungeeBuildTx, BungeeEventStatus, BungeeQuote, BungeeQuoteAPIRequest } from './types'
31
import { getSigner } from '../../../common/utils/wallet'
32
import { BridgeProviderQuoteError } from '../../errors'
33
import { getGasLimitEstimationForHook } from '../utils/getGasLimitEstimationForHook'
34

35
export const BUNGEE_HOOK_DAPP_ID = `${HOOK_DAPP_BRIDGE_PROVIDER_PREFIX}/bungee`
1✔
36
export const BUNGEE_SUPPORTED_NETWORKS = [mainnet, polygon, arbitrumOne, base, optimism]
1✔
37

38
/** There will be no dex swaps happening while bridging. Hence slippage will be zero */
39
const SLIPPAGE_TOLERANCE_BPS = 0
1✔
40

41
export interface BungeeBridgeProviderOptions {
42
  getRpcProvider(chainId: SupportedChainId): JsonRpcProvider
43

44
  // API options
45
  apiOptions?: BungeeApiOptions
46

47
  // Cow-shed options
48
  cowShedOptions?: CowShedSdkOptions
49
}
50

51
export interface BungeeQuoteResult extends BridgeQuoteResult {
52
  bungeeQuote: BungeeQuote
53
  buildTx: BungeeBuildTx
54
}
55

56
export class BungeeBridgeProvider implements BridgeProvider<BungeeQuoteResult> {
57
  protected api: BungeeApi
58
  protected cowShedSdk: CowShedSdk
59
  public getRpcProvider: (chainId: SupportedChainId) => JsonRpcProvider
60

61
  constructor(private options: BungeeBridgeProviderOptions) {
62
    this.api = new BungeeApi(options.apiOptions)
12✔
63
    this.cowShedSdk = new CowShedSdk(options.cowShedOptions)
12✔
64
    this.getRpcProvider = options.getRpcProvider
12✔
65
  }
66

67
  info: BridgeProviderInfo = {
12✔
68
    name: 'Bungee',
69
    logoUrl: `${RAW_PROVIDERS_FILES_PATH}/bungee/bungee-logo.png`,
70
    dappId: BUNGEE_HOOK_DAPP_ID,
71
    website: 'https://www.bungee.exchange',
72
  }
73

74
  async getNetworks(): Promise<ChainInfo[]> {
75
    return BUNGEE_SUPPORTED_NETWORKS
1✔
76
  }
77

78
  async getBuyTokens(targetChainId: TargetChainId): Promise<TokenInfo[]> {
79
    return this.api.getBuyTokens({ targetChainId })
2✔
80
  }
81

82
  async getIntermediateTokens(request: QuoteBridgeRequest): Promise<TokenInfo[]> {
83
    if (request.kind !== OrderKind.SELL) {
×
84
      throw new BridgeProviderQuoteError('Only SELL is supported for now', { kind: request.kind })
×
85
    }
86

87
    return this.api.getIntermediateTokens({
×
88
      fromChainId: request.sellTokenChainId,
89
      toChainId: request.buyTokenChainId,
90
      toTokenAddress: request.buyTokenAddress,
91
    })
92
  }
93

94
  async getQuote(request: QuoteBridgeRequest): Promise<BungeeQuoteResult> {
95
    // @note sellTokenAddress here will be the intermediate token in usage. the naming might be a bit misleading
96
    //       see getQuoteWithBridge.ts::getBaseBridgeQuoteRequest()
97
    const { sellTokenAddress, sellTokenChainId, buyTokenChainId, buyTokenAddress, amount, receiver, account, owner } =
98
      request
1✔
99

100
    // @note bungee api requires the sender address. sender address would be the cowshed account
101
    // fetch the cowshed account
102
    const ownerAddress = owner ?? account
1✔
103
    const cowshedAccount = this.cowShedSdk.getCowShedAccount(sellTokenChainId, ownerAddress)
1✔
104

105
    // fetch quote from bungee api
106
    const bungeeQuoteRequest: BungeeQuoteAPIRequest = {
1✔
107
      userAddress: cowshedAccount,
108
      originChainId: sellTokenChainId.toString(),
109
      destinationChainId: buyTokenChainId.toString(),
110
      inputToken: sellTokenAddress, // use intermediate token for the bridging quote
111
      inputAmount: amount.toString(),
112
      receiverAddress: receiver ?? account, // receiver is required on bungee api
1!
113
      outputToken: buyTokenAddress,
114
      includeBridges: this.options.apiOptions?.includeBridges,
115
      enableManual: true,
116
      disableSwapping: true,
117
      disableAuto: true,
118
    }
119
    const quoteWithBuildTx = await this.api.getBungeeQuoteWithBuildTx(bungeeQuoteRequest)
1✔
120

121
    // verify build-tx data
122
    const isBuildTxValid = await this.api.verifyBungeeBuildTx(
1✔
123
      quoteWithBuildTx.bungeeQuote,
124
      quoteWithBuildTx.buildTx,
125
      getSigner(request.signer),
126
    )
127

128
    if (!isBuildTxValid) {
1!
129
      throw new BridgeProviderQuoteError('Build tx data is invalid', quoteWithBuildTx)
×
130
    }
131

132
    // convert bungee quote response to BridgeQuoteResult
133
    return toBridgeQuoteResult(request, SLIPPAGE_TOLERANCE_BPS, quoteWithBuildTx)
1✔
134
  }
135

136
  async getUnsignedBridgeCall(request: QuoteBridgeRequest, quote: BungeeQuoteResult): Promise<EvmCall> {
137
    return createBungeeDepositCall({
×
138
      request,
139
      quote,
140
      cowShedSdk: this.cowShedSdk,
141
    })
142
  }
143

144
  async getGasLimitEstimationForHook(request: QuoteBridgeRequest): Promise<number> {
NEW
145
    return getGasLimitEstimationForHook(this.cowShedSdk, request, this.getRpcProvider(request.sellTokenChainId))
×
146
  }
147

148
  async getSignedHook(
149
    chainId: SupportedChainId,
150
    unsignedCall: EvmCall,
151
    signer: Signer,
152
    bridgeHookNonce: string,
153
    deadline: bigint,
154
    hookGasLimit: number,
155
  ): Promise<BridgeHook> {
156
    // Sign the multicall
157
    const { signedMulticall, cowShedAccount, gasLimit } = await this.cowShedSdk.signCalls({
×
158
      calls: [
159
        {
160
          target: unsignedCall.to,
161
          value: unsignedCall.value,
162
          callData: unsignedCall.data,
163
          allowFailure: false,
164
          isDelegateCall: true,
165
        },
166
      ],
167
      chainId,
168
      signer,
169
      gasLimit: BigInt(hookGasLimit),
170
      deadline,
171
      nonce: bridgeHookNonce,
172
    })
173

174
    const { to, data } = signedMulticall
×
175
    return {
×
176
      postHook: {
177
        target: to,
178
        callData: data,
179
        gasLimit: gasLimit.toString(),
180
        dappId: BUNGEE_HOOK_DAPP_ID,
181
      },
182
      recipient: cowShedAccount,
183
    }
184
  }
185

186
  async getBridgingParams(_chainId: ChainId, orderId: string, _txHash: string): Promise<BridgingDepositParams | null> {
UNCOV
187
    const events = await this.api.getEvents({ orderId })
×
188
    const event = events[0]
×
189

190
    if (!event) return null
×
191

192
    return {
×
193
      inputTokenAddress: event.srcTokenAddress,
194
      outputTokenAddress: event.destTokenAddress,
195
      inputAmount: BigInt(event.srcAmount),
196
      outputAmount: event.destAmount ? BigInt(event.destAmount) : null,
×
197
      owner: event.sender,
198
      quoteTimestamp: null,
199
      fillDeadline: null,
200
      recipient: event.recipient,
201
      sourceChainId: event.fromChainId,
202
      destinationChainId: event.toChainId,
203
      bridgingId: orderId,
204
    }
205
  }
206

207
  async decodeBridgeHook(_hook: latestAppData.CoWHook): Promise<BridgeDeposit> {
208
    // Decoding the full quote from just hook calldata is quite hard right now
209
    // This will need more context and thus changes to either the hook calldata or the function interface
210
    // Can revisit once the approach is decided
211
    throw new Error('Not implemented')
1✔
212
  }
213

214
  async getBridgingId(_orderUid: string, _settlementTx: string, _logIndex: number): Promise<string> {
215
    // order uid itself can be used as bridging id on Bungee
216
    return _orderUid
1✔
217
  }
218

219
  getExplorerUrl(bridgingId: string): string {
220
    return `https://socketscan.io/tx/${bridgingId}`
1✔
221
  }
222

223
  async getStatus(_bridgingId: string): Promise<BridgeStatusResult> {
224
    // fetch indexed event from api
225
    const events = await this.api.getEvents({ orderId: _bridgingId })
2✔
226

227
    // if empty, return not_initiated
228
    // order id exists so order is valid, but
229
    // - bungee may not have indexed the event yet, which it will do eventually
230
    // - or the order is not filled yet on cowswap
231
    if (!events?.length) {
2✔
232
      return { status: BridgeStatus.UNKNOWN }
1✔
233
    }
234
    const event = events[0]
1✔
235

236
    // if srcTxStatus = pending, return in_progress
237
    if (event.srcTxStatus === BungeeEventStatus.PENDING) {
1!
238
      return { status: BridgeStatus.IN_PROGRESS }
×
239
    }
240

241
    // if srcTxStatus = completed & destTxStatus = pending,
242
    if (event.srcTxStatus === BungeeEventStatus.COMPLETED && event.destTxStatus === BungeeEventStatus.PENDING) {
1!
243
      // if bridgeName = across,
244
      if (event.bridgeName === BungeeBridgeName.ACROSS) {
×
245
        try {
×
246
          // check across api to check status is expired or refunded
247
          const acrossStatus = await this.api.getAcrossStatus(event.orderId)
×
248
          if (acrossStatus === 'expired') {
×
249
            return { status: BridgeStatus.EXPIRED, depositTxHash: event.srcTransactionHash }
×
250
          }
251
          if (acrossStatus === 'refunded') {
×
252
            // refunded means failed
253
            return { status: BridgeStatus.REFUND, depositTxHash: event.srcTransactionHash }
×
254
          }
255
        } catch (e) {
256
          console.error('BungeeBridgeProvider get across status error', e)
×
257
        }
258
      }
259
      // if not across or across API fails, waiting for dest tx, return in_progress
260
      return { status: BridgeStatus.IN_PROGRESS, depositTxHash: event.srcTransactionHash }
×
261
    }
262

263
    // if srcTxStatus = completed & destTxStatus = completed, return executed
264
    if (event.srcTxStatus === BungeeEventStatus.COMPLETED && event.destTxStatus === BungeeEventStatus.COMPLETED) {
1!
265
      return {
1✔
266
        status: BridgeStatus.EXECUTED,
267
        depositTxHash: event.srcTransactionHash,
268
        fillTxHash: event.destTransactionHash,
269
      }
270
    }
271

272
    // there is no failed case for across - gets auto-refunded - or cctp - attestation can be relayed by anyone on destination chain
273
    throw new Error('Unknown status')
×
274
  }
275

276
  async getCancelBridgingTx(_bridgingId: string): Promise<EvmCall> {
277
    // Support for cancellation will depend on the actual bridge an order went through.
278
    // Across & CCTP doesn't support cancellation.
279
    // Therefore, not implementing cancellation
280
    throw new Error('Not implemented')
1✔
281
  }
282

283
  async getRefundBridgingTx(_bridgingId: string): Promise<EvmCall> {
284
    // Support for refund will depend on the actual bridge an order went through.
285
    // CCTP doesn't support refund.
286
    // Across auto-relays refund txns some time after the order expires. No user action needed.
287
    // Therefore, not implementing refund
288
    throw new Error('Not implemented')
1✔
289
  }
290
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc