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

cowprotocol / cow-sdk / #1714

16 May 2025 06:06PM UTC coverage: 77.476% (+0.03%) from 77.449%
#1714

Pull #314

anxolin
chore: apply nit
Pull Request #314: Slippage calculation rounding

470 of 662 branches covered (71.0%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

4 existing lines in 2 files now uncovered.

1071 of 1327 relevant lines covered (80.71%)

20.19 hits per line

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

49.06
/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
  QuoteBridgeRequest,
12
} from '../../types'
13

14
import { DEFAULT_GAS_COST_FOR_HOOK_ESTIMATION, RAW_PROVIDERS_FILES_PATH } from '../../const'
15

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

18
import { ACROSS_TOKEN_MAPPING } from './const/tokens'
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 { getChainConfigs, getTokenAddress, getTokenSymbol, toBridgeQuoteResult } from './util'
28
import { CowShedSdk, CowShedSdkOptions } from '../../../cow-shed'
29
import { createAcrossDepositCall } from './createAcrossDepositCall'
30
import { OrderKind } from '@cowprotocol/contracts'
31
import { HOOK_DAPP_BRIDGE_PROVIDER_PREFIX } from './const/misc'
32
import { SuggestedFeesResponse } from './types'
33

34
const HOOK_DAPP_ID = `${HOOK_DAPP_BRIDGE_PROVIDER_PREFIX}/across`
1✔
35
export const ACROSS_SUPPORTED_NETWORKS = [mainnet, polygon, arbitrumOne, base, optimism]
1✔
36

37
// 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
38
const SLIPPAGE_TOLERANCE_BPS = 0
1✔
39
export interface AcrossBridgeProviderOptions {
40
  /**
41
   * Token info provider
42
   * @param chainId - The chain ID
43
   * @param addresses - The addresses of the tokens to get the info for
44
   * @returns The token infos
45
   */
46
  getTokenInfos?: (chainId: ChainId, addresses: string[]) => Promise<TokenInfo[]>
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

63
  constructor(private options: AcrossBridgeProviderOptions = {}) {
×
64
    this.api = new AcrossApi(options.apiOptions)
11✔
65
    this.cowShedSdk = new CowShedSdk(options.cowShedOptions)
11✔
66
  }
67

68
  info: BridgeProviderInfo = {
11✔
69
    name: 'Across',
70
    logoUrl: `${RAW_PROVIDERS_FILES_PATH}/across/across-logo.png`,
71
  }
72

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

77
  async getBuyTokens(targetChainId: TargetChainId): Promise<TokenInfo[]> {
78
    if (!this.options.getTokenInfos) {
2!
79
      throw new Error("'getTokenInfos' parameter is required for AcrossBridgeProvider constructor")
×
80
    }
81

82
    const chainConfig = ACROSS_TOKEN_MAPPING[targetChainId as TargetChainId]
2✔
83
    if (!chainConfig) {
2✔
84
      return []
1✔
85
    }
86

87
    const tokenAddresses = Object.values(chainConfig.tokens).filter((address): address is string => Boolean(address))
5✔
88
    return this.options.getTokenInfos(targetChainId, tokenAddresses)
1✔
89
  }
90

91
  async getIntermediateTokens(request: QuoteBridgeRequest): Promise<string[]> {
92
    if (request.kind !== OrderKind.SELL) {
×
93
      throw new Error('Only SELL is supported for now')
×
94
    }
95

96
    const { sellTokenChainId, buyTokenChainId, buyTokenAddress } = request
×
97
    const chainConfigs = getChainConfigs(sellTokenChainId, buyTokenChainId)
×
98
    if (!chainConfigs) return []
×
99

100
    const { sourceChainConfig, targetChainConfig } = chainConfigs
×
101

102
    // Find the token symbol for the target token
103
    const targetTokenSymbol = getTokenSymbol(buyTokenAddress, targetChainConfig)
×
104
    if (!targetTokenSymbol) return []
×
105

106
    // Use the tokenSymbol to find the outputToken in the target chain
107
    const intermediateToken = getTokenAddress(targetTokenSymbol, sourceChainConfig)
×
108
    return intermediateToken ? [intermediateToken] : []
×
109
  }
110

111
  async getQuote(request: QuoteBridgeRequest): Promise<AcrossQuoteResult> {
112
    const { sellTokenAddress, sellTokenChainId, buyTokenChainId, amount, receiver } = request
1✔
113

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

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

131
    return toBridgeQuoteResult(request, SLIPPAGE_TOLERANCE_BPS, suggestedFees)
1✔
132
  }
133

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

142
  getGasLimitEstimationForHook(_request: QuoteBridgeRequest): number {
143
    return DEFAULT_GAS_COST_FOR_HOOK_ESTIMATION
×
144
  }
145

146
  async getSignedHook(chainId: SupportedChainId, unsignedCall: EvmCall, signer: Signer): Promise<BridgeHook> {
147
    // Sign the multicall
UNCOV
148
    const { signedMulticall, cowShedAccount, gasLimit } = await this.cowShedSdk.signCalls({
×
149
      calls: [
150
        {
151
          target: unsignedCall.to,
152
          value: unsignedCall.value,
153
          callData: unsignedCall.data,
154
          allowFailure: false,
155
          isDelegateCall: true,
156
        },
157
      ],
158
      chainId,
159
      signer,
160
    })
161

UNCOV
162
    const { to, data } = signedMulticall
×
UNCOV
163
    return {
×
164
      postHook: {
165
        target: to,
166
        callData: data,
167
        gasLimit: gasLimit.toString(),
168
        dappId: HOOK_DAPP_ID, // TODO: I think we should have some additional parameter to type the hook (using dappId for now)
169
      },
170
      recipient: cowShedAccount,
171
    }
172
  }
173

174
  async decodeBridgeHook(_hook: latestAppData.CoWHook): Promise<BridgeDeposit> {
175
    throw new Error('Not implemented')
1✔
176
  }
177

178
  async getBridgingId(_orderUid: string, _settlementTx: string, _logIndex: number): Promise<string> {
179
    // TODO: get events from the mined transaction, extract the deposit id
180
    // Important. A settlement could have many bridge-and-swap transactions, maybe even using different providers, this is why the log index might be handy to find which of the depositIds corresponds to the bridging transaction
181
    throw new Error('Not implemented')
1✔
182
  }
183

184
  getExplorerUrl(bridgingId: string): string {
185
    // TODO: Review with across how we get the explorer url based on the bridgingId
186
    return `https://app.across.to/transactions/${bridgingId}`
1✔
187
  }
188

189
  async getStatus(_bridgingId: string): Promise<BridgeStatusResult> {
190
    throw new Error('Not implemented')
1✔
191
  }
192

193
  async getCancelBridgingTx(_bridgingId: string): Promise<EvmCall> {
194
    throw new Error('Not implemented')
1✔
195
  }
196
  async getRefundBridgingTx(_bridgingId: string): Promise<EvmCall> {
197
    throw new Error('Not implemented')
1✔
198
  }
199
}
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