Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: build cross swap tx #1274

Merged
merged 12 commits into from
Nov 20, 2024
Merged
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
105 changes: 104 additions & 1 deletion api/_dexes/cross-swap.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { SpokePool } from "@across-protocol/contracts/dist/typechain";

import {
isRouteEnabled,
isInputTokenBridgeable,
isOutputTokenBridgeable,
getBridgeQuoteForMinOutput,
getSpokePool,
latestGasPriceCache,
} from "../_utils";
import {
getUniswapCrossSwapQuotesForMinOutputB2A,
getUniswapCrossSwapQuotesForMinOutputA2B,
getBestUniswapCrossSwapQuotesForMinOutputA2A,
} from "./uniswap";
import { CrossSwap, CrossSwapQuotes } from "./types";
import { getSwapAndBridge } from "./utils";
import { tagIntegratorId } from "../_integrator-id";
import { PopulatedTransaction } from "ethers";
import { getMultiCallHandlerAddress } from "../_multicall-handler";

export type CrossSwapType =
(typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE];

export type AmountType = (typeof AMOUNT_TYPE)[keyof typeof AMOUNT_TYPE];

export type LeftoverType = (typeof LEFTOVER_TYPE)[keyof typeof LEFTOVER_TYPE];

export const AMOUNT_TYPE = {
EXACT_INPUT: "exactInput",
MIN_OUTPUT: "minOutput",
Expand All @@ -28,6 +38,11 @@
ANY_TO_ANY: "anyToAny",
} as const;

export const LEFTOVER_TYPE = {
OUTPUT_TOKEN: "outputToken",
BRIDGEABLE_TOKEN: "bridgeableToken",
} as const;

export const PREFERRED_BRIDGE_TOKENS = ["WETH", "USDC"];

export async function getCrossSwapQuotes(
Expand Down Expand Up @@ -73,7 +88,7 @@
}

// @TODO: Implement the following function
export async function getCrossSwapQuotesForExactInput(crossSwap: CrossSwap) {

Check warning on line 91 in api/_dexes/cross-swap.ts

View workflow job for this annotation

GitHub Actions / format-and-lint

'crossSwap' is defined but never used. Allowed unused args must match /^_/u
throw new Error("Not implemented yet");
}

Expand Down Expand Up @@ -139,4 +154,92 @@
return CROSS_SWAP_TYPE.ANY_TO_ANY;
}

export function calcFees() {}
export async function buildCrossSwapTx(
crossSwapQuotes: CrossSwapQuotes,
integratorId?: string
) {
const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId;
const destinationChainId = crossSwapQuotes.crossSwap.outputToken.chainId;
const spokePool = getSpokePool(originChainId);
const deposit = {
depositor: crossSwapQuotes.crossSwap.depositor,
recipient: crossSwapQuotes.destinationSwapQuote
? getMultiCallHandlerAddress(destinationChainId)
: crossSwapQuotes.crossSwap.recipient,
inputToken: crossSwapQuotes.bridgeQuote.inputToken.address,
outputToken: crossSwapQuotes.bridgeQuote.outputToken.address,
Copy link
Contributor

Choose a reason for hiding this comment

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

In the case of outputToken bridged USDC on a CCTP chain, would we short-circuit the destination swap?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep, we do the check here https://github.com/across-protocol/frontend-v2/blob/8ac1e952dee670245ffd52501e469f6b6b5e2c49/api/_dexes/cross-swap.ts#L129

and if it's a normal bridge route we don't populate a destination swap quote

inputAmount: crossSwapQuotes.bridgeQuote.inputAmount,
outputAmount: crossSwapQuotes.bridgeQuote.outputAmount,
destinationChainid: crossSwapQuotes.bridgeQuote.outputToken.chainId,
exclusiveRelayer:
crossSwapQuotes.bridgeQuote.suggestedFees.exclusiveRelayer,
quoteTimestamp: crossSwapQuotes.bridgeQuote.suggestedFees.timestamp,
fillDeadline: await getFillDeadline(spokePool),
exclusivityDeadline:
crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline,
message: crossSwapQuotes.bridgeQuote.message || "0x",
};

let tx: PopulatedTransaction;
let toAddress: string;

if (crossSwapQuotes.originSwapQuote) {
const swapAndBridge = getSwapAndBridge("uniswap", originChainId);
tx = await swapAndBridge.populateTransaction.swapAndBridge(
crossSwapQuotes.originSwapQuote.tokenIn.address,
crossSwapQuotes.originSwapQuote.tokenOut.address,
crossSwapQuotes.originSwapQuote.swapTx.data,
crossSwapQuotes.originSwapQuote.maximumAmountIn,
crossSwapQuotes.originSwapQuote.minAmountOut,
deposit
);
toAddress = swapAndBridge.address;
} else {
const spokePool = getSpokePool(
crossSwapQuotes.crossSwap.inputToken.chainId
);
tx = await spokePool.populateTransaction.depositV3(
deposit.depositor,
deposit.recipient,
deposit.inputToken,
deposit.outputToken,
deposit.inputAmount,
deposit.outputAmount,
deposit.destinationChainid,
deposit.exclusiveRelayer,
deposit.quoteTimestamp,
deposit.fillDeadline,
deposit.exclusivityDeadline,
deposit.message
);
toAddress = spokePool.address;
}

const [gas, gasPrice] = await Promise.all([
spokePool.provider.estimateGas({
from: crossSwapQuotes.crossSwap.depositor,
...tx,
}),
latestGasPriceCache(originChainId).get(),
]);

return {
from: crossSwapQuotes.crossSwap.depositor,
to: toAddress,
data: integratorId ? tagIntegratorId(integratorId, tx.data!) : tx.data,
gas,
gasPrice,
value: tx.value,
};
}

async function getFillDeadline(spokePool: SpokePool): Promise<number> {
const calls = [
spokePool.interface.encodeFunctionData("getCurrentTime"),
spokePool.interface.encodeFunctionData("fillDeadlineBuffer"),
];

const [currentTime, fillDeadlineBuffer] =
await spokePool.callStatic.multicall(calls);
return Number(currentTime) + Number(fillDeadlineBuffer);
}
Comment on lines +236 to +245
Copy link
Contributor

Choose a reason for hiding this comment

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

This also exists in src/utils/bridge.ts - is there anywhere common for generic utilities to be placed here, or do we have to upstream that to the sdk?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, we are currently in a weird state with API and FE source code... We can not share unfortunately because our current folder structure is not optimized for sharing utilities between API and FE without blowing up the bundle size.

So upstreaming to the SDK would prob make sense

5 changes: 4 additions & 1 deletion api/_dexes/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber } from "ethers";
import { getSuggestedFees } from "../_utils";
import { AmountType, CrossSwapType } from "./cross-swap";
import { AmountType, CrossSwapType, LeftoverType } from "./cross-swap";

export type { AmountType, CrossSwapType };

Expand All @@ -19,15 +19,18 @@ export type Swap = {
recipient: string;
slippageTolerance: number;
type: AmountType;
leftoverType?: LeftoverType;
};

export type CrossSwap = {
amount: BigNumber;
inputToken: Token;
outputToken: Token;
depositor: string;
recipient: string;
slippageTolerance: number;
type: AmountType;
leftoverType?: LeftoverType;
refundOnOrigin: boolean;
refundAddress?: string;
};
Expand Down
Loading
Loading