@gibs/bridge-sdk 1.13.1 → 1.17.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 (217) hide show
  1. package/README.md +244 -18
  2. package/abis/package.json +6 -0
  3. package/bridge-limits/package.json +6 -0
  4. package/chain-info/package.json +6 -0
  5. package/chain-names/package.json +6 -0
  6. package/chains/package.json +6 -0
  7. package/claims/package.json +6 -0
  8. package/config/package.json +6 -0
  9. package/consolidated-quote/package.json +6 -0
  10. package/destination-calldata/package.json +6 -0
  11. package/dist/chain-info.d.ts +1 -1
  12. package/dist/chain-info.js +11 -12
  13. package/dist/chains.d.ts +25 -0
  14. package/dist/chains.js +55 -20
  15. package/dist/cjs/abis.d.ts +1 -0
  16. package/dist/cjs/abis.js +19 -0
  17. package/dist/cjs/bridge-limits.d.ts +103 -0
  18. package/dist/cjs/bridge-limits.js +89 -0
  19. package/dist/cjs/chain-info.d.ts +169 -0
  20. package/dist/cjs/chain-info.js +452 -0
  21. package/dist/cjs/chain-names.d.ts +160 -0
  22. package/dist/cjs/chain-names.js +207 -0
  23. package/dist/cjs/chains.d.ts +90 -0
  24. package/dist/cjs/chains.js +357 -0
  25. package/dist/cjs/claims.d.ts +198 -0
  26. package/dist/cjs/claims.js +182 -0
  27. package/dist/cjs/config.d.ts +240 -0
  28. package/dist/cjs/config.js +537 -0
  29. package/dist/cjs/consolidated-quote.d.ts +648 -0
  30. package/dist/cjs/consolidated-quote.js +1048 -0
  31. package/dist/cjs/destination-calldata.d.ts +118 -0
  32. package/dist/cjs/destination-calldata.js +156 -0
  33. package/dist/cjs/ecosystems.d.ts +217 -0
  34. package/dist/cjs/ecosystems.js +298 -0
  35. package/dist/cjs/execution.d.ts +346 -0
  36. package/dist/cjs/execution.js +468 -0
  37. package/dist/cjs/fee-type.d.ts +10 -0
  38. package/dist/cjs/fee-type.js +10 -0
  39. package/dist/cjs/hyperliquid-execution.d.ts +152 -0
  40. package/dist/cjs/hyperliquid-execution.js +175 -0
  41. package/dist/cjs/image-links.d.ts +117 -0
  42. package/dist/cjs/image-links.js +153 -0
  43. package/dist/cjs/index.d.ts +23 -0
  44. package/dist/cjs/index.js +39 -0
  45. package/dist/cjs/lifi-execution-guard.d.ts +210 -0
  46. package/dist/cjs/lifi-execution-guard.js +409 -0
  47. package/dist/cjs/near-intents-conformance.d.ts +85 -0
  48. package/dist/cjs/near-intents-conformance.js +337 -0
  49. package/dist/cjs/near-intents-route.d.ts +47 -0
  50. package/dist/cjs/near-intents-route.js +303 -0
  51. package/dist/cjs/near-intents.d.ts +524 -0
  52. package/dist/cjs/near-intents.js +1071 -0
  53. package/dist/cjs/origin-deposit.d.ts +326 -0
  54. package/dist/cjs/origin-deposit.js +82 -0
  55. package/dist/cjs/package.json +3 -0
  56. package/dist/cjs/pre-quote.d.ts +198 -0
  57. package/dist/cjs/pre-quote.js +180 -0
  58. package/dist/cjs/prices.d.ts +107 -0
  59. package/dist/cjs/prices.js +172 -0
  60. package/dist/cjs/providers.d.ts +238 -0
  61. package/dist/cjs/providers.js +171 -0
  62. package/dist/cjs/quote-fees.d.ts +102 -0
  63. package/dist/cjs/quote-fees.js +201 -0
  64. package/dist/cjs/referral.d.ts +85 -0
  65. package/dist/cjs/referral.js +85 -0
  66. package/dist/cjs/relay-currencies.d.ts +47 -0
  67. package/dist/cjs/relay-currencies.js +95 -0
  68. package/dist/cjs/relay-depository.d.ts +2 -0
  69. package/dist/cjs/relay-depository.js +85 -0
  70. package/dist/cjs/relay-quote.d.ts +301 -0
  71. package/dist/cjs/relay-quote.js +996 -0
  72. package/dist/cjs/route-steps.d.ts +138 -0
  73. package/dist/cjs/route-steps.js +310 -0
  74. package/dist/cjs/routing/amounts.d.ts +80 -0
  75. package/dist/cjs/routing/amounts.js +126 -0
  76. package/dist/cjs/routing/candidate-route.d.ts +43 -0
  77. package/dist/cjs/routing/candidate-route.js +50 -0
  78. package/dist/cjs/routing/candidate.d.ts +237 -0
  79. package/dist/cjs/routing/candidate.js +100 -0
  80. package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
  81. package/dist/cjs/routing/carriers/ask-answer.js +2 -0
  82. package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
  83. package/dist/cjs/routing/carriers/omnibridge.js +459 -0
  84. package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
  85. package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
  86. package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
  87. package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
  88. package/dist/cjs/routing/carriers/pulsex.d.ts +159 -0
  89. package/dist/cjs/routing/carriers/pulsex.js +565 -0
  90. package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
  91. package/dist/cjs/routing/carriers/wrap.js +119 -0
  92. package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
  93. package/dist/cjs/routing/composed-route-quality.js +144 -0
  94. package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
  95. package/dist/cjs/routing/entry-chain-anchor.js +59 -0
  96. package/dist/cjs/routing/format.d.ts +31 -0
  97. package/dist/cjs/routing/format.js +64 -0
  98. package/dist/cjs/routing/index.d.ts +38 -0
  99. package/dist/cjs/routing/index.js +134 -0
  100. package/dist/cjs/routing/producers.d.ts +770 -0
  101. package/dist/cjs/routing/producers.js +1617 -0
  102. package/dist/cjs/routing/providers.d.ts +31 -0
  103. package/dist/cjs/routing/providers.js +48 -0
  104. package/dist/cjs/routing/route-data.d.ts +355 -0
  105. package/dist/cjs/routing/route-data.js +2 -0
  106. package/dist/cjs/routing/search/compose.d.ts +168 -0
  107. package/dist/cjs/routing/search/compose.js +104 -0
  108. package/dist/cjs/routing/search/graph.d.ts +668 -0
  109. package/dist/cjs/routing/search/graph.js +461 -0
  110. package/dist/cjs/routing/search/plan-route-data.d.ts +269 -0
  111. package/dist/cjs/routing/search/plan-route-data.js +848 -0
  112. package/dist/cjs/routing/search/score.d.ts +356 -0
  113. package/dist/cjs/routing/search/score.js +445 -0
  114. package/dist/cjs/routing/swap-transactions.d.ts +91 -0
  115. package/dist/cjs/routing/swap-transactions.js +131 -0
  116. package/dist/cjs/routing/usd-value.d.ts +17 -0
  117. package/dist/cjs/routing/usd-value.js +29 -0
  118. package/dist/cjs/settings.d.ts +785 -0
  119. package/dist/cjs/settings.js +1074 -0
  120. package/dist/cjs/ton-connect.d.ts +98 -0
  121. package/dist/cjs/ton-connect.js +206 -0
  122. package/dist/cjs/ton-execution.d.ts +7 -0
  123. package/dist/cjs/ton-execution.js +23 -0
  124. package/dist/cjs/ton.d.ts +37 -0
  125. package/dist/cjs/ton.js +83 -0
  126. package/dist/cjs/tron-execution.d.ts +146 -0
  127. package/dist/cjs/tron-execution.js +248 -0
  128. package/dist/cjs/types.d.ts +51 -0
  129. package/dist/cjs/types.js +2 -0
  130. package/dist/claims.d.ts +198 -0
  131. package/dist/claims.js +140 -0
  132. package/dist/config.d.ts +24 -0
  133. package/dist/config.js +39 -5
  134. package/dist/consolidated-quote.d.ts +109 -15
  135. package/dist/consolidated-quote.js +310 -52
  136. package/dist/execution.d.ts +346 -0
  137. package/dist/execution.js +428 -0
  138. package/dist/hyperliquid-execution.js +2 -3
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +3 -0
  141. package/dist/lifi-execution-guard.d.ts +13 -2
  142. package/dist/lifi-execution-guard.js +4 -0
  143. package/dist/near-intents.js +2 -3
  144. package/dist/pre-quote.d.ts +6 -6
  145. package/dist/pre-quote.js +10 -14
  146. package/dist/providers.d.ts +17 -0
  147. package/dist/providers.js +6 -0
  148. package/dist/referral.d.ts +12 -4
  149. package/dist/referral.js +12 -4
  150. package/dist/relay-depository.d.ts +2 -0
  151. package/dist/relay-depository.js +82 -0
  152. package/dist/relay-quote.js +2 -3
  153. package/dist/routing/candidate.d.ts +3 -3
  154. package/dist/routing/candidate.js +3 -3
  155. package/dist/routing/carriers/ask-answer.d.ts +17 -0
  156. package/dist/routing/carriers/omnibridge.js +23 -21
  157. package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
  158. package/dist/routing/carriers/pulsex-paths.js +92 -0
  159. package/dist/routing/carriers/pulsex-split.d.ts +175 -0
  160. package/dist/routing/carriers/pulsex-split.js +185 -0
  161. package/dist/routing/carriers/pulsex.d.ts +31 -21
  162. package/dist/routing/carriers/pulsex.js +141 -71
  163. package/dist/routing/composed-route-quality.d.ts +28 -6
  164. package/dist/routing/composed-route-quality.js +36 -5
  165. package/dist/routing/entry-chain-anchor.d.ts +1 -1
  166. package/dist/routing/entry-chain-anchor.js +1 -1
  167. package/dist/routing/index.d.ts +6 -2
  168. package/dist/routing/index.js +6 -2
  169. package/dist/routing/producers.js +1 -1
  170. package/dist/routing/providers.d.ts +5 -0
  171. package/dist/routing/providers.js +5 -0
  172. package/dist/routing/search/graph.d.ts +15 -0
  173. package/dist/routing/search/plan-route-data.d.ts +25 -0
  174. package/dist/routing/search/plan-route-data.js +43 -7
  175. package/dist/routing/search/score.d.ts +3 -0
  176. package/dist/routing/search/score.js +11 -3
  177. package/dist/routing/swap-transactions.d.ts +91 -0
  178. package/dist/routing/swap-transactions.js +125 -0
  179. package/dist/settings.d.ts +94 -1
  180. package/dist/settings.js +95 -3
  181. package/dist/ton-connect.d.ts +98 -0
  182. package/dist/ton-connect.js +200 -0
  183. package/dist/ton-execution.d.ts +4 -114
  184. package/dist/ton-execution.js +4 -244
  185. package/dist/ton.d.ts +37 -0
  186. package/dist/ton.js +65 -0
  187. package/docs/examples/example-a-ether-to-pulse.ts +300 -0
  188. package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
  189. package/docs/examples/shared.ts +261 -0
  190. package/docs/examples/tsconfig.json +18 -0
  191. package/docs/integration-guide.md +617 -0
  192. package/ecosystems/package.json +6 -0
  193. package/execution/package.json +6 -0
  194. package/fee-type/package.json +6 -0
  195. package/hyperliquid-execution/package.json +6 -0
  196. package/image-links/package.json +6 -0
  197. package/lifi-execution-guard/package.json +6 -0
  198. package/llms.txt +75 -10
  199. package/near-intents/package.json +6 -0
  200. package/near-intents-route/package.json +6 -0
  201. package/origin-deposit/package.json +6 -0
  202. package/package.json +817 -10
  203. package/pre-quote/package.json +6 -0
  204. package/prices/package.json +6 -0
  205. package/providers/package.json +6 -0
  206. package/quote-fees/package.json +6 -0
  207. package/referral/package.json +6 -0
  208. package/relay-currencies/package.json +6 -0
  209. package/relay-quote/package.json +6 -0
  210. package/route-steps/package.json +6 -0
  211. package/routing/package.json +6 -0
  212. package/settings/package.json +6 -0
  213. package/ton/package.json +6 -0
  214. package/ton-connect/package.json +6 -0
  215. package/ton-execution/package.json +6 -0
  216. package/tron-execution/package.json +6 -0
  217. package/types/package.json +6 -0
@@ -0,0 +1,459 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.withOmnibridgeEntryFusing = exports.createOmnibridgeCarrier = void 0;
37
+ const multicall_1 = require("@gibs/common/multicall");
38
+ const viem_1 = require("viem");
39
+ const abis = __importStar(require("../../abis.js"));
40
+ const bridge_limits_js_1 = require("../../bridge-limits.js");
41
+ const chains_js_1 = require("../../chains.js");
42
+ const config_js_1 = require("../../config.js");
43
+ const ecosystems_js_1 = require("../../ecosystems.js");
44
+ const providers_js_1 = require("../../providers.js");
45
+ const amounts_js_1 = require("../amounts.js");
46
+ const entry_chain_anchor_js_1 = require("../entry-chain-anchor.js");
47
+ const producers_js_1 = require("../producers.js");
48
+ const graph_js_1 = require("../search/graph.js");
49
+ /** Builds an {@link AssetNode} for an Ethereum-Virtual-Machine token. */
50
+ const evmNode = (token) => ({
51
+ chainId: token.chainId,
52
+ address: token.address,
53
+ decimals: token.decimals,
54
+ ecosystem: 'evm',
55
+ });
56
+ /**
57
+ * A directed pair of node keys, joined for use as a `Map` key. Never leaves
58
+ * this module — see `capabilities.ts`'s former `directedPairKey` doc for why
59
+ * a plain `|` join is enough here.
60
+ */
61
+ const directedPairKey = (from, to) => `${(0, graph_js_1.assetNodeKey)(from)}|${(0, graph_js_1.assetNodeKey)(to)}`;
62
+ /**
63
+ * Finds which omnibridge provider, if any, has a live pathway carrying funds
64
+ * from `fromChainId` to `toChainId`. Checks BOTH omnibridge providers rather
65
+ * than assuming one: `Providers.PULSECHAIN` carries the Ethereum pathway,
66
+ * `Providers.TOKENSEX` the Binance Smart Chain one.
67
+ */
68
+ const omnibridgeCarrierBetween = (fromChainId, toChainId, isProductionNetwork) => {
69
+ const fromChain = (0, config_js_1.toChain)(fromChainId);
70
+ const toChainKey = (0, config_js_1.toChain)(toChainId);
71
+ const omnibridgeProviders = [config_js_1.Providers.PULSECHAIN, config_js_1.Providers.TOKENSEX];
72
+ for (const provider of omnibridgeProviders) {
73
+ const group = (0, config_js_1.pathwayGroup)(provider, fromChain, isProductionNetwork);
74
+ if (group?.[toChainKey]) {
75
+ return provider;
76
+ }
77
+ }
78
+ return null;
79
+ };
80
+ /**
81
+ * Builds the omnibridge's directed edge map from the bridgeable pairs and the
82
+ * live pathway config, in both directions. INBOUND goes through
83
+ * {@link entryChainAnchorsForPair} so a native coin and its wrapped form both
84
+ * anchor the same PulseChain mint; OUTBOUND needs no aliasing.
85
+ */
86
+ const buildOmnibridgeEdgeMap = (bridgeablePairs, isProductionNetwork) => {
87
+ const edges = new Map();
88
+ for (const pair of bridgeablePairs) {
89
+ const inboundCarrier = omnibridgeCarrierBetween(pair.entryChainId, pair.pulsechainToken.chainId, isProductionNetwork);
90
+ if (inboundCarrier !== null) {
91
+ const anchors = (0, entry_chain_anchor_js_1.entryChainAnchorsForPair)({
92
+ entryChainToken: pair.entryChainToken,
93
+ pulsechainToken: pair.pulsechainToken,
94
+ entryChainId: pair.entryChainId,
95
+ });
96
+ for (const anchor of anchors) {
97
+ edges.set(directedPairKey(evmNode(anchor.token), evmNode(anchor.landsAs)), inboundCarrier);
98
+ }
99
+ }
100
+ const outboundCarrier = omnibridgeCarrierBetween(pair.pulsechainToken.chainId, pair.entryChainId, isProductionNetwork);
101
+ if (outboundCarrier !== null) {
102
+ edges.set(directedPairKey(evmNode(pair.pulsechainToken), evmNode(pair.entryChainToken)), outboundCarrier);
103
+ }
104
+ }
105
+ return edges;
106
+ };
107
+ /**
108
+ * Resolves one edge's crossing details from the live pathway config, or null
109
+ * when no pathway matches — the same "no live pathway" case `edgesFrom`
110
+ * already prunes, kept here as a guard rather than assumed away, because
111
+ * `prepare` walks every node pair itself rather than trusting the caller to
112
+ * hand it only edges this carrier already offered.
113
+ */
114
+ const resolveCrossing = (edge, isProductionNetwork) => {
115
+ const bridgeKey = [edge.carrier, (0, config_js_1.toChain)(edge.from.chainId), (0, config_js_1.toChain)(edge.to.chainId)];
116
+ const wrappedNativeAddress = config_js_1.nativeAssetOut[(0, config_js_1.toChain)(edge.from.chainId)];
117
+ const isNativeEntry = edge.from.address.toLowerCase() === viem_1.zeroAddress;
118
+ const entryTokenAddress = isNativeEntry && wrappedNativeAddress !== undefined ? (0, viem_1.getAddress)(wrappedNativeAddress) : (0, viem_1.getAddress)(edge.from.address);
119
+ const path = (0, config_js_1.pathway)(bridgeKey, isProductionNetwork, entryTokenAddress);
120
+ if (path === undefined)
121
+ return null;
122
+ const feeManagerHostChainId = path.feeManager === 'from' ? edge.from.chainId : edge.to.chainId;
123
+ const feeManagerHostAddress = path.feeManager === 'from' ? path.from : path.to;
124
+ return {
125
+ edge,
126
+ entryTokenAddress,
127
+ entryMediator: path.from,
128
+ limitsChainId: edge.from.chainId,
129
+ feeManagerHostAddress,
130
+ feeManagerHostChainId,
131
+ feeTypeKey: path.toHome ? config_js_1.FOREIGN_TO_HOME_FEE : config_js_1.HOME_TO_FOREIGN_FEE,
132
+ };
133
+ };
134
+ /** Groups values by a computed chain id, preserving the deduped-by-`keyOf` invariant callers rely on. */
135
+ const groupByChainId = (values, chainIdOf) => {
136
+ const groups = new Map();
137
+ for (const value of values) {
138
+ const chainId = chainIdOf(value);
139
+ const group = groups.get(chainId);
140
+ if (group === undefined) {
141
+ groups.set(chainId, [value]);
142
+ }
143
+ else {
144
+ group.push(value);
145
+ }
146
+ }
147
+ return groups;
148
+ };
149
+ /** Stage-one read: `minPerTx`, `maxAvailablePerTx` (an entry mediator's own bounds) and `feeManager()` (the fee contract's address) — every one a fixed-arity view call, so one multicall per chain covers all three kinds at once. */
150
+ const readStageOneBatch = async ({ resolvedCrossings, clientForChain, }) => {
151
+ const limitsByKey = new Map();
152
+ const feeManagerAddressByKey = new Map();
153
+ const limitsRequests = new Map();
154
+ for (const crossing of resolvedCrossings) {
155
+ limitsRequests.set(limitsKey(crossing), {
156
+ chainId: crossing.limitsChainId,
157
+ mediator: crossing.entryMediator,
158
+ token: crossing.entryTokenAddress,
159
+ });
160
+ }
161
+ const feeManagerAddressRequests = new Map();
162
+ for (const crossing of resolvedCrossings) {
163
+ feeManagerAddressRequests.set(feeManagerHostKey(crossing), {
164
+ chainId: crossing.feeManagerHostChainId,
165
+ mediatorHost: crossing.feeManagerHostAddress,
166
+ });
167
+ }
168
+ const requestsByChain = groupByChainId([
169
+ ...Array.from(limitsRequests.entries()).map(([key, request]) => ({ kind: 'limits', key, ...request })),
170
+ ...Array.from(feeManagerAddressRequests.entries()).map(([key, request]) => ({
171
+ kind: 'fee-manager-address',
172
+ key,
173
+ chainId: request.chainId,
174
+ mediatorHost: request.mediatorHost,
175
+ })),
176
+ ], (request) => request.chainId);
177
+ await Promise.all(Array.from(requestsByChain.entries()).map(async ([chainId, requests]) => {
178
+ const client = clientForChain(chainId);
179
+ if (client === null)
180
+ return;
181
+ const results = await (0, multicall_1.multicallRead)({
182
+ chain: chains_js_1.chainsMetadata[(0, config_js_1.toChain)(chainId)],
183
+ client,
184
+ abi: bridge_limits_js_1.entryLimitsAbi,
185
+ calls: requests.flatMap((request) => request.kind === 'limits'
186
+ ? [
187
+ { functionName: 'minPerTx', args: [request.token], target: request.mediator, allowFailure: true },
188
+ { functionName: 'maxAvailablePerTx', args: [request.token], target: request.mediator, allowFailure: true },
189
+ ]
190
+ : [{ functionName: 'feeManager', abi: abis.inputBridge, args: [], target: request.mediatorHost, allowFailure: true }]),
191
+ });
192
+ let cursor = 0;
193
+ for (const request of requests) {
194
+ if (request.kind === 'limits') {
195
+ const minimumPerTransaction = results[cursor];
196
+ const availablePerTransaction = results[cursor + 1];
197
+ cursor += 2;
198
+ if (typeof minimumPerTransaction === 'bigint' && typeof availablePerTransaction === 'bigint') {
199
+ limitsByKey.set(request.key, { minimumPerTransaction, availablePerTransaction });
200
+ }
201
+ continue;
202
+ }
203
+ const feeManagerAddress = results[cursor];
204
+ cursor += 1;
205
+ // A DECODED address is exactly 42 characters ("0x" + 40 hex digits).
206
+ // `multicallRead` returns the UNDECODED revert bytes on a failed
207
+ // `allowFailure` call — also a `string`, usually a different length
208
+ // (`'0x'` for a bare revert) — so `typeof` alone cannot tell success
209
+ // from failure here the way it does for the `bigint` reads above.
210
+ if (typeof feeManagerAddress === 'string' && feeManagerAddress.length === 42) {
211
+ feeManagerAddressByKey.set(request.key, (0, viem_1.getAddress)(feeManagerAddress));
212
+ }
213
+ }
214
+ }));
215
+ return { limitsByKey, feeManagerAddressByKey };
216
+ };
217
+ /** Stage-two read: `getFee(feeTypeKey, address(0))` on every fee manager contract stage one resolved — needs the address stage one just learned, so it cannot join that multicall. */
218
+ const readStageTwoBatch = async ({ resolvedCrossings, feeManagerAddressByKey, clientForChain, }) => {
219
+ const feeRateByKey = new Map();
220
+ const feeRateRequests = new Map();
221
+ for (const crossing of resolvedCrossings) {
222
+ const feeManagerAddress = feeManagerAddressByKey.get(feeManagerHostKey(crossing));
223
+ if (feeManagerAddress === undefined)
224
+ continue;
225
+ feeRateRequests.set(feeRateKey(crossing, feeManagerAddress), {
226
+ chainId: crossing.feeManagerHostChainId,
227
+ feeManagerAddress,
228
+ feeTypeKey: crossing.feeTypeKey,
229
+ });
230
+ }
231
+ const requestsByChain = groupByChainId(Array.from(feeRateRequests.entries()), ([, request]) => request.chainId);
232
+ await Promise.all(Array.from(requestsByChain.entries()).map(async ([chainId, entries]) => {
233
+ const client = clientForChain(chainId);
234
+ if (client === null)
235
+ return;
236
+ const results = await (0, multicall_1.multicallRead)({
237
+ chain: chains_js_1.chainsMetadata[(0, config_js_1.toChain)(chainId)],
238
+ client,
239
+ abi: abis.feeManager,
240
+ calls: entries.map(([, request]) => ({
241
+ functionName: 'getFee',
242
+ args: [request.feeTypeKey, viem_1.zeroAddress],
243
+ target: request.feeManagerAddress,
244
+ allowFailure: true,
245
+ })),
246
+ });
247
+ entries.forEach(([key], index) => {
248
+ const rate = results[index];
249
+ if (typeof rate === 'bigint') {
250
+ feeRateByKey.set(key, rate);
251
+ }
252
+ });
253
+ }));
254
+ return feeRateByKey;
255
+ };
256
+ const limitsKey = (crossing) => `${crossing.limitsChainId}|${crossing.entryMediator}|${crossing.entryTokenAddress}`;
257
+ const feeManagerHostKey = (crossing) => `${crossing.feeManagerHostChainId}|${crossing.feeManagerHostAddress}`;
258
+ const feeRateKey = (crossing, feeManagerAddress) => `${crossing.feeManagerHostChainId}|${feeManagerAddress}|${crossing.feeTypeKey}`;
259
+ const preparedEdgeKey = (edge) => `${edge.carrier}|${(0, graph_js_1.assetNodeKey)(edge.from)}|${(0, graph_js_1.assetNodeKey)(edge.to)}`;
260
+ /**
261
+ * Batch-reads every entry-mediator bound and fee rate `nodes` implies, once,
262
+ * so {@link createOmnibridgeCarrier}'s `exact` can answer every crossing
263
+ * SYNCHRONOUSLY afterward — the owner's own model, "each individual step and
264
+ * its associated fees can be known ahead of time."
265
+ *
266
+ * TWO MULTICALLS PER CHAIN, NOT ONE: `getFee` needs the fee manager's
267
+ * address, which stage one is what LEARNS — the same two-stage shape
268
+ * `chain-info.ts`'s `loadBridgeFees` already uses for one pathway at a time,
269
+ * generalized here to batch across every crossing the search's node universe
270
+ * touches. `HOME_TO_FOREIGN_FEE`/`FOREIGN_TO_HOME_FEE` are not re-read from
271
+ * the chain — they are fixed `keccak256` constants (`config.ts`), so reading
272
+ * them per pathway (as `loadBridgeFees` still does, defensively) would only
273
+ * spend a third multicall confirming a value that cannot change.
274
+ *
275
+ * WATCH PAYLOAD SIZE (request-payload-size-traps.md). `nodes` is the search
276
+ * graph's own hub-token-restricted universe, not a full token list — this
277
+ * function reads one `minPerTx`/`maxAvailablePerTx` pair per DISTINCT
278
+ * (mediator, token) crossing in that universe, never per token a chain
279
+ * lists. `multicallRead` (`@gibs/common`) chunks by measured encoded size on
280
+ * top of that, so a universe large enough to matter still cannot repeat the
281
+ * 8.5 MB trap that memory records.
282
+ *
283
+ * @param nodes - every node the search might branch through
284
+ * @param edgesFrom - this carrier's own edge enumerator, reused to find every candidate crossing among `nodes`
285
+ * @param isProductionNetwork - see `CreateOmnibridgeCarrierOptions`'s own field
286
+ * @param clientForChain - see `CreateOmnibridgeCarrierOptions`'s own field
287
+ * @returns every resolved crossing's edge key, mapped to what `exact` needs — never thrown; a chain this host has no client for is simply left unanswered
288
+ */
289
+ const prepareOmnibridgeReads = async ({ nodes, edgesFrom, isProductionNetwork, clientForChain, }) => {
290
+ const edges = [];
291
+ for (const from of nodes) {
292
+ for (const to of nodes) {
293
+ if ((0, graph_js_1.assetNodeKey)(from) === (0, graph_js_1.assetNodeKey)(to))
294
+ continue;
295
+ edges.push(...edgesFrom(from, to));
296
+ }
297
+ }
298
+ const resolvedCrossings = edges
299
+ .map((edge) => resolveCrossing(edge, isProductionNetwork))
300
+ .filter((resolved) => resolved !== null);
301
+ const preparedByEdgeKey = new Map();
302
+ if (resolvedCrossings.length === 0)
303
+ return preparedByEdgeKey;
304
+ const { limitsByKey, feeManagerAddressByKey } = await readStageOneBatch({ resolvedCrossings, clientForChain });
305
+ const feeRateByKey = await readStageTwoBatch({ resolvedCrossings, feeManagerAddressByKey, clientForChain });
306
+ for (const crossing of resolvedCrossings) {
307
+ const feeManagerAddress = feeManagerAddressByKey.get(feeManagerHostKey(crossing));
308
+ const feePerEther = feeManagerAddress === undefined ? null : (feeRateByKey.get(feeRateKey(crossing, feeManagerAddress)) ?? null);
309
+ preparedByEdgeKey.set(preparedEdgeKey(crossing.edge), {
310
+ limits: limitsByKey.get(limitsKey(crossing)) ?? null,
311
+ feePerEther,
312
+ });
313
+ }
314
+ return preparedByEdgeKey;
315
+ };
316
+ /**
317
+ * The refusal an entry mediator's own bound produces, carrying the bound
318
+ * itself as a figure: `minimumAmount` below the floor, `availableAmount`
319
+ * above what is left today. A reader is told the number to type, so the
320
+ * number must survive here rather than live only inside the reason string.
321
+ *
322
+ * @param verdict - the crossed bound, in the entry token's base units
323
+ * @returns the refusal, in `edge.from`'s base units
324
+ */
325
+ const entryAmountRefusal = (verdict) => verdict.reason === 'below-minimum'
326
+ ? {
327
+ kind: 'below-minimum',
328
+ reason: `below the entry mediator's minimum of ${verdict.minimumPerTransaction} base units`,
329
+ minimumAmount: verdict.minimumPerTransaction,
330
+ }
331
+ : {
332
+ kind: 'above-available',
333
+ reason: `above what the entry mediator has left today (${verdict.availablePerTransaction} base units)`,
334
+ minimumAmount: null,
335
+ availableAmount: verdict.availablePerTransaction,
336
+ };
337
+ /**
338
+ * Builds the omnibridge crossing as a plug-in carrier.
339
+ *
340
+ * @param options - see {@link CreateOmnibridgeCarrierOptions}
341
+ * @returns the carrier — `id: 'omnibridge'`; every EDGE it offers carries the
342
+ * real `'pulsechain'`/`'tokensex'` provider id, since one registration can
343
+ * speak for either entry chain depending on the pair
344
+ */
345
+ const createOmnibridgeCarrier = (options) => {
346
+ const { bridgeablePairs, isProductionNetwork, readEntryLimits, readFeePerEther, clientForChain } = options;
347
+ const edgeMap = buildOmnibridgeEdgeMap(bridgeablePairs, isProductionNetwork);
348
+ const edgesFrom = (from, to) => {
349
+ if ((0, graph_js_1.assetNodeKey)(from) === (0, graph_js_1.assetNodeKey)(to))
350
+ return [];
351
+ const carrier = edgeMap.get(directedPairKey(from, to));
352
+ if (carrier === undefined)
353
+ return [];
354
+ return [{ carrier, from, to, fusesNext: false, origin: 'wallet', floors: [] }];
355
+ };
356
+ // Populated by `prepare`, read by `exact` — see that method's own doc for
357
+ // why a mutable closure here is safe: this carrier is a client-side,
358
+ // one-search-at-a-time object (quote-service.md: the omnibridge crossing
359
+ // is "never built into" the shared, multi-tenant display-quote service),
360
+ // so nothing else can be mutating this map while a search reads it.
361
+ let preparedByEdgeKey = new Map();
362
+ const prepare = async (nodes) => {
363
+ if (clientForChain === undefined)
364
+ return;
365
+ preparedByEdgeKey = await prepareOmnibridgeReads({ nodes, edgesFrom, isProductionNetwork, clientForChain });
366
+ };
367
+ const exact = (edge, amountIn) => {
368
+ const prepared = preparedByEdgeKey.get(preparedEdgeKey(edge));
369
+ if (prepared === undefined)
370
+ return null;
371
+ const verdict = (0, bridge_limits_js_1.checkEntryAmount)({ amount: amountIn, limits: prepared.limits });
372
+ if (!verdict.fits) {
373
+ return { outcome: 'refused', refusal: entryAmountRefusal(verdict) };
374
+ }
375
+ return { outcome: 'delivered', deliveredAmount: (0, amounts_js_1.netDeliveredAmount)(amountIn, prepared.feePerEther) };
376
+ };
377
+ const ask = async ({ edge, inputAmount }) => {
378
+ const limits = await readEntryLimits(edge);
379
+ const verdict = (0, bridge_limits_js_1.checkEntryAmount)({ amount: inputAmount, limits });
380
+ if (!verdict.fits) {
381
+ return { ok: false, refusal: entryAmountRefusal(verdict) };
382
+ }
383
+ const feePerEther = await readFeePerEther(edge);
384
+ const deliveredAmount = (0, amounts_js_1.netDeliveredAmount)(inputAmount, feePerEther);
385
+ return {
386
+ ok: true,
387
+ deliveredAmount,
388
+ // An exact local computation: the guaranteed figure IS the expected one.
389
+ minimumDeliveredAmount: deliveredAmount,
390
+ claimedDurationSeconds: producers_js_1.BRIDGE_SETTLEMENT_ETA_MINUTES * 60,
391
+ };
392
+ };
393
+ return {
394
+ id: 'omnibridge',
395
+ edgesFrom,
396
+ ask,
397
+ prepare,
398
+ exact,
399
+ isRateLimited: false,
400
+ // Crossing requires the reader's own wallet signature on the entry
401
+ // chain — never a by-hand deposit.
402
+ depositMode: 'wallet-signed',
403
+ sender: 'omnibridge',
404
+ settlementSeconds: producers_js_1.BRIDGE_SETTLEMENT_ETA_MINUTES * 60,
405
+ };
406
+ };
407
+ exports.createOmnibridgeCarrier = createOmnibridgeCarrier;
408
+ /**
409
+ * Wraps ANY carrier-shaped object (built-in or a host's own) with the
410
+ * omnibridge's OWN fuse rule: whether THIS carrier's execution, landing on
411
+ * arrival, also performs the omnibridge's entry call — so the two hops
412
+ * together cost the reader one signature instead of two.
413
+ *
414
+ * EVERY CONDITION IS A FACT THE PROVIDER'S OWN DESCRIPTOR STATES
415
+ * (`../../providers.ts`), never a name compared here:
416
+ *
417
+ * - it executes destination calls at all (`executesDestinationCalls` — true
418
+ * for LI.FI and Relay, false for NEAR Intents, which delivers to the
419
+ * reader's own wallet and stops);
420
+ * - it can do so FROM this hop's origin chain (`destinationCallOriginChainIds`
421
+ * — LI.FI's contract-calls endpoint is Ethereum only, so from Base or
422
+ * Arbitrum a LI.FI hop is an unfused delivery followed by a crossing the
423
+ * reader signs);
424
+ * - when the entry is the omnibridge's NATIVE one (the provider lands a
425
+ * native coin, and `wrapAndRelayTokens` is payable), it can attach value to
426
+ * the call (`destinationCallCarriesNativeValue`);
427
+ * - and the next hop really IS the omnibridge entry, landing on PulseChain
428
+ * (`relayTokens`/`wrapAndRelayTokens` both MINT there; any other
429
+ * destination is the omnibridge's OUTBOUND leg, which no arrival can make).
430
+ *
431
+ * THIS IS THE ONE PLACE `'pulsechain'`/`'tokensex'`/PulseChain'S CHAIN ID
432
+ * APPEAR IN A `fusesWithNext` CALLBACK — gibs's own registration
433
+ * (`packages/ui/src/lib/state/useRouteSearch.ts`) wraps `createLifiCarrier`/
434
+ * `createRelayCarrier` with this before registering them, so the generic
435
+ * search engine (`@gibs/quotes`'s `capabilities.ts`) never has to know what
436
+ * "the bridge" means.
437
+ *
438
+ * @param carrier - the carrier to wrap; its own shape is preserved, only `fusesWithNext` is added/replaced
439
+ * @returns the same carrier, with a `fusesWithNext` that recognizes the omnibridge entry call
440
+ */
441
+ const withOmnibridgeEntryFusing = (carrier) => {
442
+ const descriptor = (0, providers_js_1.findProviderById)(carrier.id);
443
+ return {
444
+ ...carrier,
445
+ fusesWithNext: (nextHop) => {
446
+ if (descriptor === null || !descriptor.executesDestinationCalls)
447
+ return false;
448
+ const nextHopIsOmnibridgeCrossing = nextHop.nextHopCarrier === config_js_1.Providers.PULSECHAIN || nextHop.nextHopCarrier === config_js_1.Providers.TOKENSEX;
449
+ if (!nextHopIsOmnibridgeCrossing || nextHop.nextHopDestinationChainId !== producers_js_1.pulsechainChainId)
450
+ return false;
451
+ const origins = descriptor.destinationCallOriginChainIds;
452
+ if (origins !== 'any' && !origins.includes(nextHop.hopFrom.chainId))
453
+ return false;
454
+ const entersNative = (0, ecosystems_js_1.isNativeAsset)(nextHop.nextHopFrom.address, nextHop.nextHopFrom.ecosystem);
455
+ return !entersNative || descriptor.destinationCallCarriesNativeValue;
456
+ },
457
+ };
458
+ };
459
+ exports.withOmnibridgeEntryFusing = withOmnibridgeEntryFusing;
@@ -0,0 +1,80 @@
1
+ import { type Hex } from 'viem';
2
+ /**
3
+ * Which swap path a PulseX hop takes. The hop no longer always goes through
4
+ * Wrapped PLS: the carrier prices every candidate path from the pool reserves
5
+ * it already read, and keeps the one that delivers the most. These are the
6
+ * pure parts of that choice — the hub list, the candidate paths, and the
7
+ * pick — kept apart from the carrier so each can be tested on its own.
8
+ */
9
+ /** One token a PulseX swap may pass through on its way from the input token to the output token. */
10
+ export type PulseXHubToken = {
11
+ /** The token's on-chain `symbol()`, for logs and tests only. Never used to match a token. */
12
+ readonly symbol: string;
13
+ /** The token's checksummed PulseChain address. Tokens are always matched by this. */
14
+ readonly address: Hex;
15
+ };
16
+ /**
17
+ * The tokens a PulseX swap may route through, Wrapped PLS first. Each address
18
+ * comes from a constant this package already holds; none is written here:
19
+ *
20
+ * - WPLS — `nativeAssetOut[Chains.PLS]` (`config.ts`).
21
+ * - PLSX, HEX, USDC, USDT — `pulsechainTokenAddresses` (`config.ts`).
22
+ * - DAI (from Ethereum) — `PULSECHAIN_USD_ANCHOR_ADDRESS` (`prices.ts`).
23
+ * - WETH (from Ethereum) — the Ethereum pathway's `bridgedNativeAssetOut` (`config.ts`).
24
+ *
25
+ * KEPT SMALL ON PURPOSE. Each hub adds pools to the one batched reserve read
26
+ * the carrier makes per search, and adds paths to every price it computes.
27
+ * Seven hubs give at most 20 candidate paths per hop (see
28
+ * {@link candidatePulseXPaths}).
29
+ */
30
+ export declare const pulseXHubTokens: readonly PulseXHubToken[];
31
+ /**
32
+ * Every swap path a PulseX hop from `from` to `to` may take:
33
+ *
34
+ * - direct: `[from, to]`;
35
+ * - one hub: `[from, hub, to]` for each hub, Wrapped PLS among them;
36
+ * - two hubs: `[from, wrappedNative, hub, to]` and `[from, hub, wrappedNative, to]`
37
+ * for each other hub — Wrapped PLS holds the deepest pools on PulseX, so it
38
+ * is the one middle token worth pairing with every other hub.
39
+ *
40
+ * A path that names a token twice is dropped. That removes every path whose
41
+ * hub IS `from` or `to`, and the same-token hop when `from` equals `to`.
42
+ *
43
+ * The fixed path this carrier used before — `[from, WPLS, to]`, or
44
+ * `[from, to]` when one side is WPLS — is always in the result, so the pick
45
+ * can only match or beat it.
46
+ *
47
+ * @param options.from - the pool token the swap spends
48
+ * @param options.to - the pool token the swap buys
49
+ * @param options.wrappedNative - Wrapped PLS
50
+ * @param options.hubs - the hub token addresses, Wrapped PLS included
51
+ * @returns the candidate paths, direct first, each at least two tokens long
52
+ */
53
+ export declare const candidatePulseXPaths: (options: {
54
+ readonly from: Hex;
55
+ readonly to: Hex;
56
+ readonly wrappedNative: Hex;
57
+ readonly hubs: readonly Hex[];
58
+ }) => readonly (readonly Hex[])[];
59
+ /** One candidate path, priced. */
60
+ export type PulseXPathQuote<Step> = {
61
+ /** The tokens the swap passes through, in order. */
62
+ readonly path: readonly Hex[];
63
+ /** What the path delivers for the trade, in the last token's base units. */
64
+ readonly deliveredAmount: bigint;
65
+ /** The pools the path crosses, in order — kept so the caller can measure price impact on the same pools. */
66
+ readonly steps: readonly Step[];
67
+ };
68
+ /**
69
+ * The priced path that delivers the most. A path that delivers nothing is
70
+ * never chosen. Among equal outputs the path with fewer pools wins, and among
71
+ * equal lengths the earlier candidate wins, so the pick is deterministic.
72
+ *
73
+ * Gas is not weighed beyond that tie-break: the search charges one flat gas
74
+ * figure per swap signature, whatever its path length (`route-search-gas.ts`
75
+ * in the interface), so there is no per-pool gas figure to subtract here.
76
+ *
77
+ * @param quotes - the priced candidates; paths with a missing pool are already left out
78
+ * @returns the best candidate, or null when none delivers anything
79
+ */
80
+ export declare const bestPulseXPathQuote: <Step>(quotes: readonly PulseXPathQuote<Step>[]) => PulseXPathQuote<Step> | null;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bestPulseXPathQuote = exports.candidatePulseXPaths = exports.pulseXHubTokens = void 0;
4
+ const viem_1 = require("viem");
5
+ const config_js_1 = require("../../config.js");
6
+ const prices_js_1 = require("../../prices.js");
7
+ /** Wrapped PLS: the first hub, and the middle token of every two-hub path. */
8
+ const wrappedPulseAddress = (0, viem_1.getAddress)(config_js_1.nativeAssetOut[config_js_1.Chains.PLS]);
9
+ /** The Ethereum-bridged WETH on PulseChain, read off the Ethereum pathway's own config. Undefined only if that pathway is removed. */
10
+ const bridgedWrappedEtherAddress = config_js_1.pathways[config_js_1.Providers.PULSECHAIN][config_js_1.Chains.PLS]?.[config_js_1.Chains.ETH]?.bridgedNativeAssetOut;
11
+ /**
12
+ * The tokens a PulseX swap may route through, Wrapped PLS first. Each address
13
+ * comes from a constant this package already holds; none is written here:
14
+ *
15
+ * - WPLS — `nativeAssetOut[Chains.PLS]` (`config.ts`).
16
+ * - PLSX, HEX, USDC, USDT — `pulsechainTokenAddresses` (`config.ts`).
17
+ * - DAI (from Ethereum) — `PULSECHAIN_USD_ANCHOR_ADDRESS` (`prices.ts`).
18
+ * - WETH (from Ethereum) — the Ethereum pathway's `bridgedNativeAssetOut` (`config.ts`).
19
+ *
20
+ * KEPT SMALL ON PURPOSE. Each hub adds pools to the one batched reserve read
21
+ * the carrier makes per search, and adds paths to every price it computes.
22
+ * Seven hubs give at most 20 candidate paths per hop (see
23
+ * {@link candidatePulseXPaths}).
24
+ */
25
+ exports.pulseXHubTokens = Object.freeze([
26
+ { symbol: 'WPLS', address: wrappedPulseAddress },
27
+ { symbol: 'PLSX', address: config_js_1.pulsechainTokenAddresses.PLSX },
28
+ { symbol: 'HEX', address: config_js_1.pulsechainTokenAddresses.HEX },
29
+ { symbol: 'DAI', address: (0, viem_1.getAddress)(prices_js_1.PULSECHAIN_USD_ANCHOR_ADDRESS) },
30
+ { symbol: 'USDC', address: config_js_1.pulsechainTokenAddresses.USDC },
31
+ { symbol: 'USDT', address: config_js_1.pulsechainTokenAddresses.USDT },
32
+ ...(bridgedWrappedEtherAddress === undefined ? [] : [{ symbol: 'WETH', address: (0, viem_1.getAddress)(bridgedWrappedEtherAddress) }]),
33
+ ].map((hub) => Object.freeze(hub)));
34
+ /** Whether a path names any token twice, compared without regard to letter case. */
35
+ const repeatsAToken = (path) => new Set(path.map((address) => address.toLowerCase())).size !== path.length;
36
+ /** Whether two addresses are the same token, compared without regard to letter case. */
37
+ const sameToken = (left, right) => left.toLowerCase() === right.toLowerCase();
38
+ /**
39
+ * Every swap path a PulseX hop from `from` to `to` may take:
40
+ *
41
+ * - direct: `[from, to]`;
42
+ * - one hub: `[from, hub, to]` for each hub, Wrapped PLS among them;
43
+ * - two hubs: `[from, wrappedNative, hub, to]` and `[from, hub, wrappedNative, to]`
44
+ * for each other hub — Wrapped PLS holds the deepest pools on PulseX, so it
45
+ * is the one middle token worth pairing with every other hub.
46
+ *
47
+ * A path that names a token twice is dropped. That removes every path whose
48
+ * hub IS `from` or `to`, and the same-token hop when `from` equals `to`.
49
+ *
50
+ * The fixed path this carrier used before — `[from, WPLS, to]`, or
51
+ * `[from, to]` when one side is WPLS — is always in the result, so the pick
52
+ * can only match or beat it.
53
+ *
54
+ * @param options.from - the pool token the swap spends
55
+ * @param options.to - the pool token the swap buys
56
+ * @param options.wrappedNative - Wrapped PLS
57
+ * @param options.hubs - the hub token addresses, Wrapped PLS included
58
+ * @returns the candidate paths, direct first, each at least two tokens long
59
+ */
60
+ const candidatePulseXPaths = (options) => {
61
+ const { from, to, wrappedNative, hubs } = options;
62
+ const oneHubPaths = hubs.map((hub) => [from, hub, to]);
63
+ const twoHubPaths = hubs
64
+ .filter((hub) => !sameToken(hub, wrappedNative))
65
+ .flatMap((hub) => [
66
+ [from, wrappedNative, hub, to],
67
+ [from, hub, wrappedNative, to],
68
+ ]);
69
+ return [[from, to], ...oneHubPaths, ...twoHubPaths].filter((path) => !repeatsAToken(path));
70
+ };
71
+ exports.candidatePulseXPaths = candidatePulseXPaths;
72
+ /**
73
+ * Whether `candidate` should replace `best`: it delivers more, or it delivers
74
+ * the same and crosses fewer pools. A tie goes to the shorter path because
75
+ * each extra pool costs gas, and nothing else separates the two.
76
+ */
77
+ const beats = (candidate, best) => {
78
+ if (candidate.deliveredAmount !== best.deliveredAmount)
79
+ return candidate.deliveredAmount > best.deliveredAmount;
80
+ return candidate.path.length < best.path.length;
81
+ };
82
+ /**
83
+ * The priced path that delivers the most. A path that delivers nothing is
84
+ * never chosen. Among equal outputs the path with fewer pools wins, and among
85
+ * equal lengths the earlier candidate wins, so the pick is deterministic.
86
+ *
87
+ * Gas is not weighed beyond that tie-break: the search charges one flat gas
88
+ * figure per swap signature, whatever its path length (`route-search-gas.ts`
89
+ * in the interface), so there is no per-pool gas figure to subtract here.
90
+ *
91
+ * @param quotes - the priced candidates; paths with a missing pool are already left out
92
+ * @returns the best candidate, or null when none delivers anything
93
+ */
94
+ const bestPulseXPathQuote = (quotes) => quotes
95
+ .filter((quote) => quote.deliveredAmount > 0n)
96
+ .reduce((best, quote) => (best === null || beats(quote, best) ? quote : best), null);
97
+ exports.bestPulseXPathQuote = bestPulseXPathQuote;