Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions api/_bridges/cctp/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
CCTP_SUPPORTED_CHAINS,
CCTP_SUPPORTED_TOKENS,
CCTP_FINALITY_THRESHOLDS,
DEFAULT_CCTP_ACROSS_FINALIZER_ADDRESS,
getCctpFinalizerAddress,
getCctpTokenMessengerAddress,
getCctpMessageTransmitterAddress,
getCctpDomainId,
Expand Down Expand Up @@ -89,12 +89,7 @@ export function getCctpBridgeStrategy(
const isDestinationChainSupported = CCTP_SUPPORTED_CHAINS.includes(
params.outputToken.chainId
);
if (
!isOriginChainSupported ||
!isDestinationChainSupported ||
// NOTE: Our finalizer doesn't support destination Solana yet. Block the route until we do.
sdk.utils.chainIsSvm(params.outputToken.chainId)
) {
if (!isOriginChainSupported || !isDestinationChainSupported) {
return false;
}

Expand Down Expand Up @@ -295,6 +290,7 @@ export function getCctpBridgeStrategy(
const destinationChainId = crossSwap.outputToken.chainId;
const destinationDomain = getCctpDomainId(destinationChainId);
const tokenMessenger = getCctpTokenMessengerAddress(originChainId);
const destinationFinalizer = getCctpFinalizerAddress(destinationChainId);
// Circle's API returns a minimum fee. Add 1 unit as buffer to ensure the transfer meets the threshold for fast mode eligibility.
const hasFastFee = bridgeQuote.fees.amount.gt(0);
const maxFee = hasFastFee
Expand All @@ -309,7 +305,7 @@ export function getCctpBridgeStrategy(
amount: bridgeQuote.inputAmount,
destinationDomain,
mintRecipient: crossSwap.recipient,
destinationCaller: DEFAULT_CCTP_ACROSS_FINALIZER_ADDRESS,
destinationCaller: destinationFinalizer,
maxFee,
minFinalityThreshold,
};
Expand Down
13 changes: 11 additions & 2 deletions api/_bridges/cctp/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BigNumber, ethers } from "ethers";
import * as sdk from "@across-protocol/sdk";
import { CCTP_NO_DOMAIN } from "@across-protocol/constants";
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP, CHAINS } from "../../../_constants";
import { InvalidParamError } from "../../../_errors";
Expand Down Expand Up @@ -35,9 +36,17 @@ export const CCTP_FINALITY_THRESHOLDS = {
standard: 2000,
};

// CCTP Across Finalizer address
export const DEFAULT_CCTP_ACROSS_FINALIZER_ADDRESS =
// CCTP Across Finalizer addresses
const CCTP_ACROSS_FINALIZER_ADDRESS_EVM =
"0x72adB07A487f38321b6665c02D289C413610B081";
const CCTP_ACROSS_FINALIZER_ADDRESS_SVM =
"5v4SXbcAKKo3YbPBXU9K7zNBMgJ2RQFsvQmg2RAFZT6t";

export const getCctpFinalizerAddress = (chainId: number): string => {
return sdk.utils.chainIsSvm(chainId)
? CCTP_ACROSS_FINALIZER_ADDRESS_SVM
: CCTP_ACROSS_FINALIZER_ADDRESS_EVM;
};

// CCTP TokenMessenger contract addresses
// Source: https://developers.circle.com/cctp/evm-smart-contracts
Expand Down
106 changes: 106 additions & 0 deletions test/api/_bridges/cctp/strategy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as sdk from "@across-protocol/sdk";
import { _buildCctpTxForAllowanceHolderEvm } from "../../../../api/_bridges/cctp/strategy";
import { CrossSwapQuotes } from "../../../../api/_dexes/types";
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../../../../api/_constants";
import { encodeDepositForBurn } from "../../../../api/_bridges/cctp/utils/constants";

// Mock only the SVM utilities we need
jest.mock("@across-protocol/sdk", () => {
Expand Down Expand Up @@ -160,4 +161,109 @@ describe("CCTP Strategy - EVM to Solana mint recipient", () => {
"0xencoded-mintRecipient:0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"
);
});

it("passes through SVM destination caller to encodeDepositForBurn", async () => {
const solanaDestinationCaller =
"5v4SXbcAKKo3YbPBXU9K7zNBMgJ2RQFsvQmg2RAFZT6t";
const quotes: CrossSwapQuotes = {
crossSwap: {
amount: BigNumber.from("1000000"),
inputToken: {
address: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.OPTIMISM],
decimals: 6,
symbol: "USDC",
chainId: CHAIN_IDs.OPTIMISM,
},
outputToken: {
address: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.SOLANA],
decimals: 6,
symbol: "USDC",
chainId: CHAIN_IDs.SOLANA,
},
depositor: "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D",
recipient: "FmMK62wrtWVb5SVoTZftSCGw3nEDA79hDbZNTRnC1R6t",
slippageTolerance: 0.01,
type: "exactInput",
refundOnOrigin: false,
embeddedActions: [],
strictTradeType: true,
isDestinationSvm: true,
},
bridgeQuote: {} as any,
contracts: {} as any,
};

await _buildCctpTxForAllowanceHolderEvm({
crossSwapQuotes: quotes,
originChainId: CHAIN_IDs.OPTIMISM,
destinationChainId: CHAIN_IDs.SOLANA,
tokenMessenger: "0x1234567890123456789012345678901234567890",
depositForBurnParams: {
amount: BigNumber.from("1000000"),
destinationDomain: 5,
mintRecipient: quotes.crossSwap.recipient,
destinationCaller: solanaDestinationCaller,
maxFee: BigNumber.from(0),
minFinalityThreshold: 12,
},
});

expect(encodeDepositForBurn).toHaveBeenCalledWith(
expect.objectContaining({
destinationCaller: solanaDestinationCaller,
})
);
});

it("passes through EVM destination caller to encodeDepositForBurn", async () => {
const evmDestinationCaller = "0x72adB07A487f38321b6665c02D289C413610B081";
const quotes: CrossSwapQuotes = {
crossSwap: {
amount: BigNumber.from("1000000"),
inputToken: {
address: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.OPTIMISM],
decimals: 6,
symbol: "USDC",
chainId: CHAIN_IDs.OPTIMISM,
},
outputToken: {
address: TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.BASE],
decimals: 6,
symbol: "USDC",
chainId: CHAIN_IDs.BASE,
},
depositor: "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D",
recipient: "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D",
slippageTolerance: 0.01,
type: "exactInput",
refundOnOrigin: false,
embeddedActions: [],
strictTradeType: true,
isDestinationSvm: false,
},
bridgeQuote: {} as any,
contracts: {} as any,
};

await _buildCctpTxForAllowanceHolderEvm({
crossSwapQuotes: quotes,
originChainId: CHAIN_IDs.OPTIMISM,
destinationChainId: CHAIN_IDs.BASE,
tokenMessenger: "0x1234567890123456789012345678901234567890",
depositForBurnParams: {
amount: BigNumber.from("1000000"),
destinationDomain: 6,
mintRecipient: quotes.crossSwap.recipient,
destinationCaller: evmDestinationCaller,
maxFee: BigNumber.from(0),
minFinalityThreshold: 12,
},
});

expect(encodeDepositForBurn).toHaveBeenCalledWith(
expect.objectContaining({
destinationCaller: evmDestinationCaller,
})
);
});
});
Loading