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
66 changes: 62 additions & 4 deletions api/_bridges/cctp-sponsored/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,31 @@ export async function getQuoteForExactInput(
}
: params.outputToken,
});
outputAmount = unsponsoredOutputAmount;

// For swap pairs, simulate the HyperLiquid market order to get actual output with swap impact
if (isSwapPair) {
const simResult = await simulateMarketOrder({
chainId: outputToken.chainId,
tokenIn: {
symbol: "USDC",
decimals: SPOT_TOKEN_DECIMALS,
},
tokenOut: {
symbol: getNormalizedSpotTokenSymbol(outputToken.symbol),
decimals: SPOT_TOKEN_DECIMALS,
},
amount: unsponsoredOutputAmount,
amountType: "input",
});

outputAmount = ConvertDecimals(
SPOT_TOKEN_DECIMALS,
outputToken.decimals
)(simResult.outputAmount);
} else {
outputAmount = unsponsoredOutputAmount;
}

provider = "cctp";
fees = unsponsoredFees;
}
Expand Down Expand Up @@ -222,6 +246,7 @@ export async function getQuoteForOutput(
assertSupportedRoute({ inputToken, outputToken });

let inputAmount: BigNumber;
let outputAmount: BigNumber = minOutputAmount;
let provider: "sponsored-cctp" | "cctp" = "sponsored-cctp";
let fees: {
amount: BigNumber;
Expand All @@ -247,6 +272,37 @@ export async function getQuoteForOutput(
} else {
const isSwapPair =
inputToken.symbol !== getNormalizedSpotTokenSymbol(outputToken.symbol);

let bridgeOutputRequired = minOutputAmount;
if (isSwapPair) {
// For swap pairs, simulate to determine how much bridge output we need
const simResult = await simulateMarketOrder({
chainId: outputToken.chainId,
tokenIn: {
symbol: "USDC",
decimals: SPOT_TOKEN_DECIMALS,
},
tokenOut: {
symbol: getNormalizedSpotTokenSymbol(outputToken.symbol),
decimals: SPOT_TOKEN_DECIMALS,
},
amount: ConvertDecimals(
outputToken.decimals,
SPOT_TOKEN_DECIMALS
)(minOutputAmount),
amountType: "output",
});

outputAmount = ConvertDecimals(
SPOT_TOKEN_DECIMALS,
outputToken.decimals
)(simResult.outputAmount);
bridgeOutputRequired = ConvertDecimals(
SPOT_TOKEN_DECIMALS,
outputToken.decimals
)(simResult.inputAmount);
}

const {
bridgeQuote: {
inputAmount: unsponsoredInputAmount,
Expand All @@ -258,6 +314,7 @@ export async function getQuoteForOutput(
useForwardFee: false,
}).getQuoteForOutput({
...params,
minOutputAmount: isSwapPair ? bridgeOutputRequired : minOutputAmount,
outputToken: isSwapPair
? {
...TOKEN_SYMBOLS_MAP["USDC-SPOT"],
Expand All @@ -279,8 +336,8 @@ export async function getQuoteForOutput(
inputToken,
outputToken,
inputAmount,
outputAmount: minOutputAmount,
minOutputAmount,
outputAmount,
minOutputAmount: outputAmount,
estimatedFillTimeSec: getEstimatedFillTime(
inputToken.chainId,
CCTP_TRANSFER_MODE
Expand Down Expand Up @@ -594,7 +651,8 @@ export async function calculateMaxBpsToSponsor(params: {
symbol: outputToken.symbol,
decimals: outputToken.decimals,
},
inputAmount: bridgeOutputAmountOutputTokenDecimals,
amount: bridgeOutputAmountOutputTokenDecimals,
amountType: "input",
});
swapSlippageBps = BigNumber.from(
Math.ceil(simResult.slippagePercent * 100)
Expand Down
12 changes: 0 additions & 12 deletions api/_bridges/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,6 @@ export const bridgeStrategies: BridgeStrategiesConfig = {
[TOKEN_SYMBOLS_MAP.USDH.symbol]: getUsdhIntentsBridgeStrategy(),
},
},
// NOTE: Disable subset of HyperCore destination routes via mint/burn routes until we
// fully support them. We force return the Across bridge strategy here to avoid
// routing to via our algorithm. TODO until we can enable these routes:
// - https://linear.app/uma/issue/ACX-4895/api-return-swap-fees-for-unsponsored-flows
[CHAIN_IDs.HYPERCORE]: {
[TOKEN_SYMBOLS_MAP.USDC.symbol]: {
[TOKEN_SYMBOLS_MAP["USDT-SPOT"].symbol]: getAcrossBridgeStrategy(),
},
[TOKEN_SYMBOLS_MAP.USDT.symbol]: {
[TOKEN_SYMBOLS_MAP["USDC-SPOT"].symbol]: getAcrossBridgeStrategy(),
},
},
},
fromToChains: {
[CHAIN_IDs.HYPEREVM]: {
Expand Down
108 changes: 91 additions & 17 deletions api/_bridges/oft-sponsored/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
simulateMarketOrder,
SPOT_TOKEN_DECIMALS,
isToHyperCore,
getNormalizedSpotTokenSymbol,
} from "../../_hypercore";
import { tagIntegratorId, tagSwapApiMarker } from "../../_integrator-id";
import {
Expand Down Expand Up @@ -188,11 +189,41 @@ export async function getSponsoredOftQuoteForExactInput(

const nativeToken = getNativeTokenInfo(inputToken.chainId);

// Convert output amount from intermediary token decimals to final output token decimals
const finalOutputAmount = ConvertDecimals(
intermediaryToken.decimals,
outputToken.decimals
)(outputAmount);
const isSwapPair =
inputToken.symbol !== getNormalizedSpotTokenSymbol(outputToken.symbol);

let finalOutputAmount: BigNumber;
if (isSwapPair) {
// For swap pairs, simulate the HyperLiquid market order to get actual output with swap impact
const bridgeOutputInSpotDecimals = ConvertDecimals(
TOKEN_SYMBOLS_MAP.USDT.decimals,
SPOT_TOKEN_DECIMALS
)(outputAmount);

const simResult = await simulateMarketOrder({
chainId: outputToken.chainId,
tokenIn: {
symbol: "USDT",
decimals: SPOT_TOKEN_DECIMALS,
},
tokenOut: {
symbol: getNormalizedSpotTokenSymbol(outputToken.symbol),
decimals: SPOT_TOKEN_DECIMALS,
},
amount: bridgeOutputInSpotDecimals,
amountType: "input",
});

finalOutputAmount = ConvertDecimals(
SPOT_TOKEN_DECIMALS,
outputToken.decimals
)(simResult.outputAmount);
} else {
finalOutputAmount = ConvertDecimals(
intermediaryToken.decimals,
outputToken.decimals
)(outputAmount);
}

return {
bridgeQuote: {
Expand Down Expand Up @@ -238,21 +269,61 @@ export async function getSponsoredOftQuoteForOutput(
// All sponsored OFT transfers route through HyperEVM USDT before reaching final destination
const intermediaryToken = await getIntermediaryToken();

// Convert minOutputAmount to input token decimals
const minOutputInInputDecimals = ConvertDecimals(
outputToken.decimals,
const isSwapPair =
inputToken.symbol !== getNormalizedSpotTokenSymbol(outputToken.symbol);

let bridgeOutputRequired: BigNumber;
let finalOutputAmount: BigNumber;

if (isSwapPair) {
// For swap pairs, simulate the HyperLiquid market order to get actual input needed with swap impact
const simResult = await simulateMarketOrder({
chainId: outputToken.chainId,
tokenIn: {
symbol: "USDT",
decimals: SPOT_TOKEN_DECIMALS,
},
tokenOut: {
symbol: getNormalizedSpotTokenSymbol(outputToken.symbol),
decimals: SPOT_TOKEN_DECIMALS,
},
amount: ConvertDecimals(
outputToken.decimals,
SPOT_TOKEN_DECIMALS
)(minOutputAmount),
amountType: "output",
});

bridgeOutputRequired = ConvertDecimals(
SPOT_TOKEN_DECIMALS,
TOKEN_SYMBOLS_MAP.USDT.decimals
)(simResult.inputAmount);
finalOutputAmount = ConvertDecimals(
SPOT_TOKEN_DECIMALS,
outputToken.decimals
)(simResult.outputAmount);
} else {
bridgeOutputRequired = ConvertDecimals(
outputToken.decimals,
intermediaryToken.decimals
)(minOutputAmount);
finalOutputAmount = minOutputAmount;
}

// Convert bridge output required to input token decimals for OFT quote
const bridgeOutputInInputDecimals = ConvertDecimals(
intermediaryToken.decimals,
inputToken.decimals
)(minOutputAmount);
)(bridgeOutputRequired);

// Get OFT quote to intermediary token and estimated fill time
const [
{ inputAmount, outputAmount: intermediaryOutputAmount, nativeFee },
estimatedFillTimeSec,
] = await Promise.all([
getQuote({
inputToken,
outputToken: intermediaryToken,
inputAmount: minOutputInInputDecimals,
inputAmount: bridgeOutputInInputDecimals,
recipient: recipient!,
}),
getEstimatedFillTime(
Expand All @@ -262,11 +333,13 @@ export async function getSponsoredOftQuoteForOutput(
),
]);

// Convert output amount from intermediary token decimals to output token decimals
const finalOutputAmount = ConvertDecimals(
intermediaryToken.decimals,
outputToken.decimals
)(intermediaryOutputAmount);
// For non-swap case, update finalOutputAmount based on actual bridge output
if (!isSwapPair) {
finalOutputAmount = ConvertDecimals(
intermediaryToken.decimals,
outputToken.decimals
)(intermediaryOutputAmount);
}

// OFT precision limitations may prevent delivering the exact minimum amount
// We validate against the rounded amount (maximum possible given shared decimals)
Expand Down Expand Up @@ -334,7 +407,8 @@ export async function calculateMaxBpsToSponsor(params: {
symbol: "USDC",
decimals: SPOT_TOKEN_DECIMALS, // Spot token decimals always 8
},
inputAmount: ConvertDecimals(
amountType: "input",
amount: ConvertDecimals(
TOKEN_SYMBOLS_MAP.USDT.decimals,
SPOT_TOKEN_DECIMALS
)(bridgeOutputAmount), // Convert USDT to USDT-SPOT, as `bridgeOutputAmount` is in USDT decimals
Expand Down
Loading
Loading