@gibs/bridge-sdk 1.18.0 → 1.20.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 (120) hide show
  1. package/README.md +33 -0
  2. package/dist/abis.js +0 -2
  3. package/dist/bridge-limits.js +0 -38
  4. package/dist/chain-info.js +0 -116
  5. package/dist/chain-names.js +0 -151
  6. package/dist/chains.js +0 -198
  7. package/dist/cjs/abis.js +0 -2
  8. package/dist/cjs/bridge-limits.js +0 -38
  9. package/dist/cjs/chain-info.js +0 -116
  10. package/dist/cjs/chain-names.js +0 -151
  11. package/dist/cjs/chains.js +0 -198
  12. package/dist/cjs/claims.js +0 -60
  13. package/dist/cjs/config.js +1 -208
  14. package/dist/cjs/consolidated-quote.js +2 -464
  15. package/dist/cjs/destination-calldata.js +0 -71
  16. package/dist/cjs/ecosystems.js +0 -183
  17. package/dist/cjs/execution.js +0 -92
  18. package/dist/cjs/fee-type.js +0 -1
  19. package/dist/cjs/hyperliquid-execution.js +0 -98
  20. package/dist/cjs/image-links.js +0 -108
  21. package/dist/cjs/lifi-execution-guard.js +65 -172
  22. package/dist/cjs/near-intents-conformance.js +0 -110
  23. package/dist/cjs/near-intents-route.d.ts +13 -0
  24. package/dist/cjs/near-intents-route.js +7 -140
  25. package/dist/cjs/near-intents.js +0 -562
  26. package/dist/cjs/origin-deposit.js +0 -56
  27. package/dist/cjs/pre-quote.js +0 -80
  28. package/dist/cjs/prices.js +0 -113
  29. package/dist/cjs/providers.js +0 -83
  30. package/dist/cjs/quote-fees.js +0 -80
  31. package/dist/cjs/referral.js +0 -75
  32. package/dist/cjs/relay-currencies.js +0 -31
  33. package/dist/cjs/relay-depository.js +52 -77
  34. package/dist/cjs/relay-quote.js +0 -394
  35. package/dist/cjs/route-steps.js +0 -100
  36. package/dist/cjs/routing/amounts.js +0 -83
  37. package/dist/cjs/routing/candidate-route.d.ts +46 -1
  38. package/dist/cjs/routing/candidate-route.js +12 -40
  39. package/dist/cjs/routing/candidate.js +0 -46
  40. package/dist/cjs/routing/carrier-provider-key.js +0 -27
  41. package/dist/cjs/routing/carriers/omnibridge.js +0 -120
  42. package/dist/cjs/routing/carriers/pulsex-paths.js +0 -57
  43. package/dist/cjs/routing/carriers/pulsex-split.js +0 -92
  44. package/dist/cjs/routing/carriers/pulsex.js +0 -279
  45. package/dist/cjs/routing/carriers/wrap.js +0 -57
  46. package/dist/cjs/routing/composed-route-quality.js +0 -95
  47. package/dist/cjs/routing/entry-chain-anchor.js +0 -27
  48. package/dist/cjs/routing/format.js +0 -50
  49. package/dist/cjs/routing/index.d.ts +1 -1
  50. package/dist/cjs/routing/index.js +3 -30
  51. package/dist/cjs/routing/producers.js +0 -854
  52. package/dist/cjs/routing/providers.js +0 -27
  53. package/dist/cjs/routing/search/compose.js +0 -56
  54. package/dist/cjs/routing/search/graph.js +0 -364
  55. package/dist/cjs/routing/search/plan-route-data.js +5 -510
  56. package/dist/cjs/routing/search/score.js +0 -344
  57. package/dist/cjs/routing/swap-transactions.js +0 -74
  58. package/dist/cjs/routing/usd-value.js +0 -12
  59. package/dist/cjs/settings.js +1 -456
  60. package/dist/cjs/swap-venues.js +0 -17
  61. package/dist/cjs/ton-connect.js +0 -82
  62. package/dist/cjs/ton-execution.js +0 -6
  63. package/dist/cjs/ton.js +0 -42
  64. package/dist/cjs/tron-execution.js +0 -145
  65. package/dist/cjs/types.d.ts +8 -0
  66. package/dist/claims.js +0 -60
  67. package/dist/config.js +1 -211
  68. package/dist/consolidated-quote.js +2 -464
  69. package/dist/destination-calldata.js +0 -71
  70. package/dist/ecosystems.js +0 -183
  71. package/dist/execution.js +0 -92
  72. package/dist/fee-type.js +0 -2
  73. package/dist/hyperliquid-execution.js +0 -98
  74. package/dist/image-links.js +0 -108
  75. package/dist/lifi-execution-guard.js +65 -172
  76. package/dist/near-intents-conformance.js +0 -110
  77. package/dist/near-intents-route.d.ts +13 -0
  78. package/dist/near-intents-route.js +5 -139
  79. package/dist/near-intents.js +0 -562
  80. package/dist/origin-deposit.js +0 -56
  81. package/dist/pre-quote.js +0 -80
  82. package/dist/prices.js +0 -113
  83. package/dist/providers.js +0 -83
  84. package/dist/quote-fees.js +0 -80
  85. package/dist/referral.js +0 -75
  86. package/dist/relay-currencies.js +0 -31
  87. package/dist/relay-depository.js +52 -77
  88. package/dist/relay-quote.js +0 -394
  89. package/dist/route-steps.js +0 -100
  90. package/dist/routing/amounts.js +0 -83
  91. package/dist/routing/candidate-route.d.ts +46 -1
  92. package/dist/routing/candidate-route.js +10 -39
  93. package/dist/routing/candidate.js +0 -46
  94. package/dist/routing/carrier-provider-key.js +0 -27
  95. package/dist/routing/carriers/omnibridge.js +0 -120
  96. package/dist/routing/carriers/pulsex-paths.js +0 -57
  97. package/dist/routing/carriers/pulsex-split.js +0 -92
  98. package/dist/routing/carriers/pulsex.js +0 -279
  99. package/dist/routing/carriers/wrap.js +0 -57
  100. package/dist/routing/composed-route-quality.js +0 -95
  101. package/dist/routing/entry-chain-anchor.js +0 -27
  102. package/dist/routing/format.js +0 -50
  103. package/dist/routing/index.d.ts +1 -1
  104. package/dist/routing/index.js +1 -29
  105. package/dist/routing/producers.js +0 -854
  106. package/dist/routing/providers.js +0 -27
  107. package/dist/routing/search/compose.js +0 -56
  108. package/dist/routing/search/graph.js +0 -364
  109. package/dist/routing/search/plan-route-data.js +6 -511
  110. package/dist/routing/search/score.js +0 -344
  111. package/dist/routing/swap-transactions.js +0 -74
  112. package/dist/routing/usd-value.js +0 -12
  113. package/dist/settings.js +1 -456
  114. package/dist/swap-venues.js +0 -17
  115. package/dist/ton-connect.js +0 -82
  116. package/dist/ton-execution.js +0 -6
  117. package/dist/ton.js +0 -42
  118. package/dist/tron-execution.js +0 -145
  119. package/dist/types.d.ts +8 -0
  120. package/package.json +3 -3
@@ -34,236 +34,53 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.buildSameChainSwapRouteData = exports.buildPulseXSwapEstimateData = exports.buildConversionRouteData = exports.buildPulseXSwapSectionData = exports.buildOutboundRouteData = exports.buildDirectRouteData = exports.buildCandidateRouteData = exports.candidateNetValueUsd = exports.candidateOutsideFeesUsd = exports.candidateDeliveredUsd = exports.candidatePaidUsd = exports.usdNumber = exports.resolveEntryChainName = exports.providerDisplayName = exports.subtractUsdValues = exports.addUsdValues = exports.omnibridgeProviderDisplayName = exports.pulsechainChainId = exports.BRIDGE_SETTLEMENT_ETA_MINUTES = exports.PULSEX_MARGINAL_REFERENCE_DIVISOR = void 0;
37
- /**
38
- * Pure route-producer helpers and constants shared by every route builder.
39
- * Lives in the software development kit so the builders can relocate here in
40
- * subsequent tasks without pulling user-interface dependencies into the kit.
41
- *
42
- * All helpers are framework-agnostic: no React, no Svelte, no display formatters.
43
- */
44
37
  const config_js_1 = require("../config.js");
45
38
  const chain_names_js_1 = require("../chain-names.js");
46
39
  const usd_value_js_1 = require("./usd-value.js");
47
40
  const imageLinks = __importStar(require("../image-links.js"));
48
41
  const format_js_1 = require("./format.js");
49
42
  const viem_1 = require("viem");
50
- // ---------------------------------------------------------------------------
51
- // Constants
52
- // ---------------------------------------------------------------------------
53
- /**
54
- * The PulseX swap fee in basis points (hundredths of a percent). The deferred
55
- * outbound leg swaps the bridged intermediate token into the wanted token on the
56
- * entry chain at this rate, so the deferred section's fee is APPROXIMATE — the
57
- * binding quote is only known when the user returns. 29 basis points is roughly
58
- * 0.29%.
59
- */
60
43
  const PULSEX_SWAP_FEE_BASIS_POINTS = 29n;
61
- /** The denominator that turns {@link PULSEX_SWAP_FEE_BASIS_POINTS} into a fraction. */
62
44
  const BASIS_POINTS_DENOMINATOR = 10000n;
63
- /**
64
- * Divisor for the MARGINAL reference swap quote. A swap's price impact cannot be
65
- * read from the trade quote alone — `getAmountsOut(fullInput)` already folds the
66
- * impact into its output, and the PulseChain USD prices are themselves PulseX
67
- * pool quotes, so differencing them cancels the impact out (it vanishes to
68
- * $0.00). Instead the estimate quotes a TINY reference input — `1/this` of the
69
- * trade — whose own impact is negligible, giving the near-spot marginal rate. The
70
- * gap between that marginal rate and the full trade's realized rate IS the price
71
- * impact (the swap fee rides in BOTH quotes, so it cancels and the gap is impact
72
- * alone). 1/10000 of the trade is small enough to be effectively marginal yet
73
- * large enough that its output does not round away.
74
- */
75
45
  exports.PULSEX_MARGINAL_REFERENCE_DIVISOR = 10000n;
76
- /**
77
- * The approximate wall-clock wait for an omnibridge crossing to settle before a
78
- * DEFERRED swap leg can be signed — the same ~20 minutes whether the swap happens
79
- * on the entry chain (outbound PulseChain → Ethereum/Binance Smart Chain) or on
80
- * PulseChain (conversion route: bridge an anchor in, then swap on PulseX).
81
- * Surfaced compactly as `~20 min` in the deferred section head, mirroring how
82
- * every other section shows its estimate, rather than spelled out inside the
83
- * deferred note.
84
- */
85
46
  exports.BRIDGE_SETTLEMENT_ETA_MINUTES = 20;
86
- /**
87
- * The approximate wall-clock duration of the SWAP itself, in seconds — a PulseX
88
- * swap (or an entry-chain swap) settles in roughly ten seconds once signed. This
89
- * is the swap leg's OWN time, distinct from the ~20-minute crossing it waits
90
- * behind ({@link BRIDGE_SETTLEMENT_ETA_MINUTES}): the wait belongs on the BRIDGE
91
- * leg, the near-instant swap on the SWAP leg, so each section reads honestly
92
- * ("~20 min" to cross, then "~10 sec" to swap) rather than stamping the crossing's
93
- * twenty minutes onto the swap.
94
- */
95
47
  const PULSEX_SWAP_ETA_SECONDS = 10;
96
- /** Decimal chain id for PulseChain — the shared destination of every inbound route. */
97
48
  exports.pulsechainChainId = Number(config_js_1.Chains.PLS);
98
- // ---------------------------------------------------------------------------
99
- // Private backing maps
100
- // ---------------------------------------------------------------------------
101
- /**
102
- * The fully spelled-out display name of each omnibridge provider, keyed by the
103
- * raw provider identifier the builders carry. These are the headline names the
104
- * route strip shows as the route's bridge.
105
- *
106
- * Typed `Record<Provider, string>`, exhaustive over the two real omnibridge
107
- * crossings, so a third one added to `Providers` without a name here is a
108
- * compile error rather than a silent gap in {@link omnibridgeProviderDisplayName}.
109
- */
110
49
  const omnibridgeProviderDisplayNames = {
111
50
  [config_js_1.Providers.PULSECHAIN]: 'PulseChain Bridge',
112
51
  [config_js_1.Providers.TOKENSEX]: 'Tokens Express Bridge',
113
52
  };
114
- /**
115
- * Whether `value` genuinely names an omnibridge crossing, narrowing it to
116
- * {@link Provider}.
117
- *
118
- * @param value - a string that may or may not be a real omnibridge provider
119
- * @returns whether `value` is one of the two registered crossings
120
- */
121
53
  const isOmnibridgeProvider = (value) => value === config_js_1.Providers.PULSECHAIN || value === config_js_1.Providers.TOKENSEX;
122
- /**
123
- * The display name for an omnibridge crossing, fully spelled out. Narrowed to
124
- * exactly {@link Provider} — the two real crossings — rather than any string,
125
- * so passing an aggregator's `ProviderId` (or anything else) is a COMPILE
126
- * ERROR here, not a silently wrong name.
127
- *
128
- * PREFER THIS OVER {@link providerDisplayName} wherever the caller already
129
- * holds a genuine `Provider`. `providerDisplayName` exists only because some
130
- * callers (`RouteCandidate.omnibridgeProvider`, `RouteData.headlineProviderKey`)
131
- * are typed `string` at their own source and cannot be narrowed from here.
132
- *
133
- * @param provider - a real omnibridge provider
134
- * @returns the spelled-out provider name
135
- */
136
54
  const omnibridgeProviderDisplayName = (provider) => omnibridgeProviderDisplayNames[provider];
137
55
  exports.omnibridgeProviderDisplayName = omnibridgeProviderDisplayName;
138
- /**
139
- * The fully spelled-out display name of an omnibridge ENTRY chain, keyed by its
140
- * decimal chain id. Acronyms are spelled out rather than read from viem's terse
141
- * `name`. Ethereum and Binance Smart Chain are the only omnibridge entry points;
142
- * any other chain id has no entry and {@link resolveEntryChainName} returns null.
143
- */
144
56
  const entryChainDisplayName = {
145
57
  [Number(config_js_1.Chains.ETH)]: 'Ethereum',
146
58
  [Number(config_js_1.Chains.BNB)]: 'Binance Smart Chain',
147
59
  };
148
- // ---------------------------------------------------------------------------
149
- // Helpers
150
- // ---------------------------------------------------------------------------
151
- /**
152
- * Adds two nullable United States dollar NUMBERS. A null operand contributes
153
- * nothing; two nulls yield null so an all-unpriced route keeps a null cost rather
154
- * than a misleading 0.
155
- *
156
- * This sums the RAW values and leaves the single cent-rounding to the display
157
- * adapter (round(a + b)). It deliberately differs from the pre-split
158
- * `sumFormattedUsd`, which summed two already-cent-rounded strings
159
- * (round(a) + round(b)); the two can diverge by a cent when both operands carry
160
- * sub-cent fractions. Rounding the total once is the more accurate result and
161
- * keeps this helper free of any display formatter, so the raw producers stay
162
- * relocatable. See the "sums the raw total once" test for the locked behavior.
163
- *
164
- * @param first - the first dollar value, or null/undefined when absent
165
- * @param second - the second dollar value, or null/undefined when absent
166
- * @returns the sum, or null when both operands are absent
167
- */
168
60
  const addUsdValues = (first, second) => {
169
61
  if (first == null && second == null)
170
62
  return null;
171
63
  return (first ?? 0) + (second ?? 0);
172
64
  };
173
65
  exports.addUsdValues = addUsdValues;
174
- /**
175
- * Subtracts a cost from a dollar value on the SAME basis — the operation every
176
- * producer uses to derive what a leg delivers from what entered it.
177
- *
178
- * Unlike {@link addUsdValues} this is deliberately NOT symmetric about null: an
179
- * unknown starting value cannot be reduced, so it stays unknown, while an unknown
180
- * cost is treated as no cost. That asymmetry is what keeps a route with one
181
- * unpriced leg from reporting a delivered value it never established.
182
- *
183
- * @param value - the dollar value entering the leg, or null/undefined when unknown
184
- * @param cost - the leg's dollar cost, or null/undefined when unpriced
185
- * @returns the value less the cost, or null when the starting value is unknown
186
- */
187
66
  const subtractUsdValues = (value, cost) => {
188
67
  if (value == null)
189
68
  return null;
190
69
  return value - (cost ?? 0);
191
70
  };
192
71
  exports.subtractUsdValues = subtractUsdValues;
193
- /**
194
- * The display name for an omnibridge crossing, accepting a raw `string` rather
195
- * than {@link Provider}.
196
- *
197
- * PREFER {@link omnibridgeProviderDisplayName} INSTEAD wherever the caller
198
- * already holds a genuine `Provider` — that one is a compile error for
199
- * anything else. This wide version exists only for callers whose OWN field is
200
- * typed `string` at its source and so cannot be narrowed here:
201
- * `RouteCandidate.omnibridgeProvider` and `RouteData.headlineProviderKey`
202
- * (`routing/candidate.ts`, `routing/route-data.ts`).
203
- *
204
- * KEPT DELIBERATELY NON-THROWING, UNLIKE {@link providerById}. The obvious
205
- * next step — throw on an unrecognized key, the way `providerById` does for
206
- * an aggregator — was tried and reverted: `toRouteView.ts`'s headline
207
- * resolution calls this with `RouteData.headlineProviderKey`, a `string` this
208
- * repository does not otherwise guarantee is a real provider of ANY kind, and
209
- * an existing, deliberate test (`toRouteView.test.ts`, "headline falls back
210
- * to 'Omnibridge' for an unknown headline provider key") requires that a
211
- * genuinely-unrecognized key render a generic label rather than crash the
212
- * whole route card. A NEAR Intents leg being mislabeled "Omnibridge" was a
213
- * real defect (see `near-intents-route.ts`), fixed there by calling the
214
- * RIGHT function (`providerById(id).label`) instead of this one — not by
215
- * making this one less forgiving of input nobody validates before it arrives.
216
- *
217
- * `'PulseX'` IS RECOGNIZED HERE TOO, unlike an aggregator `ProviderId`, and for
218
- * a different reason than the omnibridge case: it needs no registry lookup —
219
- * there is exactly one PulseX, the label IS the key — so there is nothing an
220
- * aggregator-style `providerById(id).label` call would add. A same-chain
221
- * PulseX swap route (`RouteData.kind === 'swap'`) has NO omnibridge leg at
222
- * all, so falling back to "Omnibridge" for it would name a bridge that never
223
- * ran.
224
- *
225
- * @param provider - the raw provider key (e.g. `'pulsechain'`, `'PulseX'`)
226
- * @returns the spelled-out provider name, or "Omnibridge" when unregistered
227
- */
228
72
  const providerDisplayName = (provider) => {
229
73
  if (provider === 'PulseX')
230
74
  return 'PulseX';
231
75
  return isOmnibridgeProvider(provider) ? (0, exports.omnibridgeProviderDisplayName)(provider) : 'Omnibridge';
232
76
  };
233
77
  exports.providerDisplayName = providerDisplayName;
234
- /**
235
- * The fully spelled-out display name of an omnibridge entry chain, or null when
236
- * the chain id is null or is not one of the two entry points (Ethereum, Binance
237
- * Smart Chain). A null result makes route builders return null, so an
238
- * unrecognised destination leaves the strip empty rather than naming a chain it
239
- * cannot bridge to.
240
- *
241
- * @param entryChainId - the decimal entry chain id, or null for direct routes
242
- * @returns the spelled-out chain name, or null
243
- */
244
78
  const resolveEntryChainName = (entryChainId) => {
245
79
  if (entryChainId === null)
246
80
  return null;
247
81
  return entryChainDisplayName[entryChainId] ?? null;
248
82
  };
249
83
  exports.resolveEntryChainName = resolveEntryChainName;
250
- /**
251
- * Parses one of the aggregator's own United States dollar figures —
252
- * `fromAmountUsd` or `toAmountUsd` on a candidate's quote.
253
- *
254
- * `Number('')` IS `0`, NOT "UNKNOWN", and both the LI.FI and Relay quote
255
- * parsers can hand this module a blank amount string. Read with a bare
256
- * `Number()`, a blank `fromAmountUsd` priced a route's origin leg at zero
257
- * cost — one measured case showed a route reporting a net gain of +$273 that
258
- * had in fact cost real money, and led the strip on the strength of it — and
259
- * a blank `toAmountUsd` would do the same to what a direct route (no
260
- * omnibridge leg) reports as delivered. A dollar figure only means something
261
- * when it is a positive, finite number: blank text, non-numeric text, zero,
262
- * and a negative number are all treated as UNPRICED rather than free.
263
- *
264
- * @param raw - the provider's amount string, or null/undefined when absent
265
- * @returns the parsed value, or null when the field cannot be trusted
266
- */
267
84
  const usdNumber = (raw) => {
268
85
  if (raw == null || raw.trim() === '')
269
86
  return null;
@@ -271,74 +88,18 @@ const usdNumber = (raw) => {
271
88
  return Number.isFinite(value) && value > 0 ? value : null;
272
89
  };
273
90
  exports.usdNumber = usdNumber;
274
- /**
275
- * What the user pays for a candidate's origin leg, in United States dollars, as
276
- * the provider itself valued it (`fromAmountUsd`). This is the candidate's SOURCE
277
- * BASIS: every dollar figure the route reports is derived from it, so the costs
278
- * shown and the value delivered can never drift onto different price sources.
279
- *
280
- * @param candidate - the priced route candidate
281
- * @returns the amount paid in United States dollars, or null when the provider gave none
282
- */
283
- /**
284
- * Exported so `search/plan-route-data.ts` can generalize this SAME valuation
285
- * to a general-search plan's first hop, per the route-search design's
286
- * `RouteScoreInputs` doc ("import and reuse them; do not write a second
287
- * valuation") — a candidate-shaped module boundary, not a second formula.
288
- */
289
91
  const candidatePaidUsd = (candidate) => (0, exports.usdNumber)(candidate.quote.fromAmountUsd);
290
92
  exports.candidatePaidUsd = candidatePaidUsd;
291
- /**
292
- * The cost of a candidate, in United States dollars, as the difference between
293
- * what the user pays for the aggregator leg ({@link candidatePaidUsd}) and what the
294
- * delivered tokens are worth. Returns null when the provider gave no
295
- * `fromAmountUsd` or the delivered tokens are unpriced — the fee then shows only
296
- * what it can. The figure is clamped at zero because a fleeting price-source
297
- * skew should never render a negative fee.
298
- *
299
- * @param candidate - the priced route candidate
300
- * @returns the fee in United States dollars, or null when either side is unpriced
301
- */
302
93
  const candidateFeeUsd = (candidate) => {
303
94
  const paid = (0, exports.candidatePaidUsd)(candidate);
304
95
  if (paid === null)
305
96
  return null;
306
- // Charges levied outside `fromAmount` are part of the cost even though the
307
- // difference between paid and delivered cannot see them.
308
97
  const outside = (0, exports.candidateOutsideFeesUsd)(candidate);
309
98
  const delivered = (0, exports.candidateDeliveredUsd)(candidate);
310
99
  if (delivered === null)
311
100
  return null;
312
101
  return Math.max(0, paid + outside - delivered);
313
102
  };
314
- /**
315
- * What a candidate's delivered tokens are worth, in United States dollars.
316
- *
317
- * TWO SOURCES, AND WHICH ONE APPLIES IS DECIDED BY WHETHER A SECOND LEG
318
- * FOLLOWS — never by which one happens to be present.
319
- *
320
- * A route with no omnibridge leg ends where the provider drops the funds, so
321
- * the provider valued the very tokens the reader receives, in the same answer
322
- * and on the same feed as `fromAmountUsd`. The difference between the two is
323
- * then the route's cost and nothing else. This is the case that had no price at
324
- * all: the destination chain is not one the bridge's own price sources cover,
325
- * so `destinationUsdPrice` arrived null and every direct route reported no fee.
326
- *
327
- * A route that continues through the omnibridge must NOT use it. The provider's
328
- * delivered side there is the entry chain's tokens, handed to the mediator —
329
- * not what finally lands on PulseChain, which is what `deliveredAmount` counts.
330
- * Those two differ by the omnibridge's own fee, so reading the provider's
331
- * figure would report a crossing as free.
332
- *
333
- * Exported because the ranking must value a route on the SAME basis the route
334
- * states. The ranker used to price the delivered tokens against
335
- * `destinationUsdPrice` alone, which is null on every direct route, so every
336
- * direct route was unpriced and the ranker fell back to an order that could not
337
- * see what each route delivers.
338
- *
339
- * @param candidate - the priced route candidate
340
- * @returns the delivered value in United States dollars, or null when unpriced
341
- */
342
103
  const candidateDeliveredUsd = (candidate) => {
343
104
  if (candidate.omnibridgeProvider === null) {
344
105
  const quoted = (0, exports.usdNumber)(candidate.quote.toAmountUsd);
@@ -354,34 +115,7 @@ const candidateDeliveredUsd = (candidate) => {
354
115
  });
355
116
  };
356
117
  exports.candidateDeliveredUsd = candidateDeliveredUsd;
357
- /**
358
- * The share of the requested amount below which an unspent remainder is treated
359
- * as dust and not reported, in basis points (hundredths of a percent). Ten basis
360
- * points is 0.1%.
361
- *
362
- * A backward-solved quote lands a handful of base units away from the amount it
363
- * was asked for even when it consumes essentially all of it — the control case
364
- * measured eleven wei left over on a five-hundredths-of-an-ether transfer. A
365
- * remainder that small is rounding, not money the user gets to keep, and naming
366
- * it would add a line to every route that says nothing.
367
- */
368
118
  const UNSPENT_MATERIAL_BASIS_POINTS = 10n;
369
- /**
370
- * The origin tokens a candidate's quote does NOT consume — the difference between
371
- * what the user typed and what the backward-solved quote turned out to need.
372
- *
373
- * Valued from the candidate's OWN source basis (`fromAmountUsd` divided across
374
- * `fromAmount`) rather than from a separate price feed, for the same reason every
375
- * other figure on the route is: two feeds disagree, and a remainder priced against
376
- * a different one would reopen the gap this whole model exists to close.
377
- *
378
- * Returns null when the requested amount is unknown, when the quote consumed all
379
- * of it, or when the remainder is below {@link UNSPENT_MATERIAL_BASIS_POINTS} —
380
- * a route that spends what it was given says nothing about unspent tokens.
381
- *
382
- * @param candidate - the priced route candidate
383
- * @returns the unspent remainder, or null when there is none worth naming
384
- */
385
119
  const candidateUnspent = (candidate) => {
386
120
  const requested = candidate.requestedAmount;
387
121
  if (requested == null || requested <= 0n)
@@ -394,9 +128,6 @@ const candidateUnspent = (candidate) => {
394
128
  return null;
395
129
  }
396
130
  const paidUsd = (0, exports.candidatePaidUsd)(candidate);
397
- // The remainder's value is the paid basis scaled by the ratio of the two
398
- // amounts, which is exact regardless of the origin token's decimals and needs
399
- // no per-token price at all.
400
131
  const usdValue = paidUsd === null
401
132
  ? null
402
133
  : (paidUsd * Number((0, viem_1.formatUnits)(remainder, candidate.originToken.decimals))) /
@@ -408,86 +139,22 @@ const candidateUnspent = (candidate) => {
408
139
  usdValue: usdValue !== null && Number.isFinite(usdValue) ? usdValue : null,
409
140
  };
410
141
  };
411
- /**
412
- * A token address shortened for display, for example `0x1234…5678`. Preserves
413
- * short addresses that are already readable without truncation.
414
- *
415
- * @param address - the full token address
416
- * @returns the shortened address string
417
- */
418
142
  const shortenAddress = (address) => address.length > 11 ? `${address.slice(0, 6)}…${address.slice(-4)}` : address;
419
- /**
420
- * The token label for a step endpoint: the provider-supplied SYMBOL when present
421
- * (for example "USDC"), otherwise a SHORTENED address. Never the raw full address,
422
- * which is unreadable in the hop conversion line.
423
- *
424
- * @param symbol - the token symbol, or null when not supplied by the provider
425
- * @param address - the token's full on-chain address, used as fallback
426
- * @returns the label to display for this endpoint
427
- */
428
143
  const stepTokenLabel = (symbol, address) => symbol ?? shortenAddress(address);
429
- /**
430
- * The swap conversion label for a normalized aggregator route step — the
431
- * "from token → to token" string shown in the hop's conversion line. Returns null
432
- * when the step carries no token addresses (Relay steps are empty; the hop then
433
- * shows no conversion clause).
434
- *
435
- * @param step - one normalized aggregator route step
436
- * @returns the conversion string, or null when address data is absent
437
- */
438
144
  const stepConversion = (step) => {
439
145
  if (!step.fromToken || !step.toToken)
440
146
  return null;
441
147
  return `${stepTokenLabel(step.fromTokenSymbol, step.fromToken)} → ${stepTokenLabel(step.toTokenSymbol, step.toToken)}`;
442
148
  };
443
- /**
444
- * A fee rate as percent text: 3e15 reads "0.3%". Exact, because it scales the
445
- * integer and lets `formatUnits` place the point, rather than going through a
446
- * floating point number that prints 2.9699999999999998.
447
- */
448
149
  const rateAsPercentText = (rate) => `${(0, viem_1.formatUnits)(rate * 100n, 18)}%`;
449
- /** A label, with the rate beside it when the rate is known. */
450
150
  const labelWithRate = (label, rate) => rate == null ? label : `${label} (${rateAsPercentText(rate)})`;
451
- /** A dollar value as the cent string a {@link QuoteFee} carries, or null when unpriced. */
452
151
  const feeAmountUsdText = (value) => value === null ? null : (0, format_js_1.roundUsdToCents)(value).toFixed(2);
453
- /**
454
- * The token the bridge takes its own fee in.
455
- *
456
- * The fee manager lives on the home mediator, on PulseChain, and the pathway
457
- * says which side of the crossing that is. Leaving PulseChain it is `'from'`:
458
- * the home mediator takes the fee from the tokens it receives, before it sends
459
- * the message. Arriving it is `'to'`: the home mediator takes the fee from the
460
- * tokens it releases. So the fee is always in the PulseChain side's token.
461
- *
462
- * @returns the fee's token, or null when the bridge key names no pathway
463
- */
464
152
  const bridgeFeeToken = ({ bridgeKey, sourceToken, arrivingToken, }) => {
465
- // Not production-only: the testnet pathways live in a second map, and a
466
- // mainnet key finds its own pathway in the first map either way.
467
153
  const crossing = (0, config_js_1.pathway)(bridgeKey, false);
468
154
  if (!crossing)
469
155
  return null;
470
156
  return crossing.feeManager === 'from' ? sourceToken : arrivingToken;
471
157
  };
472
- /**
473
- * The cost of one omnibridge crossing: the bridge's own fee plus the Gibs
474
- * delivery fee, both valued on the route's source basis.
475
- *
476
- * A PARTIAL SUM IS NEVER A COST. When either fee is unknown the figure is null,
477
- * because the known half alone would read as the whole cost and understate it.
478
- * A delivery the reader did not ask for is not unknown: it is a real zero.
479
- *
480
- * The delivery fee is in the ARRIVING token: the destination router takes it
481
- * from the value that reaches it.
482
- *
483
- * EVERY AMOUNT IS COUNTED IN SOURCE BASE UNITS, because the settings cascade
484
- * derives both fees from `amountToBridge`, which is. So both are valued with the
485
- * source token's decimals and price, the same basis the route's delivered value
486
- * uses. The bridge keeps a token's decimals when it creates the counterpart, so
487
- * that count is also the count in the arriving token each charge names.
488
- *
489
- * @returns the crossing's cost and the charges behind it
490
- */
491
158
  const crossingCost = ({ bridgeKey, feeRoots, sourceToken, arrivingToken, sourceUsdPrice, }) => {
492
159
  const [provider] = bridgeKey;
493
160
  const { bridgeFeeAmount, bridgeFeeRate, deliveryFee } = feeRoots;
@@ -531,25 +198,11 @@ const crossingCost = ({ bridgeKey, feeRoots, sourceToken, arrivingToken, sourceU
531
198
  : bridgeFeeUsdValue + deliveryFeeUsdValue;
532
199
  return { feeUsdValue, fees: [...bridgeFees, ...deliveryFees] };
533
200
  };
534
- /** A token as the fee token a {@link QuoteFee} names, on the chain it is presented on. */
535
201
  const toFeeToken = (token, chainId) => ({
536
202
  symbol: token.symbol,
537
203
  decimals: token.decimals,
538
204
  chainId,
539
205
  });
540
- /**
541
- * The raw token reference for one end of a normalized aggregator step — the shape
542
- * an intermediate stop is rendered from.
543
- *
544
- * The provider's own token image wins when present. When it supplies none, the
545
- * logo is left UNSET for a `0x` address so the adapter derives it from the image
546
- * service, and set to null for a non-Ethereum address (Bitcoin, Solana, Tron, The
547
- * Open Network), which the image service cannot key by a checksummed address.
548
- * Decimals are omitted: an intermediate stop formats no amount.
549
- *
550
- * @param endpoint - one end of a normalized step
551
- * @returns the raw token reference for that end
552
- */
553
206
  const stepTokenRef = (endpoint) => ({
554
207
  symbol: endpoint.symbol ?? shortenAddress(endpoint.address),
555
208
  address: endpoint.address,
@@ -557,25 +210,18 @@ const stepTokenRef = (endpoint) => ({
557
210
  logoUri: endpoint.logoUri ?? (endpoint.address.startsWith('0x') ? undefined : null),
558
211
  chainLogoUri: imageLinks.networkOrPseudo(endpoint.chainId),
559
212
  });
560
- /** The token a step consumes, as a raw reference. */
561
213
  const stepFromTokenRef = (step) => stepTokenRef({
562
214
  address: step.fromToken,
563
215
  symbol: step.fromTokenSymbol,
564
216
  chainId: step.fromChainId,
565
217
  logoUri: step.fromTokenLogoUri,
566
218
  });
567
- /** The token a step produces, as a raw reference — the stop it lands the funds on. */
568
219
  const stepToTokenRef = (step) => stepTokenRef({
569
220
  address: step.toToken,
570
221
  symbol: step.toTokenSymbol,
571
222
  chainId: step.toChainId,
572
223
  logoUri: step.toTokenLogoUri,
573
224
  });
574
- /**
575
- * True when two references name the same token on the same chain. Hex addresses
576
- * compare case-insensitively (providers disagree on checksum casing); anything
577
- * else compares exactly, because a base58 address is genuinely case-sensitive.
578
- */
579
225
  const isSameTokenRef = (left, right) => {
580
226
  if (left.chainId !== right.chainId)
581
227
  return false;
@@ -585,20 +231,6 @@ const isSameTokenRef = (left, right) => {
585
231
  return false;
586
232
  return left.address.toLowerCase() === right.address.toLowerCase();
587
233
  };
588
- /**
589
- * The token flow a set of hops walks: the origin, then the token each hop
590
- * PRODUCES. Every entry except the first is a stop the funds actually reach, and
591
- * the last hop's output is the destination — so the intermediate stops are exactly
592
- * the hops minus the last one.
593
- *
594
- * A hop that names no output token contributes nothing (the flow still reaches the
595
- * destination through the hops that do), and a hop that produces the token the
596
- * flow is already holding is collapsed rather than drawn twice.
597
- *
598
- * @param origin - the token the route starts from
599
- * @param hops - the hops the signature performs, first to last
600
- * @returns the ordered token flow, origin first and destination last
601
- */
602
234
  const deriveTokenFlow = (origin, hops) => hops.reduce((flow, hop) => {
603
235
  const produced = hop.toToken;
604
236
  if (!produced)
@@ -608,24 +240,6 @@ const deriveTokenFlow = (origin, hops) => hops.reduce((flow, hop) => {
608
240
  return flow;
609
241
  return [...flow, produced];
610
242
  }, [origin]);
611
- /**
612
- * Everything a candidate is charged that sits OUTSIDE `fromAmount`, in United
613
- * States dollars.
614
- *
615
- * THIS REPLACES A PER-STEP GAS SUM that could only ever see part of the picture.
616
- * That sum read `step.gasUsd`, so it was ZERO for every Relay quote — Relay
617
- * publishes no per-leg breakdown — and for LI.FI it counted the network gas
618
- * while missing the larger charge: a LayerZero fee of about 1.37 dollars marked
619
- * `included: false`, paid on top in native currency. Reading the quote's own
620
- * normalized fee list catches both, and cannot double-count the per-step gas
621
- * against the aggregate the provider reports.
622
- *
623
- * A charge whose dollar value the provider did not give is skipped rather than
624
- * guessed. It is still named in the breakdown the reader can open.
625
- *
626
- * Exported for the same reason {@link candidatePaidUsd} is — `search/plan-route-data.ts`
627
- * reuses this exact valuation rather than restating it for a plan's last hop.
628
- */
629
243
  const candidateOutsideFeesUsd = (candidate) => {
630
244
  const fromQuote = candidate.quote.fees.reduce((total, fee) => {
631
245
  if (fee.includedInFromAmount) {
@@ -634,11 +248,6 @@ const candidateOutsideFeesUsd = (candidate) => {
634
248
  const value = Number(fee.amountUsd);
635
249
  return Number.isFinite(value) ? total + value : total;
636
250
  }, 0);
637
- // A provider that reports its network cost once, for the whole route, gives a
638
- // gas row here and that row is the whole of it. One that reports gas only per
639
- // leg gives no such row, and the per-step figures are then the only record of
640
- // it — counting them as well when the row IS present would bill the same gas
641
- // twice, so this is a fallback and never an addition.
642
251
  const reportsGasOnce = candidate.quote.fees.some((fee) => !fee.includedInFromAmount && fee.kind === 'gas');
643
252
  if (reportsGasOnce) {
644
253
  return fromQuote;
@@ -646,25 +255,6 @@ const candidateOutsideFeesUsd = (candidate) => {
646
255
  return fromQuote + candidate.quote.steps.reduce((total, step) => total + (step.gasUsd ?? 0), 0);
647
256
  };
648
257
  exports.candidateOutsideFeesUsd = candidateOutsideFeesUsd;
649
- /**
650
- * The user's NET dollar outcome for a candidate: the value delivered minus
651
- * everything paid to reach it. Three cost components:
652
- *
653
- * - the origin tokens spent (`quote.fromAmountUsd`);
654
- * - every charge paid on top ({@link candidateOutsideFeesUsd}), NOT part of
655
- * `fromAmount` — the sender's own network gas, and any provider fee the
656
- * quote marked as charged outside the amount;
657
- * - the omnibridge CROSSING fee, already netted into `deliveredAmount`.
658
- *
659
- * The delivered value comes from {@link candidateDeliveredUsd}, the same figure
660
- * the route's own dollar value and cost are built from. This is the one ranking
661
- * rule: the route strip orders by it, so the route the reader sees costing least
662
- * is the route that leads.
663
- *
664
- * @param candidate - the priced route candidate
665
- * @returns the net outcome in United States dollars, or null when the leg or
666
- * the delivered tokens are unpriced
667
- */
668
258
  const candidateNetValueUsd = (candidate) => {
669
259
  const paid = (0, exports.candidatePaidUsd)(candidate);
670
260
  if (paid === null)
@@ -675,41 +265,8 @@ const candidateNetValueUsd = (candidate) => {
675
265
  return delivered - paid - (0, exports.candidateOutsideFeesUsd)(candidate);
676
266
  };
677
267
  exports.candidateNetValueUsd = candidateNetValueUsd;
678
- /**
679
- * Builds the RAW, data-only {@link RouteData} for one priced aggregator candidate
680
- * — the framework-agnostic half of the candidate path, carrying NUMBERS, raw
681
- * token references, and raw provider keys ONLY. The display wrapper runs this
682
- * through `toRouteView` to resolve every logo, name, formatted string, and the
683
- * structural id.
684
- *
685
- * The candidate is a single signature (the aggregator swap auto-calls the
686
- * omnibridge), so it produces exactly one {@link RouteSectionData} whose hops are
687
- * the aggregator legs the provider reported (Relay reports none) followed by the
688
- * omnibridge crossing leg. Each hop carries its OWN numeric fee so the user sees
689
- * where cost accrues.
690
- *
691
- * @param candidate - one priced route candidate
692
- * @returns the raw, data-only route for the candidate
693
- */
694
268
  const buildCandidateRouteData = (candidate) => {
695
269
  const feeUsdValue = candidateFeeUsd(candidate);
696
- // THE PROVIDER'S OWN FIGURE, IN ITS OWN UNIT, CARRIED THROUGH UNROUNDED.
697
- //
698
- // This used to keep only `Math.ceil(seconds / 60)`, and that one expression
699
- // erased the difference the reader most needed to see. Measured live on Base
700
- // to Polygon: LI.FI claimed 29 seconds and Relay claimed 3, and both rendered
701
- // "~1 min". A reader comparing them had no way to tell a route that settles
702
- // before they look away from one that claims ten times the wait — so the
703
- // strip handed the choice to a 0.05% difference in delivered tokens, which is
704
- // nine cents on a hundred-and-twenty-six-dollar transfer.
705
- //
706
- // The section therefore carries the seconds and the view picks the unit (see
707
- // `sectionWaitLabel` in the route strip). `etaMinutes` stays beside it for the
708
- // route-level field and for every consumer that still reads minutes.
709
- //
710
- // It is the provider's CLAIM and nothing more. One live route ran about
711
- // seventy-five times its own estimate, which is why the strip labels this the
712
- // provider's estimate rather than presenting it as a fact.
713
270
  const etaSeconds = candidate.quote.executionDurationSeconds > 0 ? candidate.quote.executionDurationSeconds : null;
714
271
  const etaMinutes = candidate.quote.executionDurationSeconds > 0
715
272
  ? Math.ceil(candidate.quote.executionDurationSeconds / 60)
@@ -720,79 +277,38 @@ const buildCandidateRouteData = (candidate) => {
720
277
  ` entryChainId=${candidate.entryChainId}` +
721
278
  ` omnibridgeProvider=${candidate.omnibridgeProvider}` +
722
279
  ` quote.fromAmountUsd=${candidate.quote.fromAmountUsd}` +
723
- // BOTH delivered-value sources, because which one applies depends on
724
- // whether a second leg follows — naming only one sends whoever reads
725
- // this to the wrong place. See `candidateDeliveredUsd`.
726
280
  ` quote.toAmountUsd=${candidate.quote.toAmountUsd ?? null}` +
727
281
  ` destinationUsdPrice=${candidate.destinationUsdPrice}`);
728
282
  }
729
283
  const { originToken, destinationToken } = candidate;
730
- // Token logos are pre-resolved here via the image service (which lives in the
731
- // software development kit, so the producer stays framework-agnostic), passing
732
- // the FULL token so its own `logoURI` is preferred over the address-derived
733
- // fallback — matching the pre-split `buildCandidateTokenFlow` exactly.
734
284
  const originNode = {
735
285
  symbol: originToken.symbol,
736
286
  address: originToken.address,
737
287
  chainId: originToken.chainId,
738
288
  decimals: originToken.decimals,
739
- // `tokenImageOrChainFallback` — see the identical note on `destinationNode`
740
- // below; the same "address not resolvable yet" case applies to either end.
741
289
  logoUri: imageLinks.tokenImageOrChainFallback(originToken),
742
- // A pay-by-hand origin (Zcash) carries a local, invented chain id
743
- // `imageLinks.network` cannot look up by number — see `networkOrPseudo`.
744
290
  chainLogoUri: imageLinks.networkOrPseudo(originToken.chainId),
745
291
  };
746
- // Read up front, before the node below, so the node itself never claims
747
- // PulseChain for a candidate that does not enter the omnibridge. A candidate
748
- // whose omnibridge leg is absent (NEAR Intents, or any aggregator delivering
749
- // straight to its own destination chain) lands on `destinationToken.chainId`
750
- // — Base, in the case that pinned this — and a node hardcoded to PulseChain
751
- // named the wrong chain for every stop that reused it, including the
752
- // token-flow fallback below.
753
292
  const omnibridgeProviderKey = candidate.omnibridgeProvider;
754
- // THE CHAIN THIS SIGNATURE ACTUALLY ENDS ON. The label was the constant
755
- // 'Bridge to PulseChain', true of every route that existed when it was
756
- // written. A route the omnibridge does not carry ends somewhere else
757
- // entirely, and titled that way the tracker named a chain the funds never
758
- // touch — over a transfer to Base that had already landed.
759
293
  const landingChainId = omnibridgeProviderKey === null ? destinationToken.chainId : exports.pulsechainChainId;
760
294
  const destinationNode = {
761
295
  symbol: destinationToken.symbol,
762
296
  address: destinationToken.address,
763
297
  chainId: landingChainId,
764
298
  decimals: destinationToken.decimals,
765
- // `tokenImageOrChainFallback`, NOT `image` — a not-yet-bridged token's
766
- // address can still be unresolved when this producer draws the DELIVERED
767
- // node (see the repository's own "Not-Yet-Bridged Tokens" gotcha), and
768
- // `image` either throws on it or silently draws nothing. See that
769
- // helper's own note.
770
299
  logoUri: imageLinks.tokenImageOrChainFallback(destinationToken),
771
- // `networkOrPseudo`, matching `originNode` — a destination is not known to
772
- // ever be a deposit-address pseudo chain today, but the badge helper is the
773
- // one every other node in this builder goes through, and there is no reason
774
- // this one should be the exception that 404s if that ever changes.
775
300
  chainLogoUri: imageLinks.networkOrPseudo(landingChainId),
776
301
  };
777
302
  const aggregatorHops = candidate.quote.steps.map((step, index) => ({
778
303
  providerName: step.label,
779
- // The aggregator step carries its tool's real logo url straight from the quote
780
- // — raw data, not a derived view concern, so it rides on the raw hop.
781
304
  providerLogoUri: step.providerLogoUri,
782
305
  title: step.label,
783
306
  conversion: stepConversion(step),
784
- // The tokens this leg moves between, structurally — the section's token flow is
785
- // derived from these, so every stop the funds make is one the provider named.
786
307
  fromToken: stepFromTokenRef(step),
787
308
  toToken: stepToTokenRef(step),
788
309
  feeUsdValue: step.feeUsd,
789
- // The first hop is the user's single signature; later hops run inside it.
790
310
  automatic: index > 0,
791
311
  }));
792
- // NO BRIDGE LEG MEANS NO BRIDGE HOP. A candidate the omnibridge does not
793
- // carry ends where the aggregator leaves it, so the crossing hop below simply
794
- // does not exist — appending one would draw a step nobody performs and name a
795
- // bridge that is not involved.
796
312
  const omnibridgeHop = omnibridgeProviderKey === null
797
313
  ? null
798
314
  : {
@@ -800,8 +316,6 @@ const buildCandidateRouteData = (candidate) => {
800
316
  providerKey: omnibridgeProviderKey,
801
317
  title: 'Bridge to PulseChain',
802
318
  conversion: `→ ${candidate.destinationToken.symbol}`,
803
- // The crossing consumes whatever the last aggregator leg produced — the anchor
804
- // token on the entry chain — or the origin token when there were no legs.
805
319
  fromToken: aggregatorHops[aggregatorHops.length - 1]?.toToken ?? originNode,
806
320
  toToken: destinationNode,
807
321
  feeUsdValue: (0, usd_value_js_1.usdValueOfAmount)({
@@ -809,38 +323,17 @@ const buildCandidateRouteData = (candidate) => {
809
323
  decimals: candidate.destinationToken.decimals,
810
324
  price: candidate.destinationUsdPrice,
811
325
  }),
812
- // The omnibridge call is always auto-called by the aggregator leg before it.
813
326
  automatic: aggregatorHops.length > 0,
814
327
  };
815
328
  const hops = omnibridgeHop === null ? aggregatorHops : [...aggregatorHops, omnibridgeHop];
816
- // Origin, then every stop the funds reach on the way — NOT a collapsed pair. The
817
- // last hop is the crossing, so the flow still ends on the destination, EXCEPT
818
- // when the provider itemised no legs at all (Relay and NEAR Intents both report
819
- // zero `quote.steps`) and there is no omnibridge hop to supply one either — a
820
- // candidate delivering straight to its own destination chain. `deriveTokenFlow`
821
- // then has nothing to reduce over and stops at the origin, which is the exact
822
- // defect that showed a NEAR Intents route to Base USD Coin as "At ETH on
823
- // Ethereum": the pill drew the one-and-only flow node it was given, and that
824
- // node had never left the origin. The route's own `destinationToken` is known
825
- // regardless of what got itemised, so it is appended here whenever the flow
826
- // does not already end on it — deduplicated, so an itemised leg that DID reach
827
- // the destination is never drawn twice.
828
329
  const rawTokenFlow = deriveTokenFlow(originNode, hops);
829
330
  const lastStop = rawTokenFlow[rawTokenFlow.length - 1];
830
331
  const tokenFlow = lastStop && isSameTokenRef(lastStop, destinationNode) ? rawTokenFlow : [...rawTokenFlow, destinationNode];
831
332
  const section = {
832
- // The section id stays a stable string key derived from the candidate id —
833
- // section ids are within-route keys and do not need to be structural hashes.
834
333
  id: `${candidate.id}-signature-1`,
835
334
  label: `Bridge to ${(0, chain_names_js_1.chainDisplayName)(landingChainId)}`,
836
- // The signature is executed by the cross-chain AGGREGATOR (LI.FI / Relay) that
837
- // carries the bridge leg — that is the provider to surface on this section, not
838
- // the omnibridge headline.
839
335
  providerKey: candidate.quote.provider,
840
336
  tokenFlow,
841
- // What this signature leaves the user holding on PulseChain. Valued on the
842
- // same source basis as the route's output so the step's landing figure and
843
- // the route's delivered figure are the same number, not two derivations of it.
844
337
  landing: {
845
338
  amount: candidate.deliveredAmount,
846
339
  decimals: candidate.destinationToken.decimals,
@@ -849,8 +342,6 @@ const buildCandidateRouteData = (candidate) => {
849
342
  },
850
343
  unspent: candidateUnspent(candidate),
851
344
  feeUsdValue,
852
- // The itemization behind `feeUsdValue`, plus the charges that difference
853
- // cannot see: anything the provider levies outside `fromAmount`.
854
345
  fees: candidate.quote.fees,
855
346
  etaMinutes,
856
347
  etaSeconds,
@@ -858,26 +349,13 @@ const buildCandidateRouteData = (candidate) => {
858
349
  };
859
350
  return {
860
351
  kind: 'aggregated',
861
- // The entry chain this aggregator route crosses through (Ethereum or Binance
862
- // Smart Chain), carried explicitly so Bridge.tsx can link the selected route
863
- // back to its quoted entry candidate without string-parsing the id.
864
352
  entryChainId: candidate.entryChainId,
865
- // The route's headline provider is the omnibridge it crosses on — or, when
866
- // it crosses none, the aggregator that carries the whole journey. A route
867
- // has to be named after something that actually performs it.
868
353
  headlineProviderKey: omnibridgeProviderKey ?? candidate.quote.provider,
869
354
  sections: [section],
870
355
  output: {
871
356
  amount: candidate.deliveredAmount,
872
357
  decimals: candidate.destinationToken.decimals,
873
358
  symbol: candidate.destinationToken.symbol,
874
- // Valued on the SOURCE basis — what was paid, less this route's cost — NOT by
875
- // re-pricing the delivered token against its own feed. The two differ whenever
876
- // the destination asset trades away from its origin-chain counterpart, and that
877
- // difference used to land silently in this figure: the page then stated the
878
- // route's cost twice and the two never agreed. Deriving it keeps the fee clamp
879
- // above from opening a gap too. Falls back to the destination feed only when
880
- // the provider gave no `fromAmountUsd`, so an unpriced leg still shows what it can.
881
359
  usdValue: (0, exports.subtractUsdValues)((0, exports.candidatePaidUsd)(candidate), feeUsdValue) ??
882
360
  (0, usd_value_js_1.usdValueOfAmount)({
883
361
  balance: candidate.deliveredAmount,
@@ -885,37 +363,17 @@ const buildCandidateRouteData = (candidate) => {
885
363
  price: candidate.destinationUsdPrice,
886
364
  }),
887
365
  },
888
- // The aggregate cost is the single section's spread (this route is one
889
- // signature), so it equals the section fee; null when the leg is unpriced.
890
366
  totalCostUsdValue: feeUsdValue,
891
- // The net dollar outcome this route is ranked on (delivered value minus origin
892
- // tokens paid, network gas, and the omnibridge crossing fee).
893
367
  netValueUsd: (0, exports.candidateNetValueUsd)(candidate),
894
368
  etaMinutes,
895
369
  };
896
370
  };
897
371
  exports.buildCandidateRouteData = buildCandidateRouteData;
898
- // ---------------------------------------------------------------------------
899
- // Producer: direct route
900
- // ---------------------------------------------------------------------------
901
- /**
902
- * Builds the ALWAYS-PRESENT direct omnibridge route from the current outbound
903
- * settings, so the strip is never empty for a resolvable pathway.
904
- *
905
- * The direct route is a SINGLE SIGNATURE — the user signs the omnibridge call
906
- * once — so it is exactly ONE {@link RouteSectionData}. Returns null only when
907
- * the pathway is NOT resolvable (a missing input or output asset).
908
- *
909
- * @param roots - the resolved settings + price + logo roots (see {@link DirectRouteRoots})
910
- * @returns the single direct {@link RouteData}, or null when unresolvable
911
- */
912
372
  const buildDirectRouteData = (roots) => {
913
373
  const { bridgeKey, assetIn, assetOut, estimatedAmountOut, assetInUsdPrice, assetOutUsdPrice, assetInLogoUri, assetOutLogoUri, fromChainLogoUri, toChainLogoUri, advanced, } = roots;
914
374
  if (!assetIn || !assetOut)
915
375
  return null;
916
376
  const [provider, fromChain, toChain] = bridgeKey;
917
- // The bridge's own fee plus the delivery fee, valued on the source basis; null
918
- // when either is unknown or the input asset is unpriced.
919
377
  const { feeUsdValue, fees } = crossingCost({
920
378
  bridgeKey,
921
379
  feeRoots: roots,
@@ -925,30 +383,15 @@ const buildDirectRouteData = (roots) => {
925
383
  });
926
384
  const hop = {
927
385
  providerName: (0, exports.omnibridgeProviderDisplayName)(provider),
928
- // The direct route's provider is the omnibridge it crosses on; the adapter
929
- // resolves its logo from this key.
930
386
  providerKey: provider,
931
387
  title: (0, exports.omnibridgeProviderDisplayName)(provider),
932
388
  conversion: `${assetIn.symbol} → ${assetOut.symbol}`,
933
389
  feeUsdValue,
934
- // Delivery auto-calls the completion on the PulseChain side, so the user's
935
- // single signature covers the whole leg.
936
390
  automatic: true,
937
391
  };
938
392
  const section = {
939
393
  id: `direct-${provider}-${fromChain}-signature-1`,
940
- // NAMED FROM `toChain`, NOT HARD-CODED. A direct crossing runs both ways —
941
- // a foreign chain into PulseChain, or PulseChain out to a foreign chain —
942
- // and a literal "Bridge to PulseChain" on every direct route named the
943
- // wrong destination on every outbound crossing: the label read "Bridge to
944
- // PulseChain" while the arrival stage it produces (derived correctly, from
945
- // the section's own last token-flow node — see `tracker/trackerView.ts`'s
946
- // `sectionArrivalStage`) correctly read "Arrive on Ethereum" for the exact
947
- // same signature. One route, two destinations stated for it.
948
394
  label: `Bridge to ${(0, chain_names_js_1.chainDisplayName)(Number(toChain))}`,
949
- // The direct section carries NO section provider (matching the pre-split
950
- // builder): the route strip falls back to the hop's provider for the badge,
951
- // and the structural id stays identical.
952
395
  providerKey: null,
953
396
  tokenFlow: [
954
397
  {
@@ -968,9 +411,6 @@ const buildDirectRouteData = (roots) => {
968
411
  chainLogoUri: toChainLogoUri,
969
412
  },
970
413
  ],
971
- // The direct crossing IS the whole route, so what this step lands in is what
972
- // the route delivers — priced on the source basis for the same reason the
973
- // output is (see the output note below).
974
414
  landing: {
975
415
  amount: estimatedAmountOut,
976
416
  decimals: assetOut.decimals,
@@ -982,22 +422,13 @@ const buildDirectRouteData = (roots) => {
982
422
  }),
983
423
  },
984
424
  feeUsdValue,
985
- // Who takes what: the bridge, then the delivery service.
986
425
  fees,
987
- // The direct crossing waits on the SAME omnibridge settlement as the
988
- // outbound route's own "Bridge to {entry chain}" section
989
- // ({@link buildOutboundRouteData}), which quotes this identical constant for
990
- // the identical wait. Leaving it null here was the one crossing section that
991
- // never got it, so the strip and the tracker both had no duration to show for
992
- // the most common route in the product.
993
426
  etaMinutes: exports.BRIDGE_SETTLEMENT_ETA_MINUTES,
994
427
  hops: [hop],
995
428
  advanced,
996
429
  };
997
430
  return {
998
431
  kind: 'direct',
999
- // The direct omnibridge route IS the crossing — it does not pivot through a
1000
- // distinct aggregator entry chain — so `entryChainId` is null.
1001
432
  entryChainId: null,
1002
433
  headlineProviderKey: provider,
1003
434
  sections: [section],
@@ -1005,66 +436,19 @@ const buildDirectRouteData = (roots) => {
1005
436
  amount: estimatedAmountOut,
1006
437
  decimals: assetOut.decimals,
1007
438
  symbol: assetOut.symbol,
1008
- // Priced with the SOURCE asset's price, deliberately, even though the amount is
1009
- // counted in the destination token's units. A direct crossing hands back the
1010
- // same asset on another chain, and the bridged counterpart routinely trades at
1011
- // a discount to it — pricing the two sides from their own feeds made a
1012
- // zero-fee crossing report a loss (100 USDC in, 100 USDC out, "$100.03" paid
1013
- // and "$99.42" received). The delivered COUNT still comes from the destination
1014
- // token's decimals; only the basis is shared. The destination's own price
1015
- // remains the fallback so a route whose SOURCE side is unpriced still shows a
1016
- // value rather than nothing.
1017
439
  usdValue: (0, usd_value_js_1.usdValueOfAmount)({
1018
440
  balance: estimatedAmountOut,
1019
441
  decimals: assetOut.decimals,
1020
442
  price: assetInUsdPrice ?? assetOutUsdPrice ?? null,
1021
443
  }),
1022
444
  },
1023
- // The direct route is a single signature, so its aggregate cost is that one
1024
- // section's fee: the bridge's own fee plus delivery. Null when either is unknown.
1025
445
  totalCostUsdValue: feeUsdValue,
1026
- // THE SAME BASIS AGGREGATED ROUTES ARE RANKED ON: delivered value less what
1027
- // was paid. The delivered amount is what is sent less both fees, and both
1028
- // sides are priced on the source token, so the difference is exactly the
1029
- // negated cost. It leaves out the sender's own network gas, which this
1030
- // route does not know; an aggregated route counts it when its quote does.
1031
446
  netValueUsd: feeUsdValue === null ? null : -feeUsdValue,
1032
- // The direct route is the only option for now, so it is always the best.
1033
447
  isBest: true,
1034
448
  deltaPercent: null,
1035
449
  };
1036
450
  };
1037
451
  exports.buildDirectRouteData = buildDirectRouteData;
1038
- /**
1039
- * Builds the TWO-SIGNATURE OUTBOUND raw {@link RouteData} from PulseChain, across
1040
- * an omnibridge entry chain (Ethereum or Binance Smart Chain), to wherever the
1041
- * second signature lands.
1042
- *
1043
- * The route is exactly TWO {@link RouteSectionData}s:
1044
- * - SECTION 1 ("Bridge to {entry chain}") — the omnibridge out.
1045
- * - SECTION 2 ("Complete at {final chain}") — DEFERRED. Signed only after section
1046
- * 1's crossing settles, and carrying no binding quote until then.
1047
- *
1048
- * SECTION 2 HAS TWO SHAPES, and `roots.onward` is what picks between them.
1049
- * Without it the deferred leg is a swap that stays on the entry chain: the
1050
- * existing behaviour, down to its approximate ~0.29% PulseX fee. With it the
1051
- * deferred leg is an onward BRIDGE to `onward.chainId`, carried by the named
1052
- * provider, and it states NO fee — see the fee note in the body for why quoting
1053
- * the swap constant there would be a fabricated number.
1054
- *
1055
- * WHY THE FIRST LEG CANNOT WIDEN THE SAME WAY. The bridged message carries
1056
- * `(token, receiver, value, data)` and no chain id, and a mediator's counterpart
1057
- * is wired in at deploy time, so the omnibridge's destination is structural
1058
- * rather than a parameter. The reach comes from composing a second signature on
1059
- * the far side, which is what this builder draws.
1060
- *
1061
- * Returns null when `assetIn`, `intermediateToken`, or `finalToken` is missing,
1062
- * when the crossing's destination is not one of the two omnibridge entry chains,
1063
- * or when `onward.chainId` disagrees with the final token's own `chainId`.
1064
- *
1065
- * @param roots - the resolved settings + price + logo roots (see {@link OutboundRouteRoots})
1066
- * @returns the two-section raw outbound {@link RouteData}, or null when unresolvable
1067
- */
1068
452
  const buildOutboundRouteData = (roots) => {
1069
453
  const { bridgeKey, assetIn, intermediateToken, finalToken, estimatedAmountOut, assetInUsdPrice, intermediateUsdPrice, assetInLogoUri, assetOutLogoUri, finalTokenLogoUri, fromChainLogoUri, intermediateChainLogoUri, onward = null, } = roots;
1070
454
  if (!assetIn || !intermediateToken || !finalToken)
@@ -1074,21 +458,10 @@ const buildOutboundRouteData = (roots) => {
1074
458
  const entryChainName = (0, exports.resolveEntryChainName)(entryChainId);
1075
459
  if (entryChainName === null)
1076
460
  return null;
1077
- // A TOKEN DOES NOT CARRY A CHAIN, so the caller states the onward chain and the
1078
- // token states its own, and the two must agree. Reading one from the other is
1079
- // the cross-chain address mistake this codebase has already paid for: an origin
1080
- // address asked for on the destination chain fails silently, and succeeds by
1081
- // coincidence when some unrelated contract answers at the same address.
1082
- // Disagreement here means one of the two is wrong and there is no way to tell
1083
- // which, so the route is not drawn at all.
1084
461
  if (onward !== null && onward.chainId !== finalToken.chainId)
1085
462
  return null;
1086
- // Where the SECOND signature leaves the user. Same as the crossing's landing
1087
- // when the deferred leg is a swap; the onward chain when it is a bridge.
1088
463
  const finalChainId = onward === null ? entryChainId : onward.chainId;
1089
464
  const finalChainName = onward === null ? entryChainName : onward.chainName;
1090
- // SECTION 1 fee: the crossing's bridge fee plus its delivery fee, priced in the
1091
- // source token. Null when either is unknown.
1092
465
  const { feeUsdValue: sectionOneFeeValue, fees: sectionOneFees } = crossingCost({
1093
466
  bridgeKey,
1094
467
  feeRoots: roots,
@@ -1096,16 +469,6 @@ const buildOutboundRouteData = (roots) => {
1096
469
  arrivingToken: toFeeToken(intermediateToken, entryChainId),
1097
470
  sourceUsdPrice: assetInUsdPrice,
1098
471
  });
1099
- // SECTION 2's swap fee is APPROXIMATE: PulseX takes ~0.29% of the swapped value,
1100
- // applied here to the bridged intermediate amount.
1101
- //
1102
- // AN ONWARD HOP GETS NO FIGURE AT ALL, and that is the honest answer rather
1103
- // than a gap. `PULSEX_SWAP_FEE_BASIS_POINTS` prices a PulseX swap; an
1104
- // aggregator bridge to another chain charges its own spread plus that chain's
1105
- // gas, and neither is 0.29%. Reusing the constant would print a number that
1106
- // looks measured and is not, beside section 1's fee, which really is measured.
1107
- // The aggregator quotes the leg when the user returns to sign it, and until
1108
- // then null is what we know.
1109
472
  const sectionTwoFeeValue = onward !== null
1110
473
  ? null
1111
474
  : (0, usd_value_js_1.usdValueOfAmount)({
@@ -1119,8 +482,6 @@ const buildOutboundRouteData = (roots) => {
1119
482
  title: (0, exports.omnibridgeProviderDisplayName)(provider),
1120
483
  conversion: `${assetIn.symbol} → ${intermediateToken.symbol}`,
1121
484
  feeUsdValue: sectionOneFeeValue,
1122
- // Delivery auto-completes on the entry chain, so the single signature covers
1123
- // the whole omnibridge leg.
1124
485
  automatic: true,
1125
486
  };
1126
487
  const sectionOne = {
@@ -1144,9 +505,6 @@ const buildOutboundRouteData = (roots) => {
1144
505
  chainLogoUri: intermediateChainLogoUri,
1145
506
  },
1146
507
  ],
1147
- // The crossing lands the user in the INTERMEDIATE token on the entry chain —
1148
- // the whole point of naming a step's landing, since this is exactly the stop
1149
- // a two-signature route leaves you sitting at while you wait to sign again.
1150
508
  landing: {
1151
509
  amount: estimatedAmountOut,
1152
510
  decimals: intermediateToken.decimals,
@@ -1158,16 +516,10 @@ const buildOutboundRouteData = (roots) => {
1158
516
  }),
1159
517
  },
1160
518
  feeUsdValue: sectionOneFeeValue,
1161
- // Who takes what on the crossing: the bridge, then the delivery service.
1162
519
  fees: sectionOneFees,
1163
- // The omnibridge crossing out is the ~20-minute wait the deferred swap sits
1164
- // behind, so the wait is shown HERE, on the crossing leg.
1165
520
  etaMinutes: exports.BRIDGE_SETTLEMENT_ETA_MINUTES,
1166
521
  hops: [bridgeHop],
1167
522
  };
1168
- // SECTION 2 — DEFERRED. Signed only after section 1 settles. A swap on the entry
1169
- // chain (approximate ~0.29% fee), or an onward bridge carried by a named
1170
- // aggregator (no fee stated until it is quoted).
1171
523
  const deferredHop = onward === null
1172
524
  ? {
1173
525
  providerName: 'Swap',
@@ -1177,9 +529,6 @@ const buildOutboundRouteData = (roots) => {
1177
529
  automatic: false,
1178
530
  }
1179
531
  : {
1180
- // THE SERVICE THAT WILL ACTUALLY CARRY IT. "Swap" names PulseX, and a
1181
- // reader who saw it beside a Base destination would be told the wrong
1182
- // thing about who holds their funds for the second leg.
1183
532
  providerName: onward.providerName,
1184
533
  title: `Bridge to ${onward.chainName}`,
1185
534
  conversion: `${intermediateToken.symbol} → ${finalToken.symbol}`,
@@ -1202,17 +551,11 @@ const buildOutboundRouteData = (roots) => {
1202
551
  symbol: finalToken.symbol,
1203
552
  address: finalToken.address,
1204
553
  decimals: finalToken.decimals,
1205
- // THE LINE THIS WHOLE CHANGE IS ABOUT. Pinned to `entryChainId`, the
1206
- // deferred leg could only ever be a swap where the crossing landed, and
1207
- // the route strip drew a Base token wearing Ethereum's badge.
1208
554
  chainId: finalChainId,
1209
555
  logoUri: finalTokenLogoUri,
1210
556
  chainLogoUri: onward === null ? intermediateChainLogoUri : onward.chainLogoUri,
1211
557
  },
1212
558
  ],
1213
- // The deferred swap names the token it will land in but NOT an amount: it is
1214
- // quoted only when the user returns, and stating a figure now would present an
1215
- // estimate with the same weight as the crossing's real one.
1216
559
  landing: {
1217
560
  amount: null,
1218
561
  decimals: finalToken.decimals,
@@ -1220,13 +563,6 @@ const buildOutboundRouteData = (roots) => {
1220
563
  usdValue: null,
1221
564
  },
1222
565
  feeUsdValue: sectionTwoFeeValue,
1223
- // The swap itself is near-instant (~10 seconds); the ~20-minute wait it sits
1224
- // behind is shown on section 1 (the crossing).
1225
- //
1226
- // AN ONWARD HOP STATES NO ESTIMATE, for the same reason it states no fee. Ten
1227
- // seconds is how long a PulseX swap takes. How long an aggregator takes to
1228
- // carry value to another chain depends on the pair and on the route it picks,
1229
- // so the figure would be borrowed from a different operation.
1230
566
  ...(onward === null ? { etaSeconds: PULSEX_SWAP_ETA_SECONDS } : {}),
1231
567
  deferred: true,
1232
568
  deferredNote: onward === null
@@ -1235,72 +571,37 @@ const buildOutboundRouteData = (roots) => {
1235
571
  hops: [deferredHop],
1236
572
  };
1237
573
  return {
1238
- // 'direct' EVEN WHEN THE SECOND LEG IS AN AGGREGATOR, and this is a decision
1239
- // rather than a leftover. `kind` is what the interface dispatches EXECUTION
1240
- // on: `executesViaConsolidated` sends a 'direct' route through the omnibridge
1241
- // sender and everything else through the aggregator sender. The only
1242
- // signature this route asks for now is section 1 — a plain omnibridge
1243
- // crossing out of PulseChain — so the omnibridge sender is the correct one.
1244
- // Calling it 'aggregated' would hand the button to a sender holding no quote
1245
- // for it, because the onward leg is not quoted until the user returns and
1246
- // starts a fresh route from the entry chain.
1247
574
  kind: 'direct',
1248
575
  entryChainId: null,
1249
- // The route is shown crossing VIA the chain it bridges to. Already correct
1250
- // for an onward hop: PulseChain to Base really does travel via Ethereum.
1251
576
  viaChainId: entryChainId,
1252
577
  headlineProviderKey: provider,
1253
578
  sections: [sectionOne, sectionTwo],
1254
579
  output: {
1255
- // The final amount is quoted on return (deferred leg) — unknown at display time.
1256
580
  amount: null,
1257
581
  decimals: finalToken.decimals,
1258
582
  symbol: finalToken.symbol,
1259
583
  usdValue: null,
1260
584
  },
1261
- // Aggregate cost is section 1's crossing fees plus section 2's APPROXIMATE swap
1262
- // fee (~0.29%). Section 2 drops out when it states no figure (an onward hop
1263
- // is quoted only on return). Section 1 cannot: it is the signature the reader
1264
- // makes now, so without its cost there is no total, only the swap's guess.
1265
585
  totalCostUsdValue: sectionOneFeeValue === null ? null : (0, exports.addUsdValues)(sectionOneFeeValue, sectionTwoFeeValue),
1266
586
  isBest: true,
1267
587
  deltaPercent: null,
1268
588
  };
1269
589
  };
1270
590
  exports.buildOutboundRouteData = buildOutboundRouteData;
1271
- /**
1272
- * Builds a single composable raw {@link RouteSectionData} for a swap on PulseX,
1273
- * the decentralized exchange on PulseChain. A composing route appends this to its
1274
- * `sections` array. Both tokens live on PulseChain (chain id 369), so both
1275
- * token-flow nodes carry the PulseChain chain badge.
1276
- *
1277
- * The section carries raw numeric fields (`feePercent`, `priceImpactPercent`,
1278
- * `feeUsdValue`); the adapter (`toRouteView`) formats them into the display
1279
- * breakdown string.
1280
- *
1281
- * @param roots - the section's identity, tokens, pricing, and logos
1282
- * @returns one raw PulseX swap {@link RouteSectionData}
1283
- */
1284
591
  const buildPulseXSwapSectionData = (roots) => {
1285
592
  const { id, fromToken, toToken, feeUsdValue = null, priceImpactPercent = null, landedAmount = null, landedUsdValue = null, etaMinutes = null, etaSeconds = null, fromTokenLogoUri, toTokenLogoUri, pulsechainChainLogoUri, deferred = false, advanced = null, } = roots;
1286
593
  const swapHop = {
1287
594
  providerName: 'PulseX',
1288
- // Resolve the PulseX logo via the provider key in the adapter; the raw hop
1289
- // carries only the key, not the url.
1290
595
  providerKey: 'PulseX',
1291
596
  title: `Swap to ${toToken.symbol}`,
1292
597
  conversion: `${fromToken.symbol} → ${toToken.symbol}`,
1293
598
  feeUsdValue,
1294
- // A standalone PulseX swap is the user's own signature, not an automatic hop.
1295
599
  automatic: false,
1296
600
  };
1297
601
  const tokenFlow = [
1298
602
  { symbol: fromToken.symbol, address: fromToken.address, chainId: exports.pulsechainChainId, decimals: fromToken.decimals, logoUri: fromTokenLogoUri, chainLogoUri: pulsechainChainLogoUri },
1299
603
  { symbol: toToken.symbol, address: toToken.address, chainId: exports.pulsechainChainId, decimals: toToken.decimals, logoUri: toTokenLogoUri, chainLogoUri: pulsechainChainLogoUri },
1300
604
  ];
1301
- // The swap section always shows the flat PulseX fee in its breakdown; the price
1302
- // impact clause is added by the adapter only when the impact is present and
1303
- // non-trivial. Round the impact to two decimals and drop it when it is <= 0.
1304
605
  const roundedImpact = priceImpactPercent === null || Number(priceImpactPercent.toFixed(2)) <= 0
1305
606
  ? null
1306
607
  : Number(priceImpactPercent.toFixed(2));
@@ -1309,8 +610,6 @@ const buildPulseXSwapSectionData = (roots) => {
1309
610
  label: 'Swap on PulseX',
1310
611
  providerKey: 'PulseX',
1311
612
  tokenFlow,
1312
- // The swap is the last step of whatever composes it, so what it lands in is
1313
- // what the user ends up holding.
1314
613
  landing: {
1315
614
  amount: landedAmount,
1316
615
  decimals: toToken.decimals,
@@ -1318,177 +617,64 @@ const buildPulseXSwapSectionData = (roots) => {
1318
617
  usdValue: landedUsdValue,
1319
618
  },
1320
619
  feeUsdValue,
1321
- // 0.29 — the flat PulseX fee percentage, kept in lockstep with the basis-point
1322
- // constant so the breakdown line tracks the rate the estimate applies.
1323
620
  feePercent: Number(PULSEX_SWAP_FEE_BASIS_POINTS) / 100,
1324
621
  priceImpactPercent: roundedImpact,
1325
622
  etaMinutes,
1326
623
  etaSeconds,
1327
624
  hops: [swapHop],
1328
- // A PulseX swap is normally an immediate signature; deferred only when it sits
1329
- // behind a prior section's crossing.
1330
625
  deferred: deferred ? true : undefined,
1331
- // NO `bridgeFrom`/`bridgeTo` — a same-chain swap crosses no bridge, and
1332
- // leaving them unset (rather than null) keeps the disclosure from naming a
1333
- // mediator that never runs. `destinationRouter` is the PulseX router
1334
- // itself here, not a delivery leg inside a crossing — see the field's own
1335
- // note on `RouteSectionData`.
1336
626
  advanced: advanced === null ? undefined : advanced,
1337
627
  };
1338
628
  };
1339
629
  exports.buildPulseXSwapSectionData = buildPulseXSwapSectionData;
1340
- /**
1341
- * Composes a raw {@link RouteData} for the conversion route: bridge a liquid anchor
1342
- * token onto PulseChain, then a DEFERRED PulseX swap to the wanted token.
1343
- *
1344
- * Operates entirely at the raw data level — no display formatting. The caller runs
1345
- * the result through `toRouteView` to get the display-ready `RouteOption`.
1346
- *
1347
- * Per the owner's two locked decisions:
1348
- *
1349
- * - **LOCKED DECISION 1 — normalise via the liquid anchor.** When the omnibridge
1350
- * cannot deliver the wanted token directly (the wanted token has not yet been
1351
- * bridged, or its canonical foreign-chain pair is illiquid), the rails bridge a
1352
- * well-known liquid anchor token instead and a PulseX swap leg converts it on
1353
- * PulseChain. The {@link ConversionRouteComposition.baseData} is the raw route
1354
- * that delivers that anchor; this function appends the conversion leg. The two
1355
- * combined sections represent one complete user journey: bridge to the anchor,
1356
- * then swap the anchor to the wanted token.
1357
- *
1358
- * - **LOCKED DECISION 2 — the swap is a deferred second signature.** The PulseX
1359
- * swap can only be signed AFTER the PulseChain Bridge lands the anchor on
1360
- * PulseChain — the funds must arrive before the user can authorise the swap. The
1361
- * appended section therefore carries `deferred: true` and an ESTIMATED fee that
1362
- * firms up on arrival (price impact and pool depth may shift in the ~20 minutes
1363
- * the crossing takes). Do not collapse these into one signature.
1364
- *
1365
- * The composed route:
1366
- * - keeps the base route's provider and entry chain (`entryChainId` / `via`) — it
1367
- * is the same PulseChain Bridge crossing with a swap bolted on at the
1368
- * destination;
1369
- * - takes a DISTINCT identifier (`<baseRouteId>-pulsex-swap`) so React keys and
1370
- * selection never collide with the bare anchor-delivery route;
1371
- * - re-drives the output to the wanted token: the estimate's delivered amount, its
1372
- * United States dollar value, and the wanted token's symbol (the bare anchor
1373
- * route would otherwise claim the anchor as the delivered token);
1374
- * - sums the swap's total cost (fee + price impact) into `totalCostUsdValue` so
1375
- * the aggregate cost is honest — aggregator spread + omnibridge crossing +
1376
- * PulseX swap — and subtracts it from `netValueUsd`.
1377
- *
1378
- * @param composition - the base route data, swap tokens, estimate, logos, and the
1379
- * injected base route id (computed by the caller via `toRouteView`)
1380
- * @returns the composed raw two-section conversion route (bridge, then deferred PulseX swap)
1381
- */
1382
630
  const buildConversionRouteData = (composition) => {
1383
631
  const { baseData, baseRouteId, anchorToken, wantedToken, estimate, anchorTokenLogoUri, wantedTokenLogoUri, pulsechainChainLogoUri, } = composition;
1384
- // The anchor bridges the two legs: it is BOTH the base route's delivered node and
1385
- // the swap's input node. Reuse the delivered node's pre-resolved logo so the same
1386
- // token shows ONE icon across the boundary; fall back to the threaded anchor logo.
1387
632
  const deliveredAnchorNode = baseData.sections.at(-1)?.tokenFlow.at(-1);
1388
633
  const boundaryAnchorLogoUri = deliveredAnchorNode?.logoUri ?? anchorTokenLogoUri;
1389
634
  const swapSection = (0, exports.buildPulseXSwapSectionData)({
1390
635
  id: `${baseRouteId}-pulsex-swap`,
1391
636
  fromToken: anchorToken,
1392
637
  toToken: wantedToken,
1393
- // The section cost is the swap's TOTAL value loss (fee + price impact) so the
1394
- // route reconciles: input value − each step's cost = delivered output.
1395
638
  feeUsdValue: estimate.costUsdValue,
1396
639
  priceImpactPercent: estimate.priceImpactPercent,
1397
- // The swap lands the user in the wanted token. Its value is the bridge leg's
1398
- // landing less what the swap costs — the same subtraction the route's output
1399
- // uses, so the last step's landing figure and the route's delivered figure
1400
- // cannot drift apart.
1401
640
  landedAmount: estimate.outputAmount,
1402
641
  landedUsdValue: (0, exports.subtractUsdValues)(baseData.output.usdValue, estimate.costUsdValue),
1403
- // The swap is near-instant (~10s); the ~20-minute wait sits on the crossing below.
1404
642
  etaSeconds: PULSEX_SWAP_ETA_SECONDS,
1405
643
  fromTokenLogoUri: boundaryAnchorLogoUri,
1406
644
  toTokenLogoUri: wantedTokenLogoUri,
1407
645
  pulsechainChainLogoUri,
1408
- // The bridge must land on PulseChain before the swap can be signed.
1409
646
  deferred: true,
1410
647
  });
1411
- // The ~20-minute crossing wait belongs on the section that LANDS THE FUNDS ON
1412
- // PULSECHAIN, replacing whatever entry-chain-only estimate that section carried.
1413
- //
1414
- // THE INDEX IS NOT A PROXY HERE, and the difference is worth stating because
1415
- // this project has been bitten by one. A positional "last section" stood in for
1416
- // "the section Gibs delivers" elsewhere and broke the moment a deferred leg
1417
- // appeared. This is the other case: a base route is BUILT to end on PulseChain —
1418
- // that is what makes it a base a swap can be appended to — so its final section
1419
- // IS the landing section by construction, for a one-signature base and for the
1420
- // two-signature NEAR Intents base alike. The name says which fact is meant, so a
1421
- // future base that ends somewhere else fails a test rather than a reader.
1422
648
  const pulsechainLandingSectionIndex = baseData.sections.length - 1;
1423
649
  const bridgeSections = baseData.sections.map((section, index) => index === pulsechainLandingSectionIndex
1424
650
  ? { ...section, etaMinutes: exports.BRIDGE_SETTLEMENT_ETA_MINUTES, etaSeconds: null }
1425
651
  : section);
1426
652
  return {
1427
653
  ...baseData,
1428
- // The conversion route crosses the aggregator entry chain then swaps on PulseX.
1429
654
  kind: 'conversion',
1430
- // Carry the aggregator entry chain so Bridge.tsx can link back to the candidate.
1431
655
  entryChainId: baseData.entryChainId ?? null,
1432
656
  sections: [...bridgeSections, swapSection],
1433
657
  output: {
1434
658
  amount: estimate.outputAmount,
1435
659
  decimals: wantedToken.decimals,
1436
660
  symbol: wantedToken.symbol,
1437
- // The wanted token's COUNT comes from the swap quote, but its value continues
1438
- // the base route's basis: what the anchor was worth on arrival, less the swap's
1439
- // cost. Re-pricing the wanted token against its own feed — which is what this
1440
- // used to do — sets the figure by the router's realized rate instead, and that
1441
- // rate routinely disagrees with the ratio of the two feeds by about a dollar on
1442
- // a hundred. The route then appeared to deliver more than its own cost rows allowed.
1443
661
  usdValue: (0, exports.subtractUsdValues)(baseData.output.usdValue, estimate.costUsdValue),
1444
662
  },
1445
- // Aggregate cost = base cost + the swap's total loss; null only when both unknown.
1446
663
  totalCostUsdValue: (0, exports.addUsdValues)(baseData.totalCostUsdValue, estimate.costUsdValue),
1447
- // Net = base net − the swap's total loss; null when either is unknown.
1448
664
  netValueUsd: baseData.netValueUsd == null || estimate.costUsdValue == null
1449
665
  ? null
1450
666
  : baseData.netValueUsd - estimate.costUsdValue,
1451
667
  };
1452
668
  };
1453
669
  exports.buildConversionRouteData = buildConversionRouteData;
1454
- /**
1455
- * Derives a {@link PulseXSwapEstimateData} — the raw NUMERIC form of the PulseX
1456
- * swap estimate — from a PulseX `getAmountsOut` quote.
1457
- *
1458
- * Returns raw numbers rather than formatted currency strings, so the conversion
1459
- * composition can flow them straight into a {@link RouteData} without re-parsing
1460
- * currency strings.
1461
- *
1462
- * Returns null when the quote is absent or empty (the router reverted or has not
1463
- * loaded) — the composing route then has no swap leg to append.
1464
- *
1465
- * @param params.amountsOut - the router's amounts-out array, or null when unquoted
1466
- * @param params.inputAmount - the anchor amount entering the swap, in its base units
1467
- * @param params.inputToken - the anchor token (the swap input)
1468
- * @param params.inputUsdPrice - the anchor token's per-token dollar price, or null
1469
- * @param params.outputToken - the wanted token (the swap output)
1470
- * @param params.outputUsdPrice - the wanted token's per-token dollar price, or null
1471
- * @param params.referenceAmountsOut - marginal reference quote amounts-out, or null
1472
- * @param params.referenceInputAmount - the reference quote's input amount
1473
- * @returns the derived estimate with numeric fields, or null when the quote yields no output amount
1474
- */
1475
670
  const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inputUsdPrice, outputToken, outputUsdPrice, referenceAmountsOut = null, referenceInputAmount = 0n, }) => {
1476
671
  if (!amountsOut || amountsOut.length === 0)
1477
672
  return null;
1478
673
  const outputAmount = amountsOut[amountsOut.length - 1];
1479
674
  if (typeof outputAmount !== 'bigint')
1480
675
  return null;
1481
- // The swap fee is APPROXIMATE: PulseX takes a flat ~0.29% of the trade. Apply that
1482
- // rate to the swapped value, preferring the input (anchor) side and falling back
1483
- // to the output (wanted) side, so the fee still shows when only ONE side is priced.
1484
676
  const feeUsdValue = (0, usd_value_js_1.usdValueOfAmount)({ balance: (inputAmount * PULSEX_SWAP_FEE_BASIS_POINTS) / BASIS_POINTS_DENOMINATOR, decimals: inputToken.decimals, price: inputUsdPrice }) ??
1485
677
  (0, usd_value_js_1.usdValueOfAmount)({ balance: (outputAmount * PULSEX_SWAP_FEE_BASIS_POINTS) / BASIS_POINTS_DENOMINATOR, decimals: outputToken.decimals, price: outputUsdPrice });
1486
- // PRICE IMPACT comes from the MARGINAL reference quote, NOT from differencing the
1487
- // two PulseChain USD prices: those prices are themselves PulseX pool quotes, so
1488
- // value-in minus value-out cancels the fee and impact to ~$0 (the bug this
1489
- // replaces). Instead compare the full trade's realized rate against the tiny
1490
- // reference quote's near-spot rate. The swap fee rides in BOTH `getAmountsOut`
1491
- // results, so it cancels in the ratio and the gap is price impact ALONE.
1492
678
  const marginalRate = (() => {
1493
679
  if (!referenceAmountsOut || referenceAmountsOut.length === 0)
1494
680
  return null;
@@ -1505,18 +691,11 @@ const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inpu
1505
691
  })();
1506
692
  const inputWhole = Number((0, viem_1.formatUnits)(inputAmount, inputToken.decimals));
1507
693
  const outputWhole = Number((0, viem_1.formatUnits)(outputAmount, outputToken.decimals));
1508
- // The output the trade WOULD deliver at the marginal (near-spot) rate with no
1509
- // price impact — fee still applied, since the marginal rate carries it too.
1510
694
  const idealOutputWhole = marginalRate !== null ? inputWhole * marginalRate : null;
1511
- // The impact loss in OUTPUT-token units: what the marginal rate promised minus
1512
- // what the curve actually delivered. The fee is in both, so this is impact only.
1513
695
  const impactLossOutput = idealOutputWhole !== null ? Math.max(0, idealOutputWhole - outputWhole) : null;
1514
696
  const priceImpactPercent = impactLossOutput !== null && idealOutputWhole !== null && idealOutputWhole > 0
1515
697
  ? Math.max(0, (impactLossOutput / idealOutputWhole) * 100)
1516
698
  : null;
1517
- // Value the impact loss in dollars — preferring the output token's price, falling
1518
- // back to the input side via the marginal rate so the impact still shows when
1519
- // only the anchor is priced.
1520
699
  const impactUsd = (() => {
1521
700
  if (impactLossOutput === null)
1522
701
  return null;
@@ -1527,9 +706,6 @@ const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inpu
1527
706
  }
1528
707
  return null;
1529
708
  })();
1530
- // Total cost is the flat fee PLUS the price impact — both real, neither cancelled.
1531
- // Round the fee to cents the SAME way the pre-split builder did (it summed the
1532
- // already-formatted fee string), so the total cost is identical to the cent.
1533
709
  const feeRounded = feeUsdValue === null ? null : (0, format_js_1.roundUsdToCents)(feeUsdValue);
1534
710
  const costUsdValue = feeRounded !== null && Number.isFinite(feeRounded)
1535
711
  ? feeRounded + (impactUsd ?? 0)
@@ -1549,33 +725,6 @@ const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inpu
1549
725
  };
1550
726
  };
1551
727
  exports.buildPulseXSwapEstimateData = buildPulseXSwapEstimateData;
1552
- /**
1553
- * Composes a raw {@link RouteData} for a SAME-CHAIN PulseX swap: PulseChain on
1554
- * both sides of the selection, one signature, no crossing at all.
1555
- *
1556
- * WHY THIS IS ITS OWN PRODUCER, RATHER THAN `buildConversionRouteData` WITH NO
1557
- * BASE. The conversion route APPENDS a swap section to a bridge route that
1558
- * already delivered the anchor onto PulseChain; its cost, its `entryChainId`,
1559
- * and its "deferred" swap all inherit from that base. A same-chain swap has no
1560
- * base — the reader already holds the input token on PulseChain before
1561
- * pressing anything — so the section is signed IMMEDIATELY (`deferred` stays
1562
- * false, the {@link buildPulseXSwapSectionData} default) and the route has no
1563
- * `entryChainId` at all, exactly like the direct omnibridge route it sits
1564
- * beside in the strip.
1565
- *
1566
- * NOT DEFERRED, AND THAT IS LOAD-BEARING FOR THE TRACKER. `deferred` means "the
1567
- * reader has not yet signed this" (see `sectionProgress.ts`); a same-chain swap
1568
- * is signed in the very press that builds it, so marking it deferred would tell
1569
- * the tracker a signature is still owed for a transaction already broadcast.
1570
- * What DOES need care downstream is the opposite failure the tracker's own
1571
- * `routeChainSequence` can produce for a route whose every stop shares one
1572
- * chain id — see `routeProgress.ts`'s `reachedChainIndex` for the fix that
1573
- * keeps a same-chain route from reading "delivered" the instant it is merely
1574
- * broadcast.
1575
- *
1576
- * @param roots - the route's identity, tokens, pricing, and logos
1577
- * @returns the one-section same-chain swap {@link RouteData}
1578
- */
1579
728
  const buildSameChainSwapRouteData = (roots) => {
1580
729
  const { id, fromToken, toToken, estimate, inputUsdValue, fromTokenLogoUri, toTokenLogoUri, pulsechainChainLogoUri, advanced, } = roots;
1581
730
  const swapSection = (0, exports.buildPulseXSwapSectionData)({
@@ -1590,13 +739,11 @@ const buildSameChainSwapRouteData = (roots) => {
1590
739
  fromTokenLogoUri,
1591
740
  toTokenLogoUri,
1592
741
  pulsechainChainLogoUri,
1593
- // Signed immediately — see the function doc above for why this must stay false.
1594
742
  deferred: false,
1595
743
  advanced,
1596
744
  });
1597
745
  return {
1598
746
  kind: 'swap',
1599
- // No aggregator entry chain: the whole journey is one signature on PulseChain.
1600
747
  entryChainId: null,
1601
748
  headlineProviderKey: 'PulseX',
1602
749
  sections: [swapSection],
@@ -1610,7 +757,6 @@ const buildSameChainSwapRouteData = (roots) => {
1610
757
  netValueUsd: inputUsdValue == null || estimate.costUsdValue == null
1611
758
  ? null
1612
759
  : inputUsdValue - estimate.costUsdValue,
1613
- // The swap's own near-instant duration; there is no crossing wait to add.
1614
760
  etaMinutes: null,
1615
761
  };
1616
762
  };