From 8802f9820a564bd87243a4bdd12fd0ee206b1169 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Thu, 12 Mar 2026 15:13:46 +0000 Subject: [PATCH 1/8] feat: use CCTP v2 for bungee instead of CCTP v1 --- packages/bridging/src/providers/bungee/consts.ts | 2 +- packages/bridging/src/providers/bungee/types.ts | 6 +++--- packages/contracts-ts/src/generated/packageVersion.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bridging/src/providers/bungee/consts.ts b/packages/bridging/src/providers/bungee/consts.ts index de67b7003..1ce713481 100644 --- a/packages/bridging/src/providers/bungee/consts.ts +++ b/packages/bridging/src/providers/bungee/consts.ts @@ -8,7 +8,7 @@ export const BUNGEE_MANUAL_API_URL = `${BUNGEE_BASE_URL}${BUNGEE_MANUAL_API_PATH export const BUNGEE_EVENTS_API_URL = 'https://microservices.socket.tech/loki' export const ACROSS_API_URL = 'https://app.across.to/api' -export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp', 'gnosis-native-bridge'] +export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp-v2', 'gnosis-native-bridge'] export const errorMessageMap = { bungee: 'Bungee Api Error', diff --git a/packages/bridging/src/providers/bungee/types.ts b/packages/bridging/src/providers/bungee/types.ts index af4d8669a..ca5f8b68b 100644 --- a/packages/bridging/src/providers/bungee/types.ts +++ b/packages/bridging/src/providers/bungee/types.ts @@ -1,6 +1,6 @@ import type { SupportedChainId, TargetChainId } from '@cowprotocol/sdk-config' -export type SupportedBridge = 'across' | 'cctp' | 'gnosis-native-bridge' +export type SupportedBridge = 'across' | 'cctp-v2' | 'gnosis-native-bridge' export interface BungeeQuoteAPIRequest { userAddress: string @@ -110,7 +110,7 @@ export interface BungeeQuoteAPIResponse { export enum BungeeBridge { 'Across' = 'across', - 'CircleCCTP' = 'cctp', + 'CircleCCTP' = 'cctp-v2', 'GnosisNative' = 'gnosis-native-bridge', } @@ -209,7 +209,7 @@ export enum BungeeEventStatus { export enum BungeeBridgeName { ACROSS = 'across', - CCTP = 'cctp', + CCTP = 'cctp-v2', } export type BungeeEvent = { diff --git a/packages/contracts-ts/src/generated/packageVersion.ts b/packages/contracts-ts/src/generated/packageVersion.ts index b72301f05..06af74000 100644 --- a/packages/contracts-ts/src/generated/packageVersion.ts +++ b/packages/contracts-ts/src/generated/packageVersion.ts @@ -1,3 +1,3 @@ // Automatically generated by the script in scripts/version.js // Do not edit this file manually -export const CONTRACTS_PKG_VERSION = '1.8.0'; +export const CONTRACTS_PKG_VERSION = '2.0.0'; From 22491c4fa3ca8a360db91cad8ebe7f2b34d5b541 Mon Sep 17 00:00:00 2001 From: Rookmate <14072042+rookmate@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:41:48 +0400 Subject: [PATCH 2/8] feat(bridge): migrate bungee cctp support to cctp-v2 --- packages/bridging/src/README.md | 10 +++++----- .../src/providers/bungee/BungeeBridgeProvider.ts | 4 ++-- packages/bridging/src/providers/bungee/const/misc.ts | 4 ++-- packages/bridging/src/providers/bungee/consts.ts | 2 +- .../src/providers/bungee/createBungeeDepositCall.ts | 2 +- .../providers/bungee/getBridgingStatusFromEvents.ts | 2 +- packages/bridging/src/providers/bungee/types.ts | 8 ++++---- packages/bridging/src/providers/bungee/util.test.ts | 6 ++++-- packages/bridging/src/providers/bungee/util.ts | 4 ++-- 9 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/bridging/src/README.md b/packages/bridging/src/README.md index dff6883bd..96723e3d3 100644 --- a/packages/bridging/src/README.md +++ b/packages/bridging/src/README.md @@ -34,7 +34,7 @@ The BridgingSdk supports two types of operations: Supported bridge providers: -- **Bungee/Socket**: Multi-bridge aggregation platform with support for multiple underlying bridges including Across, CCTP, and others +- **Bungee/Socket**: Multi-bridge aggregation platform with support for multiple underlying bridges including Across, CCTP V2, and others > Since `BridgingSdk` is compatible with [TradingSDK](https://github.com/cowprotocol/cow-sdk/tree/main/packages/trading/README.md), almost everything described for `TradingSDK` applies to `BridgingSdk` as well. > The main difference is smart contract wallet support. Currently, `BridgingSdk` only supports EOA wallets; this will likely change soon — stay tuned! @@ -137,7 +137,7 @@ const adapter = new ViemAdapter({ // Initialize the bridge provider const bungeeProvider = new BungeeBridgeProvider({ apiOptions: { - includeBridges: ['across', 'cctp'], // Optional: specify which bridges to include + includeBridges: ['across', 'cctp-v2'], // Optional: specify which bridges to include }, }) @@ -288,7 +288,7 @@ const tradingSdk: TradingSdk = new TradingSdk( const bungeeProvider: BungeeBridgeProvider = new BungeeBridgeProvider({ apiOptions: { - includeBridges: ['across', 'cctp'], + includeBridges: ['across', 'cctp-v2'], }, }) @@ -411,7 +411,7 @@ const adapter = new ViemAdapter({ const bungeeProvider = new BungeeBridgeProvider({ apiOptions: { - includeBridges: ['across', 'cctp'], // Optional: specify which bridges to include + includeBridges: ['across', 'cctp-v2'], // Optional: specify which bridges to include }, }) @@ -526,7 +526,7 @@ async function main() { // Initialize bridge provider const bungeeProvider = new BungeeBridgeProvider({ apiOptions: { - includeBridges: ['across', 'cctp'], // Optional: specify which bridges to include + includeBridges: ['across', 'cctp-v2'], // Optional: specify which bridges to include }, }) diff --git a/packages/bridging/src/providers/bungee/BungeeBridgeProvider.ts b/packages/bridging/src/providers/bungee/BungeeBridgeProvider.ts index 43f1ba97b..601adbb30 100644 --- a/packages/bridging/src/providers/bungee/BungeeBridgeProvider.ts +++ b/packages/bridging/src/providers/bungee/BungeeBridgeProvider.ts @@ -263,14 +263,14 @@ export class BungeeBridgeProvider implements HookBridgeProvider { // Support for cancellation will depend on the actual bridge an order went through. - // Across & CCTP doesn't support cancellation. + // Across & CCTP V2 don't support cancellation. // Therefore, not implementing cancellation throw new Error('Not implemented') } async getRefundBridgingTx(_bridgingId: string): Promise { // Support for refund will depend on the actual bridge an order went through. - // CCTP doesn't support refund. + // CCTP V2 doesn't support refund. // Across auto-relays refund txns some time after the order expires. No user action needed. // Therefore, not implementing refund throw new Error('Not implemented') diff --git a/packages/bridging/src/providers/bungee/const/misc.ts b/packages/bridging/src/providers/bungee/const/misc.ts index 797b1547a..91d6e592c 100644 --- a/packages/bridging/src/providers/bungee/const/misc.ts +++ b/packages/bridging/src/providers/bungee/const/misc.ts @@ -28,9 +28,9 @@ export const BungeeTxDataBytesIndices: BungeeTxDataBytesIndicesType = { }, }, }, - cctp: { + 'cctp-v2': { // bridgeERC20To - ['0xb7dfe9d0'.toLowerCase()]: { + ['0x3ca7f5bc'.toLowerCase()]: { inputAmount: { bytes_startIndex: 8, // first 8 bytes are the routeId, followed by the function selector bytes_length: 32, // first 32 bytes of the params are the amount diff --git a/packages/bridging/src/providers/bungee/consts.ts b/packages/bridging/src/providers/bungee/consts.ts index de67b7003..1ce713481 100644 --- a/packages/bridging/src/providers/bungee/consts.ts +++ b/packages/bridging/src/providers/bungee/consts.ts @@ -8,7 +8,7 @@ export const BUNGEE_MANUAL_API_URL = `${BUNGEE_BASE_URL}${BUNGEE_MANUAL_API_PATH export const BUNGEE_EVENTS_API_URL = 'https://microservices.socket.tech/loki' export const ACROSS_API_URL = 'https://app.across.to/api' -export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp', 'gnosis-native-bridge'] +export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp-v2', 'gnosis-native-bridge'] export const errorMessageMap = { bungee: 'Bungee Api Error', diff --git a/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts b/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts index c64382b98..e0d07c470 100644 --- a/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts +++ b/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts @@ -31,7 +31,7 @@ export async function createBungeeDepositCall(params: { const inputAmountStartIndex = functionParams.inputAmount.bytes_startIndex const modifyOutputAmount = false const outputAmountStartIndex = 0 - const nativeTokenExtraFee = 0n // neither across nor cctp requires additional native token transfer for now + const nativeTokenExtraFee = 0n // neither across nor cctp-v2 requires additional native token transfer for now // Encode extra data as bytes const modifyCalldataParams = adapter.utils.encodeAbi( diff --git a/packages/bridging/src/providers/bungee/getBridgingStatusFromEvents.ts b/packages/bridging/src/providers/bungee/getBridgingStatusFromEvents.ts index a51859520..5163611c4 100644 --- a/packages/bridging/src/providers/bungee/getBridgingStatusFromEvents.ts +++ b/packages/bridging/src/providers/bungee/getBridgingStatusFromEvents.ts @@ -51,6 +51,6 @@ export async function getBridgingStatusFromEvents( } } - // there is no failed case for across - gets auto-refunded - or cctp - attestation can be relayed by anyone on destination chain + // there is no failed case for across - gets auto-refunded - or cctp-v2 - attestation can be relayed by anyone on destination chain throw new Error('Unknown status') } diff --git a/packages/bridging/src/providers/bungee/types.ts b/packages/bridging/src/providers/bungee/types.ts index af4d8669a..8a96d9981 100644 --- a/packages/bridging/src/providers/bungee/types.ts +++ b/packages/bridging/src/providers/bungee/types.ts @@ -1,6 +1,6 @@ import type { SupportedChainId, TargetChainId } from '@cowprotocol/sdk-config' -export type SupportedBridge = 'across' | 'cctp' | 'gnosis-native-bridge' +export type SupportedBridge = 'across' | 'cctp-v2' | 'gnosis-native-bridge' export interface BungeeQuoteAPIRequest { userAddress: string @@ -110,14 +110,14 @@ export interface BungeeQuoteAPIResponse { export enum BungeeBridge { 'Across' = 'across', - 'CircleCCTP' = 'cctp', + 'CircleCCTPV2' = 'cctp-v2', 'GnosisNative' = 'gnosis-native-bridge', } // Map display names to enum values export const BungeeBridgeNames: Record = { Across: BungeeBridge.Across, - 'Circle CCTP': BungeeBridge.CircleCCTP, + 'Circle CCTP V2': BungeeBridge.CircleCCTPV2, 'Gnosis Native': BungeeBridge.GnosisNative, } @@ -209,7 +209,7 @@ export enum BungeeEventStatus { export enum BungeeBridgeName { ACROSS = 'across', - CCTP = 'cctp', + CCTP_V2 = 'cctp-v2', } export type BungeeEvent = { diff --git a/packages/bridging/src/providers/bungee/util.test.ts b/packages/bridging/src/providers/bungee/util.test.ts index 7ad3536d5..82779130e 100644 --- a/packages/bridging/src/providers/bungee/util.test.ts +++ b/packages/bridging/src/providers/bungee/util.test.ts @@ -249,13 +249,13 @@ describe('Bungee Utils', () => { it('should convert object to URLSearchParams correctly', () => { const params = { userAddress: '0x123', - includeBridges: ['across', 'cctp'], + includeBridges: ['across', 'cctp-v2'], amount: '1000', } const result = objectToSearchParams(params) expect(result.get('userAddress')).toBe('0x123') - expect(result.get('includeBridges')).toBe('across,cctp') + expect(result.get('includeBridges')).toBe('across,cctp-v2') expect(result.get('amount')).toBe('1000') }) }) @@ -263,6 +263,8 @@ describe('Bungee Utils', () => { describe('BungeeBridge helpers', () => { it('should get bridge from display name', () => { expect(getBungeeBridgeFromDisplayName('Across')).toBe(BungeeBridge.Across) + expect(getBungeeBridgeFromDisplayName('Circle CCTP V2')).toBe(BungeeBridge.CircleCCTPV2) + expect(getBungeeBridgeFromDisplayName('Gnosis Native')).toBe(BungeeBridge.GnosisNative) expect(getBungeeBridgeFromDisplayName('Invalid')).toBeUndefined() }) diff --git a/packages/bridging/src/providers/bungee/util.ts b/packages/bridging/src/providers/bungee/util.ts index d33487f25..ec364a66f 100644 --- a/packages/bridging/src/providers/bungee/util.ts +++ b/packages/bridging/src/providers/bungee/util.ts @@ -158,10 +158,10 @@ export function calculateFeeBps(feeAmountBig: bigint, amountBig: bigint): number * @example * const params = { * userAddress: '0x123', - * includeBridges: ['across', 'cctp'] + * includeBridges: ['across', 'cctp-v2'] * } * const searchParams = objectToSearchParams(params) - * Results in: ?userAddress=0x123&includeBridges=across,cctp + * Results in: ?userAddress=0x123&includeBridges=across,cctp-v2 */ export function objectToSearchParams(params: object): URLSearchParams { const searchParams = new URLSearchParams() From 1f74863e45d04623fd48bc613d43f3933dd9390b Mon Sep 17 00:00:00 2001 From: Rookmate <14072042+rookmate@users.noreply.github.com> Date: Fri, 13 Mar 2026 13:54:42 +0400 Subject: [PATCH 3/8] fix: take into account cctpv2 variable fees --- .../src/providers/bungee/const/misc.ts | 6 + .../bungee/createBungeeDepositCall.test.ts | 193 ++++++++++++++++++ .../bungee/createBungeeDepositCall.ts | 11 +- .../bridging/src/providers/bungee/types.ts | 4 +- .../src/providers/bungee/util.test.ts | 13 ++ .../bridging/src/providers/bungee/util.ts | 25 ++- 6 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts diff --git a/packages/bridging/src/providers/bungee/const/misc.ts b/packages/bridging/src/providers/bungee/const/misc.ts index 91d6e592c..370d0e0f8 100644 --- a/packages/bridging/src/providers/bungee/const/misc.ts +++ b/packages/bridging/src/providers/bungee/const/misc.ts @@ -37,6 +37,12 @@ export const BungeeTxDataBytesIndices: BungeeTxDataBytesIndicesType = { bytesString_startIndex: 2 + 8 * 2, // first two characters are 0x and 8 bytes = 16 chars for the amount bytesString_length: 32 * 2, // 32 bytes = 64 chars for the amount }, + outputAmount: { + bytes_startIndex: 200, // feeAmount is the 7th parameter -> 8 + 6*32 = 200 + bytes_length: 32, + bytesString_startIndex: 2 + 200 * 2, + bytesString_length: 32 * 2, + }, }, }, 'gnosis-native-bridge': { diff --git a/packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts b/packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts new file mode 100644 index 000000000..e459fe381 --- /dev/null +++ b/packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts @@ -0,0 +1,193 @@ +import { setGlobalAdapter } from '@cowprotocol/sdk-common' +import { SupportedChainId } from '@cowprotocol/sdk-config' +import { OrderKind } from '@cowprotocol/sdk-order-book' +import { createAdapters } from '../../../tests/setup' +import { QuoteBridgeRequest } from '../../types' +import { BUNGEE_APPROVE_AND_BRIDGE_V1_ABI } from './abi' +import { createBungeeDepositCall } from './createBungeeDepositCall' +import { BungeeBridge, BungeeQuoteWithBuildTx } from './types' +import { decodeAmountsBungeeTxData, toBridgeQuoteResult } from './util' + +export const CCTP_V2_TX_DATA = + '0x000001a63ca7f5bc0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000002713000000000000000000000000664b591ab924c6bb2caca533ed702386934a11d6000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e8' + +function applyPctDiff(base: bigint, compare: bigint, target: bigint): bigint { + return (target * compare) / base +} + +describe('createBungeeDepositCall', () => { + const adapters = createAdapters() + + beforeEach(() => { + setGlobalAdapter(adapters.ethersV5Adapter) + }) + + it('encodes cctp-v2 calldata rewrite metadata for feeAmount as the proportional secondary field', async () => { + const request: QuoteBridgeRequest = { + kind: OrderKind.SELL, + amount: 100000000n, + owner: '0x664B591AB924c6bb2caCA533Ed702386934A11d6', + sellTokenChainId: SupportedChainId.BASE, + sellTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + sellTokenDecimals: 6, + buyTokenChainId: SupportedChainId.MAINNET, + buyTokenAddress: '0xA0b86991c6218b36c1d19d4a2e9Eb0cE3606eB48', + buyTokenDecimals: 6, + appCode: 'bungee', + account: '0x664B591AB924c6bb2caCA533Ed702386934A11d6', + signer: '0x664B591AB924c6bb2caCA533Ed702386934A11d6', + } + + const quoteWithBuildTx: BungeeQuoteWithBuildTx = { + bungeeQuote: { + originChainId: SupportedChainId.BASE, + destinationChainId: SupportedChainId.MAINNET, + userAddress: '0x664B591AB924c6bb2caCA533Ed702386934A11d6', + receiverAddress: '0x664B591AB924c6bb2caCA533Ed702386934A11d6', + input: { + token: { + chainId: SupportedChainId.BASE, + address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + name: 'USD Coin', + symbol: 'USDC', + decimals: 6, + logoURI: '', + icon: '', + }, + amount: '100000000', + priceInUsd: 1, + valueInUsd: 100, + }, + route: { + quoteId: 'fd868dfdece0f3b1', + quoteExpiry: 0, + output: { + token: { + chainId: SupportedChainId.MAINNET, + address: '0xA0b86991c6218b36c1d19d4a2e9Eb0cE3606eB48', + name: 'USD Coin', + symbol: 'USDC', + decimals: 6, + logoURI: '', + icon: '', + }, + amount: '98000000', + priceInUsd: 1, + valueInUsd: 98, + minAmountOut: '98000000', + effectiveReceivedInUsd: 98, + }, + affiliateFee: null, + approvalData: { + spenderAddress: '0x3a23f943181408eac424116af7b7790c94cb97a5', + amount: '100000000', + tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + userAddress: '0x664B591AB924c6bb2caCA533Ed702386934A11d6', + }, + gasFee: { + gasToken: { + chainId: SupportedChainId.BASE, + address: '0x0000000000000000000000000000000000000000', + symbol: 'ETH', + name: 'Ethereum', + decimals: 18, + icon: '', + logoURI: '', + chainAgnosticId: null, + }, + gasLimit: '0', + gasPrice: '0', + estimatedFee: '0', + feeInUsd: 0, + }, + slippage: 0, + estimatedTime: 0, + routeDetails: { + name: 'Circle CCTP V2', + logoURI: '', + routeFee: { + token: { + chainId: SupportedChainId.BASE, + address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + name: 'USD Coin', + symbol: 'USDC', + decimals: 6, + logoURI: '', + icon: '', + }, + amount: '0', + feeInUsd: 0, + priceInUsd: 1, + }, + dexDetails: null, + }, + refuel: null, + }, + routeBridge: BungeeBridge.CircleCCTPV2, + quoteTimestamp: 0, + }, + buildTx: { + approvalData: { + spenderAddress: '0x3a23f943181408eac424116af7b7790c94cb97a5', + amount: '100000000', + tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', + userAddress: '0x664B591AB924c6bb2caCA533Ed702386934A11d6', + }, + txData: { + data: CCTP_V2_TX_DATA, + to: '0x3a23f943181408eac424116af7b7790c94cb97a5', + chainId: SupportedChainId.BASE, + value: '0', + }, + userOp: '', + }, + } + + const quote = toBridgeQuoteResult(request, 0, quoteWithBuildTx) + const call = await createBungeeDepositCall({ request, quote }) + const decoded = adapters.ethersV5Adapter.utils.decodeFunctionData( + BUNGEE_APPROVE_AND_BRIDGE_V1_ABI, + 'approveAndBridge', + call.data, + ) as [string, { toString(): string }, { toString(): string }, string] + + expect(decoded[0]).toBe(request.sellTokenAddress) + expect(decoded[1].toString()).toBe('100000000') + expect(decoded[2].toString()).toBe('0') + expect(decoded[3].startsWith(CCTP_V2_TX_DATA)).toBe(true) + + const modifyCalldataParams = `0x${decoded[3].slice(-192)}` + const [inputAmountStartIndex, modifyOutputAmount, outputAmountStartIndex] = adapters.ethersV5Adapter.utils.decodeAbi( + ['uint256', 'bool', 'uint256'], + modifyCalldataParams, + ) as [bigint, boolean, bigint] + + expect(inputAmountStartIndex).toBe(8n) + expect(modifyOutputAmount).toBe(true) + expect(outputAmountStartIndex).toBe(200n) + }) + + it('would lower feeAmount proportionally when the actual bridged amount is below the quote', () => { + const result = decodeAmountsBungeeTxData(CCTP_V2_TX_DATA, BungeeBridge.CircleCCTPV2) + + const quotedAmount = result.inputAmountBigNumber + const quotedFeeAmount = result.outputAmountBigNumber! + const actualBridgedAmount = 90000000n + const adjustedFeeAmount = applyPctDiff(quotedAmount, actualBridgedAmount, quotedFeeAmount) + + expect(adjustedFeeAmount).toBe(1800000n) + expect(adjustedFeeAmount).toBeLessThan(quotedFeeAmount) + }) + + it('would raise feeAmount proportionally when the actual bridged amount is above the quote', () => { + const result = decodeAmountsBungeeTxData(CCTP_V2_TX_DATA, BungeeBridge.CircleCCTPV2) + + const quotedAmount = result.inputAmountBigNumber + const quotedFeeAmount = result.outputAmountBigNumber! + const actualBridgedAmount = 110000000n + const adjustedFeeAmount = applyPctDiff(quotedAmount, actualBridgedAmount, quotedFeeAmount) + + expect(adjustedFeeAmount).toBe(2200000n) + expect(adjustedFeeAmount).toBeGreaterThan(quotedFeeAmount) + }) +}) diff --git a/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts b/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts index e0d07c470..58a288575 100644 --- a/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts +++ b/packages/bridging/src/providers/bungee/createBungeeDepositCall.ts @@ -1,6 +1,7 @@ import { BungeeQuoteResult } from './BungeeBridgeProvider' import { QuoteBridgeRequest } from '../../types' import { BungeeTxDataBytesIndices } from './const/misc' +import { InputOutputAmountTxDataBytesIndices } from './types' import { BUNGEE_APPROVE_AND_BRIDGE_V1_ABI } from './abi' import { BungeeApproveAndBridgeV1Addresses } from './const/contracts' import { decodeBungeeBridgeTxData } from './util' @@ -29,10 +30,16 @@ export async function createBungeeDepositCall(params: { } const inputAmountStartIndex = functionParams.inputAmount.bytes_startIndex - const modifyOutputAmount = false - const outputAmountStartIndex = 0 + let modifyOutputAmount = false + let outputAmountStartIndex = 0 const nativeTokenExtraFee = 0n // neither across nor cctp-v2 requires additional native token transfer for now + // Some bridges encode a second proportional uint256 that must be updated alongside input amount. + if ('outputAmount' in functionParams) { + modifyOutputAmount = true + outputAmountStartIndex = (functionParams as InputOutputAmountTxDataBytesIndices).outputAmount.bytes_startIndex + } + // Encode extra data as bytes const modifyCalldataParams = adapter.utils.encodeAbi( ['uint256', 'bool', 'uint256'], diff --git a/packages/bridging/src/providers/bungee/types.ts b/packages/bridging/src/providers/bungee/types.ts index 8a96d9981..a97e0149c 100644 --- a/packages/bridging/src/providers/bungee/types.ts +++ b/packages/bridging/src/providers/bungee/types.ts @@ -183,9 +183,7 @@ export interface InputOutputAmountTxDataBytesIndices export type BungeeTxDataBytesIndicesType = { [K in BungeeBridge]: { - [functionSelector: string]: K extends BungeeBridge.Across - ? InputAmountTxDataBytesIndices - : InputAmountTxDataBytesIndices + [functionSelector: string]: InputAmountTxDataBytesIndices | InputOutputAmountTxDataBytesIndices } } diff --git a/packages/bridging/src/providers/bungee/util.test.ts b/packages/bridging/src/providers/bungee/util.test.ts index 82779130e..a89fdf911 100644 --- a/packages/bridging/src/providers/bungee/util.test.ts +++ b/packages/bridging/src/providers/bungee/util.test.ts @@ -11,6 +11,8 @@ import { import { BungeeBridge, BungeeQuoteWithBuildTx } from './types' import { OrderKind } from '@cowprotocol/sdk-order-book' import { SupportedChainId } from '@cowprotocol/sdk-config' +import { decodeAmountsBungeeTxData } from './util' +import { CCTP_V2_TX_DATA } from './createBungeeDepositCall.test' describe('Bungee Utils', () => { describe('toBridgeQuoteResult', () => { @@ -274,6 +276,17 @@ describe('Bungee Utils', () => { }) }) + describe('decodeAmountsBungeeTxData', () => { + it('should decode the cctp-v2 input amount and feeAmount from txData', () => { + const result = decodeAmountsBungeeTxData(CCTP_V2_TX_DATA, BungeeBridge.CircleCCTPV2) + + expect(result.inputAmountBytes).toBe('0x0000000000000000000000000000000000000000000000000000000005f5e100') + expect(result.inputAmountBigNumber).toBe(100000000n) + expect(result.outputAmountBytes).toBe('0x00000000000000000000000000000000000000000000000000000000001e8480') + expect(result.outputAmountBigNumber).toBe(2000000n) + }) + }) + describe('feeBuyToken calculation in toBridgeQuoteResult', () => { it('should correctly calculate feeBuyToken based on price ratio (18 decimals sell -> 6 decimals buy)', () => { const request: QuoteBridgeRequest = { diff --git a/packages/bridging/src/providers/bungee/util.ts b/packages/bridging/src/providers/bungee/util.ts index ec364a66f..d001a5e92 100644 --- a/packages/bridging/src/providers/bungee/util.ts +++ b/packages/bridging/src/providers/bungee/util.ts @@ -187,7 +187,15 @@ export const getDisplayNameFromBungeeBridge = (bridge: BungeeBridge): string | u return Object.entries(BungeeBridge).find(([_, value]) => value === bridge)?.[0] } -export const decodeAmountsBungeeTxData = (txData: string, bridge: BungeeBridge) => { +export const decodeAmountsBungeeTxData = ( + txData: string, + bridge: BungeeBridge, +): { + inputAmountBytes: string + inputAmountBigNumber: bigint + outputAmountBytes?: string + outputAmountBigNumber?: bigint +} => { if (!txData || !txData.startsWith('0x')) { throw new Error('Invalid txData format') } @@ -212,6 +220,21 @@ export const decodeAmountsBungeeTxData = (txData: string, bridge: BungeeBridge) )}` const inputAmountBigNumber = BigInt(inputAmountBytes) + if ('outputAmount' in functionParams) { + const outputAmountBytes = `0x${txData.slice( + functionParams.outputAmount.bytesString_startIndex, + functionParams.outputAmount.bytesString_startIndex + functionParams.outputAmount.bytesString_length, + )}` + const outputAmountBigNumber = BigInt(outputAmountBytes) + + return { + inputAmountBytes, + inputAmountBigNumber, + outputAmountBytes, + outputAmountBigNumber, + } + } + return { inputAmountBytes, inputAmountBigNumber, From 3c2ed51475565bfc281da52e9ccdd63f28261ce1 Mon Sep 17 00:00:00 2001 From: Rookmate <14072042+rookmate@users.noreply.github.com> Date: Fri, 13 Mar 2026 14:06:47 +0400 Subject: [PATCH 4/8] fix: add missing Gnosis Native to enum BungeeBridgeName --- packages/bridging/src/providers/bungee/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bridging/src/providers/bungee/types.ts b/packages/bridging/src/providers/bungee/types.ts index a97e0149c..116f3dd21 100644 --- a/packages/bridging/src/providers/bungee/types.ts +++ b/packages/bridging/src/providers/bungee/types.ts @@ -208,6 +208,7 @@ export enum BungeeEventStatus { export enum BungeeBridgeName { ACROSS = 'across', CCTP_V2 = 'cctp-v2', + GNOSIS_NATIVE = 'gnosis-native-bridge', } export type BungeeEvent = { From c3fb29566d1338ead44efb773671a5eef235494d Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 18 Mar 2026 17:26:14 +0000 Subject: [PATCH 5/8] fix: don't share fixtures by exporting from a .test.ts module --- .../src/providers/bungee/createBungeeDepositCall.test.ts | 4 +--- packages/bridging/src/providers/bungee/testData.ts | 2 ++ packages/bridging/src/providers/bungee/util.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 packages/bridging/src/providers/bungee/testData.ts diff --git a/packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts b/packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts index e459fe381..506e9975a 100644 --- a/packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts +++ b/packages/bridging/src/providers/bungee/createBungeeDepositCall.test.ts @@ -7,9 +7,7 @@ import { BUNGEE_APPROVE_AND_BRIDGE_V1_ABI } from './abi' import { createBungeeDepositCall } from './createBungeeDepositCall' import { BungeeBridge, BungeeQuoteWithBuildTx } from './types' import { decodeAmountsBungeeTxData, toBridgeQuoteResult } from './util' - -export const CCTP_V2_TX_DATA = - '0x000001a63ca7f5bc0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000002713000000000000000000000000664b591ab924c6bb2caca533ed702386934a11d6000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e8' +import { CCTP_V2_TX_DATA } from './testData' function applyPctDiff(base: bigint, compare: bigint, target: bigint): bigint { return (target * compare) / base diff --git a/packages/bridging/src/providers/bungee/testData.ts b/packages/bridging/src/providers/bungee/testData.ts new file mode 100644 index 000000000..0bbe2ad45 --- /dev/null +++ b/packages/bridging/src/providers/bungee/testData.ts @@ -0,0 +1,2 @@ +export const CCTP_V2_TX_DATA = + '0x000001a63ca7f5bc0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000002713000000000000000000000000664b591ab924c6bb2caca533ed702386934a11d6000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e8' diff --git a/packages/bridging/src/providers/bungee/util.test.ts b/packages/bridging/src/providers/bungee/util.test.ts index a89fdf911..a40c9e155 100644 --- a/packages/bridging/src/providers/bungee/util.test.ts +++ b/packages/bridging/src/providers/bungee/util.test.ts @@ -12,7 +12,7 @@ import { BungeeBridge, BungeeQuoteWithBuildTx } from './types' import { OrderKind } from '@cowprotocol/sdk-order-book' import { SupportedChainId } from '@cowprotocol/sdk-config' import { decodeAmountsBungeeTxData } from './util' -import { CCTP_V2_TX_DATA } from './createBungeeDepositCall.test' +import { CCTP_V2_TX_DATA } from './testData' describe('Bungee Utils', () => { describe('toBridgeQuoteResult', () => { From 945ade15e4b9b247cd7f75d7bce094bcbead6cd5 Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 18 Mar 2026 17:41:24 +0000 Subject: [PATCH 6/8] fix: add cctp back to avoid breaking api change --- .../src/providers/bungee/BungeeApi.test.ts | 30 ++++++++++++++----- .../src/providers/bungee/BungeeApi.ts | 5 +++- .../bridging/src/providers/bungee/consts.ts | 2 +- .../bridging/src/providers/bungee/types.ts | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/bridging/src/providers/bungee/BungeeApi.test.ts b/packages/bridging/src/providers/bungee/BungeeApi.test.ts index 55ac7d141..2bebdb3e0 100644 --- a/packages/bridging/src/providers/bungee/BungeeApi.test.ts +++ b/packages/bridging/src/providers/bungee/BungeeApi.test.ts @@ -120,7 +120,7 @@ const mockQuoteRequest: BungeeQuoteAPIRequest = { disableAuto: true, } -describe.skip('BungeeApi', () => { +describe('BungeeApi', () => { let api: BungeeApi beforeEach(() => { @@ -149,6 +149,20 @@ describe.skip('BungeeApi', () => { expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/quote'), expect.any(Object)) }) + it('should normalize "cctp" bridge to "cctp-v2"', async () => { + const request: BungeeQuoteAPIRequest = { + ...mockQuoteRequest, + includeBridges: ['cctp', 'across'], + } + + await api.getBungeeQuote(request) + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('includeBridges=cctp-v2%2Cacross'), + expect.any(Object), + ) + }) + it('should handle API errors', async () => { mockFetch.mockResolvedValue({ ok: false, @@ -160,7 +174,7 @@ describe.skip('BungeeApi', () => { }) }) - describe.skip('getBungeeBuildTx', () => { + describe('getBungeeBuildTx', () => { const mockBuildTxResponse = { success: true, statusCode: 200, @@ -383,7 +397,7 @@ describe.skip('BungeeApi', () => { }) }) - describe.skip('getEvents', () => { + describe('getEvents', () => { const mockEventsResponse = { success: true, result: [ @@ -454,7 +468,7 @@ describe.skip('BungeeApi', () => { }) }) - describe.skip('getAcrossStatus', () => { + describe('getAcrossStatus', () => { const mockAcrossStatusResponse = { status: 'filled', } @@ -484,7 +498,7 @@ describe.skip('BungeeApi', () => { }) }) - describe.skip('custom API URL', () => { + describe('custom API URL', () => { it('should use custom API URL when provided', async () => { const customUrl = 'https://custom-api.example.com' const customApi = new BungeeApi({ apiBaseUrl: customUrl }) @@ -500,7 +514,7 @@ describe.skip('BungeeApi', () => { }) }) - describe.skip('fallback mechanism', () => { + describe('fallback mechanism', () => { beforeEach(() => { jest.useFakeTimers() }) @@ -509,7 +523,7 @@ describe.skip('BungeeApi', () => { jest.useRealTimers() }) - describe.skip('fallback configuration', () => { + describe('fallback configuration', () => { it('should use default fallback timeout (5 minutes) when not specified', () => { const api = new BungeeApi() // Access private property for testing @@ -843,7 +857,7 @@ describe.skip('BungeeApi', () => { }) }) - describe.skip('apiKey and customApiBaseUrl', () => { + describe('apiKey and customApiBaseUrl', () => { describe('when both apiKey and customApiBaseUrl are present', () => { const customApiBaseUrl = 'https://custom-bungee-api.example.com' const apiKey = 'test-api-key-123' diff --git a/packages/bridging/src/providers/bungee/BungeeApi.ts b/packages/bridging/src/providers/bungee/BungeeApi.ts index 8f8d8557e..6c4b812f1 100644 --- a/packages/bridging/src/providers/bungee/BungeeApi.ts +++ b/packages/bridging/src/providers/bungee/BungeeApi.ts @@ -350,7 +350,10 @@ export class BungeeApi { } private getSupportedBridges(bridges?: SupportedBridge[]): SupportedBridge[] { - return bridges ?? this.options.includeBridges ?? SUPPORTED_BRIDGES + const bridgeList = bridges ?? this.options.includeBridges ?? SUPPORTED_BRIDGES + const normalized = bridgeList.map((bridge) => (bridge === 'cctp' ? 'cctp-v2' : bridge)) + + return Array.from(new Set(normalized)) } private isBungeeApi(apiType: BungeeApiType): boolean { diff --git a/packages/bridging/src/providers/bungee/consts.ts b/packages/bridging/src/providers/bungee/consts.ts index 1ce713481..c3d9673f5 100644 --- a/packages/bridging/src/providers/bungee/consts.ts +++ b/packages/bridging/src/providers/bungee/consts.ts @@ -8,7 +8,7 @@ export const BUNGEE_MANUAL_API_URL = `${BUNGEE_BASE_URL}${BUNGEE_MANUAL_API_PATH export const BUNGEE_EVENTS_API_URL = 'https://microservices.socket.tech/loki' export const ACROSS_API_URL = 'https://app.across.to/api' -export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp-v2', 'gnosis-native-bridge'] +export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp-v2', 'cctp', 'gnosis-native-bridge'] export const errorMessageMap = { bungee: 'Bungee Api Error', diff --git a/packages/bridging/src/providers/bungee/types.ts b/packages/bridging/src/providers/bungee/types.ts index 116f3dd21..682ebe181 100644 --- a/packages/bridging/src/providers/bungee/types.ts +++ b/packages/bridging/src/providers/bungee/types.ts @@ -1,6 +1,6 @@ import type { SupportedChainId, TargetChainId } from '@cowprotocol/sdk-config' -export type SupportedBridge = 'across' | 'cctp-v2' | 'gnosis-native-bridge' +export type SupportedBridge = 'across' | 'cctp-v2' | 'cctp' | 'gnosis-native-bridge' export interface BungeeQuoteAPIRequest { userAddress: string From 1ad6afddbdd612f1cd386c75b6ce2f3a5eeeb6fe Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Wed, 18 Mar 2026 17:53:01 +0000 Subject: [PATCH 7/8] fix: avoid txData being truncated --- packages/bridging/src/providers/bungee/util.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/bridging/src/providers/bungee/util.ts b/packages/bridging/src/providers/bungee/util.ts index d001a5e92..1a4530bc1 100644 --- a/packages/bridging/src/providers/bungee/util.ts +++ b/packages/bridging/src/providers/bungee/util.ts @@ -214,16 +214,26 @@ export const decodeAmountsBungeeTxData = ( } // decode input amount + const inputAmountEndIndex = functionParams.inputAmount.bytesString_startIndex + functionParams.inputAmount.bytesString_length + if (txData.length < inputAmountEndIndex) { + throw new Error(`Invalid txData: insufficient data for inputAmount. Expected at least ${inputAmountEndIndex} characters, got ${txData.length}`) + } + const inputAmountBytes = `0x${txData.slice( functionParams.inputAmount.bytesString_startIndex, - functionParams.inputAmount.bytesString_startIndex + functionParams.inputAmount.bytesString_length, + inputAmountEndIndex, )}` const inputAmountBigNumber = BigInt(inputAmountBytes) if ('outputAmount' in functionParams) { + const outputAmountEndIndex = functionParams.outputAmount.bytesString_startIndex + functionParams.outputAmount.bytesString_length + if (txData.length < outputAmountEndIndex) { + throw new Error(`Invalid txData: insufficient data for outputAmount. Expected at least ${outputAmountEndIndex} characters, got ${txData.length}`) + } + const outputAmountBytes = `0x${txData.slice( functionParams.outputAmount.bytesString_startIndex, - functionParams.outputAmount.bytesString_startIndex + functionParams.outputAmount.bytesString_length, + outputAmountEndIndex, )}` const outputAmountBigNumber = BigInt(outputAmountBytes) From 1061355195d6ea0641056b07e69e59fae51259b4 Mon Sep 17 00:00:00 2001 From: shoom3301 Date: Mon, 20 Jul 2026 12:13:06 +0200 Subject: [PATCH 8/8] chore: disable cctp --- packages/bridging/src/providers/bungee/consts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridging/src/providers/bungee/consts.ts b/packages/bridging/src/providers/bungee/consts.ts index c3d9673f5..1ce713481 100644 --- a/packages/bridging/src/providers/bungee/consts.ts +++ b/packages/bridging/src/providers/bungee/consts.ts @@ -8,7 +8,7 @@ export const BUNGEE_MANUAL_API_URL = `${BUNGEE_BASE_URL}${BUNGEE_MANUAL_API_PATH export const BUNGEE_EVENTS_API_URL = 'https://microservices.socket.tech/loki' export const ACROSS_API_URL = 'https://app.across.to/api' -export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp-v2', 'cctp', 'gnosis-native-bridge'] +export const SUPPORTED_BRIDGES: SupportedBridge[] = ['across', 'cctp-v2', 'gnosis-native-bridge'] export const errorMessageMap = { bungee: 'Bungee Api Error',