@mento-protocol/mento-sdk 1.0.9 → 1.10.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.
Files changed (121) hide show
  1. package/README.md +42 -2
  2. package/dist/cjs/scripts/cacheTradablePairs/config.d.ts +5 -0
  3. package/dist/cjs/scripts/cacheTradablePairs/config.js +6 -0
  4. package/dist/cjs/scripts/quotes/config.d.ts +20 -0
  5. package/dist/cjs/scripts/quotes/config.js +33 -0
  6. package/dist/cjs/scripts/quotes/spread.d.ts +25 -0
  7. package/dist/cjs/scripts/quotes/spread.js +166 -0
  8. package/dist/cjs/scripts/shared/network.d.ts +23 -0
  9. package/dist/cjs/scripts/shared/network.js +57 -0
  10. package/dist/cjs/{constants → src/constants}/addresses.js +0 -23
  11. package/dist/cjs/src/constants/index.d.ts +2 -0
  12. package/dist/cjs/{constants → src/constants}/index.js +1 -0
  13. package/dist/cjs/src/constants/tradablePairs.42220.d.ts +2 -0
  14. package/dist/cjs/src/constants/tradablePairs.42220.js +6407 -0
  15. package/dist/cjs/src/constants/tradablePairs.44787.d.ts +2 -0
  16. package/dist/cjs/src/constants/tradablePairs.44787.js +6407 -0
  17. package/dist/cjs/src/constants/tradablePairs.d.ts +16 -0
  18. package/dist/cjs/src/constants/tradablePairs.js +53 -0
  19. package/dist/cjs/{enums → src/enums}/chainId.d.ts +1 -2
  20. package/dist/cjs/{enums → src/enums}/chainId.js +0 -1
  21. package/dist/{esm → cjs/src}/index.d.ts +4 -3
  22. package/dist/cjs/{index.js → src/index.js} +3 -2
  23. package/dist/{esm → cjs/src}/mento.d.ts +14 -4
  24. package/dist/cjs/{mento.js → src/mento.js} +36 -90
  25. package/dist/cjs/src/routeUtils.d.ts +304 -0
  26. package/dist/cjs/src/routeUtils.js +372 -0
  27. package/dist/cjs/{utils.d.ts → src/utils.d.ts} +8 -0
  28. package/dist/cjs/{utils.js → src/utils.js} +18 -1
  29. package/dist/esm/scripts/cacheTradablePairs/config.d.ts +5 -0
  30. package/dist/esm/scripts/cacheTradablePairs/config.js +3 -0
  31. package/dist/esm/scripts/quotes/config.d.ts +20 -0
  32. package/dist/esm/scripts/quotes/config.js +30 -0
  33. package/dist/esm/scripts/quotes/spread.d.ts +25 -0
  34. package/dist/esm/scripts/quotes/spread.js +161 -0
  35. package/dist/esm/scripts/shared/network.d.ts +23 -0
  36. package/dist/esm/scripts/shared/network.js +52 -0
  37. package/dist/esm/{constants → src/constants}/addresses.js +0 -23
  38. package/dist/esm/src/constants/index.d.ts +2 -0
  39. package/dist/esm/src/constants/index.js +2 -0
  40. package/dist/esm/src/constants/tradablePairs.42220.d.ts +2 -0
  41. package/dist/esm/src/constants/tradablePairs.42220.js +6404 -0
  42. package/dist/esm/src/constants/tradablePairs.44787.d.ts +2 -0
  43. package/dist/esm/src/constants/tradablePairs.44787.js +6404 -0
  44. package/dist/esm/src/constants/tradablePairs.d.ts +16 -0
  45. package/dist/esm/src/constants/tradablePairs.js +26 -0
  46. package/dist/esm/{enums → src/enums}/chainId.d.ts +1 -2
  47. package/dist/esm/{enums → src/enums}/chainId.js +0 -1
  48. package/dist/{cjs → esm/src}/index.d.ts +4 -3
  49. package/dist/esm/{index.js → src/index.js} +3 -2
  50. package/dist/{cjs → esm/src}/mento.d.ts +14 -4
  51. package/dist/esm/{mento.js → src/mento.js} +37 -91
  52. package/dist/esm/src/routeUtils.d.ts +304 -0
  53. package/dist/esm/src/routeUtils.js +362 -0
  54. package/dist/esm/{utils.d.ts → src/utils.d.ts} +8 -0
  55. package/dist/esm/{utils.js → src/utils.js} +16 -0
  56. package/package.json +18 -6
  57. package/dist/cjs/constants/index.d.ts +0 -1
  58. package/dist/cjs/constants/tradablePairs.d.ts +0 -3
  59. package/dist/cjs/constants/tradablePairs.js +0 -9098
  60. package/dist/esm/constants/index.d.ts +0 -1
  61. package/dist/esm/constants/index.js +0 -1
  62. package/dist/esm/constants/tradablePairs.d.ts +0 -3
  63. package/dist/esm/constants/tradablePairs.js +0 -9094
  64. /package/dist/cjs/{ChainClient.d.ts → src/ChainClient.d.ts} +0 -0
  65. /package/dist/cjs/{ChainClient.js → src/ChainClient.js} +0 -0
  66. /package/dist/cjs/{TestChainClient.d.ts → src/TestChainClient.d.ts} +0 -0
  67. /package/dist/cjs/{TestChainClient.js → src/TestChainClient.js} +0 -0
  68. /package/dist/cjs/{constants → src/constants}/addresses.d.ts +0 -0
  69. /package/dist/cjs/{enums → src/enums}/index.d.ts +0 -0
  70. /package/dist/cjs/{enums → src/enums}/index.js +0 -0
  71. /package/dist/cjs/{enums → src/enums}/proposalState.d.ts +0 -0
  72. /package/dist/cjs/{enums → src/enums}/proposalState.js +0 -0
  73. /package/dist/cjs/{governance.d.ts → src/governance.d.ts} +0 -0
  74. /package/dist/cjs/{governance.js → src/governance.js} +0 -0
  75. /package/dist/cjs/{interfaces → src/interfaces}/IChainClient.d.ts +0 -0
  76. /package/dist/cjs/{interfaces → src/interfaces}/IChainClient.js +0 -0
  77. /package/dist/cjs/{interfaces → src/interfaces}/index.d.ts +0 -0
  78. /package/dist/cjs/{interfaces → src/interfaces}/index.js +0 -0
  79. /package/dist/cjs/{interfaces → src/interfaces}/tradingLimit.d.ts +0 -0
  80. /package/dist/cjs/{interfaces → src/interfaces}/tradingLimit.js +0 -0
  81. /package/dist/cjs/{interfaces → src/interfaces}/tradingLimitsConfig.d.ts +0 -0
  82. /package/dist/cjs/{interfaces → src/interfaces}/tradingLimitsConfig.js +0 -0
  83. /package/dist/cjs/{interfaces → src/interfaces}/tradingLimitsState.d.ts +0 -0
  84. /package/dist/cjs/{interfaces → src/interfaces}/tradingLimitsState.js +0 -0
  85. /package/dist/cjs/{limits.d.ts → src/limits.d.ts} +0 -0
  86. /package/dist/cjs/{limits.js → src/limits.js} +0 -0
  87. /package/dist/cjs/{types → src/types}/contractAddressMap.d.ts +0 -0
  88. /package/dist/cjs/{types → src/types}/contractAddressMap.js +0 -0
  89. /package/dist/cjs/{types → src/types}/contractAddresses.d.ts +0 -0
  90. /package/dist/cjs/{types → src/types}/contractAddresses.js +0 -0
  91. /package/dist/cjs/{types → src/types}/index.d.ts +0 -0
  92. /package/dist/cjs/{types → src/types}/index.js +0 -0
  93. /package/dist/esm/{ChainClient.d.ts → src/ChainClient.d.ts} +0 -0
  94. /package/dist/esm/{ChainClient.js → src/ChainClient.js} +0 -0
  95. /package/dist/esm/{TestChainClient.d.ts → src/TestChainClient.d.ts} +0 -0
  96. /package/dist/esm/{TestChainClient.js → src/TestChainClient.js} +0 -0
  97. /package/dist/esm/{constants → src/constants}/addresses.d.ts +0 -0
  98. /package/dist/esm/{enums → src/enums}/index.d.ts +0 -0
  99. /package/dist/esm/{enums → src/enums}/index.js +0 -0
  100. /package/dist/esm/{enums → src/enums}/proposalState.d.ts +0 -0
  101. /package/dist/esm/{enums → src/enums}/proposalState.js +0 -0
  102. /package/dist/esm/{governance.d.ts → src/governance.d.ts} +0 -0
  103. /package/dist/esm/{governance.js → src/governance.js} +0 -0
  104. /package/dist/esm/{interfaces → src/interfaces}/IChainClient.d.ts +0 -0
  105. /package/dist/esm/{interfaces → src/interfaces}/IChainClient.js +0 -0
  106. /package/dist/esm/{interfaces → src/interfaces}/index.d.ts +0 -0
  107. /package/dist/esm/{interfaces → src/interfaces}/index.js +0 -0
  108. /package/dist/esm/{interfaces → src/interfaces}/tradingLimit.d.ts +0 -0
  109. /package/dist/esm/{interfaces → src/interfaces}/tradingLimit.js +0 -0
  110. /package/dist/esm/{interfaces → src/interfaces}/tradingLimitsConfig.d.ts +0 -0
  111. /package/dist/esm/{interfaces → src/interfaces}/tradingLimitsConfig.js +0 -0
  112. /package/dist/esm/{interfaces → src/interfaces}/tradingLimitsState.d.ts +0 -0
  113. /package/dist/esm/{interfaces → src/interfaces}/tradingLimitsState.js +0 -0
  114. /package/dist/esm/{limits.d.ts → src/limits.d.ts} +0 -0
  115. /package/dist/esm/{limits.js → src/limits.js} +0 -0
  116. /package/dist/esm/{types → src/types}/contractAddressMap.d.ts +0 -0
  117. /package/dist/esm/{types → src/types}/contractAddressMap.js +0 -0
  118. /package/dist/esm/{types → src/types}/contractAddresses.d.ts +0 -0
  119. /package/dist/esm/{types → src/types}/contractAddresses.js +0 -0
  120. /package/dist/esm/{types → src/types}/index.d.ts +0 -0
  121. /package/dist/esm/{types → src/types}/index.js +0 -0
@@ -0,0 +1,372 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasSpreadData = exports.getIntermediateToken = exports.selectBestRoute = exports.selectOptimalRoutes = exports.createTwoHopPair = exports.generateAllRoutes = exports.buildConnectivityStructures = void 0;
4
+ /**
5
+ * Builds the connectivity data structures needed for route generation.
6
+ *
7
+ * Transforms a list of direct trading pairs into our ConnectivityData
8
+ * that allow us to quickly find trading routes.
9
+ *
10
+ * **Construction Process:**
11
+ *
12
+ * ```
13
+ * Input: TradablePairs = [
14
+ * { id: 'cUSD-CELO', assets: [cUSD, CELO], path: [exchange1_CELO_cUSD] },
15
+ * { id: 'CELO-cEUR', assets: [CELO, cEUR], path: [exchange2_CELO_cEUR] }
16
+ * ]
17
+ *
18
+ * Step 1 - Build addrToSymbol map:
19
+ * cUSD.address → 'cUSD'
20
+ * CELO.address → 'CELO'
21
+ * cEUR.address → 'cEUR'
22
+ *
23
+ * Step 2 - Build directPathMap (sorted alphabetically for consistency):
24
+ * 'CELO_addr-cEUR_addr' → exchange2_CELO_cEUR
25
+ * 'CELO_addr-cUSD_addr' → exchange1_CELO_cUSD
26
+ *
27
+ * Step 3 - Build bidirectional tokenGraph:
28
+ * cUSD.address → Set([CELO.address])
29
+ * CELO.address → Set([cUSD.address, cEUR.address])
30
+ * cEUR.address → Set([CELO.address])
31
+ * ```
32
+ *
33
+ * **Result**: We can now efficiently answer:
34
+ * - "What's the symbol for address X?" → addrToSymbol.get(addr)
35
+ * - "What exchange connects tokens X and Y?" → directPathMap.get(sortedAddressPairKey)
36
+ * - "What tokens can I reach from token X?" → tokenGraph.get(X)
37
+ *
38
+ * @param directPairs - Array of direct trading pairs
39
+ * @returns Connectivity data structure for efficient route generation
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const directPairs = [
44
+ * { id: 'cUSD-CELO', assets: [cUSD, CELO], path: [exchange1] },
45
+ * { id: 'CELO-cEUR', assets: [CELO, cEUR], path: [exchange2] }
46
+ * ]
47
+ *
48
+ * const connectivityData = buildConnectivityStructures(directPairs)
49
+ *
50
+ * // Now we can efficiently find routes:
51
+ * // 1. Check if cUSD connects to anything: connectivityData.tokenGraph.get(cUSD.address) → [CELO.address]
52
+ * // 2. Check if CELO connects to cEUR: connectivityData.tokenGraph.get(CELO.address) → [cUSD.address, cEUR.address] ✓
53
+ * // 3. Get exchange details: connectivityData.directPathMap.get('CELO_addr-cEUR_addr') → exchange2_CELO_cEUR
54
+ * // Result: Found route cUSD → CELO → cEUR with exchange details
55
+ * ```
56
+ */
57
+ function buildConnectivityStructures(directPairs) {
58
+ const addrToSymbol = new Map();
59
+ const directPathMap = new Map();
60
+ const tokenGraph = new Map();
61
+ for (const pair of directPairs) {
62
+ const [assetA, assetB] = pair.assets;
63
+ // Build address-to-symbol map for quick symbol lookups
64
+ addrToSymbol.set(assetA.address, assetA.symbol);
65
+ addrToSymbol.set(assetB.address, assetB.symbol);
66
+ // Build direct path map (sorted addresses as key for consistency)
67
+ // for quick lookup of exchange details for any token pair
68
+ const sortedAddresses = [assetA.address, assetB.address]
69
+ .sort()
70
+ .join('-');
71
+ if (!directPathMap.has(sortedAddresses)) {
72
+ directPathMap.set(sortedAddresses, pair.path[0]);
73
+ }
74
+ // Build bidirectional connectivity graph for route traversal
75
+ // Each token can reach its directly connected tokens
76
+ if (!tokenGraph.has(assetA.address))
77
+ tokenGraph.set(assetA.address, new Set());
78
+ if (!tokenGraph.has(assetB.address))
79
+ tokenGraph.set(assetB.address, new Set());
80
+ tokenGraph.get(assetA.address).add(assetB.address);
81
+ tokenGraph.get(assetB.address).add(assetA.address);
82
+ }
83
+ return { addrToSymbol, directPathMap, tokenGraph, directPairs };
84
+ }
85
+ exports.buildConnectivityStructures = buildConnectivityStructures;
86
+ /**
87
+ * Generates all possible routes (direct + two-hop) using connectivity data.
88
+ *
89
+ * This function implements a route discovery algorithm that:
90
+ *
91
+ * 1. **Adds all direct pairs** (single-hop routes)
92
+ * 2. **Discovers two-hop routes** using graph traversal:
93
+ * - For each token A, find its neighbors (tokens directly connected)
94
+ * - For each neighbor B, find B's neighbors
95
+ * - If B connects to token C (C ≠ A), then A->B->C is a valid route
96
+ *
97
+ * **Route Deduplication**: Multiple routes between the same token pair
98
+ * are collected in arrays, allowing the selection algorithm to choose
99
+ * the best one based on spread data or heuristics.
100
+ *
101
+ * **Canonical Pair IDs**: All pairs use alphabetically sorted symbols
102
+ * (e.g., 'cEUR-cUSD' not 'cUSD-cEUR') for consistent identification.
103
+ *
104
+ * @param connectivityData - The connectivity data from buildConnectivityStructures()
105
+ * @returns Map of pair ID -> array of possible routes for that pair
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * // Given direct pairs: cUSD-CELO, CELO-cEUR, cUSD-USDC
110
+ * const allRoutes = generateAllRoutes(connectivityData)
111
+ *
112
+ * // Results might include:
113
+ * // 'cUSD-CELO' -> [{ path: [cUSD->CELO] }] // direct route
114
+ * // 'cEUR-cUSD' -> [
115
+ * // { path: [cUSD->USDC, USDC->cEUR] } // two-hop via USDC
116
+ * // { path: [cUSD->CELO, CELO->cEUR] } // two-hop via CELO
117
+ * // ]
118
+ * ```
119
+ */
120
+ function generateAllRoutes(connectivityData) {
121
+ const { addrToSymbol, directPathMap, tokenGraph, directPairs } = connectivityData;
122
+ const allRoutes = new Map();
123
+ // Step 1: Add all direct pairs (single-hop routes)
124
+ for (const pair of directPairs) {
125
+ if (!allRoutes.has(pair.id)) {
126
+ allRoutes.set(pair.id, []);
127
+ }
128
+ allRoutes.get(pair.id).push(pair);
129
+ }
130
+ // Step 2: Generate two-hop pairs using graph traversal
131
+ // Algorithm: For each token, explore all paths of length 2
132
+ // OUTER LOOP: "For each starting token..." (e.g., cUSD, CELO, cEUR, etc.)
133
+ for (const [start, neighbors] of tokenGraph.entries()) {
134
+ // MIDDLE LOOP: "Where can I go from the starting token?" (first hop)
135
+ // Example: If start = cUSD, neighbors might be [CELO, USDC, cKES]
136
+ for (const intermediate of neighbors) {
137
+ // Get all tokens reachable from this intermediate token (second hop destinations)
138
+ const secondHopNeighbors = tokenGraph.get(intermediate);
139
+ if (!secondHopNeighbors)
140
+ continue;
141
+ // INNER LOOP: "From the intermediate token, where can I go?" (second hop)
142
+ // Example: If intermediate = CELO, secondHopNeighbors might be [cUSD, cEUR, cBRL]
143
+ for (const end of secondHopNeighbors) {
144
+ // Skip circular routes like cUSD → CELO → cUSD (pointless)
145
+ if (end === start)
146
+ continue;
147
+ // At this point we have a potential route: start → intermediate → end
148
+ // Example: cUSD → CELO → cEUR
149
+ // Try to create a valid two-hop trading pair from this route
150
+ const twoHopPair = createTwoHopPair(start, intermediate, end, addrToSymbol, directPathMap);
151
+ // If we successfully created the pair, add it to our collection
152
+ if (twoHopPair) {
153
+ if (!allRoutes.has(twoHopPair.id)) {
154
+ allRoutes.set(twoHopPair.id, []);
155
+ }
156
+ allRoutes.get(twoHopPair.id).push(twoHopPair);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ return allRoutes;
162
+ }
163
+ exports.generateAllRoutes = generateAllRoutes;
164
+ /**
165
+ * Creates a two-hop tradable pair if valid exchange hops exist.
166
+ *
167
+ * 1. **Validates tokens exist** in the asset map
168
+ * 2. **Finds exchange hops** for both segments of the route
169
+ * 3. **Creates canonical pair structure** with sorted symbols
170
+ *
171
+ * **Route Structure**: The resulting pair represents trading from start->end
172
+ * via intermediate token, but the assets are ordered alphabetically by symbol
173
+ * for consistency (canonical form).
174
+ *
175
+ * **Path Representation**: The path array contains the actual exchange hops
176
+ * needed to execute the trade, preserving the routing information.
177
+ *
178
+ * @param startToken - Starting token address
179
+ * @param intermediate - Intermediate token address for routing
180
+ * @param end - Destination token address
181
+ * @param assetMap - Map of token address -> Asset details
182
+ * @param directPathMap - Map of token pairs -> exchange hop details
183
+ * @returns Route if valid route exists, null otherwise
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * // Create route: cUSD -> CELO -> cEUR
188
+ * const pair = createTwoHopPair(
189
+ * '0x765D...', // cUSD address
190
+ * '0x471E...', // CELO address
191
+ * '0xD876...', // cEUR address
192
+ * addrToSymbol,
193
+ * directPathMap
194
+ * )
195
+ *
196
+ * // Result:
197
+ * // {
198
+ * // id: 'cEUR-cUSD', // alphabetical order
199
+ * // assets: [cEUR, cUSD], // alphabetical order
200
+ * // path: [ // actual routing path
201
+ * // { cUSD->CELO exchange },
202
+ * // { CELO->cEUR exchange }
203
+ * // ]
204
+ * // }
205
+ * ```
206
+ */
207
+ function createTwoHopPair(startToken, intermediateToken, endToken, addrToSymbol, directPathMap) {
208
+ // Validate that both start and end tokens exist in our address-to-symbol map
209
+ const startSymbol = addrToSymbol.get(startToken);
210
+ const endSymbol = addrToSymbol.get(endToken);
211
+ if (!startSymbol || !endSymbol)
212
+ return null;
213
+ // Create Asset objects from address and symbol
214
+ const startAsset = { address: startToken, symbol: startSymbol };
215
+ const endAsset = { address: endToken, symbol: endSymbol };
216
+ // Find exchange hops for both segments of the two-hop route
217
+ // Keys are sorted token addresses for consistent lookup
218
+ const hop1Key = [startToken, intermediateToken].sort().join('-');
219
+ const hop2Key = [intermediateToken, endToken].sort().join('-');
220
+ const hop1 = directPathMap.get(hop1Key);
221
+ const hop2 = directPathMap.get(hop2Key);
222
+ // If either hop doesn't exist, this route is invalid
223
+ if (!hop1 || !hop2)
224
+ return null;
225
+ // Create canonical pair structure (alphabetical symbol ordering)
226
+ const sortedSymbols = [startSymbol, endSymbol].sort();
227
+ const pairId = `${sortedSymbols[0]}-${sortedSymbols[1]}`;
228
+ // Assets array follows alphabetical ordering for consistency
229
+ const assets = startSymbol <= endSymbol ? [startAsset, endAsset] : [endAsset, startAsset];
230
+ return {
231
+ id: pairId,
232
+ assets,
233
+ path: [hop1, hop2], // Preserves actual routing path for execution
234
+ };
235
+ }
236
+ exports.createTwoHopPair = createTwoHopPair;
237
+ /**
238
+ * Selects optimal routes from all candidates based on spread data or heuristics.
239
+ *
240
+ * This is the route optimization engine that implements the following logic:
241
+ *
242
+ * **For Single Route**: Use it directly (no optimization needed)
243
+ *
244
+ * **For Multiple Routes**:
245
+ * - If `returnAllRoutes=true`: Return all routes (used for cache generation)
246
+ * - If `returnAllRoutes=false`: Apply optimization to select the best route
247
+ *
248
+ * **Route Selection Strategy**: Delegates to `selectBestRoute()` which uses
249
+ * a multi-tier approach prioritizing cost efficiency and reliability.
250
+ *
251
+ * @param allRoutes - Map of pair ID -> array of possible routes
252
+ * @param returnAllRoutes - Whether to return all routes or optimize selection
253
+ * @param assetMap - Asset map for token symbol lookups during optimization
254
+ * @returns Array of selected optimal routes
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * // Multiple routes for cUSD-cEUR pair
259
+ * const candidates = new Map([
260
+ * ['cEUR-cUSD', [
261
+ * { path: [cUSD->CELO->cEUR], spreadData: { totalSpreadPercent: 0.5 } },
262
+ * { path: [cUSD->cREAL->cEUR], spreadData: { totalSpreadPercent: 0.3 } },
263
+ * { path: [cUSD->cEUR] } // direct route, no spread data
264
+ * ]]
265
+ * ])
266
+ *
267
+ * const optimal = selectOptimalRoutes(candidates, false, assetMap)
268
+ * // Returns the cUSD->cREAL->cEUR route (lowest spread: 0.3%)
269
+ * ```
270
+ */
271
+ function selectOptimalRoutes(allRoutes, returnAllRoutes, addrToSymbol) {
272
+ const result = new Map();
273
+ for (const [pairId, routes] of allRoutes) {
274
+ if (routes.length === 1) {
275
+ // Only one route available - use it directly
276
+ result.set(pairId, routes[0]);
277
+ }
278
+ else if (returnAllRoutes) {
279
+ // Return all routes with unique keys (used for cache generation)
280
+ routes.forEach((route, index) => {
281
+ result.set(`${pairId}_${index}`, route);
282
+ });
283
+ }
284
+ else {
285
+ // Multiple routes - select the best one using optimization logic
286
+ const bestRoute = selectBestRoute(routes, addrToSymbol);
287
+ result.set(pairId, bestRoute);
288
+ }
289
+ }
290
+ return Array.from(result.values());
291
+ }
292
+ exports.selectOptimalRoutes = selectOptimalRoutes;
293
+ /**
294
+ * Selects the best route from candidates using spread data or fallback heuristics.
295
+ *
296
+ * This function implements a sophisticated route selection algorithm with
297
+ * multiple optimization tiers:
298
+ *
299
+ * **Tier 1 - Spread-Based Optimization** (Preferred):
300
+ * - Use routes with spread data (actual cost information)
301
+ * - Select route with lowest `totalSpreadPercent`
302
+ * - This provides the most cost-efficient trading
303
+ *
304
+ * **Tier 2 - Direct Route Preference** (Fallback):
305
+ * - If no spread data available, prefer direct (single-hop) routes
306
+ * - Direct routes have lower execution risk and gas costs
307
+ *
308
+ * **Tier 3 - Major Stablecoin Preference** (Final Fallback):
309
+ * - For two-hop routes, prefer those going through major stablecoins
310
+ * - Major FX currencies like cUSD and cEUR typically have better liquidity
311
+ *
312
+ * **Tier 4 - First Available** (Last Resort):
313
+ * - If no other heuristics apply, use the first route found
314
+ *
315
+ * @param candidates - Array of possible routes for the same token pair
316
+ * @param assetMap - Asset map for token symbol lookups
317
+ * @returns The optimal route selected using the tier system
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * const candidates = [
322
+ * { path: [A->B->C], spreadData: { totalSpreadPercent: 0.8 } },
323
+ * { path: [A->D->C], spreadData: { totalSpreadPercent: 0.4 } }, // Winner: lowest spread
324
+ * { path: [A->C] }, // direct route, no spread data
325
+ * ]
326
+ *
327
+ * const best = selectBestRoute(candidates, assetMap)
328
+ * // Returns the A->D->C route (0.4% spread)
329
+ * ```
330
+ */
331
+ function selectBestRoute(candidates, addrToSymbol) {
332
+ // Tier 1: Prefer routes with spread data (lowest spread wins)
333
+ const candidatesWithSpread = candidates.filter(hasSpreadData);
334
+ if (candidatesWithSpread.length > 0) {
335
+ return candidatesWithSpread.reduce((best, current) => current.spreadData.totalSpreadPercent < best.spreadData.totalSpreadPercent
336
+ ? current
337
+ : best);
338
+ }
339
+ // Tier 2: Prefer direct routes (single-hop, lower risk)
340
+ const directRoute = candidates.find((c) => c.path.length === 1);
341
+ if (directRoute)
342
+ return directRoute;
343
+ // Tier 3: Prefer routes through major stablecoins (better liquidity)
344
+ const stablecoins = ['cUSD', 'cEUR', 'USDC', 'USDT'];
345
+ const routeWithStablecoin = candidates.find((candidate) => {
346
+ const intermediateToken = getIntermediateToken(candidate);
347
+ if (!intermediateToken)
348
+ return false;
349
+ const symbol = addrToSymbol.get(intermediateToken);
350
+ return symbol && stablecoins.includes(symbol);
351
+ });
352
+ // Tier 4: Use first available route as last resort
353
+ return routeWithStablecoin || candidates[0];
354
+ }
355
+ exports.selectBestRoute = selectBestRoute;
356
+ /**
357
+ * Extracts the intermediate token address from a two-hop route.
358
+ * In a two-hop route A->B->C, this function finds token B (the intermediate).
359
+ */
360
+ function getIntermediateToken(route) {
361
+ // Find the common token between the two hops
362
+ const [hop1, hop2] = route.path;
363
+ return hop1.assets.find((addr) => hop2.assets.includes(addr));
364
+ }
365
+ exports.getIntermediateToken = getIntermediateToken;
366
+ /**
367
+ * Type guard to check if a Route has spread data.
368
+ */
369
+ function hasSpreadData(pair) {
370
+ return 'spreadData' in pair && pair.spreadData !== undefined;
371
+ }
372
+ exports.hasSpreadData = hasSpreadData;
@@ -1,5 +1,6 @@
1
1
  import { BigNumberish, providers, Signer } from 'ethers';
2
2
  import { Address } from './interfaces';
3
+ import { TradablePair } from './mento';
3
4
  /**
4
5
  * Gets the chain ID from a signer or provider
5
6
  * @param signerOrProvider an ethers provider or signer
@@ -40,3 +41,10 @@ export declare function getSymbolFromTokenAddress(tokenAddr: Address, signerOrPr
40
41
  * @returns the populated TransactionRequest object
41
42
  */
42
43
  export declare function increaseAllowance(tokenAddr: string, spender: string, amount: BigNumberish, signerOrProvider: Signer | providers.Provider): Promise<providers.TransactionRequest>;
44
+ /**
45
+ * Find a token address by its symbol from tradable pairs
46
+ * @param pairs array of tradable pairs to search through
47
+ * @param symbol the token symbol to find (case-insensitive)
48
+ * @returns the token address if found, null otherwise
49
+ */
50
+ export declare function findTokenBySymbol(pairs: readonly TradablePair[], symbol: string): string | null;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.increaseAllowance = exports.getSymbolFromTokenAddress = exports.getBrokerAddressFromRegistry = exports.validateSignerOrProvider = exports.validateSigner = exports.getChainId = void 0;
12
+ exports.findTokenBySymbol = exports.increaseAllowance = exports.getSymbolFromTokenAddress = exports.getBrokerAddressFromRegistry = exports.validateSignerOrProvider = exports.validateSigner = exports.getChainId = void 0;
13
13
  const ethers_1 = require("ethers");
14
14
  /**
15
15
  * Gets the chain ID from a signer or provider
@@ -110,3 +110,20 @@ function increaseAllowance(tokenAddr, spender, amount, signerOrProvider) {
110
110
  });
111
111
  }
112
112
  exports.increaseAllowance = increaseAllowance;
113
+ /**
114
+ * Find a token address by its symbol from tradable pairs
115
+ * @param pairs array of tradable pairs to search through
116
+ * @param symbol the token symbol to find (case-insensitive)
117
+ * @returns the token address if found, null otherwise
118
+ */
119
+ function findTokenBySymbol(pairs, symbol) {
120
+ for (const pair of pairs) {
121
+ for (const asset of pair.assets) {
122
+ if (asset.symbol.toLowerCase() === symbol.toLowerCase()) {
123
+ return asset.address;
124
+ }
125
+ }
126
+ }
127
+ return null;
128
+ }
129
+ exports.findTokenBySymbol = findTokenBySymbol;
@@ -0,0 +1,5 @@
1
+ import { TradablePairWithSpread } from '../../src/constants/tradablePairs';
2
+ import { NETWORK_MAP, rpcUrls, SupportedChainId } from '../shared/network';
3
+ export { NETWORK_MAP, rpcUrls };
4
+ export { TradablePairWithSpread };
5
+ export type { SupportedChainId };
@@ -0,0 +1,3 @@
1
+ import { NETWORK_MAP, rpcUrls } from '../shared/network';
2
+ // Re-export network constants for backward compatibility
3
+ export { NETWORK_MAP, rpcUrls };
@@ -0,0 +1,20 @@
1
+ export declare const RPC_URLS: Record<number, string>;
2
+ export declare const CHAIN_NAMES: Record<number, string>;
3
+ export declare const CHAIN_NAME_TO_ID: Record<string, number>;
4
+ export declare const DEFAULT_CHAIN_ID = 42220;
5
+ export declare const DEFAULT_TIMEOUT_MS = 10000;
6
+ export declare const QUOTE_TIMEOUT_MS = 8000;
7
+ export declare const DEFAULT_BATCH_SIZE = 10;
8
+ export declare const DEFAULT_BATCH_DELAY_MS = 100;
9
+ export declare const DEFAULT_SPREAD_FALLBACK = 0.25;
10
+ export declare const MAX_DECIMAL_PLACES = 4;
11
+ export declare const TOKEN_ABI_FRAGMENT: readonly [{
12
+ readonly name: "decimals";
13
+ readonly type: "function";
14
+ readonly inputs: readonly [];
15
+ readonly outputs: readonly [{
16
+ readonly type: "uint8";
17
+ }];
18
+ readonly stateMutability: "view";
19
+ }];
20
+ export declare const DEFAULT_DECIMALS = 18;
@@ -0,0 +1,30 @@
1
+ export const RPC_URLS = {
2
+ 42220: 'https://forno.celo.org',
3
+ 44787: 'https://alfajores-forno.celo-testnet.org',
4
+ };
5
+ export const CHAIN_NAMES = {
6
+ 42220: 'Celo',
7
+ 44787: 'Alfajores',
8
+ };
9
+ export const CHAIN_NAME_TO_ID = {
10
+ celo: 42220,
11
+ alfajores: 44787,
12
+ };
13
+ export const DEFAULT_CHAIN_ID = 42220;
14
+ export const DEFAULT_TIMEOUT_MS = 10000;
15
+ export const QUOTE_TIMEOUT_MS = 8000; // Timeout for individual quote calculations
16
+ export const DEFAULT_BATCH_SIZE = 10;
17
+ export const DEFAULT_BATCH_DELAY_MS = 100;
18
+ export const DEFAULT_SPREAD_FALLBACK = 0.25;
19
+ export const MAX_DECIMAL_PLACES = 4;
20
+ // ERC20 token ABI fragment for decimals
21
+ export const TOKEN_ABI_FRAGMENT = [
22
+ {
23
+ name: 'decimals',
24
+ type: 'function',
25
+ inputs: [],
26
+ outputs: [{ type: 'uint8' }],
27
+ stateMutability: 'view',
28
+ },
29
+ ];
30
+ export const DEFAULT_DECIMALS = 18;
@@ -0,0 +1,25 @@
1
+ import { TradablePair } from '../../src/mento';
2
+ import { TradablePairWithSpread } from '../cacheTradablePairs/config';
3
+ /**
4
+ * Calculates the total spread for a trading route by compounding spreads across all hops.
5
+ *
6
+ * For multi-hop routes, spreads compound multiplicatively:
7
+ * - Single hop: spread = pairSpread
8
+ * - Multi-hop: spread = (1 - ((1 - spread1) * (1 - spread2) * ...))
9
+ *
10
+ * @param route - The route to calculate spread for
11
+ * @param allPairs - All available pairs with spread data
12
+ * @returns Total spread as a decimal (e.g., 0.005 = 0.5%)
13
+ */
14
+ export declare function calculateCompoundSpread(route: TradablePair, allPairs: readonly TradablePair[] | readonly TradablePairWithSpread[]): number;
15
+ /**
16
+ * Creates a human-readable display string for a trading route.
17
+ * Shows token symbols and route structure (e.g., "USDC → cUSD → cEUR").
18
+ *
19
+ * @param tradablePair - The route to display
20
+ * @param fromSymbol - Symbol of the input token
21
+ * @param toSymbol - Symbol of the output token
22
+ * @param allPairs - All pairs data for symbol resolution
23
+ * @returns Formatted route string
24
+ */
25
+ export declare function buildRouteDisplay(tradablePair: TradablePair, fromSymbol: string, toSymbol: string, allPairs: readonly TradablePair[] | readonly TradablePairWithSpread[]): string;
@@ -0,0 +1,161 @@
1
+ import { DEFAULT_SPREAD_FALLBACK } from './config';
2
+ /**
3
+ * Calculates the total spread for a trading route by compounding spreads across all hops.
4
+ *
5
+ * For multi-hop routes, spreads compound multiplicatively:
6
+ * - Single hop: spread = pairSpread
7
+ * - Multi-hop: spread = (1 - ((1 - spread1) * (1 - spread2) * ...))
8
+ *
9
+ * @param route - The route to calculate spread for
10
+ * @param allPairs - All available pairs with spread data
11
+ * @returns Total spread as a decimal (e.g., 0.005 = 0.5%)
12
+ */
13
+ export function calculateCompoundSpread(route, allPairs) {
14
+ if (route.path.length === 1) {
15
+ return getFixedSpreadForRoute(route, allPairs);
16
+ }
17
+ // For multi-hop routes, calculate compound spread
18
+ let compoundRate = 1; // Start with 100% (no loss)
19
+ for (const hop of route.path) {
20
+ const hopSpreadPercent = getHopSpreadPercent(hop, allPairs);
21
+ // Convert spread percentage to rate multiplier
22
+ // If spread is 0.25%, rate multiplier is 0.9975 (1 - 0.0025)
23
+ const hopRate = 1 - hopSpreadPercent / 100;
24
+ compoundRate *= hopRate;
25
+ }
26
+ // Convert back to spread percentage
27
+ return (1 - compoundRate) * 100;
28
+ }
29
+ /**
30
+ * Creates a human-readable display string for a trading route.
31
+ * Shows token symbols and route structure (e.g., "USDC → cUSD → cEUR").
32
+ *
33
+ * @param tradablePair - The route to display
34
+ * @param fromSymbol - Symbol of the input token
35
+ * @param toSymbol - Symbol of the output token
36
+ * @param allPairs - All pairs data for symbol resolution
37
+ * @returns Formatted route string
38
+ */
39
+ export function buildRouteDisplay(tradablePair, fromSymbol, toSymbol, allPairs) {
40
+ if (tradablePair.path.length === 1) {
41
+ return `${fromSymbol} → ${toSymbol}`;
42
+ }
43
+ const routeSymbols = buildRouteSymbols(tradablePair, fromSymbol, toSymbol, allPairs);
44
+ return routeSymbols.length < tradablePair.path.length + 1
45
+ ? `${fromSymbol} → ${toSymbol} (${tradablePair.path.length} hops)`
46
+ : routeSymbols.join(' → ');
47
+ }
48
+ function getFixedSpreadForRoute(route, allPairs) {
49
+ const matchingPair = findMatchingPair(route, allPairs);
50
+ if (matchingPair && hasSpreadData(matchingPair)) {
51
+ return matchingPair.spreadData
52
+ .totalSpreadPercent;
53
+ }
54
+ return route.path.length * DEFAULT_SPREAD_FALLBACK;
55
+ }
56
+ function getHopSpreadPercent(hop, allPairs) {
57
+ const directPair = allPairs.find((pair) => pair.path.length === 1 &&
58
+ // @ts-ignore - hop structure is known
59
+ pair.path[0].id === hop.id &&
60
+ // @ts-ignore - hop structure is known
61
+ pair.path[0].providerAddr === hop.providerAddr);
62
+ if (directPair && hasSpreadData(directPair)) {
63
+ return directPair.spreadData
64
+ .totalSpreadPercent;
65
+ }
66
+ return DEFAULT_SPREAD_FALLBACK;
67
+ }
68
+ function findMatchingPair(route, allPairs) {
69
+ return allPairs.find((pair) => {
70
+ if (pair.path.length !== route.path.length)
71
+ return false;
72
+ return route.path.every((hop, index) => {
73
+ const pairHop = pair.path[index];
74
+ return (hop.id === pairHop.id &&
75
+ hop.providerAddr === pairHop.providerAddr &&
76
+ hop.assets[0] === pairHop.assets[0] &&
77
+ hop.assets[1] === pairHop.assets[1]);
78
+ });
79
+ });
80
+ }
81
+ function hasSpreadData(pair) {
82
+ return ('spreadData' in pair &&
83
+ pair.spreadData &&
84
+ typeof pair.spreadData === 'object' &&
85
+ 'totalSpreadPercent' in pair.spreadData);
86
+ }
87
+ function buildRouteSymbols(tradablePair, fromSymbol, toSymbol, allPairs) {
88
+ var _a, _b;
89
+ const addressToSymbol = createAddressToSymbolMap(allPairs);
90
+ // Handle special case where user input might be different from actual symbol
91
+ // For example, user inputs "USDT" but actual symbol is "USD₮"
92
+ const actualFromSymbol = findActualSymbolForInput(fromSymbol, tradablePair.assets);
93
+ const actualToSymbol = findActualSymbolForInput(toSymbol, tradablePair.assets);
94
+ // Get the addresses from tradablePair.assets using actual symbols
95
+ const fromAddress = (_a = tradablePair.assets
96
+ .find((asset) => asset.symbol === actualFromSymbol)) === null || _a === void 0 ? void 0 : _a.address.toLowerCase();
97
+ const toAddress = (_b = tradablePair.assets
98
+ .find((asset) => asset.symbol === actualToSymbol)) === null || _b === void 0 ? void 0 : _b.address.toLowerCase();
99
+ if (!fromAddress || !toAddress) {
100
+ return [fromSymbol, toSymbol];
101
+ }
102
+ // For multi-hop routes, we need to find the common intermediate tokens
103
+ // First, collect all unique addresses in the path
104
+ const allAddresses = new Set();
105
+ tradablePair.path.forEach((hop) => {
106
+ hop.assets.forEach((addr) => {
107
+ allAddresses.add(addr.toLowerCase());
108
+ });
109
+ });
110
+ // Remove our from and to addresses to find intermediates
111
+ allAddresses.delete(fromAddress);
112
+ allAddresses.delete(toAddress);
113
+ // Convert intermediate addresses to symbols
114
+ const intermediateTokens = Array.from(allAddresses)
115
+ .map((addr) => addressToSymbol.get(addr))
116
+ .filter((symbol) => symbol !== undefined);
117
+ // Build the route: from -> intermediates -> to
118
+ const route = [fromSymbol];
119
+ // Add intermediate tokens
120
+ intermediateTokens.forEach((token) => {
121
+ if (!route.includes(token)) {
122
+ route.push(token);
123
+ }
124
+ });
125
+ // Add destination
126
+ route.push(toSymbol);
127
+ return route;
128
+ }
129
+ /**
130
+ * Finds the actual symbol in the tradable pair assets for a given input symbol.
131
+ * Handles special cases like "USDT" mapping to "USD₮".
132
+ */
133
+ function findActualSymbolForInput(inputSymbol, assets) {
134
+ // First try exact match
135
+ const exactMatch = assets.find((asset) => asset.symbol === inputSymbol);
136
+ if (exactMatch) {
137
+ return inputSymbol;
138
+ }
139
+ // Handle USDT special case
140
+ if (inputSymbol.toLowerCase() === 'usdt') {
141
+ // Look for USD₮ or other USDT variants
142
+ const usdtVariant = assets.find((asset) => {
143
+ const normalizedSymbol = asset.symbol.replace(/[^\w]/g, '').toLowerCase();
144
+ return normalizedSymbol === 'usdt' || asset.symbol === 'USD₮';
145
+ });
146
+ if (usdtVariant) {
147
+ return usdtVariant.symbol;
148
+ }
149
+ }
150
+ // Fallback to original input
151
+ return inputSymbol;
152
+ }
153
+ function createAddressToSymbolMap(allPairs) {
154
+ const addressToSymbol = new Map();
155
+ allPairs.forEach((pair) => {
156
+ pair.assets.forEach((asset) => {
157
+ addressToSymbol.set(asset.address.toLowerCase(), asset.symbol);
158
+ });
159
+ });
160
+ return addressToSymbol;
161
+ }