@marko00/routing-finder-mare 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/copilot.data.migration.agent.xml +6 -0
- package/.idea/copilot.data.migration.ask.xml +6 -0
- package/.idea/copilot.data.migration.ask2agent.xml +6 -0
- package/.idea/copilot.data.migration.edit.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/php.xml +19 -0
- package/.idea/ratex-sdk.iml +8 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE-MIT +21 -0
- package/README.md +209 -0
- package/contracts/abi/BalancerHelperAbi.ts +1 -0
- package/contracts/abi/CamelotHelperAbi.ts +1 -0
- package/contracts/abi/RateXAbi.ts +482 -0
- package/contracts/abi/SushiSwapHelperAbi.ts +1 -0
- package/contracts/abi/UniswapHelperAbi.ts +1 -0
- package/contracts/abi/UniswapV2HelperAbi.ts +1 -0
- package/contracts/addresses-arbitrum.ts +8 -0
- package/contracts/addresses-mainnet.ts +7 -0
- package/contracts/addresses-polkadot.ts +9 -0
- package/contracts/addresses-sei.ts +9 -0
- package/contracts/rateX/BalancerHelper.ts +13 -0
- package/contracts/rateX/CamelotHelper.ts +13 -0
- package/contracts/rateX/SushiSwapHelper.ts +13 -0
- package/contracts/rateX/UniswapHelper.ts +19 -0
- package/contracts/rateX/UniswapV2Helper.ts +19 -0
- package/dexes/dexIdsList.ts +9 -0
- package/dexes/graph_queries/BalancerV2.ts +150 -0
- package/dexes/graph_queries/CamelotV2.ts +202 -0
- package/dexes/graph_queries/SushiSwapV2.ts +283 -0
- package/dexes/graph_queries/UniswapV2.ts +252 -0
- package/dexes/graph_queries/UniswapV3.ts +229 -0
- package/dexes/graph_queries/graphQueryFilters.ts +41 -0
- package/dexes/graph_queries/x_template.ts +67 -0
- package/dexes/pools/Balancer/BalancerState.ts +34 -0
- package/dexes/pools/Balancer/BalancerWeightedPool.ts +96 -0
- package/dexes/pools/Camelot.ts +164 -0
- package/dexes/pools/SushiSwapV2.ts +35 -0
- package/dexes/pools/UniswapV2.ts +36 -0
- package/dexes/pools/uniswap/UniswapV3.ts +40 -0
- package/dexes/pools/uniswap/testUniswapOffchainQuoter.ts +169 -0
- package/dexes/pools/uniswap/types.ts +174 -0
- package/dexes/pools/uniswap/uniswapOffchainQuoter.ts +173 -0
- package/dexes/pools/uniswap/uniswapState.ts +56 -0
- package/dexes/pools/uniswap/utils.ts +71 -0
- package/dist/contracts/abi/BalancerHelperAbi.d.ts +25 -0
- package/dist/contracts/abi/BalancerHelperAbi.js +4 -0
- package/dist/contracts/abi/CamelotHelperAbi.d.ts +45 -0
- package/dist/contracts/abi/CamelotHelperAbi.js +4 -0
- package/dist/contracts/abi/RateXAbi.d.ts +71 -0
- package/dist/contracts/abi/RateXAbi.js +485 -0
- package/dist/contracts/abi/SushiSwapHelperAbi.d.ts +45 -0
- package/dist/contracts/abi/SushiSwapHelperAbi.js +4 -0
- package/dist/contracts/abi/UniswapHelperAbi.d.ts +39 -0
- package/dist/contracts/abi/UniswapHelperAbi.js +4 -0
- package/dist/contracts/abi/UniswapV2HelperAbi.d.ts +45 -0
- package/dist/contracts/abi/UniswapV2HelperAbi.js +4 -0
- package/dist/contracts/addresses-arbitrum.d.ts +6 -0
- package/dist/contracts/addresses-arbitrum.js +10 -0
- package/dist/contracts/addresses-mainnet.d.ts +6 -0
- package/dist/contracts/addresses-mainnet.js +10 -0
- package/dist/contracts/addresses-polkadot.d.ts +6 -0
- package/dist/contracts/addresses-polkadot.js +10 -0
- package/dist/contracts/addresses-sei.d.ts +6 -0
- package/dist/contracts/addresses-sei.js +10 -0
- package/dist/contracts/rateX/BalancerHelper.d.ts +26 -0
- package/dist/contracts/rateX/BalancerHelper.js +14 -0
- package/dist/contracts/rateX/CamelotHelper.d.ts +46 -0
- package/dist/contracts/rateX/CamelotHelper.js +14 -0
- package/dist/contracts/rateX/SushiSwapHelper.d.ts +46 -0
- package/dist/contracts/rateX/SushiSwapHelper.js +14 -0
- package/dist/contracts/rateX/UniswapHelper.d.ts +40 -0
- package/dist/contracts/rateX/UniswapHelper.js +22 -0
- package/dist/contracts/rateX/UniswapV2Helper.d.ts +46 -0
- package/dist/contracts/rateX/UniswapV2Helper.js +22 -0
- package/dist/dexes/dexIdsList.d.ts +9 -0
- package/dist/dexes/dexIdsList.js +12 -0
- package/dist/dexes/graph_queries/BalancerV2.d.ts +14 -0
- package/dist/dexes/graph_queries/BalancerV2.js +141 -0
- package/dist/dexes/graph_queries/CamelotV2.d.ts +14 -0
- package/dist/dexes/graph_queries/CamelotV2.js +183 -0
- package/dist/dexes/graph_queries/SushiSwapV2.d.ts +14 -0
- package/dist/dexes/graph_queries/SushiSwapV2.js +263 -0
- package/dist/dexes/graph_queries/UniswapV2.d.ts +14 -0
- package/dist/dexes/graph_queries/UniswapV2.js +217 -0
- package/dist/dexes/graph_queries/UniswapV3.d.ts +14 -0
- package/dist/dexes/graph_queries/UniswapV3.js +198 -0
- package/dist/dexes/graph_queries/graphQueryFilters.d.ts +19 -0
- package/dist/dexes/graph_queries/graphQueryFilters.js +40 -0
- package/dist/dexes/graph_queries/x_template.d.ts +12 -0
- package/dist/dexes/graph_queries/x_template.js +57 -0
- package/dist/dexes/pools/Balancer/BalancerState.d.ts +6 -0
- package/dist/dexes/pools/Balancer/BalancerState.js +32 -0
- package/dist/dexes/pools/Balancer/BalancerWeightedPool.d.ts +12 -0
- package/dist/dexes/pools/Balancer/BalancerWeightedPool.js +109 -0
- package/dist/dexes/pools/Camelot.d.ts +12 -0
- package/dist/dexes/pools/Camelot.js +135 -0
- package/dist/dexes/pools/SushiSwapV2.d.ts +9 -0
- package/dist/dexes/pools/SushiSwapV2.js +34 -0
- package/dist/dexes/pools/UniswapV2.d.ts +9 -0
- package/dist/dexes/pools/UniswapV2.js +34 -0
- package/dist/dexes/pools/uniswap/UniswapV3.d.ts +7 -0
- package/dist/dexes/pools/uniswap/UniswapV3.js +36 -0
- package/dist/dexes/pools/uniswap/types.d.ts +76 -0
- package/dist/dexes/pools/uniswap/types.js +111 -0
- package/dist/dexes/pools/uniswap/uniswapOffchainQuoter.d.ts +13 -0
- package/dist/dexes/pools/uniswap/uniswapOffchainQuoter.js +121 -0
- package/dist/dexes/pools/uniswap/uniswapState.d.ts +14 -0
- package/dist/dexes/pools/uniswap/uniswapState.js +51 -0
- package/dist/dexes/pools/uniswap/utils.d.ts +3 -0
- package/dist/dexes/pools/uniswap/utils.js +41 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +41 -0
- package/dist/routes.d.ts +1 -0
- package/dist/routes.js +20 -0
- package/dist/routing/iterative_spliting/main.d.ts +3 -0
- package/dist/routing/iterative_spliting/main.js +104 -0
- package/dist/routing/iterative_spliting/multiHopSwap.d.ts +4 -0
- package/dist/routing/iterative_spliting/multiHopSwap.js +83 -0
- package/dist/routing/main.d.ts +2 -0
- package/dist/routing/main.js +22 -0
- package/dist/routing/uni_like_algo/algo_config.d.ts +2 -0
- package/dist/routing/uni_like_algo/algo_config.js +8 -0
- package/dist/routing/uni_like_algo/amount_distribution.d.ts +2 -0
- package/dist/routing/uni_like_algo/amount_distribution.js +17 -0
- package/dist/routing/uni_like_algo/compute_routes_backtrack.d.ts +3 -0
- package/dist/routing/uni_like_algo/compute_routes_backtrack.js +44 -0
- package/dist/routing/uni_like_algo/main.d.ts +2 -0
- package/dist/routing/uni_like_algo/main.js +49 -0
- package/dist/routing/uni_like_algo/routes_quoter.d.ts +21 -0
- package/dist/routing/uni_like_algo/routes_quoter.js +53 -0
- package/dist/routing/uni_like_algo/swap_finder.d.ts +25 -0
- package/dist/routing/uni_like_algo/swap_finder.js +154 -0
- package/dist/routing/uni_like_algo/types.d.ts +40 -0
- package/dist/routing/uni_like_algo/types.js +12 -0
- package/dist/swap/graph_communication.d.ts +5 -0
- package/dist/swap/graph_communication.js +187 -0
- package/dist/swap/my_local_storage.d.ts +8 -0
- package/dist/swap/my_local_storage.js +16 -0
- package/dist/utils/addresses.d.ts +24 -0
- package/dist/utils/addresses.js +60 -0
- package/dist/utils/math/fixed-points.d.ts +14 -0
- package/dist/utils/math/fixed-points.js +123 -0
- package/dist/utils/math/log-exp.d.ts +5 -0
- package/dist/utils/math/log-exp.js +385 -0
- package/dist/utils/math/math.d.ts +12 -0
- package/dist/utils/math/math.js +50 -0
- package/dist/utils/types/types.d.ts +51 -0
- package/dist/utils/types/types.js +25 -0
- package/dist/utils/utils.d.ts +20 -0
- package/dist/utils/utils.js +72 -0
- package/images/decenter_logo.png +0 -0
- package/index.ts +50 -0
- package/package.json +39 -0
- package/routes.ts +27 -0
- package/routing/iterative_spliting/main.ts +131 -0
- package/routing/iterative_spliting/multiHopSwap.ts +98 -0
- package/routing/main.ts +22 -0
- package/routing/uni_like_algo/algo_config.ts +7 -0
- package/routing/uni_like_algo/amount_distribution.ts +16 -0
- package/routing/uni_like_algo/compute_routes_backtrack.ts +81 -0
- package/routing/uni_like_algo/main.ts +65 -0
- package/routing/uni_like_algo/routes_quoter.ts +63 -0
- package/routing/uni_like_algo/swap_finder.ts +185 -0
- package/routing/uni_like_algo/types.ts +54 -0
- package/swap/graph_communication.ts +212 -0
- package/swap/my_local_storage.ts +27 -0
- package/tsconfig.json +26 -0
- package/utils/addresses.ts +64 -0
- package/utils/math/fixed-points.ts +88 -0
- package/utils/math/log-exp.ts +469 -0
- package/utils/math/math.ts +46 -0
- package/utils/types/types.ts +100 -0
- package/utils/utils.ts +125 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@marko00/routing-finder-mare",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@graphql-typed-document-node/core": "^3.2.0",
|
|
8
|
+
"@uniswap/sdk-core": "^4.0.6",
|
|
9
|
+
"@uniswap/v3-sdk": "^3.10.0",
|
|
10
|
+
"bignumber.js": "^9.1.2",
|
|
11
|
+
"crypto": "^1.0.1",
|
|
12
|
+
"ethers": "^6.7.0",
|
|
13
|
+
"graphql": "^16.7.1",
|
|
14
|
+
"graphql-request": "^6.1.0",
|
|
15
|
+
"js-sha256": "^0.10.1",
|
|
16
|
+
"mnemonist": "^0.39.5",
|
|
17
|
+
"object-hash": "^3.0.0",
|
|
18
|
+
"path": "^0.12.7",
|
|
19
|
+
"ratex-sdk": "^2.0.1",
|
|
20
|
+
"sha.js": "^2.4.11",
|
|
21
|
+
"sha256": "^0.2.0",
|
|
22
|
+
"ts-node": "^10.9.1",
|
|
23
|
+
"tstl": "^2.5.13",
|
|
24
|
+
"web3": "^4.11.1"
|
|
25
|
+
},
|
|
26
|
+
"main": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/object-hash": "^3.0.6",
|
|
35
|
+
"@types/web3": "^1.2.2",
|
|
36
|
+
"typescript": "^5.5.4"
|
|
37
|
+
},
|
|
38
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
39
|
+
}
|
package/routes.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {RateX, Dexes} from "./index";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const rateX = new RateX({
|
|
5
|
+
rpcUrl: "https://tiniest-cold-general.sei-pacific.quiknode.pro/dfa7ceb1fbe6d4fdbce164d5e52201488999dd15",
|
|
6
|
+
chainId: 1329,
|
|
7
|
+
dexes: [Dexes.UNISWAP_V2, Dexes.UNISWAP_V3],
|
|
8
|
+
graphApiKey: "",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const tokenIn = "0xE30feDd158A2e3b13e9badaeABaFc5516e95e8C7"; // real Sei token
|
|
12
|
+
const tokenOut = "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392"; // real Sei token
|
|
13
|
+
const amountIn = BigInt("1000000000000000000");
|
|
14
|
+
const quote = await rateX.getQuote(tokenIn, tokenOut, amountIn);
|
|
15
|
+
|
|
16
|
+
const swapParams = await rateX.getSwapParameters(
|
|
17
|
+
tokenIn,
|
|
18
|
+
tokenOut,
|
|
19
|
+
amountIn,
|
|
20
|
+
1, // 1% slippage
|
|
21
|
+
"0xEC744EA7c6792c2D78abdd3E1B1254fdCd32E6fe",
|
|
22
|
+
30 // 30 minutes deadline
|
|
23
|
+
);
|
|
24
|
+
console.log("Swap parameters:", swapParams);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { myLocalStorage } from "../../swap/my_local_storage";
|
|
2
|
+
import { Quote, Route, Pool, PoolInfo } from "../../utils/types/types";
|
|
3
|
+
import { createGraph, multiHopSwap } from "./multiHopSwap";
|
|
4
|
+
|
|
5
|
+
// Simple route key - just concatenate pool IDs (much faster than object-hash)
|
|
6
|
+
function getRouteKey(swaps: { poolId: string }[]): string {
|
|
7
|
+
return swaps.map(s => s.poolId).join('→');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Graph cache for performance optimization
|
|
11
|
+
interface CachedGraph {
|
|
12
|
+
graph: Map<string, Pool[]>;
|
|
13
|
+
poolIds: string; // Hash of pool IDs to detect changes
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let graphCache: CachedGraph | null = null;
|
|
18
|
+
const GRAPH_CACHE_TTL_MS = 2000; // 2 second cache
|
|
19
|
+
|
|
20
|
+
function getPoolsHash(pools: Pool[]): string {
|
|
21
|
+
return pools.map(p => p.poolId).sort().join(',');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getCachedGraph(pools: Pool[]): Map<string, Pool[]> {
|
|
25
|
+
const poolsHash = getPoolsHash(pools);
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
|
|
28
|
+
// Return cached graph if valid
|
|
29
|
+
if (graphCache &&
|
|
30
|
+
graphCache.poolIds === poolsHash &&
|
|
31
|
+
now - graphCache.timestamp < GRAPH_CACHE_TTL_MS) {
|
|
32
|
+
return graphCache.graph;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create new graph and cache it
|
|
36
|
+
const graph = createGraph(pools);
|
|
37
|
+
graphCache = {
|
|
38
|
+
graph,
|
|
39
|
+
poolIds: poolsHash,
|
|
40
|
+
timestamp: now
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return graph;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Simple algorithm that splits the input amount into (100/step) parts of step% each and finds the best route for each split.
|
|
47
|
+
The algorithm to find the best route for each iteration finds the route with the highest output amount.
|
|
48
|
+
(code is seen in ./multiHopSwap.ts)
|
|
49
|
+
After each iteration, the pools are updated with the amounts that passed through them.
|
|
50
|
+
*/
|
|
51
|
+
async function findRouteWithIterativeSplitting(tokenA: string, tokenB: string, amountIn: bigint, pools: Pool[], chainId: number): Promise<Quote> {
|
|
52
|
+
const graph = getCachedGraph(pools)
|
|
53
|
+
|
|
54
|
+
// percentage of the amountIn that we split into (5% = 20 iterations, faster than 2% = 50 iterations)
|
|
55
|
+
const step: number = 5
|
|
56
|
+
|
|
57
|
+
let amountOut: bigint = BigInt(0)
|
|
58
|
+
const poolMap: Map<string, Pool> = new Map<string, Pool>(pools.map((pool: Pool) => [pool.poolId, pool]))
|
|
59
|
+
const routes: Map<string, Route> = new Map<string, Route>()
|
|
60
|
+
const splitAmountIn: bigint = (amountIn * BigInt(step)) / BigInt(100)
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < 100; i += step) {
|
|
63
|
+
const route: Route = multiHopSwap(splitAmountIn, tokenA, tokenB, graph)
|
|
64
|
+
const routeHash = getRouteKey(route.swaps)
|
|
65
|
+
|
|
66
|
+
let existingRoute: Route | undefined = routes.get(routeHash)
|
|
67
|
+
if (!existingRoute) {
|
|
68
|
+
route.percentage = step
|
|
69
|
+
routes.set(routeHash, route)
|
|
70
|
+
} else {
|
|
71
|
+
existingRoute.percentage += step
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
amountOut += route.quote
|
|
75
|
+
updatePoolsInRoute(poolMap, route, splitAmountIn)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const foundRoutes: Route[] = [];
|
|
79
|
+
|
|
80
|
+
for (let route of routes.values()) {
|
|
81
|
+
route.amountIn = (BigInt(route.percentage) * amountIn) / BigInt(100);
|
|
82
|
+
foundRoutes.push(route);
|
|
83
|
+
}
|
|
84
|
+
const missingAmount = amountIn - foundRoutes.reduce((acc, route) => acc + route.amountIn, BigInt(0));
|
|
85
|
+
foundRoutes[0].amountIn += missingAmount;
|
|
86
|
+
|
|
87
|
+
const quote: Quote = { routes: foundRoutes, quote: amountOut };
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
let total = BigInt(0);
|
|
92
|
+
const resetPools = new Set<string>()
|
|
93
|
+
for (const route of quote.routes) {
|
|
94
|
+
let progress = route.amountIn;
|
|
95
|
+
for (const swap of route.swaps) {
|
|
96
|
+
const pool = myLocalStorage.getItem(swap.poolId.toLowerCase());
|
|
97
|
+
if (!pool)
|
|
98
|
+
throw Error("Error caching pools");
|
|
99
|
+
if (!resetPools.has(swap.poolId.toLowerCase())) {
|
|
100
|
+
pool.reset();
|
|
101
|
+
resetPools.add(swap.poolId.toLowerCase());
|
|
102
|
+
}
|
|
103
|
+
const amount = pool.calculateExpectedOutputAmount(swap.tokenIn, swap.tokenOut, progress)
|
|
104
|
+
pool.update(swap.tokenIn, swap.tokenOut, progress, amount);
|
|
105
|
+
progress = amount;
|
|
106
|
+
}
|
|
107
|
+
route.quote = progress;
|
|
108
|
+
total += progress;
|
|
109
|
+
}
|
|
110
|
+
quote.quote = total;
|
|
111
|
+
if (quote.routes[0].swaps.length == 0)
|
|
112
|
+
quote.quote = BigInt(0)
|
|
113
|
+
return quote;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Function to update all the pools in a route with the amounts that passed through them
|
|
117
|
+
function updatePoolsInRoute(poolMap: Map<string, Pool>, route: Route, amountIn: bigint): void {
|
|
118
|
+
for (let swap of route.swaps) {
|
|
119
|
+
const pool: Pool | undefined = poolMap.get(swap.poolId)
|
|
120
|
+
if (!pool) {
|
|
121
|
+
console.log('Pool ', swap.poolId, " doesn't exist!")
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const amountOut: bigint = pool.calculateExpectedOutputAmount(swap.tokenIn, swap.tokenOut, amountIn)
|
|
126
|
+
pool.update(swap.tokenIn, swap.tokenOut, amountIn, amountOut)
|
|
127
|
+
amountIn = amountOut
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export { findRouteWithIterativeSplitting }
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {Route, SwapStep, Pool, Token} from '../../utils/types/types'
|
|
2
|
+
|
|
3
|
+
type DpInfo = {
|
|
4
|
+
amountOut: bigint
|
|
5
|
+
path: string[]
|
|
6
|
+
swaps: SwapStep[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const max_hops = 4
|
|
10
|
+
|
|
11
|
+
/* The algorithm to find the best route for each iteration (highest output amount) is seen below.
|
|
12
|
+
* It is based on dynamic programming.
|
|
13
|
+
* @param amountIn: The amount of tokenIn that we want to swap (in wei)
|
|
14
|
+
* @param tokenIn: The address of the token we want to swap (address on Arbitrum)
|
|
15
|
+
* @param tokenOut: The address of the token we want to receive (address on Arbitrum)
|
|
16
|
+
* @param graph: The graph of all the fetched pools
|
|
17
|
+
*/
|
|
18
|
+
function multiHopSwap(amountIn: bigint, tokenIn: string, tokenOut: string, graph: Map<string, Pool[]>): Route {
|
|
19
|
+
tokenIn = tokenIn.toLowerCase()
|
|
20
|
+
tokenOut = tokenOut.toLowerCase()
|
|
21
|
+
|
|
22
|
+
// dp[hop][token]
|
|
23
|
+
const dp: Map<number, Map<string, DpInfo>> = new Map<number, Map<string, DpInfo>>()
|
|
24
|
+
dp.set(0, new Map<string, DpInfo>())
|
|
25
|
+
dp.get(0)?.set(tokenIn, { amountOut: amountIn, path: [tokenIn], swaps: [] })
|
|
26
|
+
|
|
27
|
+
const res: DpInfo = { amountOut: BigInt(-1), path: [], swaps: [] }
|
|
28
|
+
|
|
29
|
+
for (let hop = 0; hop < max_hops - 1; hop++) {
|
|
30
|
+
dp.get(hop)?.forEach((dpInfo: DpInfo, tokenA: string) => {
|
|
31
|
+
graph.get(tokenA)?.forEach((pool: Pool) => {
|
|
32
|
+
pool.tokens.forEach((tokenB: Token) => {
|
|
33
|
+
if (dpInfo.path.includes(tokenB._address)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// console.log(hop, pool.poolId, tokenA, tokenB._address, dpInfo.amountOut)
|
|
38
|
+
const amountOut: bigint = pool.calculateExpectedOutputAmount(tokenA, tokenB._address, dpInfo.amountOut)
|
|
39
|
+
if (amountOut <= 0) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const newPath: string[] = [...dpInfo.path, tokenB._address]
|
|
44
|
+
const currSwap: SwapStep = { poolId: pool.poolId, dexId: pool.dexId, tokenIn: tokenA, tokenOut: tokenB._address }
|
|
45
|
+
const newSwaps: SwapStep[] = [...dpInfo.swaps, currSwap]
|
|
46
|
+
|
|
47
|
+
if (!dp.has(hop + 1)) {
|
|
48
|
+
dp.set(hop + 1, new Map<string, DpInfo>())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const dpEntry = dp.get(hop + 1)
|
|
52
|
+
|
|
53
|
+
if (!dpEntry?.has(tokenB._address)) {
|
|
54
|
+
dp.get(hop + 1)?.set(tokenB._address, { amountOut: amountOut, path: newPath, swaps: newSwaps })
|
|
55
|
+
} else if (amountOut > (dpEntry?.get(tokenB._address)?.amountOut || 0)) {
|
|
56
|
+
dp.get(hop + 1)?.set(tokenB._address, { amountOut: amountOut, path: newPath, swaps: newSwaps })
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (dp.get(hop + 1)?.has(tokenOut) && (dp.get(hop + 1)?.get(tokenOut)?.amountOut || -1) > res.amountOut) {
|
|
63
|
+
res.amountOut = dp.get(hop + 1)?.get(tokenOut)?.amountOut || BigInt(0)
|
|
64
|
+
res.path = dp.get(hop + 1)?.get(tokenOut)?.path || []
|
|
65
|
+
res.swaps = dp.get(hop + 1)?.get(tokenOut)?.swaps || []
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
swaps: res.swaps,
|
|
71
|
+
quote: res.amountOut,
|
|
72
|
+
percentage: 0,
|
|
73
|
+
amountIn: BigInt(0) // will be set in iterative splitting when we know percentage
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Function to create a graph from all the fetched pools
|
|
78
|
+
* Graph maps every token to a list of pools that token is in
|
|
79
|
+
* @param pools: The fetched pools
|
|
80
|
+
* @returns The graph
|
|
81
|
+
*/
|
|
82
|
+
function createGraph(pools: Pool[]): Map<string, Pool[]> {
|
|
83
|
+
const graph: Map<string, Pool[]> = new Map<string, Pool[]>()
|
|
84
|
+
for (let pool of pools) {
|
|
85
|
+
const poolId = pool.poolId
|
|
86
|
+
|
|
87
|
+
for (let token of pool.tokens) {
|
|
88
|
+
if (!graph.has(token._address)) {
|
|
89
|
+
graph.set(token._address, [])
|
|
90
|
+
}
|
|
91
|
+
graph.get(token._address)?.push(pool)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return graph
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { multiHopSwap, createGraph }
|
package/routing/main.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {Quote, Pool} from "../utils/types/types";
|
|
2
|
+
import {findRouteUniLikeAlgo} from "./uni_like_algo/main";
|
|
3
|
+
import {findRouteWithIterativeSplitting} from "./iterative_spliting/main";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This algo works good if we are using pools with two tokens,
|
|
7
|
+
* but it doesn't scale well for pools with more than two tokens because
|
|
8
|
+
* amount of routes rapidly increases with number of tokens in pool as there are more ways to go
|
|
9
|
+
* from token A to token B. This makes algo really slow, especially slow if we include curve dex.
|
|
10
|
+
* That being said, it will stay here as a reference for future algo ideas and improvements but right
|
|
11
|
+
* now iterative splitting is way to go.
|
|
12
|
+
* */
|
|
13
|
+
const UNI_LIKE_ALGO_ACTIVE = false;
|
|
14
|
+
|
|
15
|
+
export async function findRoute(tokenIn: string, tokenOut: string, amountIn: bigint, pools: Pool[], chainId: number): Promise<Quote> {
|
|
16
|
+
|
|
17
|
+
if (UNI_LIKE_ALGO_ACTIVE) {
|
|
18
|
+
return findRouteUniLikeAlgo(tokenIn, tokenOut, amountIn, pools);
|
|
19
|
+
} else {
|
|
20
|
+
return (await findRouteWithIterativeSplitting(tokenIn, tokenOut, amountIn, pools, chainId));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {AmountPercentage} from "./types";
|
|
2
|
+
|
|
3
|
+
export default function calculateAmountDistribution(amountIn: bigint, distributionPercentage: number): AmountPercentage[] {
|
|
4
|
+
const percentages: number[] = [];
|
|
5
|
+
const amounts: bigint[] = [];
|
|
6
|
+
for (let i = 1; i <= 100 / distributionPercentage; ++i) {
|
|
7
|
+
percentages.push(distributionPercentage * i);
|
|
8
|
+
amounts.push(amountIn * BigInt(distributionPercentage * i) / BigInt(100));
|
|
9
|
+
}
|
|
10
|
+
return amounts.map((amount, index) => {
|
|
11
|
+
return {
|
|
12
|
+
amountIn: amount,
|
|
13
|
+
percentage: percentages[index]
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {Pool} from "../../utils/types/types";
|
|
2
|
+
import {ComputeRoutesParams, TRoute, TRouteStep} from "./types";
|
|
3
|
+
|
|
4
|
+
export default function computeRoutes(
|
|
5
|
+
tokenIn: string,
|
|
6
|
+
tokenOut: string,
|
|
7
|
+
pools: Pool[],
|
|
8
|
+
maxHops: number
|
|
9
|
+
) {
|
|
10
|
+
const usedPools = Array<boolean>(pools.length).fill(false);
|
|
11
|
+
const routes: TRoute[] = [];
|
|
12
|
+
|
|
13
|
+
const params = new ComputeRoutesParams(tokenIn, tokenOut, pools, maxHops);
|
|
14
|
+
|
|
15
|
+
_computeRoutes(
|
|
16
|
+
params,
|
|
17
|
+
[],
|
|
18
|
+
usedPools,
|
|
19
|
+
routes,
|
|
20
|
+
tokenIn
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
return routes;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _computeRoutes(
|
|
27
|
+
params: ComputeRoutesParams,
|
|
28
|
+
currentRoute: TRouteStep[],
|
|
29
|
+
usedPools: boolean[],
|
|
30
|
+
foundRoutes: TRoute[],
|
|
31
|
+
previousTokenOut: string
|
|
32
|
+
) {
|
|
33
|
+
if (currentRoute.length > params.maxHops) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (routeFound(currentRoute, params)) {
|
|
38
|
+
foundRoutes.push({
|
|
39
|
+
steps: [...currentRoute],
|
|
40
|
+
tokenIn: params.tokenIn,
|
|
41
|
+
tokenOut: params.tokenOut
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < params.pools.length; i++) {
|
|
47
|
+
|
|
48
|
+
if (usedPools[i]) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const curPool = params.pools[i];
|
|
53
|
+
|
|
54
|
+
if (!curPool.containsToken(previousTokenOut)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tokensToExplore = curPool.tokens.filter((token) => token._address.toLowerCase() !== previousTokenOut.toLowerCase());
|
|
59
|
+
|
|
60
|
+
for (let token of tokensToExplore) {
|
|
61
|
+
currentRoute.push({pool: curPool, tokenOut: token._address});
|
|
62
|
+
usedPools[i] = true;
|
|
63
|
+
|
|
64
|
+
_computeRoutes(
|
|
65
|
+
params,
|
|
66
|
+
currentRoute,
|
|
67
|
+
usedPools,
|
|
68
|
+
foundRoutes,
|
|
69
|
+
token._address
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
usedPools[i] = false;
|
|
73
|
+
currentRoute.pop();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function routeFound(route: TRouteStep[], params: ComputeRoutesParams): boolean {
|
|
79
|
+
return route.length > 0 && route[route.length - 1].tokenOut.toLowerCase() === params.tokenOut.toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {Pool, Quote, Route, SwapStep} from "../../utils/types/types";
|
|
2
|
+
import {TQuoteUniLike, TRoute,} from "./types";
|
|
3
|
+
import computeRoutes from "./compute_routes_backtrack";
|
|
4
|
+
import calculateAmountDistribution from "./amount_distribution";
|
|
5
|
+
import {getRoutesWithQuotes} from "./routes_quoter";
|
|
6
|
+
import {SwapFinder} from "./swap_finder";
|
|
7
|
+
import {algoParams} from "./algo_config";
|
|
8
|
+
|
|
9
|
+
export function findRouteUniLikeAlgo(
|
|
10
|
+
tokenIn: string,
|
|
11
|
+
tokenOut: string,
|
|
12
|
+
amountIn: bigint,
|
|
13
|
+
pools: Pool[]
|
|
14
|
+
): Quote {
|
|
15
|
+
|
|
16
|
+
const routes: TRoute[] = computeRoutes(tokenIn, tokenOut, pools, algoParams.maxHops);
|
|
17
|
+
const amounts = calculateAmountDistribution(amountIn, algoParams.distributionPercentage);
|
|
18
|
+
console.log("Amounts:", amounts);
|
|
19
|
+
console.log("Amounts size:", amounts.length);
|
|
20
|
+
console.log("Routes size:", routes.length);
|
|
21
|
+
|
|
22
|
+
const routesWithQuotes = getRoutesWithQuotes(routes, amounts);
|
|
23
|
+
|
|
24
|
+
const swapFinder = new SwapFinder(
|
|
25
|
+
algoParams,
|
|
26
|
+
routesWithQuotes,
|
|
27
|
+
amounts.map(amount => amount.percentage),
|
|
28
|
+
amountIn
|
|
29
|
+
);
|
|
30
|
+
const quote = swapFinder.findBestRoute();
|
|
31
|
+
console.log("UniLikeQuote:", quote);
|
|
32
|
+
|
|
33
|
+
return convertResponseToFoundQuoteType(quote);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function convertResponseToFoundQuoteType(q: TQuoteUniLike): Quote {
|
|
37
|
+
const routes = q.routes.map(r => {
|
|
38
|
+
const route = r.route;
|
|
39
|
+
let tokenIn = route.tokenIn;
|
|
40
|
+
let swaps: SwapStep[] = [];
|
|
41
|
+
|
|
42
|
+
route.steps.forEach(step => {
|
|
43
|
+
swaps.push({
|
|
44
|
+
poolId: step.pool.poolId,
|
|
45
|
+
dexId: step.pool.dexId,
|
|
46
|
+
tokenIn: tokenIn,
|
|
47
|
+
tokenOut: step.tokenOut
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
tokenIn = step.tokenOut;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
swaps: swaps,
|
|
55
|
+
amountIn: r.amount.amountIn,
|
|
56
|
+
percentage: r.amount.percentage,
|
|
57
|
+
quote: r.quote
|
|
58
|
+
} as Route;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
routes: routes,
|
|
63
|
+
quote: q.quote
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {AmountPercentage, TRoute, TRouteWithQuote} from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* We will calcualte everything offchain. Why?
|
|
5
|
+
* Because if we want to call for quotes on uni, we will need separate call for each pool.
|
|
6
|
+
* We can have route that theoretically looks like this:
|
|
7
|
+
* sushiPool->uniPool->curvePool->uniPool->uniPool->balancerPool->uniPool
|
|
8
|
+
* Here, we can't batch any calls, because we need amount out from previous pool. There is no other way than
|
|
9
|
+
* to call for quote on each uni pool separately.
|
|
10
|
+
* If we have 40 routes, 10 splitted amounts, and 2 uni pool on average per route, we will need 800 rpc calls.
|
|
11
|
+
*
|
|
12
|
+
* We can batch it, but that would require to calculate everything on chain. Which is also doable to try if we have time.
|
|
13
|
+
*
|
|
14
|
+
* Start with calculating everything offchain, and check final result.
|
|
15
|
+
* The problem we can have is that for large trades we fetch only +- 15 ticks. Maybe that would be solved if
|
|
16
|
+
* we split amount on lets say 5% percentage or even 2%. That would be fast because we will have offchain calculation. The only
|
|
17
|
+
* concert is that we can end up with to many splits for large trade.
|
|
18
|
+
*
|
|
19
|
+
* */
|
|
20
|
+
|
|
21
|
+
export function getRoutesWithQuotes(
|
|
22
|
+
routes: TRoute[],
|
|
23
|
+
amounts: AmountPercentage[]
|
|
24
|
+
): TRouteWithQuote[] {
|
|
25
|
+
|
|
26
|
+
const routesWithQuotes: TRouteWithQuote[][] = [];
|
|
27
|
+
routes.forEach(route => {
|
|
28
|
+
routesWithQuotes.push(getSingleRouteWithAllQuotes(route, amounts));
|
|
29
|
+
});
|
|
30
|
+
return routesWithQuotes.flat();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getSingleRouteWithAllQuotes(route: TRoute, amounts: AmountPercentage[]): TRouteWithQuote[] {
|
|
34
|
+
const routes: TRouteWithQuote[] = [];
|
|
35
|
+
|
|
36
|
+
amounts.forEach(amount => {
|
|
37
|
+
routes.push(getSingleRouteWithSingleQuote(route, amount));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return routes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getSingleRouteWithSingleQuote(route: TRoute, amount: AmountPercentage): TRouteWithQuote {
|
|
44
|
+
let tokenIn = route.tokenIn;
|
|
45
|
+
let amountOut = amount.amountIn;
|
|
46
|
+
|
|
47
|
+
for (let step of route.steps) {
|
|
48
|
+
const tokenOut = step.tokenOut;
|
|
49
|
+
|
|
50
|
+
amountOut = step.pool.calculateExpectedOutputAmount(tokenIn, tokenOut, amountOut);
|
|
51
|
+
|
|
52
|
+
if (amountOut <= BigInt(0)) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
tokenIn = tokenOut;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
route: route,
|
|
60
|
+
quote: amountOut,
|
|
61
|
+
amount: amount
|
|
62
|
+
}
|
|
63
|
+
}
|