• 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

56.9
/src/bridging/providers/across/AcrossBridgeProvider.ts
1
import { Signer } from 'ethers'
2
import { latest as latestAppData } from '@cowprotocol/app-data'
3

4
import {
5
  BridgeDeposit,
6
  BridgeHook,
7
  BridgeProvider,
8
  BridgeProviderInfo,
9
  BridgeQuoteResult,
10
  BridgeStatusResult,
11
  BridgingDepositParams,
12
  QuoteBridgeRequest,
13
} from '../../types'
14

15
import { HOOK_DAPP_BRIDGE_PROVIDER_PREFIX, RAW_PROVIDERS_FILES_PATH } from '../../const'
16

17
import { ChainId, ChainInfo, SupportedChainId, TargetChainId } from '../../../chains'
18

19
import { EvmCall, TokenInfo } from '../../../common'
20

21
import { mainnet } from '../../../chains/details/mainnet'
22
import { polygon } from '../../../chains/details/polygon'
23
import { arbitrumOne } from '../../../chains/details/arbitrum'
24
import { base } from '../../../chains/details/base'
25
import { optimism } from '../../../chains/details/optimism'
26
import { AcrossApi, AcrossApiOptions } from './AcrossApi'
27
import { mapAcrossStatusToBridgeStatus, toBridgeQuoteResult } from './util'
28
import { CowShedSdk, CowShedSdkOptions } from '../../../cow-shed'
29
import { createAcrossDepositCall } from './createAcrossDepositCall'
30
import { OrderKind } from '@cowprotocol/contracts'
31
import { SuggestedFeesResponse } from './types'
32
import { getDepositParams } from './getDepositParams'
33
import { JsonRpcProvider } from '@ethersproject/providers'
34
import { BridgeProviderQuoteError } from '../../errors'
35
import { getGasLimitEstimationForHook } from '../utils/getGasLimitEstimationForHook'
36

37
type SupportedTokensState = Record<ChainId, Record<string, TokenInfo>>
38

39
export const ACROSS_HOOK_DAPP_ID = `${HOOK_DAPP_BRIDGE_PROVIDER_PREFIX}/across`
1✔
40
export const ACROSS_SUPPORTED_NETWORKS = [mainnet, polygon, arbitrumOne, base, optimism]
1✔
41

42
// We need to review if we should set an additional slippage tolerance, for now assuming the quote gives you the exact price of bridging and no further slippage is needed
43
const SLIPPAGE_TOLERANCE_BPS = 0
1✔
44

45
export interface AcrossBridgeProviderOptions {
46
  getRpcProvider(chainId: SupportedChainId): JsonRpcProvider
47

48
  // API options
49
  apiOptions?: AcrossApiOptions
50

51
  // Cow-shed options
52
  cowShedOptions?: CowShedSdkOptions
53
}
54

55
export interface AcrossQuoteResult extends BridgeQuoteResult {
56
  suggestedFees: SuggestedFeesResponse
57
}
58

59
export class AcrossBridgeProvider implements BridgeProvider<AcrossQuoteResult> {
60
  protected api: AcrossApi
61
  protected cowShedSdk: CowShedSdk
62
  public getRpcProvider: (chainId: SupportedChainId) => JsonRpcProvider
63

64
  private supportedTokens: SupportedTokensState | null = null
11✔
65

66
  constructor(options: AcrossBridgeProviderOptions) {
67
    this.api = new AcrossApi(options.apiOptions)
11✔
68
    this.cowShedSdk = new CowShedSdk(options.cowShedOptions)
11✔
69
    this.getRpcProvider = options.getRpcProvider
11✔
70
  }
71

72
  info: BridgeProviderInfo = {
11✔
73
    name: 'Across',
74
    logoUrl: `${RAW_PROVIDERS_FILES_PATH}/across/across-logo.png`,
75
    dappId: ACROSS_HOOK_DAPP_ID,
76
    website: 'https://across.to',
77
  }
78

79
  async getNetworks(): Promise<ChainInfo[]> {
80
    return ACROSS_SUPPORTED_NETWORKS
1✔
81
  }
82

83
  async getBuyTokens(targetChainId: TargetChainId): Promise<TokenInfo[]> {
84
    return Object.values((await this.getSupportedTokensState())[targetChainId] || {})
2✔
85
  }
86

87
  async getIntermediateTokens(request: QuoteBridgeRequest): Promise<TokenInfo[]> {
88
    if (request.kind !== OrderKind.SELL) {
×
89
      throw new BridgeProviderQuoteError('Only SELL is supported for now', { kind: request.kind })
×
90
    }
91

92
    const { sellTokenChainId, buyTokenChainId, buyTokenAddress } = request
×
93

94
    const supportedTokensState = await this.getSupportedTokensState()
×
95
    const buyTokenAddressLower = buyTokenAddress.toLowerCase()
×
96

97
    const sourceTokens = supportedTokensState[sellTokenChainId]
×
98
    const targetTokens = supportedTokensState[buyTokenChainId]
×
99

100
    // Find the token symbol for the target token
101
    const targetTokenSymbol = targetTokens && targetTokens[buyTokenAddressLower]?.symbol?.toLowerCase()
×
102
    if (!targetTokenSymbol) return []
×
103

104
    // Use the tokenSymbol to find the outputToken in the target chain
105
    return Object.values(sourceTokens || {}).filter((token) => token.symbol?.toLowerCase() === targetTokenSymbol)
×
106
  }
107

108
  async getQuote(request: QuoteBridgeRequest): Promise<AcrossQuoteResult> {
109
    const { sellTokenAddress, sellTokenChainId, buyTokenChainId, amount, receiver } = request
1✔
110

111
    const suggestedFees = await this.api.getSuggestedFees({
1✔
112
      token: sellTokenAddress,
113
      // inputToken: sellTokenAddress,
114
      // outputToken: buyTokenAddress,
115
      originChainId: sellTokenChainId,
116
      destinationChainId: buyTokenChainId,
117
      amount,
118
      recipient: receiver ?? undefined,
1!
119
    })
120

121
    // TODO: The suggested fees contain way more information. As we review more bridge providers we should revisit the
122
    // facade of the quote result.
123
    //
124
    // For example, this contains also information on the limits, so you don't need to quote again for the same pair.
125
    // potentially, this could be cached for a short period of time in the SDK so we can resolve quotes with less
126
    // requests.
127

128
    return toBridgeQuoteResult(request, SLIPPAGE_TOLERANCE_BPS, suggestedFees)
1✔
129
  }
130

131
  async getUnsignedBridgeCall(request: QuoteBridgeRequest, quote: AcrossQuoteResult): Promise<EvmCall> {
132
    return createAcrossDepositCall({
×
133
      request,
134
      quote,
135
      cowShedSdk: this.cowShedSdk,
136
    })
137
  }
138

139
  async getGasLimitEstimationForHook(request: QuoteBridgeRequest): Promise<number> {
NEW
140
    return getGasLimitEstimationForHook(this.cowShedSdk, request, this.getRpcProvider(request.sellTokenChainId))
×
141
  }
142

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

169
    const { to, data } = signedMulticall
×
170
    return {
×
171
      postHook: {
172
        target: to,
173
        callData: data,
174
        gasLimit: gasLimit.toString(),
175
        dappId: ACROSS_HOOK_DAPP_ID, // TODO: I think we should have some additional parameter to type the hook (using dappId for now)
176
      },
177
      recipient: cowShedAccount,
178
    }
179
  }
180

181
  async decodeBridgeHook(_hook: latestAppData.CoWHook): Promise<BridgeDeposit> {
182
    throw new Error('Not implemented')
1✔
183
  }
184

185
  async getBridgingParams(chainId: ChainId, orderUid: string, txHash: string): Promise<BridgingDepositParams | null> {
186
    const txReceipt = await this.getRpcProvider(chainId).getTransactionReceipt(txHash)
1✔
187

188
    return getDepositParams(chainId, orderUid, txReceipt)
1✔
189
  }
190

191
  getExplorerUrl(bridgingId: string): string {
192
    // TODO: Review with across how we get the explorer url based on the bridgingId
193
    return `https://app.across.to/transactions/${bridgingId}`
1✔
194
  }
195

196
  async getStatus(bridgingId: string, originChainId: SupportedChainId): Promise<BridgeStatusResult> {
197
    const depositStatus = await this.api.getDepositStatus({
1✔
198
      originChainId: originChainId.toString(),
199
      depositId: bridgingId,
200
    })
201

202
    return {
1✔
203
      status: mapAcrossStatusToBridgeStatus(depositStatus.status),
204
      depositTxHash: depositStatus.depositTxHash,
205
      fillTxHash: depositStatus.fillTx,
206
    }
207
  }
208

209
  async getCancelBridgingTx(_bridgingId: string): Promise<EvmCall> {
210
    throw new Error('Not implemented')
1✔
211
  }
212

213
  async getRefundBridgingTx(_bridgingId: string): Promise<EvmCall> {
214
    throw new Error('Not implemented')
1✔
215
  }
216

217
  private async getSupportedTokensState(): Promise<SupportedTokensState> {
218
    if (!this.supportedTokens) {
2!
219
      this.supportedTokens = (await this.api.getSupportedTokens()).reduce((acc, val) => {
2✔
220
        acc[val.chainId] = acc[val.chainId] || {}
4✔
221

222
        acc[val.chainId][val.address.toLowerCase()] = val
4✔
223

224
        return acc
4✔
225
      }, {} as SupportedTokensState)
226
    }
227

228
    return this.supportedTokens
2✔
229
  }
230
}
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