Skip to content
Draft
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
1 change: 1 addition & 0 deletions api/swap/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ export async function buildBaseSwapResponseJson(params: {
tokenIn: params.bridgeQuote.inputToken,
tokenOut: params.bridgeQuote.outputToken,
fees: params.bridgeQuote.fees,
provider: params.bridgeQuote.provider,
},
destinationSwap: params.destinationSwapQuote
? {
Expand Down
1 change: 0 additions & 1 deletion scripts/generate-swap-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ const enabledSwapRoutes: {
},
[TOKEN_SYMBOLS_MAP.USDT.symbol]: {
all: {
disabledOriginChains: [CHAIN_IDs.HYPEREVM],
enabledDestinationChains: [CHAIN_IDs.HYPERCORE],
enabledOutputTokens: ["USDT-SPOT"],
},
Expand Down
12 changes: 12 additions & 0 deletions src/data/universal-swap-routes_1.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,18 @@
"type": "universal-swap",
"isNative": false
},
{
"fromChain": 999,
"toChain": 1337,
"fromTokenAddress": "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
"toTokenAddress": "0x200000000000000000000000000000000000010C",
"fromTokenSymbol": "USDT",
"toTokenSymbol": "USDT-SPOT",
"fromSpokeAddress": "0x35E63eA3eb0fb7A3bc543C71FB66412e1F6B0E04",
"l1TokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"type": "universal-swap",
"isNative": false
},
{
"fromChain": 9745,
"toChain": 1337,
Expand Down
41 changes: 41 additions & 0 deletions src/hooks/useBridgeFees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,41 @@ const DEFAULT_SIMULATED_RECIPIENT_ADDRESS_EVM =
const DEFAULT_SIMULATED_RECIPIENT_ADDRESS_SVM =
"GsiZqCTNRi4T3qZrixFdmhXVeA4CSUzS7c44EQ7Rw1Tw";

const EMPTY_BRIDGE_FEES = {
totalRelayFee: {
total: BigNumber.from(0),
pct: BigNumber.from(0),
},
lpFee: {
total: BigNumber.from(0),
pct: BigNumber.from(0),
},
relayerGasFee: {
total: BigNumber.from(0),
pct: BigNumber.from(0),
},
relayerCapitalFee: {
total: BigNumber.from(0),
pct: BigNumber.from(0),
},
quoteTimestamp: BigNumber.from(0),
quoteTimestampInMs: BigNumber.from(0),
quoteLatency: BigNumber.from(0),
quoteBlock: BigNumber.from(0),
limits: {
maxDepositInstant: BigNumber.from(ethers.constants.MaxUint256),
maxDeposit: BigNumber.from(ethers.constants.MaxUint256),
maxDepositShortDelay: BigNumber.from(ethers.constants.MaxUint256),
minDeposit: BigNumber.from(0),
recommendedDepositInstant: BigNumber.from(ethers.constants.MaxUint256),
},
estimatedFillTimeSec: 1,
exclusiveRelayer: ethers.constants.AddressZero,
exclusivityDeadline: 0,
fillDeadline: 0,
isAmountTooLow: false,
};

/**
* This hook calculates the bridge fees for a given token and amount.
* @param amount - The amount to check bridge fees for.
Expand Down Expand Up @@ -63,6 +98,7 @@ export function useBridgeFees(
bridgeOutputTokenSymbol,
bridgeOriginChainId,
bridgeDestinationChainId,
didUniversalSwapLoad ? universalSwapQuote.steps.bridge.provider : "across",
externalProjectId,
recipientAddress
);
Expand All @@ -76,10 +112,15 @@ export function useBridgeFees(
amountToQuery,
fromChainIdToQuery,
toChainIdToQuery,
bridgeProviderToQuery,
externalProjectIdToQuery,
recipientAddressToQuery,
] = queryKey;

if (bridgeProviderToQuery === "hypercore") {
return EMPTY_BRIDGE_FEES;
}

const feeArgs = {
amount: BigNumber.from(amountToQuery),
inputTokenSymbol: inputTokenSymbolToQuery,
Expand Down
15 changes: 13 additions & 2 deletions src/hooks/useBridgeLimits.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { bridgeLimitsQueryKey, ChainId, getConfig } from "utils";
import { BigNumber } from "ethers";
import { BigNumber, ethers } from "ethers";
import getApiEndpoint from "utils/serverless-api";
import { UniversalSwapQuote } from "./useUniversalSwapQuote";

Expand Down Expand Up @@ -53,7 +53,8 @@ export function useBridgeLimits(
bridgeInputTokenSymbol,
bridgeOutputTokenSymbol,
bridgeOriginChainId,
bridgeDestinationChainId
bridgeDestinationChainId,
didUniversalSwapLoad ? universalSwapQuote.steps.bridge.provider : "across"
);
const { data: limits, ...delegated } = useQuery({
queryKey,
Expand All @@ -64,6 +65,7 @@ export function useBridgeLimits(
outputTokenSymbolToQuery,
fromChainIdToQuery,
toChainIdToQuery,
bridgeProviderToQuery,
] = queryKey;

if (
Expand All @@ -75,6 +77,15 @@ export function useBridgeLimits(
throw new Error("Bridge limits query not enabled");
}

if (bridgeProviderToQuery === "hypercore") {
return {
minDeposit: BigNumber.from(0),
maxDeposit: BigNumber.from(ethers.constants.MaxUint256),
maxDepositInstant: BigNumber.from(ethers.constants.MaxUint256),
maxDepositShortDelay: BigNumber.from(ethers.constants.MaxUint256),
};
}

return getApiEndpoint().limits(
config.getTokenInfoBySymbol(fromChainIdToQuery, inputTokenSymbolToQuery)
.address,
Expand Down
133 changes: 133 additions & 0 deletions src/utils/hyperevm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { ethers, BigNumber } from "ethers";

import { ChainId } from "./constants";
import { getDepositByTxHash } from "./deposits";
import { getProvider } from "./providers";
import { ERC20__factory, TransferEvent } from "./typechain";
import { toAddressType } from "./sdk";

import type {
DepositData,
DepositedInfo,
FillInfo,
} from "views/DepositStatus/hooks/useDepositTracking/types";

export async function getDepositByTxHashToHyperCore(
txHash: string,
originChainId: number
): ReturnType<typeof getDepositByTxHash> {
if (originChainId !== ChainId.HYPEREVM) {
throw new Error(
`Could not fetch HyperEVM deposit for origin chain ${originChainId}`
);
}
const destinationChainId = ChainId.HYPERCORE;

const provider = getProvider(originChainId);
const depositTxReceipt = await provider.getTransactionReceipt(txHash);
if (!depositTxReceipt) {
throw new Error(
`Could not fetch tx receipt for ${txHash} on chain ${originChainId}`
);
}

const block = await provider.getBlock(depositTxReceipt.blockNumber);

if (depositTxReceipt.status === 0) {
return {
depositTxReceipt,
parsedDepositLog: undefined,
depositTimestamp: block.timestamp,
};
}

const transferLog = parseTransferLog(depositTxReceipt.logs);
if (!transferLog) {
throw new Error(`Could not parse 'Transfer' log for ${txHash}`);
}

// Convert HyperEVM deposit log to Across deposit log
const parsedDepositLog: DepositData = {
depositId: BigNumber.from(0),
originChainId,
destinationChainId,
depositor: toAddressType(transferLog.args.from, originChainId),
// HyperCore recipient must be the HyperEVM depositor in initial Swap API support
recipient: toAddressType(transferLog.args.from, destinationChainId),
exclusiveRelayer: toAddressType(
ethers.constants.AddressZero,
destinationChainId
),
inputToken: toAddressType(transferLog.address, originChainId),
// HyperCore output token is the system address on HyperEVM, i.e. to address of Transfer log
outputToken: toAddressType(transferLog.args.to, destinationChainId),
inputAmount: transferLog.args.value,
outputAmount: transferLog.args.value,
quoteTimestamp: block.timestamp,
fillDeadline: block.timestamp,
depositTimestamp: block.timestamp,
messageHash: "0x",
message: "0x",
exclusivityDeadline: 0,
blockNumber: depositTxReceipt.blockNumber,
txnIndex: depositTxReceipt.transactionIndex,
logIndex: transferLog.logIndex,
txnRef: depositTxReceipt.transactionHash,
};

return {
depositTxReceipt,
parsedDepositLog,
depositTimestamp: block.timestamp,
};
}

export async function getFillForDepositToHyperCore(
depositOnHyperEVM: DepositedInfo
): Promise<FillInfo> {
// TODO: Use HyperCore API to check if bridge succeeded
return {
fillTxHash: depositOnHyperEVM.depositLog.txnRef,
fillTxTimestamp: depositOnHyperEVM.depositTimestamp,
depositInfo: depositOnHyperEVM,
status: "filled",
fillLog: {
...depositOnHyperEVM.depositLog,
fillTimestamp: depositOnHyperEVM.depositTimestamp,
relayer: depositOnHyperEVM.depositLog.depositor,
repaymentChainId: depositOnHyperEVM.depositLog.originChainId,
relayExecutionInfo: {
updatedRecipient: depositOnHyperEVM.depositLog.recipient,
updatedOutputAmount: depositOnHyperEVM.depositLog.outputAmount,
updatedMessageHash: depositOnHyperEVM.depositLog.messageHash,
fillType: 0, // FastFill
},
},
};
}

function parseTransferLog(
logs: Array<{
topics: string[];
data: string;
}>
) {
const erc20Iface = ERC20__factory.createInterface();
const parsedLogs = logs.flatMap((log) => {
try {
console.log("log", log);
const parsedLog = erc20Iface.parseLog(log);
return parsedLog.name === "Transfer"
? {
...log,
...parsedLog,
}
: [];
} catch (e) {
return [];
}
});
return parsedLogs.find(({ name }) => name === "Transfer") as unknown as
| TransferEvent
| undefined;
}
7 changes: 6 additions & 1 deletion src/utils/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function balanceQueryKey(
* @param amount The amount to check bridge fees for.
* @param fromChainId The origin chain of this bridge action
* @param toChainId The destination chain of this bridge action
* @param bridgeProvider The bridge provider to check bridge fees for.
* @param externalProjectId The external project id to check bridge fees for.
* @param recipientAddress The recipient address to check bridge fees for.
* @returns An array of query keys for @tanstack/react-query `useQuery` hook.
Expand All @@ -42,6 +43,7 @@ export function bridgeFeesQueryKey(
outputToken: string,
fromChainId: ChainId,
toChainId: ChainId,
bridgeProvider: string,
externalProjectId?: string,
recipientAddress?: string
) {
Expand All @@ -52,6 +54,7 @@ export function bridgeFeesQueryKey(
amount.toString(),
fromChainId,
toChainId,
bridgeProvider,
externalProjectId,
recipientAddress,
] as const;
Expand All @@ -61,14 +64,16 @@ export function bridgeLimitsQueryKey(
inputToken?: string,
outputToken?: string,
fromChainId?: ChainId,
toChainId?: ChainId
toChainId?: ChainId,
bridgeProvider?: string
) {
return [
"bridgeLimits",
inputToken,
outputToken,
fromChainId,
toChainId,
bridgeProvider,
] as const;
}

Expand Down
1 change: 1 addition & 0 deletions src/utils/serverless-api/mocked/swap-approval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export async function swapApprovalApiCall(
steps: {
originSwap: undefined,
bridge: {
provider: "across",
inputAmount: BigNumber.from("0"),
outputAmount: BigNumber.from("0"),
tokenIn: {
Expand Down
12 changes: 12 additions & 0 deletions src/utils/serverless-api/prod/swap-approval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export type SwapApprovalApiResponse = {
}[];
steps: {
originSwap?: {
provider: {
name: string;
sources: string[];
};
tokenIn: SwapApiToken;
tokenOut: SwapApiToken;
inputAmount: string;
Expand All @@ -43,6 +47,7 @@ export type SwapApprovalApiResponse = {
maxInputAmount: string;
};
bridge: {
provider: string;
inputAmount: string;
outputAmount: string;
tokenIn: SwapApiToken;
Expand All @@ -67,6 +72,10 @@ export type SwapApprovalApiResponse = {
};
};
destinationSwap?: {
provider: {
name: string;
sources: string[];
};
tokenIn: SwapApiToken;
tokenOut: SwapApiToken;
inputAmount: string;
Expand Down Expand Up @@ -138,6 +147,7 @@ export async function swapApprovalApiCall(params: SwapApprovalApiQueryParams) {
steps: {
originSwap: result.steps.originSwap
? {
provider: result.steps.originSwap.provider,
tokenIn: result.steps.originSwap.tokenIn,
tokenOut: result.steps.originSwap.tokenOut,
inputAmount: BigNumber.from(result.steps.originSwap.inputAmount),
Expand All @@ -151,6 +161,7 @@ export async function swapApprovalApiCall(params: SwapApprovalApiQueryParams) {
}
: undefined,
bridge: {
provider: result.steps.bridge.provider || "across",
inputAmount: BigNumber.from(result.steps.bridge.inputAmount),
outputAmount: BigNumber.from(result.steps.bridge.outputAmount),
tokenIn: result.steps.bridge.tokenIn,
Expand Down Expand Up @@ -178,6 +189,7 @@ export async function swapApprovalApiCall(params: SwapApprovalApiQueryParams) {
},
destinationSwap: result.steps.destinationSwap
? {
provider: result.steps.destinationSwap.provider,
tokenIn: result.steps.destinationSwap.tokenIn,
tokenOut: result.steps.destinationSwap.tokenOut,
inputAmount: BigNumber.from(
Expand Down
1 change: 1 addition & 0 deletions src/utils/typechain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export type {
TypedEvent,
TypedEventFilter,
} from "@across-protocol/contracts/dist/typechain/common";
export type { TransferEvent } from "@across-protocol/contracts/dist/typechain/@openzeppelin/contracts/token/ERC20/ERC20";
Loading