Skip to content

feat: gasless swap flows #1497

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

Draft
wants to merge 67 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
edd3518
feat: /swap handler skeleton
dohaki Nov 7, 2024
9c39be4
feat: uniswap v3 exact output support + UniversalSwapAndBridge (#1268)
dohaki Nov 10, 2024
1e36933
feat: uniswap min output cross swap quotes resolver (#1271)
dohaki Nov 14, 2024
19c0389
get token details for any address in any chainId (#1272)
gsteenkamp89 Nov 15, 2024
b245827
feat: build cross swap tx (#1274)
dohaki Nov 20, 2024
b585972
feat: support native eth as input/output (#1279)
dohaki Nov 21, 2024
3efbc6a
Feat/support arb tokens on coingecko endpoint (#1278)
gsteenkamp89 Nov 22, 2024
fc83780
feat: add allowance and balance check (#1281)
dohaki Nov 22, 2024
60313fd
fix: allowance and balance check
dohaki Nov 22, 2024
334a245
fixup
dohaki Nov 22, 2024
67657dc
Merge branch 'master' into swap-endpoint
dohaki Nov 22, 2024
a0a2e41
fixup
dohaki Nov 22, 2024
2d8627e
feat: add missing response fields
dohaki Nov 22, 2024
f064696
fixup
dohaki Nov 22, 2024
6d8d45a
feat: skip spokepool verifier temp (#1286)
dohaki Nov 22, 2024
11f2640
feat: add more swap and bridge addresses
dohaki Nov 22, 2024
81dee5a
feat: mainnet
dohaki Nov 22, 2024
1addbd7
fixup
dohaki Nov 22, 2024
91c4a56
feat: refund on origin flag (#1284)
dohaki Nov 23, 2024
e9b902a
fix: native eth handling in allowance + balance check
dohaki Nov 23, 2024
8ca1658
fix: amount adjustments
dohaki Nov 23, 2024
0357a38
feat: add approvalTxns to response
dohaki Nov 23, 2024
bd9e370
test: improve test script
dohaki Nov 23, 2024
820d472
feat: support old `SwapAndBridge` and new periphery (#1287)
dohaki Nov 25, 2024
7167488
fix: origin swap recipient address
dohaki Nov 26, 2024
c022818
fix: adjust buffer
dohaki Nov 26, 2024
e5a0353
Gerhard/acx 3321 calculate fee components (#1285)
gsteenkamp89 Nov 27, 2024
129aadd
feat: use trading api + support universal router (#1294)
dohaki Nov 29, 2024
cf83060
Merge branch 'master' into swap-endpoint
gsteenkamp89 Dec 3, 2024
bdd60f1
measure swap/allowance performance (#1301)
gsteenkamp89 Dec 4, 2024
5101979
feat: support `UniversalSwapAndBridge` in swap endpoint (#1311)
dohaki Dec 16, 2024
5eebd02
chore: add swap quotes to response
dohaki Dec 17, 2024
0a14853
perf: bridge quote for min output
dohaki Dec 17, 2024
83dc8c9
chore: improve profiling
dohaki Dec 17, 2024
a0afc7e
fix: bridge quote min output
dohaki Dec 17, 2024
28b6648
chore: allow vercel endpoint override
dohaki Dec 17, 2024
1383980
perf: use indicative quotes as guesstimates (#1337)
dohaki Dec 20, 2024
bf1f33e
feat: support permit swap (#1288)
dohaki Dec 20, 2024
d043df0
feat: base handler for `POST /relay` (#1345)
dohaki Dec 28, 2024
3e650d2
feat: gelato and local signers (#1346)
dohaki Dec 30, 2024
05d05f8
fix: update zksync periphery addresses
dohaki Dec 30, 2024
1d1399c
fix: stringifyBigNumProps
dohaki Jan 3, 2025
463573b
fix: gelato status error
dohaki Jan 3, 2025
87a0f44
support transfer with auth (#1341)
gsteenkamp89 Jan 6, 2025
73b19f8
feat: unified swap endpoint (#1353)
dohaki Jan 6, 2025
8593c50
refactor: cleanup + align swap response formats (#1355)
dohaki Jan 7, 2025
b6d634f
fixup (#1359)
dohaki Jan 8, 2025
3813cd3
Merge branch 'master' into swap-endpoint
dohaki Jan 15, 2025
e3acd1c
fixup
dohaki Jan 15, 2025
9f02d5d
feat: add stablecoins for a2a bridging (#1373)
dohaki Jan 15, 2025
058d5ed
Merge branch 'master' into swap-endpoint
dohaki Feb 10, 2025
0402826
lint fix
dohaki Feb 10, 2025
4b69afc
Merge branch 'master' into swap-endpoint
dohaki Mar 3, 2025
70cb68c
Merge branch 'master' into swap-endpoint
dohaki Mar 12, 2025
28fc3f6
disable relay + permit-based flows
dohaki Mar 12, 2025
8f281cf
delete
dohaki Mar 12, 2025
6fcdf2f
Revert "delete"
dohaki Mar 12, 2025
58a079b
fixup
dohaki Mar 12, 2025
24f613a
revert
dohaki Mar 12, 2025
7f6dce0
fixup
dohaki Mar 12, 2025
c74fb8b
Merge branch 'only-approval-swap-flow' into permit-auth-relay
dohaki Mar 12, 2025
146e89e
Merge branch 'master' into permit-auth-relay
dohaki Apr 7, 2025
a207dd2
Merge branch 'master' into permit-auth-relay
dohaki Jun 5, 2025
efbee0f
fixup
dohaki Jun 5, 2025
2aed7f4
fix: set local signers as default
dohaki Jun 5, 2025
53927b1
fixup
dohaki Jun 5, 2025
685bc1e
fixup
dohaki Jun 6, 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
252 changes: 252 additions & 0 deletions api/_dexes/uniswap/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import { MixedRouteSDK, Trade as RouterTrade } from "@uniswap/router-sdk";
import {
Currency,
CurrencyAmount,
Ether,
Token,
TradeType,
} from "@uniswap/sdk-core";
import { Pair, Route as V2Route } from "@uniswap/v2-sdk";
import { Pool, Route as V3Route, FeeAmount } from "@uniswap/v3-sdk";
import { BigNumber } from "ethers";

export type TokenInRoute = {
address: string;
chainId: number;
symbol: string;
decimals: string;
name?: string;
buyFeeBps?: string;
sellFeeBps?: string;
};

export enum PoolType {
V2Pool = "v2-pool",
V3Pool = "v3-pool",
V4Pool = "v4-pool",
}

export type V2Reserve = {
token: TokenInRoute;
quotient: string;
};

export type V2PoolInRoute = {
type: PoolType.V2Pool;
address?: string;
tokenIn: TokenInRoute;
tokenOut: TokenInRoute;
reserve0: V2Reserve;
reserve1: V2Reserve;
amountIn?: string;
amountOut?: string;
};

export type V3PoolInRoute = {
type: PoolType.V3Pool;
address?: string;
tokenIn: TokenInRoute;
tokenOut: TokenInRoute;
sqrtRatioX96: string;
liquidity: string;
tickCurrent: string;
fee: string;
amountIn?: string;
amountOut?: string;
};

export type PartialClassicQuote = {
// We need tokenIn/Out to support native currency
tokenIn: string;
tokenOut: string;
tradeType: TradeType;
route: Array<(V3PoolInRoute | V2PoolInRoute)[]>;
};

interface RouteResult {
routev3: V3Route<Currency, Currency> | null;
routev2: V2Route<Currency, Currency> | null;
mixedRoute: MixedRouteSDK<Currency, Currency> | null;
inputAmount: CurrencyAmount<Currency>;
outputAmount: CurrencyAmount<Currency>;
}

// Helper class to convert routing-specific quote entities to RouterTrade entities
// the returned RouterTrade can then be used to build the UniswapTrade entity in this package
export class RouterTradeAdapter {
// Generate a RouterTrade using fields from a classic quote response
static fromClassicQuote(quote: PartialClassicQuote) {
const { route } = quote;

if (!route) throw new Error("Expected route to be present");
if (!route.length)
throw new Error("Expected there to be at least one route");
if (route.some((r) => !r.length))
throw new Error("Expected all routes to have at least one pool");
const firstRoute = route[0];

const tokenInData = firstRoute[0].tokenIn;
const tokenOutData = firstRoute[firstRoute.length - 1].tokenOut;

if (!tokenInData || !tokenOutData)
throw new Error("Expected both tokenIn and tokenOut to be present");
if (tokenInData.chainId !== tokenOutData.chainId)
throw new Error("Expected tokenIn and tokenOut to be have same chainId");

const parsedCurrencyIn = RouterTradeAdapter.toCurrency(false, tokenInData);
const parsedCurrencyOut = RouterTradeAdapter.toCurrency(
false,
tokenOutData
);

const typedRoutes: RouteResult[] = route.map((subRoute) => {
const rawAmountIn = subRoute[0].amountIn;
const rawAmountOut = subRoute[subRoute.length - 1].amountOut;

if (!rawAmountIn || !rawAmountOut) {
throw new Error(
"Expected both raw amountIn and raw amountOut to be present"
);
}

const inputAmount = CurrencyAmount.fromRawAmount(
parsedCurrencyIn,
rawAmountIn
);
const outputAmount = CurrencyAmount.fromRawAmount(
parsedCurrencyOut,
rawAmountOut
);

const isOnlyV2 = RouterTradeAdapter.isVersionedRoute<V2PoolInRoute>(
PoolType.V2Pool,
subRoute
);
const isOnlyV3 = RouterTradeAdapter.isVersionedRoute<V3PoolInRoute>(
PoolType.V3Pool,
subRoute
);

return {
routev3: isOnlyV3
? new V3Route(
(subRoute as V3PoolInRoute[]).map(RouterTradeAdapter.toPool),
parsedCurrencyIn,
parsedCurrencyOut
)
: null,
routev2: isOnlyV2
? new V2Route(
(subRoute as V2PoolInRoute[]).map(RouterTradeAdapter.toPair),
parsedCurrencyIn,
parsedCurrencyOut
)
: null,
mixedRoute:
!isOnlyV3 && !isOnlyV2
? new MixedRouteSDK(
subRoute.map(RouterTradeAdapter.toPoolOrPair),
parsedCurrencyIn,
parsedCurrencyOut
)
: null,
inputAmount,
outputAmount,
};
});

return new RouterTrade({
v2Routes: typedRoutes
.filter((route) => route.routev2)
.map((route) => ({
routev2: route.routev2 as V2Route<Currency, Currency>,
inputAmount: route.inputAmount,
outputAmount: route.outputAmount,
})),
v3Routes: typedRoutes
.filter((route) => route.routev3)
.map((route) => ({
routev3: route.routev3 as V3Route<Currency, Currency>,
inputAmount: route.inputAmount,
outputAmount: route.outputAmount,
})),
// TODO: ROUTE-219 - Support v4 trade in universal-router sdk
v4Routes: [],
mixedRoutes: typedRoutes
.filter((route) => route.mixedRoute)
.map((route) => ({
mixedRoute: route.mixedRoute as MixedRouteSDK<Currency, Currency>,
inputAmount: route.inputAmount,
outputAmount: route.outputAmount,
})),
tradeType: quote.tradeType,
});
}

private static toCurrency(isNative: boolean, token: TokenInRoute): Currency {
if (isNative) {
return Ether.onChain(token.chainId);
}
return this.toToken(token);
}

private static toPoolOrPair = (
pool: V3PoolInRoute | V2PoolInRoute
): Pool | Pair => {
return pool.type === PoolType.V3Pool
? RouterTradeAdapter.toPool(pool)
: RouterTradeAdapter.toPair(pool);
};

private static toToken(token: TokenInRoute): Token {
const { chainId, address, decimals, symbol, buyFeeBps, sellFeeBps } = token;
return new Token(
chainId,
address,
parseInt(decimals.toString()),
symbol,
/* name */ undefined,
false,
buyFeeBps ? BigNumber.from(buyFeeBps) : undefined,
sellFeeBps ? BigNumber.from(sellFeeBps) : undefined
);
}

private static toPool({
fee,
sqrtRatioX96,
liquidity,
tickCurrent,
tokenIn,
tokenOut,
}: V3PoolInRoute): Pool {
return new Pool(
RouterTradeAdapter.toToken(tokenIn),
RouterTradeAdapter.toToken(tokenOut),
parseInt(fee) as FeeAmount,
sqrtRatioX96,
liquidity,
parseInt(tickCurrent)
);
}

private static toPair = ({ reserve0, reserve1 }: V2PoolInRoute): Pair => {
return new Pair(
CurrencyAmount.fromRawAmount(
RouterTradeAdapter.toToken(reserve0.token),
reserve0.quotient
),
CurrencyAmount.fromRawAmount(
RouterTradeAdapter.toToken(reserve1.token),
reserve1.quotient
)
);
};

private static isVersionedRoute<T extends V2PoolInRoute | V3PoolInRoute>(
type: PoolType,
route: (V3PoolInRoute | V2PoolInRoute)[]
): route is T[] {
return route.every((pool) => pool.type === type);
}
}
Loading
Loading