1- import { BigNumber , ethers , utils } from "ethers" ;
1+ import { BigNumber , utils } from "ethers" ;
22import * as sdk from "@across-protocol/sdk" ;
33import { TokenMessengerMinterV2Client } from "@across-protocol/contracts" ;
44import {
@@ -18,9 +18,8 @@ import {
1818 BridgeCapabilities ,
1919 GetOutputBridgeQuoteParams ,
2020} from "../types" ;
21- import { CrossSwap , CrossSwapQuotes } from "../../_dexes/types" ;
21+ import { CrossSwap , CrossSwapQuotes , Token } from "../../_dexes/types" ;
2222import { AppFee , CROSS_SWAP_TYPE } from "../../_dexes/utils" ;
23- import { Token } from "../../_dexes/types" ;
2423import { InvalidParamError } from "../../_errors" ;
2524import { ConvertDecimals } from "../../_utils" ;
2625import {
@@ -34,12 +33,14 @@ import {
3433 CCTP_SUPPORTED_CHAINS ,
3534 CCTP_SUPPORTED_TOKENS ,
3635 CCTP_FINALITY_THRESHOLDS ,
37- CCTP_FILL_TIME_ESTIMATES ,
36+ DEFAULT_CCTP_ACROSS_FINALIZER_ADDRESS ,
3837 getCctpTokenMessengerAddress ,
3938 getCctpMessageTransmitterAddress ,
4039 getCctpDomainId ,
4140 encodeDepositForBurn ,
4241} from "./utils/constants" ;
42+ import { getEstimatedFillTime , getTransferMode } from "./utils/fill-times" ;
43+ import { getCctpFees } from "./utils/fees" ;
4344
4445const name = "cctp" ;
4546
@@ -59,12 +60,9 @@ const capabilities: BridgeCapabilities = {
5960 * CCTP (Cross-Chain Transfer Protocol) bridge strategy for native USDC transfers.
6061 * Supports Circle's CCTP for burning USDC on source chain.
6162 */
62- export function getCctpBridgeStrategy ( ) : BridgeStrategy {
63- const getEstimatedFillTime = ( originChainId : number ) : number => {
64- // CCTP fill time is determined by the origin chain attestation process
65- return CCTP_FILL_TIME_ESTIMATES [ originChainId ] || 19 * 60 ; // Default to 19 minutes
66- } ;
67-
63+ export function getCctpBridgeStrategy (
64+ requestedTransferMode : "standard" | "fast" = "fast"
65+ ) : BridgeStrategy {
6866 const isRouteSupported = ( params : {
6967 inputToken : Token ;
7068 outputToken : Token ;
@@ -91,7 +89,12 @@ export function getCctpBridgeStrategy(): BridgeStrategy {
9189 const isDestinationChainSupported = CCTP_SUPPORTED_CHAINS . includes (
9290 params . outputToken . chainId
9391 ) ;
94- if ( ! isOriginChainSupported || ! isDestinationChainSupported ) {
92+ if (
93+ ! isOriginChainSupported ||
94+ ! isDestinationChainSupported ||
95+ // NOTE: Our finalizer doesn't support destination Solana yet. Block the route until we do.
96+ sdk . utils . chainIsSvm ( params . outputToken . chainId )
97+ ) {
9598 return false ;
9699 }
97100
@@ -149,10 +152,33 @@ export function getCctpBridgeStrategy(): BridgeStrategy {
149152 } : GetExactInputBridgeQuoteParams ) => {
150153 assertSupportedRoute ( { inputToken, outputToken } ) ;
151154
155+ let maxFee = BigNumber . from ( 0 ) ;
156+ const transferMode = await getTransferMode (
157+ inputToken . chainId ,
158+ requestedTransferMode ,
159+ exactInputAmount ,
160+ inputToken . decimals
161+ ) ;
162+
163+ if ( transferMode === "fast" ) {
164+ const { transferFeeBps, forwardFee } = await getCctpFees ( {
165+ inputToken,
166+ outputToken,
167+ transferMode,
168+ } ) ;
169+
170+ // Calculate actual fee:
171+ // transferFee = input * (bps / 10000)
172+ // maxFee = transferFee + forwardFee
173+ const transferFee = exactInputAmount . mul ( transferFeeBps ) . div ( 10000 ) ;
174+ maxFee = transferFee . add ( forwardFee ) ;
175+ }
176+
177+ const remainingInputAmount = exactInputAmount . sub ( maxFee ) ;
152178 const outputAmount = ConvertDecimals (
153179 inputToken . decimals ,
154180 outputToken . decimals
155- ) ( exactInputAmount ) ;
181+ ) ( remainingInputAmount ) ;
156182
157183 return {
158184 bridgeQuote : {
@@ -161,9 +187,16 @@ export function getCctpBridgeStrategy(): BridgeStrategy {
161187 inputAmount : exactInputAmount ,
162188 outputAmount,
163189 minOutputAmount : outputAmount ,
164- estimatedFillTimeSec : getEstimatedFillTime ( inputToken . chainId ) ,
190+ estimatedFillTimeSec : getEstimatedFillTime (
191+ inputToken . chainId ,
192+ transferMode
193+ ) ,
165194 provider : name ,
166- fees : getCctpBridgeFees ( inputToken ) ,
195+ fees : getCctpBridgeFees ( {
196+ inputToken,
197+ inputAmount : exactInputAmount ,
198+ maxFee,
199+ } ) ,
167200 } ,
168201 } ;
169202 } ,
@@ -178,10 +211,38 @@ export function getCctpBridgeStrategy(): BridgeStrategy {
178211 } : GetOutputBridgeQuoteParams ) => {
179212 assertSupportedRoute ( { inputToken, outputToken } ) ;
180213
181- const inputAmount = ConvertDecimals (
214+ let inputAmount = ConvertDecimals (
182215 outputToken . decimals ,
183216 inputToken . decimals
184217 ) ( minOutputAmount ) ;
218+ let maxFee = BigNumber . from ( 0 ) ;
219+
220+ const transferMode = await getTransferMode (
221+ inputToken . chainId ,
222+ requestedTransferMode ,
223+ inputAmount ,
224+ inputToken . decimals
225+ ) ;
226+
227+ if ( transferMode === "fast" ) {
228+ const { transferFeeBps, forwardFee } = await getCctpFees ( {
229+ inputToken,
230+ outputToken,
231+ transferMode,
232+ } ) ;
233+
234+ // Solve for required input based on the following equation:
235+ // inputAmount - (inputAmount * bps / 10000) - forwardFee = amountToArriveOnDestination
236+ // Rearranging: inputAmount * (1 - bps/10000) = amountToArriveOnDestination + forwardFee
237+ // Therefore: inputAmount = (amountToArriveOnDestination + forwardFee) * 10000 / (10000 - bps)
238+ // Note: 10000 converts basis points to the same scale as amounts (1 bps = 1/10000 of the total)
239+ const bpsFactor = BigNumber . from ( 10000 ) . sub ( transferFeeBps ) ;
240+ inputAmount = inputAmount . add ( forwardFee ) . mul ( 10000 ) . div ( bpsFactor ) ;
241+
242+ // Calculate total CCTP fee (transfer fee + forward fee)
243+ const transferFee = inputAmount . mul ( transferFeeBps ) . div ( 10000 ) ;
244+ maxFee = transferFee . add ( forwardFee ) ;
245+ }
185246
186247 return {
187248 bridgeQuote : {
@@ -190,9 +251,16 @@ export function getCctpBridgeStrategy(): BridgeStrategy {
190251 inputAmount,
191252 outputAmount : minOutputAmount ,
192253 minOutputAmount,
193- estimatedFillTimeSec : getEstimatedFillTime ( inputToken . chainId ) ,
254+ estimatedFillTimeSec : getEstimatedFillTime (
255+ inputToken . chainId ,
256+ transferMode
257+ ) ,
194258 provider : name ,
195- fees : getCctpBridgeFees ( inputToken ) ,
259+ fees : getCctpBridgeFees ( {
260+ inputToken,
261+ inputAmount,
262+ maxFee,
263+ } ) ,
196264 } ,
197265 } ;
198266 } ,
@@ -227,15 +295,23 @@ export function getCctpBridgeStrategy(): BridgeStrategy {
227295 const destinationChainId = crossSwap . outputToken . chainId ;
228296 const destinationDomain = getCctpDomainId ( destinationChainId ) ;
229297 const tokenMessenger = getCctpTokenMessengerAddress ( originChainId ) ;
298+ // Circle's API returns a minimum fee. Add 1 unit as buffer to ensure the transfer meets the threshold for fast mode eligibility.
299+ const hasFastFee = bridgeQuote . fees . amount . gt ( 0 ) ;
300+ const maxFee = hasFastFee
301+ ? bridgeQuote . fees . amount . add ( 1 )
302+ : bridgeQuote . fees . amount ;
303+ const minFinalityThreshold = hasFastFee
304+ ? CCTP_FINALITY_THRESHOLDS . fast
305+ : CCTP_FINALITY_THRESHOLDS . standard ;
230306
231307 // depositForBurn input parameters
232308 const depositForBurnParams = {
233309 amount : bridgeQuote . inputAmount ,
234310 destinationDomain,
235311 mintRecipient : crossSwap . recipient ,
236- destinationCaller : ethers . constants . AddressZero , // Anyone can finalize the message on domain when this is set to bytes32(0)
237- maxFee : BigNumber . from ( 0 ) , // maxFee set to 0 so this will be a "standard" speed transfer
238- minFinalityThreshold : CCTP_FINALITY_THRESHOLDS . standard , // Hardcoded minFinalityThreshold value for standard transfer
312+ destinationCaller : DEFAULT_CCTP_ACROSS_FINALIZER_ADDRESS ,
313+ maxFee,
314+ minFinalityThreshold,
239315 } ;
240316
241317 if ( crossSwap . isOriginSvm ) {
@@ -263,15 +339,19 @@ export function getCctpBridgeStrategy(): BridgeStrategy {
263339 } ;
264340}
265341
266- function getCctpBridgeFees ( inputToken : Token ) {
267- const zeroBN = BigNumber . from ( 0 ) ;
342+ function getCctpBridgeFees ( params : {
343+ inputToken : Token ;
344+ inputAmount : BigNumber ;
345+ maxFee ?: BigNumber ;
346+ } ) {
347+ const { inputToken, inputAmount, maxFee = BigNumber . from ( 0 ) } = params ;
348+ const pct = maxFee . mul ( sdk . utils . fixedPointAdjustment ) . div ( inputAmount ) ;
268349 return {
269- pct : zeroBN ,
270- amount : zeroBN ,
350+ pct,
351+ amount : maxFee ,
271352 token : inputToken ,
272353 } ;
273354}
274-
275355/**
276356 * Builds CCTP deposit transaction for EVM chains
277357 */
0 commit comments