@t402/smart-router 1.0.0-beta.1
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/README.md +388 -0
- package/dist/chunk-BUGQ6VOQ.js +662 -0
- package/dist/chunk-BUGQ6VOQ.js.map +1 -0
- package/dist/chunk-KJJ2L6TF.js +742 -0
- package/dist/chunk-KJJ2L6TF.js.map +1 -0
- package/dist/chunk-PCDWVENA.js +450 -0
- package/dist/chunk-PCDWVENA.js.map +1 -0
- package/dist/chunk-QIZPPHGB.js +169 -0
- package/dist/chunk-QIZPPHGB.js.map +1 -0
- package/dist/chunk-XKFKUWJY.js +504 -0
- package/dist/chunk-XKFKUWJY.js.map +1 -0
- package/dist/execution/index.d.ts +679 -0
- package/dist/execution/index.js +31 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/gas-CNpJzuy5.d.ts +454 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -0
- package/dist/liquidity/index.d.ts +418 -0
- package/dist/liquidity/index.js +24 -0
- package/dist/liquidity/index.js.map +1 -0
- package/dist/pricing/index.d.ts +74 -0
- package/dist/pricing/index.js +32 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/routing/index.d.ts +117 -0
- package/dist/routing/index.js +43 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/types-C0ey6WqI.d.ts +937 -0
- package/package.json +70 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
// src/routing/engine.ts
|
|
2
|
+
var OPTIMIZATION_WEIGHTS = {
|
|
3
|
+
cost: { cost: 0.6, speed: 0.1, confidence: 0.2, slippage: 0.1 },
|
|
4
|
+
speed: { cost: 0.1, speed: 0.6, confidence: 0.2, slippage: 0.1 },
|
|
5
|
+
privacy: { cost: 0.2, speed: 0.1, confidence: 0.3, slippage: 0.4 },
|
|
6
|
+
balanced: { cost: 0.3, speed: 0.3, confidence: 0.2, slippage: 0.2 },
|
|
7
|
+
slippage: { cost: 0.1, speed: 0.1, confidence: 0.2, slippage: 0.6 }
|
|
8
|
+
};
|
|
9
|
+
var RoutingEngine = class {
|
|
10
|
+
graph;
|
|
11
|
+
config;
|
|
12
|
+
routeCache = /* @__PURE__ */ new Map();
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
this.config = {
|
|
15
|
+
maxRoutes: config.maxRoutes ?? 5,
|
|
16
|
+
cacheTtl: config.cacheTtl ?? 3e4,
|
|
17
|
+
minConfidence: config.minConfidence ?? 50,
|
|
18
|
+
defaultSlippage: config.defaultSlippage ?? "0.5"
|
|
19
|
+
};
|
|
20
|
+
this.graph = {
|
|
21
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
22
|
+
edges: [],
|
|
23
|
+
lastUpdated: Date.now()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Find optimal routes for a payment
|
|
28
|
+
*/
|
|
29
|
+
async findRoutes(request) {
|
|
30
|
+
const cacheKey = this.getCacheKey(request);
|
|
31
|
+
const cached = this.routeCache.get(cacheKey);
|
|
32
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
33
|
+
return cached.routes;
|
|
34
|
+
}
|
|
35
|
+
const validation = this.validateRequest(request);
|
|
36
|
+
if (!validation.valid) {
|
|
37
|
+
throw new RoutingError(
|
|
38
|
+
`Invalid route request: ${validation.errors[0]?.message}`,
|
|
39
|
+
"INVALID_REQUEST"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const paths = this.findAllPaths(
|
|
43
|
+
request.sourceChain,
|
|
44
|
+
request.sourceAsset,
|
|
45
|
+
request.destinationChain,
|
|
46
|
+
request.destinationAsset ?? request.sourceAsset,
|
|
47
|
+
request.maxHops
|
|
48
|
+
);
|
|
49
|
+
if (paths.length === 0) {
|
|
50
|
+
throw new RoutingError(
|
|
51
|
+
"No route found between source and destination",
|
|
52
|
+
"NO_ROUTE"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const routes = [];
|
|
56
|
+
for (const path of paths) {
|
|
57
|
+
try {
|
|
58
|
+
const route = await this.buildRoute(request, path);
|
|
59
|
+
if (route.confidence >= this.config.minConfidence) {
|
|
60
|
+
routes.push(route);
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (routes.length === 0) {
|
|
67
|
+
throw new RoutingError(
|
|
68
|
+
"No valid routes found",
|
|
69
|
+
"NO_VALID_ROUTE"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const scoredRoutes = routes.map((route) => ({
|
|
73
|
+
route,
|
|
74
|
+
score: this.scoreRoute(route, request.optimization)
|
|
75
|
+
})).sort((a, b) => b.score - a.score).slice(0, this.config.maxRoutes).map(({ route }) => route);
|
|
76
|
+
this.routeCache.set(cacheKey, {
|
|
77
|
+
routes: scoredRoutes,
|
|
78
|
+
expiresAt: Date.now() + this.config.cacheTtl
|
|
79
|
+
});
|
|
80
|
+
return scoredRoutes;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get the best route for a payment
|
|
84
|
+
*/
|
|
85
|
+
async getBestRoute(request) {
|
|
86
|
+
const routes = await this.findRoutes(request);
|
|
87
|
+
return routes[0] ?? null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validate a route is still executable
|
|
91
|
+
*/
|
|
92
|
+
async validateRoute(route) {
|
|
93
|
+
const errors = [];
|
|
94
|
+
const warnings = [];
|
|
95
|
+
if (route.expiresAt < Date.now()) {
|
|
96
|
+
errors.push({
|
|
97
|
+
code: "EXPIRED",
|
|
98
|
+
message: "Route has expired"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (let i = 0; i < route.steps.length; i++) {
|
|
102
|
+
const step = route.steps[i];
|
|
103
|
+
const edge = this.findEdge(
|
|
104
|
+
step.chain,
|
|
105
|
+
step.inputAsset,
|
|
106
|
+
step.chain,
|
|
107
|
+
step.outputAsset,
|
|
108
|
+
step.protocol
|
|
109
|
+
);
|
|
110
|
+
if (!edge) {
|
|
111
|
+
errors.push({
|
|
112
|
+
step: i,
|
|
113
|
+
code: "PROTOCOL_UNAVAILABLE",
|
|
114
|
+
message: `Protocol ${step.protocol} not available for step ${i}`
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (BigInt(step.inputAmount) > BigInt(edge.maxAmount)) {
|
|
119
|
+
errors.push({
|
|
120
|
+
step: i,
|
|
121
|
+
code: "INSUFFICIENT_LIQUIDITY",
|
|
122
|
+
message: `Insufficient liquidity for step ${i}`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (BigInt(step.inputAmount) < BigInt(edge.minAmount)) {
|
|
126
|
+
errors.push({
|
|
127
|
+
step: i,
|
|
128
|
+
code: "BELOW_MINIMUM",
|
|
129
|
+
message: `Amount below minimum for step ${i}`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (parseFloat(route.priceImpact) > 1) {
|
|
134
|
+
warnings.push({
|
|
135
|
+
code: "HIGH_PRICE_IMPACT",
|
|
136
|
+
message: `Price impact is ${route.priceImpact}%`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
valid: errors.length === 0,
|
|
141
|
+
errors,
|
|
142
|
+
warnings
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Update the routing graph with new data
|
|
147
|
+
*/
|
|
148
|
+
updateGraph(edges) {
|
|
149
|
+
this.graph.edges = [];
|
|
150
|
+
this.graph.nodes.clear();
|
|
151
|
+
for (const edge of edges) {
|
|
152
|
+
this.addEdge(edge);
|
|
153
|
+
}
|
|
154
|
+
this.graph.lastUpdated = Date.now();
|
|
155
|
+
this.routeCache.clear();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Add an edge to the routing graph
|
|
159
|
+
*/
|
|
160
|
+
addEdge(edge) {
|
|
161
|
+
this.graph.edges.push(edge);
|
|
162
|
+
const fromKey = `${edge.from}:${edge.fromAsset}`;
|
|
163
|
+
const toKey = `${edge.to}:${edge.toAsset}`;
|
|
164
|
+
if (!this.graph.nodes.has(fromKey)) {
|
|
165
|
+
this.graph.nodes.set(fromKey, {
|
|
166
|
+
chain: edge.from,
|
|
167
|
+
asset: edge.fromAsset,
|
|
168
|
+
edges: []
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
this.graph.nodes.get(fromKey).edges.push(edge);
|
|
172
|
+
if (!this.graph.nodes.has(toKey)) {
|
|
173
|
+
this.graph.nodes.set(toKey, {
|
|
174
|
+
chain: edge.to,
|
|
175
|
+
asset: edge.toAsset,
|
|
176
|
+
edges: []
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get the current routing graph
|
|
182
|
+
*/
|
|
183
|
+
getGraph() {
|
|
184
|
+
return this.graph;
|
|
185
|
+
}
|
|
186
|
+
validateRequest(request) {
|
|
187
|
+
const errors = [];
|
|
188
|
+
if (!request.sourceChain) {
|
|
189
|
+
errors.push({ code: "MISSING_SOURCE_CHAIN", message: "Source chain is required" });
|
|
190
|
+
}
|
|
191
|
+
if (!request.sourceAsset) {
|
|
192
|
+
errors.push({ code: "MISSING_SOURCE_ASSET", message: "Source asset is required" });
|
|
193
|
+
}
|
|
194
|
+
if (!request.sourceAmount || BigInt(request.sourceAmount) <= 0n) {
|
|
195
|
+
errors.push({ code: "INVALID_AMOUNT", message: "Source amount must be positive" });
|
|
196
|
+
}
|
|
197
|
+
if (!request.destinationChain) {
|
|
198
|
+
errors.push({ code: "MISSING_DEST_CHAIN", message: "Destination chain is required" });
|
|
199
|
+
}
|
|
200
|
+
if (!request.sender) {
|
|
201
|
+
errors.push({ code: "MISSING_SENDER", message: "Sender address is required" });
|
|
202
|
+
}
|
|
203
|
+
if (!request.recipient) {
|
|
204
|
+
errors.push({ code: "MISSING_RECIPIENT", message: "Recipient address is required" });
|
|
205
|
+
}
|
|
206
|
+
return { valid: errors.length === 0, errors, warnings: [] };
|
|
207
|
+
}
|
|
208
|
+
findAllPaths(fromChain, fromAsset, toChain, toAsset, maxHops) {
|
|
209
|
+
const paths = [];
|
|
210
|
+
const startKey = `${fromChain}:${fromAsset}`;
|
|
211
|
+
const endKey = `${toChain}:${toAsset}`;
|
|
212
|
+
const queue = [{ key: startKey, path: [] }];
|
|
213
|
+
const visited = /* @__PURE__ */ new Set();
|
|
214
|
+
while (queue.length > 0) {
|
|
215
|
+
const { key, path } = queue.shift();
|
|
216
|
+
if (path.length > maxHops) continue;
|
|
217
|
+
if (key === endKey && path.length > 0) {
|
|
218
|
+
paths.push([...path]);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const node = this.graph.nodes.get(key);
|
|
222
|
+
if (!node) continue;
|
|
223
|
+
for (const edge of node.edges) {
|
|
224
|
+
const nextKey = `${edge.to}:${edge.toAsset}`;
|
|
225
|
+
const pathKey = `${key}->${nextKey}`;
|
|
226
|
+
if (visited.has(pathKey)) continue;
|
|
227
|
+
visited.add(pathKey);
|
|
228
|
+
queue.push({
|
|
229
|
+
key: nextKey,
|
|
230
|
+
path: [...path, edge]
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return paths;
|
|
235
|
+
}
|
|
236
|
+
async buildRoute(request, path) {
|
|
237
|
+
const steps = [];
|
|
238
|
+
let currentAmount = request.sourceAmount;
|
|
239
|
+
let totalGasCost = 0n;
|
|
240
|
+
let totalProtocolFees = 0n;
|
|
241
|
+
let totalBridgeFees = 0n;
|
|
242
|
+
let totalTime = 0;
|
|
243
|
+
let cumulativePriceImpact = 0;
|
|
244
|
+
for (let i = 0; i < path.length; i++) {
|
|
245
|
+
const edge = path[i];
|
|
246
|
+
const slippage2 = parseFloat(request.maxSlippage) / 100;
|
|
247
|
+
const outputAmount = this.calculateOutput(currentAmount, edge);
|
|
248
|
+
const minOutput = BigInt(Math.floor(Number(outputAmount) * (1 - slippage2))).toString();
|
|
249
|
+
const gasEstimate = this.estimateGas(edge.type);
|
|
250
|
+
const protocolFee = this.calculateProtocolFee(currentAmount, edge);
|
|
251
|
+
const bridgeFee = edge.type === "bridge" ? this.calculateBridgeFee(currentAmount, edge) : "0";
|
|
252
|
+
const step = {
|
|
253
|
+
id: `step-${i}`,
|
|
254
|
+
type: edge.type,
|
|
255
|
+
chain: edge.from,
|
|
256
|
+
protocol: edge.protocol,
|
|
257
|
+
inputAsset: edge.fromAsset,
|
|
258
|
+
outputAsset: edge.toAsset,
|
|
259
|
+
inputAmount: currentAmount,
|
|
260
|
+
outputAmount,
|
|
261
|
+
minOutputAmount: minOutput,
|
|
262
|
+
contract: this.getProtocolContract(edge),
|
|
263
|
+
estimatedGas: gasEstimate,
|
|
264
|
+
estimatedTime: edge.estimatedTime,
|
|
265
|
+
protocolFee,
|
|
266
|
+
bridgeFee: edge.type === "bridge" ? bridgeFee : void 0,
|
|
267
|
+
priceImpact: this.calculatePriceImpact(currentAmount, edge),
|
|
268
|
+
exchangeRate: this.calculateExchangeRate(edge)
|
|
269
|
+
};
|
|
270
|
+
steps.push(step);
|
|
271
|
+
currentAmount = outputAmount;
|
|
272
|
+
totalGasCost += BigInt(gasEstimate);
|
|
273
|
+
totalProtocolFees += BigInt(protocolFee);
|
|
274
|
+
totalBridgeFees += BigInt(bridgeFee);
|
|
275
|
+
totalTime += edge.estimatedTime;
|
|
276
|
+
cumulativePriceImpact += parseFloat(step.priceImpact ?? "0");
|
|
277
|
+
}
|
|
278
|
+
const totalCost = totalGasCost + totalProtocolFees + totalBridgeFees;
|
|
279
|
+
const slippage = parseFloat(request.maxSlippage) / 100;
|
|
280
|
+
const minDestination = BigInt(Math.floor(Number(currentAmount) * (1 - slippage))).toString();
|
|
281
|
+
return {
|
|
282
|
+
id: crypto.randomUUID(),
|
|
283
|
+
steps,
|
|
284
|
+
sourceChain: request.sourceChain,
|
|
285
|
+
destinationChain: request.destinationChain,
|
|
286
|
+
sourceAsset: request.sourceAsset,
|
|
287
|
+
destinationAsset: request.destinationAsset ?? request.sourceAsset,
|
|
288
|
+
sourceAmount: request.sourceAmount,
|
|
289
|
+
destinationAmount: currentAmount,
|
|
290
|
+
minDestinationAmount: minDestination,
|
|
291
|
+
totalGasCost: totalGasCost.toString(),
|
|
292
|
+
totalProtocolFees: totalProtocolFees.toString(),
|
|
293
|
+
totalBridgeFees: totalBridgeFees.toString(),
|
|
294
|
+
totalCost: totalCost.toString(),
|
|
295
|
+
estimatedTime: totalTime,
|
|
296
|
+
priceImpact: cumulativePriceImpact.toFixed(4),
|
|
297
|
+
confidence: this.calculateConfidence(path),
|
|
298
|
+
optimization: request.optimization,
|
|
299
|
+
warnings: this.generateWarnings(steps, request),
|
|
300
|
+
createdAt: Date.now(),
|
|
301
|
+
expiresAt: Date.now() + 6e4
|
|
302
|
+
// 1 minute
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
scoreRoute(route, strategy) {
|
|
306
|
+
const weights = OPTIMIZATION_WEIGHTS[strategy];
|
|
307
|
+
const costScore = 100 - Math.min(100, Number(route.totalCost) / 1e6);
|
|
308
|
+
const speedScore = 100 - Math.min(100, route.estimatedTime / 600);
|
|
309
|
+
const confidenceScore = route.confidence;
|
|
310
|
+
const slippageScore = 100 - Math.min(100, parseFloat(route.priceImpact) * 10);
|
|
311
|
+
return costScore * weights.cost + speedScore * weights.speed + confidenceScore * weights.confidence + slippageScore * weights.slippage;
|
|
312
|
+
}
|
|
313
|
+
calculateOutput(input, _edge) {
|
|
314
|
+
const inputBigInt = BigInt(input);
|
|
315
|
+
const fee = inputBigInt * 3n / 1000n;
|
|
316
|
+
return (inputBigInt - fee).toString();
|
|
317
|
+
}
|
|
318
|
+
calculateProtocolFee(amount, edge) {
|
|
319
|
+
const feeRate = edge.type === "swap" ? 30n : 10n;
|
|
320
|
+
return (BigInt(amount) * feeRate / 10000n).toString();
|
|
321
|
+
}
|
|
322
|
+
calculateBridgeFee(amount, _edge) {
|
|
323
|
+
const variableFee = BigInt(amount) * 10n / 10000n;
|
|
324
|
+
const fixedFee = 100000n;
|
|
325
|
+
return (variableFee + fixedFee).toString();
|
|
326
|
+
}
|
|
327
|
+
calculatePriceImpact(amount, edge) {
|
|
328
|
+
const liquidity = BigInt(edge.liquidity);
|
|
329
|
+
if (liquidity === 0n) return "100";
|
|
330
|
+
const amountBigInt = BigInt(amount);
|
|
331
|
+
const impact = Number(amountBigInt * 10000n / liquidity) / 100;
|
|
332
|
+
return impact.toFixed(4);
|
|
333
|
+
}
|
|
334
|
+
calculateExchangeRate(edge) {
|
|
335
|
+
if (edge.fromAsset === edge.toAsset) {
|
|
336
|
+
return "1.0";
|
|
337
|
+
}
|
|
338
|
+
return "0.997";
|
|
339
|
+
}
|
|
340
|
+
estimateGas(type) {
|
|
341
|
+
const gasEstimates = {
|
|
342
|
+
transfer: "65000",
|
|
343
|
+
swap: "150000",
|
|
344
|
+
bridge: "200000",
|
|
345
|
+
wrap: "45000",
|
|
346
|
+
unwrap: "45000",
|
|
347
|
+
approve: "46000",
|
|
348
|
+
deposit: "100000",
|
|
349
|
+
withdraw: "100000"
|
|
350
|
+
};
|
|
351
|
+
return gasEstimates[type] ?? "100000";
|
|
352
|
+
}
|
|
353
|
+
getProtocolContract(edge) {
|
|
354
|
+
return `0x${edge.protocol?.slice(0, 40).padEnd(40, "0") ?? "0".repeat(40)}`;
|
|
355
|
+
}
|
|
356
|
+
calculateConfidence(path) {
|
|
357
|
+
let confidence = 100;
|
|
358
|
+
for (const edge of path) {
|
|
359
|
+
const reduction = edge.type === "bridge" ? 10 : 3;
|
|
360
|
+
confidence -= reduction;
|
|
361
|
+
if (BigInt(edge.liquidity) < 1000000n) {
|
|
362
|
+
confidence -= 5;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return Math.max(0, Math.min(100, confidence));
|
|
366
|
+
}
|
|
367
|
+
generateWarnings(steps, request) {
|
|
368
|
+
const warnings = [];
|
|
369
|
+
if (steps.length > 3) {
|
|
370
|
+
warnings.push(`Route has ${steps.length} steps which may increase risk`);
|
|
371
|
+
}
|
|
372
|
+
const bridgeSteps = steps.filter((s) => s.type === "bridge");
|
|
373
|
+
if (bridgeSteps.length > 0) {
|
|
374
|
+
warnings.push("Route includes cross-chain bridge which may take longer");
|
|
375
|
+
}
|
|
376
|
+
if (request.deadline) {
|
|
377
|
+
const totalTime = steps.reduce((sum, s) => sum + s.estimatedTime, 0);
|
|
378
|
+
const timeRemaining = request.deadline - Date.now() / 1e3;
|
|
379
|
+
if (totalTime > timeRemaining * 0.8) {
|
|
380
|
+
warnings.push("Route may not complete before deadline");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return warnings;
|
|
384
|
+
}
|
|
385
|
+
findEdge(fromChain, fromAsset, toChain, toAsset, protocol) {
|
|
386
|
+
return this.graph.edges.find(
|
|
387
|
+
(e) => e.from === fromChain && e.fromAsset === fromAsset && e.to === toChain && e.toAsset === toAsset && (!protocol || e.protocol === protocol)
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
getCacheKey(request) {
|
|
391
|
+
return JSON.stringify({
|
|
392
|
+
sourceChain: request.sourceChain,
|
|
393
|
+
sourceAsset: request.sourceAsset,
|
|
394
|
+
sourceAmount: request.sourceAmount,
|
|
395
|
+
destinationChain: request.destinationChain,
|
|
396
|
+
destinationAsset: request.destinationAsset,
|
|
397
|
+
optimization: request.optimization
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
var RoutingError = class extends Error {
|
|
402
|
+
code;
|
|
403
|
+
constructor(message, code) {
|
|
404
|
+
super(message);
|
|
405
|
+
this.name = "RoutingError";
|
|
406
|
+
this.code = code;
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// src/routing/algorithms.ts
|
|
411
|
+
var PriorityQueue = class {
|
|
412
|
+
items = [];
|
|
413
|
+
enqueue(item, priority) {
|
|
414
|
+
this.items.push({ item, priority });
|
|
415
|
+
this.items.sort((a, b) => a.priority - b.priority);
|
|
416
|
+
}
|
|
417
|
+
dequeue() {
|
|
418
|
+
return this.items.shift()?.item;
|
|
419
|
+
}
|
|
420
|
+
isEmpty() {
|
|
421
|
+
return this.items.length === 0;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
function nodeKey(chain, asset) {
|
|
425
|
+
return `${chain}:${asset}`;
|
|
426
|
+
}
|
|
427
|
+
var COST_CONFIGS = {
|
|
428
|
+
cost: {
|
|
429
|
+
gasCostWeight: 0.4,
|
|
430
|
+
feeWeight: 0.4,
|
|
431
|
+
timeWeight: 0.1,
|
|
432
|
+
slippageWeight: 0.1,
|
|
433
|
+
hopPenalty: 0.05,
|
|
434
|
+
bridgePenalty: 0.1
|
|
435
|
+
},
|
|
436
|
+
speed: {
|
|
437
|
+
gasCostWeight: 0.1,
|
|
438
|
+
feeWeight: 0.1,
|
|
439
|
+
timeWeight: 0.6,
|
|
440
|
+
slippageWeight: 0.2,
|
|
441
|
+
hopPenalty: 0.1,
|
|
442
|
+
bridgePenalty: 0.2
|
|
443
|
+
},
|
|
444
|
+
privacy: {
|
|
445
|
+
gasCostWeight: 0.2,
|
|
446
|
+
feeWeight: 0.2,
|
|
447
|
+
timeWeight: 0.1,
|
|
448
|
+
slippageWeight: 0.5,
|
|
449
|
+
hopPenalty: 0.15,
|
|
450
|
+
bridgePenalty: 0.05
|
|
451
|
+
},
|
|
452
|
+
balanced: {
|
|
453
|
+
gasCostWeight: 0.25,
|
|
454
|
+
feeWeight: 0.25,
|
|
455
|
+
timeWeight: 0.25,
|
|
456
|
+
slippageWeight: 0.25,
|
|
457
|
+
hopPenalty: 0.08,
|
|
458
|
+
bridgePenalty: 0.12
|
|
459
|
+
},
|
|
460
|
+
slippage: {
|
|
461
|
+
gasCostWeight: 0.1,
|
|
462
|
+
feeWeight: 0.1,
|
|
463
|
+
timeWeight: 0.1,
|
|
464
|
+
slippageWeight: 0.7,
|
|
465
|
+
hopPenalty: 0.12,
|
|
466
|
+
bridgePenalty: 0.15
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
function calculateEdgeCost(edge, config) {
|
|
470
|
+
const normalizedGas = edge.cost / 1e6;
|
|
471
|
+
const normalizedTime = edge.estimatedTime / 3600;
|
|
472
|
+
let cost = normalizedGas * config.gasCostWeight + normalizedTime * config.timeWeight;
|
|
473
|
+
cost += config.hopPenalty;
|
|
474
|
+
if (edge.type === "bridge") {
|
|
475
|
+
cost += config.bridgePenalty;
|
|
476
|
+
}
|
|
477
|
+
return Math.max(1e-3, cost);
|
|
478
|
+
}
|
|
479
|
+
function dijkstra(edges, startChain, startAsset, endChain, endAsset, costConfig, maxHops = 5) {
|
|
480
|
+
const startKey = nodeKey(startChain, startAsset);
|
|
481
|
+
const endKey = nodeKey(endChain, endAsset);
|
|
482
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
483
|
+
for (const edge of edges) {
|
|
484
|
+
const key = nodeKey(edge.from, edge.fromAsset);
|
|
485
|
+
if (!adjacency.has(key)) {
|
|
486
|
+
adjacency.set(key, []);
|
|
487
|
+
}
|
|
488
|
+
adjacency.get(key).push(edge);
|
|
489
|
+
}
|
|
490
|
+
const distances = /* @__PURE__ */ new Map();
|
|
491
|
+
const previous = /* @__PURE__ */ new Map();
|
|
492
|
+
const hops = /* @__PURE__ */ new Map();
|
|
493
|
+
const pq = new PriorityQueue();
|
|
494
|
+
distances.set(startKey, 0);
|
|
495
|
+
hops.set(startKey, 0);
|
|
496
|
+
previous.set(startKey, null);
|
|
497
|
+
pq.enqueue(startKey, 0);
|
|
498
|
+
while (!pq.isEmpty()) {
|
|
499
|
+
const currentKey = pq.dequeue();
|
|
500
|
+
const currentDistance = distances.get(currentKey) ?? Infinity;
|
|
501
|
+
const currentHops = hops.get(currentKey) ?? 0;
|
|
502
|
+
if (currentKey === endKey) {
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
if (currentHops >= maxHops) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const neighbors = adjacency.get(currentKey) ?? [];
|
|
509
|
+
for (const edge of neighbors) {
|
|
510
|
+
const neighborKey = nodeKey(edge.to, edge.toAsset);
|
|
511
|
+
const edgeCost = calculateEdgeCost(edge, costConfig);
|
|
512
|
+
const newDistance = currentDistance + edgeCost;
|
|
513
|
+
if (newDistance < (distances.get(neighborKey) ?? Infinity)) {
|
|
514
|
+
distances.set(neighborKey, newDistance);
|
|
515
|
+
hops.set(neighborKey, currentHops + 1);
|
|
516
|
+
previous.set(neighborKey, { edge, node: currentKey });
|
|
517
|
+
pq.enqueue(neighborKey, newDistance);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (!previous.has(endKey)) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const path = [];
|
|
525
|
+
let current = endKey;
|
|
526
|
+
while (previous.get(current) !== null) {
|
|
527
|
+
const prev = previous.get(current);
|
|
528
|
+
path.unshift(prev.edge);
|
|
529
|
+
current = prev.node;
|
|
530
|
+
}
|
|
531
|
+
return path;
|
|
532
|
+
}
|
|
533
|
+
function astar(edges, startChain, startAsset, endChain, endAsset, costConfig, maxHops = 5) {
|
|
534
|
+
const startKey = nodeKey(startChain, startAsset);
|
|
535
|
+
const endKey = nodeKey(endChain, endAsset);
|
|
536
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const edge of edges) {
|
|
538
|
+
const key = nodeKey(edge.from, edge.fromAsset);
|
|
539
|
+
if (!adjacency.has(key)) {
|
|
540
|
+
adjacency.set(key, []);
|
|
541
|
+
}
|
|
542
|
+
adjacency.get(key).push(edge);
|
|
543
|
+
}
|
|
544
|
+
const heuristic = (key) => {
|
|
545
|
+
if (key === endKey) return 0;
|
|
546
|
+
const [chain] = key.split(":");
|
|
547
|
+
if (chain === endChain) {
|
|
548
|
+
return 0.1;
|
|
549
|
+
}
|
|
550
|
+
return 0.5;
|
|
551
|
+
};
|
|
552
|
+
const gScore = /* @__PURE__ */ new Map();
|
|
553
|
+
const fScore = /* @__PURE__ */ new Map();
|
|
554
|
+
const previous = /* @__PURE__ */ new Map();
|
|
555
|
+
const hops = /* @__PURE__ */ new Map();
|
|
556
|
+
const pq = new PriorityQueue();
|
|
557
|
+
gScore.set(startKey, 0);
|
|
558
|
+
fScore.set(startKey, heuristic(startKey));
|
|
559
|
+
hops.set(startKey, 0);
|
|
560
|
+
previous.set(startKey, null);
|
|
561
|
+
pq.enqueue(startKey, fScore.get(startKey));
|
|
562
|
+
const visited = /* @__PURE__ */ new Set();
|
|
563
|
+
while (!pq.isEmpty()) {
|
|
564
|
+
const currentKey = pq.dequeue();
|
|
565
|
+
if (currentKey === endKey) {
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
if (visited.has(currentKey)) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
visited.add(currentKey);
|
|
572
|
+
const currentHops = hops.get(currentKey) ?? 0;
|
|
573
|
+
if (currentHops >= maxHops) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const neighbors = adjacency.get(currentKey) ?? [];
|
|
577
|
+
for (const edge of neighbors) {
|
|
578
|
+
const neighborKey = nodeKey(edge.to, edge.toAsset);
|
|
579
|
+
const edgeCost = calculateEdgeCost(edge, costConfig);
|
|
580
|
+
const tentativeGScore = (gScore.get(currentKey) ?? Infinity) + edgeCost;
|
|
581
|
+
if (tentativeGScore < (gScore.get(neighborKey) ?? Infinity)) {
|
|
582
|
+
gScore.set(neighborKey, tentativeGScore);
|
|
583
|
+
fScore.set(neighborKey, tentativeGScore + heuristic(neighborKey));
|
|
584
|
+
hops.set(neighborKey, currentHops + 1);
|
|
585
|
+
previous.set(neighborKey, { edge, node: currentKey });
|
|
586
|
+
pq.enqueue(neighborKey, fScore.get(neighborKey));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (!previous.has(endKey)) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
const path = [];
|
|
594
|
+
let current = endKey;
|
|
595
|
+
while (previous.get(current) !== null) {
|
|
596
|
+
const prev = previous.get(current);
|
|
597
|
+
path.unshift(prev.edge);
|
|
598
|
+
current = prev.node;
|
|
599
|
+
}
|
|
600
|
+
return path;
|
|
601
|
+
}
|
|
602
|
+
function kShortestPaths(edges, startChain, startAsset, endChain, endAsset, costConfig, k = 5, maxHops = 5) {
|
|
603
|
+
const paths = [];
|
|
604
|
+
const firstPath = dijkstra(
|
|
605
|
+
edges,
|
|
606
|
+
startChain,
|
|
607
|
+
startAsset,
|
|
608
|
+
endChain,
|
|
609
|
+
endAsset,
|
|
610
|
+
costConfig,
|
|
611
|
+
maxHops
|
|
612
|
+
);
|
|
613
|
+
if (!firstPath) {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
paths.push(firstPath);
|
|
617
|
+
const candidates = [];
|
|
618
|
+
for (let i = 1; i < k; i++) {
|
|
619
|
+
const prevPath = paths[i - 1];
|
|
620
|
+
for (let j = 0; j < prevPath.length; j++) {
|
|
621
|
+
const excludedEdges = /* @__PURE__ */ new Set();
|
|
622
|
+
for (const existingPath of paths) {
|
|
623
|
+
if (existingPath.length > j) {
|
|
624
|
+
let match = true;
|
|
625
|
+
for (let m = 0; m < j; m++) {
|
|
626
|
+
if (existingPath[m] !== prevPath[m]) {
|
|
627
|
+
match = false;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (match && existingPath[j]) {
|
|
632
|
+
const edgeKey = `${existingPath[j].from}:${existingPath[j].fromAsset}->${existingPath[j].to}:${existingPath[j].toAsset}`;
|
|
633
|
+
excludedEdges.add(edgeKey);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const filteredEdges = edges.filter((e) => {
|
|
638
|
+
const edgeKey = `${e.from}:${e.fromAsset}->${e.to}:${e.toAsset}`;
|
|
639
|
+
return !excludedEdges.has(edgeKey);
|
|
640
|
+
});
|
|
641
|
+
const spurNode = j === 0 ? { chain: startChain, asset: startAsset } : { chain: prevPath[j - 1].to, asset: prevPath[j - 1].toAsset };
|
|
642
|
+
const spurPath = dijkstra(
|
|
643
|
+
filteredEdges,
|
|
644
|
+
spurNode.chain,
|
|
645
|
+
spurNode.asset,
|
|
646
|
+
endChain,
|
|
647
|
+
endAsset,
|
|
648
|
+
costConfig,
|
|
649
|
+
maxHops - j
|
|
650
|
+
);
|
|
651
|
+
if (spurPath) {
|
|
652
|
+
const rootPath = prevPath.slice(0, j);
|
|
653
|
+
const candidatePath = [...rootPath, ...spurPath];
|
|
654
|
+
const cost = candidatePath.reduce(
|
|
655
|
+
(sum, edge) => sum + calculateEdgeCost(edge, costConfig),
|
|
656
|
+
0
|
|
657
|
+
);
|
|
658
|
+
const pathKey = candidatePath.map(
|
|
659
|
+
(e) => `${e.from}:${e.fromAsset}->${e.to}:${e.toAsset}`
|
|
660
|
+
).join("|");
|
|
661
|
+
const isDuplicate = paths.some(
|
|
662
|
+
(p) => p.map((e) => `${e.from}:${e.fromAsset}->${e.to}:${e.toAsset}`).join("|") === pathKey
|
|
663
|
+
) || candidates.some(
|
|
664
|
+
(c) => c.path.map((e) => `${e.from}:${e.fromAsset}->${e.to}:${e.toAsset}`).join("|") === pathKey
|
|
665
|
+
);
|
|
666
|
+
if (!isDuplicate) {
|
|
667
|
+
candidates.push({ path: candidatePath, cost });
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (candidates.length === 0) {
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
candidates.sort((a, b) => a.cost - b.cost);
|
|
675
|
+
const best = candidates.shift();
|
|
676
|
+
paths.push(best.path);
|
|
677
|
+
}
|
|
678
|
+
return paths;
|
|
679
|
+
}
|
|
680
|
+
function paretoOptimalPaths(edges, startChain, startAsset, endChain, endAsset, maxHops = 5) {
|
|
681
|
+
const costPaths = kShortestPaths(
|
|
682
|
+
edges,
|
|
683
|
+
startChain,
|
|
684
|
+
startAsset,
|
|
685
|
+
endChain,
|
|
686
|
+
endAsset,
|
|
687
|
+
COST_CONFIGS.cost,
|
|
688
|
+
3,
|
|
689
|
+
maxHops
|
|
690
|
+
);
|
|
691
|
+
const speedPaths = kShortestPaths(
|
|
692
|
+
edges,
|
|
693
|
+
startChain,
|
|
694
|
+
startAsset,
|
|
695
|
+
endChain,
|
|
696
|
+
endAsset,
|
|
697
|
+
COST_CONFIGS.speed,
|
|
698
|
+
3,
|
|
699
|
+
maxHops
|
|
700
|
+
);
|
|
701
|
+
const allPaths = [...costPaths, ...speedPaths];
|
|
702
|
+
const uniquePaths = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const path of allPaths) {
|
|
704
|
+
const key = path.map(
|
|
705
|
+
(e) => `${e.from}:${e.fromAsset}->${e.to}:${e.toAsset}:${e.protocol}`
|
|
706
|
+
).join("|");
|
|
707
|
+
if (!uniquePaths.has(key)) {
|
|
708
|
+
uniquePaths.set(key, path);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const results = Array.from(uniquePaths.values()).map((path) => {
|
|
712
|
+
const cost = path.reduce((sum, e) => sum + e.cost, 0);
|
|
713
|
+
const time = path.reduce((sum, e) => sum + e.estimatedTime, 0);
|
|
714
|
+
return {
|
|
715
|
+
path,
|
|
716
|
+
metrics: { cost, time, hops: path.length }
|
|
717
|
+
};
|
|
718
|
+
});
|
|
719
|
+
return results.filter((r, i) => {
|
|
720
|
+
for (let j = 0; j < results.length; j++) {
|
|
721
|
+
if (i === j) continue;
|
|
722
|
+
const other = results[j];
|
|
723
|
+
const otherDominates = other.metrics.cost <= r.metrics.cost && other.metrics.time <= r.metrics.time && other.metrics.hops <= r.metrics.hops && (other.metrics.cost < r.metrics.cost || other.metrics.time < r.metrics.time || other.metrics.hops < r.metrics.hops);
|
|
724
|
+
if (otherDominates) {
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return true;
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
export {
|
|
733
|
+
RoutingEngine,
|
|
734
|
+
RoutingError,
|
|
735
|
+
COST_CONFIGS,
|
|
736
|
+
calculateEdgeCost,
|
|
737
|
+
dijkstra,
|
|
738
|
+
astar,
|
|
739
|
+
kShortestPaths,
|
|
740
|
+
paretoOptimalPaths
|
|
741
|
+
};
|
|
742
|
+
//# sourceMappingURL=chunk-KJJ2L6TF.js.map
|