Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a0b6184
generate swap routes to hypercore
gsteenkamp89 Sep 25, 2025
8b20af8
bump packages
gsteenkamp89 Sep 25, 2025
9a7bd0a
show universal swap routes in chain selector
gsteenkamp89 Sep 25, 2025
c77097b
fixup
dohaki Sep 26, 2025
1fe942e
fix: fees and limits query
dohaki Sep 26, 2025
ebb0d8f
fix: output token resolution
dohaki Sep 29, 2025
8d6fd83
dedup constants in lock
dohaki Sep 29, 2025
eb5fdfe
fix: output amount calculation
dohaki Sep 29, 2025
e5cd924
fixup
dohaki Sep 29, 2025
b7142ee
Merge branch 'master' into feat/gsteenkamp-enable-hypercore-in-frontend
dohaki Sep 29, 2025
a96465d
fix: fill status tracking hypercore
dohaki Sep 29, 2025
42eb9d6
fix: api types
dohaki Sep 29, 2025
4c195c8
fixup
dohaki Sep 29, 2025
d0a421c
chore: distinguished label for Spots and Perps
dohaki Sep 30, 2025
cef46c7
Merge branch 'master' into feat/gsteenkamp-enable-hypercore-in-frontend
dohaki Oct 1, 2025
d55d9b0
fix: merge hypercore via arbitrum and hyperevm
dohaki Oct 1, 2025
b638e74
fixup
dohaki Oct 1, 2025
27064a6
Merge branch 'master' into feat/gsteenkamp-enable-hypercore-in-frontend
dohaki Oct 1, 2025
37b534e
show warning if account not initialized
gsteenkamp89 Oct 1, 2025
37b5548
fixup
gsteenkamp89 Oct 1, 2025
6ba4eb4
link to USDC route
gsteenkamp89 Oct 1, 2025
ee5e280
edit styles
gsteenkamp89 Oct 6, 2025
987db84
update copy
gsteenkamp89 Oct 6, 2025
624a0fc
Merge branch 'master' into feat/gsteenkamp-enable-hypercore-in-frontend
dohaki Oct 7, 2025
d65e610
feat: correct show `Hyperliquid` as destination in tx table
dohaki Oct 7, 2025
98a4e97
Merge branch 'master' into feat/gsteenkamp-enable-hypercore-in-frontend
dohaki Oct 9, 2025
a07fca5
fix: fee calc usdt bnb -> usdt hl
dohaki Oct 9, 2025
9d79391
fixup
dohaki Oct 9, 2025
1819c7b
fixup
dohaki Oct 9, 2025
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
14 changes: 5 additions & 9 deletions api/_dexes/cross-swap-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import { getMultiCallHandlerAddress } from "../_multicall-handler";
import {
getIndirectBridgeQuoteMessage,
getIndirectDestinationRoutes,
getIndirectDestinationRoute,
} from "./utils-b2bi";
import {
InvalidParamError,
Expand Down Expand Up @@ -228,21 +228,19 @@ export async function getCrossSwapQuotesForExactInputB2BI(
): Promise<CrossSwapQuotes> {
const { depositEntryPoint } = _prepCrossSwapQuotesRetrievalB2B(crossSwap);

const indirectDestinationRoutes = getIndirectDestinationRoutes({
const indirectDestinationRoute = getIndirectDestinationRoute({
originChainId: crossSwap.inputToken.chainId,
destinationChainId: crossSwap.outputToken.chainId,
inputToken: crossSwap.inputToken.address,
outputToken: crossSwap.outputToken.address,
});

if (indirectDestinationRoutes.length === 0) {
if (!indirectDestinationRoute) {
throw new InvalidParamError({
message: "No indirect bridge routes found to specified destination chain",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
message: "No indirect bridge routes found to specified destination chain",
message: "No indirect bridge route found to specified destination chain",

});
}

const [indirectDestinationRoute] = indirectDestinationRoutes;

// For EXACT_INPUT, we need to convert the amount to the intermediary output token decimals
// to get the initial bridgeable output amount.
let bridgeableOutputAmount = ConvertDecimals(
Expand Down Expand Up @@ -329,21 +327,19 @@ export async function getCrossSwapQuotesForOutputB2BI(
): Promise<CrossSwapQuotes> {
const { depositEntryPoint } = _prepCrossSwapQuotesRetrievalB2B(crossSwap);

const indirectDestinationRoutes = getIndirectDestinationRoutes({
const indirectDestinationRoute = getIndirectDestinationRoute({
originChainId: crossSwap.inputToken.chainId,
destinationChainId: crossSwap.outputToken.chainId,
inputToken: crossSwap.inputToken.address,
outputToken: crossSwap.outputToken.address,
});

if (indirectDestinationRoutes.length === 0) {
if (!indirectDestinationRoute) {
throw new InvalidParamError({
message: "No indirect bridge routes found to specified destination chain",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
message: "No indirect bridge routes found to specified destination chain",
message: "No indirect bridge route found to specified destination chain",

});
}

const [indirectDestinationRoute] = indirectDestinationRoutes;

const outputAmountWithAppFee = crossSwap.appFeePercent
? addMarkupToAmount(crossSwap.amount, crossSwap.appFeePercent)
: crossSwap.amount;
Expand Down
179 changes: 82 additions & 97 deletions api/_dexes/utils-b2bi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,127 +28,112 @@ export function isIndirectDestinationRouteSupported(params: {
inputToken: string;
outputToken: string;
}) {
return getIndirectDestinationRoutes(params).length > 0;
return !!getIndirectDestinationRoute(params);
}

export function getIndirectDestinationRoutes(params: {
export function getIndirectDestinationRoute(params: {
originChainId: number;
destinationChainId: number;
inputToken: string;
outputToken: string;
}): IndirectDestinationRoute[] {
}): IndirectDestinationRoute | undefined {
const indirectChainDestination = indirectChains.find(
(chain) =>
chain.chainId === params.destinationChainId &&
chain.intermediaryChains &&
chain.intermediaryChain &&
chain.outputTokens.some(
(token) =>
token.address.toLowerCase() === params.outputToken.toLowerCase()
)
);

if (!indirectChainDestination) {
return [];
return;
}

const indirectDestinationRoutes =
indirectChainDestination.intermediaryChains.flatMap(
(_intermediaryChainId) => {
// Check if the indirect destination chain has token enabled
const isIntermediaryOutputTokenEnabled =
indirectChainDestination.outputTokens.some(
(token) => token.address === params.outputToken
);
if (!isIntermediaryOutputTokenEnabled) {
return [];
}
const intermediaryChainId = indirectChainDestination.intermediaryChain;

// Check if input token is known
const inputToken = getTokenByAddress(
params.inputToken,
params.originChainId
);
if (!inputToken) {
return [];
}
// Check if the indirect destination chain has token enabled
const isIntermediaryOutputTokenEnabled =
indirectChainDestination.outputTokens.some(
(token) => token.address === params.outputToken
);
if (!isIntermediaryOutputTokenEnabled) {
return;
}

// Check if the indirect destination chain supports the intermediary chain
const indirectOutputToken = getTokenByAddress(
params.outputToken,
params.destinationChainId
);
if (!indirectOutputToken) {
return [];
}
// Check if input token is known
const inputToken = getTokenByAddress(params.inputToken, params.originChainId);
if (!inputToken) {
return;
}

// Check if L1 token is known
const l1TokenAddress =
TOKEN_SYMBOLS_MAP[inputToken.symbol as keyof typeof TOKEN_SYMBOLS_MAP]
?.addresses[HUB_POOL_CHAIN_ID];
if (!l1TokenAddress) {
return [];
}
const l1Token = getTokenByAddress(l1TokenAddress, HUB_POOL_CHAIN_ID);
if (!l1Token) {
return [];
}
// Check if the indirect destination chain supports the intermediary chain
const indirectOutputToken = getTokenByAddress(
params.outputToken,
params.destinationChainId
);
if (!indirectOutputToken) {
return;
}

// Check if intermediary output token is known
const intermediaryOutputTokenAddress =
l1Token.addresses[_intermediaryChainId];
if (!intermediaryOutputTokenAddress) {
return [];
}
const intermediaryOutputToken = getTokenByAddress(
intermediaryOutputTokenAddress,
_intermediaryChainId
);
if (!intermediaryOutputToken) {
return [];
}
// Check if L1 token is known
const l1TokenAddress =
TOKEN_SYMBOLS_MAP[inputToken.symbol as keyof typeof TOKEN_SYMBOLS_MAP]
?.addresses[HUB_POOL_CHAIN_ID];
if (!l1TokenAddress) {
return;
}
const l1Token = getTokenByAddress(l1TokenAddress, HUB_POOL_CHAIN_ID);
if (!l1Token) {
return;
}

// Check if there is a route from the origin chain to the intermediary chain
if (
!isRouteEnabled(
params.originChainId,
_intermediaryChainId,
params.inputToken,
intermediaryOutputTokenAddress
)
) {
return [];
}
// Check if intermediary output token is known
const intermediaryOutputTokenAddress = l1Token.addresses[intermediaryChainId];
if (!intermediaryOutputTokenAddress) {
return;
}
const intermediaryOutputToken = getTokenByAddress(
intermediaryOutputTokenAddress,
indirectChainDestination.intermediaryChain
);
if (!intermediaryOutputToken) {
return;
}

return {
inputToken: {
symbol: inputToken.symbol,
name: inputToken.name,
decimals: inputToken.decimals,
address: inputToken.addresses[params.originChainId],
chainId: params.originChainId,
coingeckoId: inputToken.coingeckoId,
},
intermediaryOutputToken: {
symbol: intermediaryOutputToken.symbol,
name: intermediaryOutputToken.name,
decimals: intermediaryOutputToken.decimals,
address: intermediaryOutputToken.addresses[_intermediaryChainId],
chainId: _intermediaryChainId,
coingeckoId: intermediaryOutputToken.coingeckoId,
},
outputToken: {
symbol: indirectOutputToken.symbol,
name: indirectOutputToken.name,
decimals: indirectOutputToken.decimals,
address: indirectOutputToken.addresses[params.destinationChainId],
chainId: params.destinationChainId,
coingeckoId: indirectOutputToken.coingeckoId,
},
};
}
);
// Check if there is a route from the origin chain to the intermediary chain
if (
!isRouteEnabled(
params.originChainId,
intermediaryChainId,
params.inputToken,
intermediaryOutputTokenAddress
)
) {
return;
}

return indirectDestinationRoutes;
return {
inputToken: {
symbol: inputToken.symbol,
decimals: inputToken.decimals,
address: inputToken.addresses[params.originChainId],
chainId: params.originChainId,
},
intermediaryOutputToken: {
symbol: intermediaryOutputToken.symbol,
decimals: intermediaryOutputToken.decimals,
address: intermediaryOutputToken.addresses[intermediaryChainId],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type before the change was IndirectDestinationRoute[] and now it is IndirectDestinationRoute, what's the reason for the change of fields in the return type here?

chainId: intermediaryChainId,
},
outputToken: {
symbol: indirectOutputToken.symbol,
decimals: indirectOutputToken.decimals,
address: indirectOutputToken.addresses[params.destinationChainId],
chainId: params.destinationChainId,
},
};
}

export function getIndirectBridgeQuoteMessage(
Expand Down Expand Up @@ -227,7 +212,7 @@ function _buildIndirectBridgeQuoteMessageToHyperCore(
function _buildBridgeQuoteMessageToHyperCore(
crossSwap: CrossSwap,
bridgeableOutputAmount: BigNumber,
indirectDestinationRoute: ReturnType<typeof getIndirectDestinationRoutes>[0],
indirectDestinationRoute: IndirectDestinationRoute,
appFee?: AppFee
) {
const {
Expand Down
8 changes: 3 additions & 5 deletions scripts/chain-configs/hypercore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ChainConfig } from "../types";
export default {
chainId: 1337, // Arbitrary chain id for HyperCore
name: "HyperCore",
fullName: "HyperCore",
fullName: "Hyperliquid",
logoPath: "./assets/logo.svg",
grayscaleLogoPath: "./assets/grayscale-logo.svg",
spokePool: {
Expand All @@ -14,12 +14,10 @@ export default {
publicRpcUrl: "https://api.hyperliquid.xyz",
blockExplorer: "https://app.hyperliquid.xyz/explorer",
blockTimeSeconds: 1,
tokens: [],
inputTokens: [],
outputTokens: ["USDT-SPOT"],
tokens: ["USDT-SPOT"],
enableCCTP: false,
omitViemConfig: true,
nativeToken: "HYPE",
// HyperCore can only be reached via HyperEVM as an intermediary chain.
intermediaryChains: [CHAIN_IDs.HYPEREVM],
intermediaryChain: CHAIN_IDs.HYPEREVM,
} as ChainConfig;
16 changes: 1 addition & 15 deletions scripts/chain-configs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,5 @@ export type ChainConfig = {
toTokenSymbol: string;
externalProjectId?: string;
}[];
intermediaryChains?: number[];
outputTokens?: (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason behind excluding the input and output token of the route here?

| string
| {
symbol: string;
chainIds: number[];
}
)[];
inputTokens?: (
| string
| {
symbol: string;
chainIds: number[];
}
)[];
intermediaryChain?: number;
};
1 change: 1 addition & 0 deletions scripts/extern-configs/hyperliquid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ExternalProjectConfig } from "../types";

export default {
name: "Hyperliquid",
fullName: "Hyperliquid",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between name and fullName?

projectId: "hyperliquid",
explorer: "https://arbiscan.io",
logoPath: "./assets/logo.svg",
Expand Down
Loading