@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.
@@ -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