@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
@@ -2,81 +2,13 @@ import * as imageLinks from '../../image-links.js';
2
2
  import { chainDisplayName } from '../../chain-names.js';
3
3
  import { findProviderById } from '../../providers.js';
4
4
  import { WRAP_CARRIER_ID, carrierProviderKey, carrierStepKind } from '../carrier-provider-key.js';
5
- import { describeDeferredCrossing } from '../../near-intents-route.js';
5
+ import { describeDeferredCrossing, sendOrSwapLabel } from '../../near-intents-route.js';
6
6
  import { candidateDeliveredUsd, candidateOutsideFeesUsd, candidatePaidUsd, pulsechainChainId, providerDisplayName, } from '../producers.js';
7
7
  import { assetNodeKey, planComposedBasis, } from './graph.js';
8
- /**
9
- * PHASE 1 — turns a priced {@link RoutePlan} into the SAME `RouteData` /
10
- * `RouteCandidate` / `RouteScoreInputs` shapes today's four hand-written
11
- * producers emit: {@link buildNearIntentsTwoStepRouteData} (`../../near-intents-route.js`),
12
- * {@link buildConversionRouteData}, {@link buildOutboundRouteData} and
13
- * {@link buildDirectCrossingCandidate} (paired with `buildCandidateRouteData`)
14
- * — all in `../producers.js` and `../candidate.js`. See the route-search
15
- * design's section 6, "what it absorbs," and this module's own tests
16
- * (`plan-route-data.test.ts`) for the parity checks against each of the four.
17
- *
18
- * WHAT THIS MODULE DOES NOT DO: PRICE A PLAN. Every existing builder prices
19
- * a section from carrier-specific knowledge this module has no access to — a
20
- * bridge's own fee-manager rate (`crossingCost` in `../producers.js`), a
21
- * PulseX swap's estimate (`buildPulseXSwapEstimateData`), or an aggregator's
22
- * own paid/delivered spread (`candidatePaidUsd` / `candidateDeliveredUsd`,
23
- * both reused here rather than restated). A `RoutePlan` (`./graph.js`) names
24
- * the STRUCTURE of a path and a base-unit delivered amount per hop; it
25
- * carries no United States dollar figure at all. So every dollar figure this
26
- * module places on a `RouteData` — a section's fee, a section's landing
27
- * value, the route's total cost — is a CALLER-SUPPLIED input
28
- * ({@link PlanSectionPricing}), never a formula computed here. This mirrors
29
- * `RouteScoreInputs`' own doc in `score.ts`: "the caller is responsible for
30
- * turning a priced RoutePlan into these fields."
31
- *
32
- * WHAT THIS MODULE DOES DO: compose the STRUCTURE — which section a hop
33
- * belongs to, whether that section is deferred, what it is labelled, what
34
- * its hops are titled, and what its token flow is — directly from the
35
- * plan's graph shape, using exactly the same conventions today's four
36
- * builders use. Where those four builders DISAGREE with each other (and two
37
- * of them genuinely do — see `hopIsAutomatic` below), this module picks ONE
38
- * rule and documents which existing convention it follows and which it
39
- * knowingly departs from, per this project's "surface conflicts, don't
40
- * average them" rule. Every departure is named in this module's tests.
41
- */
42
- // ---------------------------------------------------------------------------
43
- // Carrier classification
44
- // ---------------------------------------------------------------------------
45
- /**
46
- * Whether `carrier` is an omnibridge crossing (`'pulsechain'` or
47
- * `'tokensex'`) — a genuine type predicate, not a plain boolean, so every
48
- * call site narrows `carrier` from the wide {@link HopCarrier} union down to
49
- * {@link Provider}. `providerDisplayName` (`../producers.js`) is total only
50
- * over `Provider`'s two keys and falls back to the generic string
51
- * "Omnibridge" for anything else; a plain-boolean guard left the call sites
52
- * below typed as `HopCarrier` and accepting a bare `string`, which is the
53
- * SAME shape of fault that let a NEAR Intents leg display as "Omnibridge"
54
- * (see `plan-route-data.test.ts`'s now-fixed "documented divergence" case).
55
- * Narrowing here makes that class of mistake fail to compile instead of
56
- * silently mis-naming a hop.
57
- */
58
8
  const isBridgeCarrier = (carrier) => carrier === 'pulsechain' || carrier === 'tokensex';
59
- /** Whether `carrier` is a consolidated-route aggregator (LI.FI, Relay, NEAR Intents). */
60
9
  const isAggregatorCarrier = (carrier) => carrier === 'lifi' || carrier === 'relay' || carrier === 'near-intents';
61
- /** Whether a hop lands on PulseChain — the signal every label/title rule below keys on. */
62
10
  const hopLandsOnPulsechain = (hop) => hop.edge.to.chainId === pulsechainChainId;
63
- /** Whether `hopIndex` is the plan's LAST hop overall — a completing leg, not a starting one. */
64
11
  const isFinalHopOfPlan = (hopIndex, plan) => hopIndex === plan.hops.length - 1;
65
- /**
66
- * Resolves a node's display token and asserts its `decimals` agree with the
67
- * NODE's own `decimals` — the field `graph.ts` added specifically so a hop
68
- * touching two independently-resolved mentions of the same token can be
69
- * checked against each other without a second lookup (see `AssetNode`'s own
70
- * doc). A name is a hypothesis, not evidence: `resolveToken` is a caller
71
- * hook this module does not control, and trusting its `decimals` blind would
72
- * silently misplace a decimal point in a real amount exactly the way
73
- * `compose.ts`'s `decimals-mismatch` refusal exists to prevent one hop
74
- * downstream. The NODE's decimals wins as the returned, asserted value.
75
- *
76
- * @param node - the graph node to resolve
77
- * @param resolveToken - resolves the node to its full display token
78
- * @returns the resolved token and the node's own, asserted decimals
79
- */
80
12
  const resolveNodeWithAssertedDecimals = (node, resolveToken) => {
81
13
  const token = resolveToken(node);
82
14
  if (token.decimals !== node.decimals) {
@@ -85,19 +17,6 @@ const resolveNodeWithAssertedDecimals = (node, resolveToken) => {
85
17
  }
86
18
  return { token, decimals: node.decimals };
87
19
  };
88
- /**
89
- * Builds the raw `RouteTokenRef` for one graph node, resolving its logo the
90
- * same way every existing builder's own token-node helper does
91
- * (`tokenNode` in `../../near-intents-route.js`, the inline node literals in
92
- * `buildCandidateRouteData`): the token's own image first, the chain badge
93
- * from `imageLinks.networkOrPseudo` second — which is what makes a
94
- * pay-by-hand origin's invented chain id resolve to a real badge instead of
95
- * a 404ing numeric lookup.
96
- *
97
- * @param node - the graph node to render
98
- * @param resolveToken - resolves the node to its full display token
99
- * @returns the raw token reference
100
- */
101
20
  const toRouteTokenRef = (node, resolveToken) => {
102
21
  const { token, decimals } = resolveNodeWithAssertedDecimals(node, resolveToken);
103
22
  return {
@@ -105,34 +24,10 @@ const toRouteTokenRef = (node, resolveToken) => {
105
24
  address: token.address,
106
25
  chainId: node.chainId,
107
26
  decimals,
108
- // NOT `image`, which checksums the address as an Ethereum one and throws
109
- // for anything else. A plan can start on a pay-by-hand chain (Zcash's
110
- // native sentinel is `t1Hsc…`), and that throw blanked the whole bridge
111
- // form the moment such a route was served. A token's own logo still wins;
112
- // an address no picture can be asked for draws its chain's mark.
113
27
  logoUri: token.logoURI ? token.logoURI : imageLinks.tokenImageOrChainFallback(token),
114
28
  chainLogoUri: imageLinks.networkOrPseudo(node.chainId),
115
29
  };
116
30
  };
117
- /**
118
- * The distinct stops a signature group's hops walk: the origin, then every
119
- * node a hop lands on, collapsing a hop that lands back on the node the
120
- * flow already holds — generalizing `deriveTokenFlow` in `../producers.js`
121
- * from RAW ADDRESS-STRING comparison (`isSameTokenRef`) to
122
- * {@link assetNodeKey}, which is already the collision-free, casing-normalized
123
- * identity every other part of the graph model uses.
124
- *
125
- * THIS IS WHERE THE RECENTLY-FIXED BUG STAYS FIXED. `deriveTokenFlow` used to
126
- * leave a route showing only its origin when a candidate itemized no hops at
127
- * all — see `../producers.js`'s own note on the defect. A `RoutePlan` always
128
- * carries at least one hop for a real crossing, and this walk always ends on
129
- * that hop's `to` node, so the flow can never stop short of the plan's own
130
- * declared destination for its group.
131
- *
132
- * @param hops - one signature group's hops, first to last
133
- * @param resolveToken - resolves a graph node to its full display token
134
- * @returns the group's token flow, origin first
135
- */
136
31
  const buildGroupTokenFlow = (hops, resolveToken) => {
137
32
  const originNode = hops[0].edge.from;
138
33
  const nodeFlow = hops.reduce((flow, hop) => {
@@ -144,49 +39,9 @@ const buildGroupTokenFlow = (hops, resolveToken) => {
144
39
  }, [originNode]);
145
40
  return nodeFlow.map((node) => toRouteTokenRef(node, resolveToken));
146
41
  };
147
- // ---------------------------------------------------------------------------
148
- // Section-level labelling
149
- // ---------------------------------------------------------------------------
150
- /**
151
- * A signature group's own hop-index list, resolved to its hops.
152
- *
153
- * @param group - one entry of `plan.signatures` — hop indices into `plan.hops`
154
- * @param plan - the plan those indices are drawn from
155
- * @returns the group's hops, first to last
156
- */
157
42
  const groupHops = (group, plan) => group.map((hopIndex) => plan.hops[hopIndex]);
158
- /**
159
- * Whether every hop of a group is carried by PulseX — the standalone-swap
160
- * shape `buildPulseXSwapSectionData` draws, labelled "Swap on PulseX"
161
- * regardless of the group's position in the plan.
162
- */
163
43
  const isPulseXOnlyGroup = (hops) => hops.every((hop) => hop.edge.carrier === 'PulseX');
164
- /** Whether a group's LAST hop is the omnibridge crossing — the fact that decides "Bridge to X". */
165
44
  const groupEndsWithBridgeHop = (hops) => isBridgeCarrier(hops[hops.length - 1].edge.carrier);
166
- /**
167
- * A signature section's label, generalizing the four distinct conventions
168
- * found across today's producers into one rule:
169
- *
170
- * - **"Swap on PulseX"** — every hop in the group is carried by PulseX.
171
- * Matches `buildPulseXSwapSectionData`'s hard-coded label.
172
- * - **"Bridge to {X}"** — the WHOLE plan is one signature (matches
173
- * `buildCandidateRouteData`, which always uses this label for its one
174
- * section, omnibridge leg or not), OR the group's last hop IS the
175
- * omnibridge crossing (matches `buildNearIntentsTwoStepRouteData`'s
176
- * second section AND `buildOutboundRouteData`'s first section).
177
- * - **"Complete at {X}"** — the group is the plan's LAST signature and does
178
- * NOT end on a bridge hop. Matches `buildOutboundRouteData`'s second
179
- * section, in BOTH its shapes (the entry-chain swap and the onward
180
- * aggregator hop) — the section label names where the journey finishes
181
- * regardless of which service carries the last leg.
182
- * - **"Send to {X}"** — every other case: a non-final, non-bridge-ending
183
- * group. Matches `buildNearIntentsTwoStepRouteData`'s first section.
184
- *
185
- * @param group - the group's hop indices
186
- * @param groupIndex - the group's position in `plan.signatures`
187
- * @param plan - the plan the group belongs to
188
- * @returns the section's label
189
- */
190
45
  const sectionLabel = (group, groupIndex, plan) => {
191
46
  const hops = groupHops(group, plan);
192
47
  if (isPulseXOnlyGroup(hops))
@@ -200,31 +55,8 @@ const sectionLabel = (group, groupIndex, plan) => {
200
55
  if (isFinalGroup) {
201
56
  return `Complete at ${chainDisplayName(destinationChainId)}`;
202
57
  }
203
- return `Send to ${chainDisplayName(destinationChainId)}`;
58
+ return sendOrSwapLabel(hops[0].edge.from.chainId, destinationChainId);
204
59
  };
205
- // ---------------------------------------------------------------------------
206
- // Hop-level rendering
207
- // ---------------------------------------------------------------------------
208
- /**
209
- * A hop's display provider name.
210
- *
211
- * A bridge carrier resolves through {@link providerDisplayName} (the same
212
- * "PulseChain Bridge" / "Tokens Express Bridge" table every builder reads).
213
- * PulseX is named directly. An aggregator carrier resolves through the
214
- * PROVIDER REGISTRY's own label (`providerById(id).label`) — DELIBERATELY,
215
- * not through `providerDisplayName`, because that function's table only
216
- * ever held the two omnibridge providers. `buildNearIntentsTwoStepRouteData`
217
- * calls `providerDisplayName(candidate.quote.provider)` for its aggregator
218
- * hop today, and `'near-intents'` is not a key in that table, so it silently
219
- * falls back to the generic string `'Omnibridge'` — naming NEAR Intents
220
- * after the bridge it is not. That reads as an unintentional defect in the
221
- * existing code, not a convention worth reproducing; this function returns
222
- * `'NEAR Intents'` instead. See this module's test file and the report back
223
- * to the owner for the specific call-out.
224
- *
225
- * @param carrier - the hop's carrier
226
- * @returns the display name for that carrier
227
- */
228
60
  const hopProviderName = (carrier) => {
229
61
  if (isBridgeCarrier(carrier))
230
62
  return providerDisplayName(carrier);
@@ -232,38 +64,8 @@ const hopProviderName = (carrier) => {
232
64
  return 'PulseX';
233
65
  if (carrierStepKind(carrier) === 'wrap')
234
66
  return 'Wrap';
235
- // TOTAL OVER EVERY CARRIER. The engine plans hops for carriers the provider
236
- // registry does not hold (a local wrap, a host's own plug-in). Casting such
237
- // an id to a provider id and asking the throwing lookup crashed the whole
238
- // bridge form the first time a plan began with a wrap. A carrier with no
239
- // registered label is named by its own id.
240
67
  return findProviderById(carrier)?.label ?? carrier;
241
68
  };
242
- /**
243
- * A hop's title, generalizing the title conventions of every existing
244
- * builder:
245
- *
246
- * - a bridge hop landing on PulseChain → `"Bridge to PulseChain"` (matches
247
- * `buildCandidateRouteData`'s omnibridge hop and
248
- * `buildNearIntentsTwoStepRouteData`'s crossing hop);
249
- * - a bridge hop landing elsewhere (leaving PulseChain) →
250
- * `providerDisplayName(carrier)`, e.g. `"PulseChain Bridge"` (matches
251
- * `buildOutboundRouteData`'s bridge hop exactly);
252
- * - a PulseX hop → `"Swap to {toSymbol}"` (matches `buildPulseXSwapSectionData`);
253
- * - an aggregator hop that is the plan's FINAL hop (nothing follows it,
254
- * so this signature is the one that COMPLETES the journey) →
255
- * `"Bridge to {toChainName}"` (matches `buildOutboundRouteData`'s onward
256
- * hop, which names the chain it finishes on even though an aggregator,
257
- * not the omnibridge, carries it);
258
- * - any other aggregator hop (something follows it) → `"Send to {toChainName}"`
259
- * (matches `buildNearIntentsTwoStepRouteData`'s aggregator hop).
260
- *
261
- * @param hop - the hop to title
262
- * @param hopIndex - the hop's index into `plan.hops`
263
- * @param plan - the plan the hop belongs to
264
- * @param resolveToken - resolves a graph node to its full display token
265
- * @returns the hop's title
266
- */
267
69
  const hopTitle = (hop, hopIndex, plan, resolveToken) => {
268
70
  const carrier = hop.edge.carrier;
269
71
  if (isBridgeCarrier(carrier)) {
@@ -274,33 +76,11 @@ const hopTitle = (hop, hopIndex, plan, resolveToken) => {
274
76
  if (carrier === 'PulseX') {
275
77
  return `Swap to ${resolveToken(hop.edge.to).symbol}`;
276
78
  }
277
- const destinationChainName = chainDisplayName(hop.edge.to.chainId);
278
- return isFinalHopOfPlan(hopIndex, plan)
279
- ? `Bridge to ${destinationChainName}`
280
- : `Send to ${destinationChainName}`;
79
+ if (isFinalHopOfPlan(hopIndex, plan)) {
80
+ return `Bridge to ${chainDisplayName(hop.edge.to.chainId)}`;
81
+ }
82
+ return sendOrSwapLabel(hop.edge.from.chainId, hop.edge.to.chainId);
281
83
  };
282
- /**
283
- * A hop's conversion subtitle.
284
- *
285
- * The source data has two conventions for this string: the candidate-based
286
- * builders (`buildCandidateRouteData`'s omnibridge hop,
287
- * `buildNearIntentsTwoStepRouteData`'s both hops) write an ARROW-ONLY clause
288
- * (`"→ USDC"`), leaving the origin side implicit; the settings-based
289
- * builders (`buildOutboundRouteData`, `buildPulseXSwapSectionData`) write
290
- * the FULL two-sided clause (`"PLS → USDC"`). Both are correct prose for
291
- * their own hop; this function reproduces BOTH conventions rather than
292
- * picking one, keyed on the exact same signal {@link hopTitle} already uses
293
- * to distinguish a starting/intermediate leg from a completing one: arrow-only
294
- * for a bridge hop landing on PulseChain or an aggregator hop that is NOT the
295
- * plan's final hop (there is more journey to draw, so the origin side is
296
- * already on screen from the previous hop); full two-sided otherwise.
297
- *
298
- * @param hop - the hop to describe
299
- * @param hopIndex - the hop's index into `plan.hops`
300
- * @param plan - the plan the hop belongs to
301
- * @param resolveToken - resolves a graph node to its full display token
302
- * @returns the hop's conversion subtitle
303
- */
304
84
  const hopConversion = (hop, hopIndex, plan, resolveToken) => {
305
85
  const carrier = hop.edge.carrier;
306
86
  const toSymbol = resolveToken(hop.edge.to).symbol;
@@ -311,51 +91,10 @@ const hopConversion = (hop, hopIndex, plan, resolveToken) => {
311
91
  const fromSymbol = resolveToken(hop.edge.from).symbol;
312
92
  return `${fromSymbol} → ${toSymbol}`;
313
93
  };
314
- /**
315
- * Whether a hop runs AUTOMATICALLY inside a signature a PRECEDING hop
316
- * already signed — true exactly when the hop is not the first hop of its
317
- * own signature group, per `RouteHopData.automatic`'s own doc ("runs
318
- * automatically inside the SECTION's single signature").
319
- *
320
- * THIS MATCHES THREE OF THE FOUR TARGET SHAPES EXACTLY —
321
- * `buildCandidateRouteData` (an aggregator hop with index > 0 inside its
322
- * section, and the omnibridge hop only when an aggregator hop preceded it),
323
- * `buildNearIntentsTwoStepRouteData` (both of its hops are their own
324
- * section's first and only hop, so both are `false`), and
325
- * `buildPulseXSwapSectionData` (its one hop is always `false`, hard-coded
326
- * for the same reason).
327
- *
328
- * IT DOES NOT MATCH `buildOutboundRouteData`'s BRIDGE HOP, which hard-codes
329
- * `true` even though it is that section's own first and only hop — a SECOND,
330
- * DIFFERENT reading of "automatic" that describes the omnibridge's own
331
- * far-side auto-completion (no reader signature needed to FINISH the
332
- * crossing once signed) rather than whether the hop itself needed no
333
- * signature to START. `buildDirectRouteData` (a separate, non-plan family
334
- * this module does not otherwise touch) hard-codes the same reading. This
335
- * function follows the field's own documented meaning and the majority
336
- * convention (three of four builders) rather than that reading — see this
337
- * module's test file for the specific, deliberate divergence this produces
338
- * against `buildOutboundRouteData`'s bridge hop.
339
- *
340
- * @param hopIndex - the hop's index into `plan.hops`
341
- * @param plan - the plan the hop belongs to
342
- * @returns whether the hop is automatic
343
- */
344
94
  const hopIsAutomatic = (hopIndex, plan) => {
345
95
  const group = plan.signatures.find((candidateGroup) => candidateGroup.includes(hopIndex));
346
96
  return group !== undefined && group[0] !== hopIndex;
347
97
  };
348
- /**
349
- * Builds the raw `RouteHopData` for one planned hop.
350
- *
351
- * @param hop - the hop to render
352
- * @param hopIndex - the hop's index into `plan.hops`
353
- * @param plan - the plan the hop belongs to
354
- * @param resolveToken - resolves a graph node to its full display token
355
- * @param feeUsdValue - the hop's own share of its section's cost, or null
356
- * @param gas - the hop's gas split, or null when the producer states none
357
- * @returns the raw hop data
358
- */
359
98
  const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue, gas) => ({
360
99
  providerName: hopProviderName(hop.edge.carrier),
361
100
  providerKey: carrierProviderKey(hop.edge.carrier),
@@ -364,23 +103,10 @@ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue, gas) =>
364
103
  conversion: hopConversion(hop, hopIndex, plan, resolveToken),
365
104
  fromToken: toRouteTokenRef(hop.edge.from, resolveToken),
366
105
  toToken: toRouteTokenRef(hop.edge.to, resolveToken),
367
- // The per-hop dollar figure is a caller-supplied input (see this module's
368
- // head doc); the caller passes it in, never derived here.
369
106
  feeUsdValue,
370
107
  ...(gas === null ? {} : gas),
371
108
  automatic: hopIsAutomatic(hopIndex, plan),
372
109
  });
373
- /**
374
- * The per-hop shares a section's pricing names, checked against the group
375
- * they describe.
376
- *
377
- * @param pricing - the section's caller-supplied pricing
378
- * @param group - the signature group's hop indices
379
- * @param groupIndex - the group's index, for the error message
380
- * @returns one share per hop, null where none was given
381
- * @throws when the caller passed a share list of the wrong length — a share
382
- * matched to the wrong hop is a figure on the wrong leg
383
- */
384
110
  const groupHopFeeUsdValues = (pricing, group, groupIndex) => {
385
111
  const shares = pricing.hopFeeUsdValues;
386
112
  if (shares === undefined)
@@ -390,97 +116,23 @@ const groupHopFeeUsdValues = (pricing, group, groupIndex) => {
390
116
  }
391
117
  return shares;
392
118
  };
393
- // ---------------------------------------------------------------------------
394
- // Route-level derivations — candidate.ts's promised derivation, made real
395
- // ---------------------------------------------------------------------------
396
- /**
397
- * The plan's omnibridge provider, or null when the plan crosses no bridge
398
- * hop at all — exactly the derivation `RouteCandidate.plan`'s own TSDoc
399
- * (`../candidate.ts`) promises: "that same hop's `carrier`... the `to` node's
400
- * `chainId` of the plan's LAST hop whose carrier is `'pulsechain'` or
401
- * `'tokensex'`." Reading the LAST such hop, not the first, matters for a
402
- * plan with two bridge crossings (an outbound leg out of PulseChain followed
403
- * later by a separate inbound one) — not a shape any of today's four
404
- * builders draws, but one the general search can.
405
- *
406
- * @param plan - the plan to derive from
407
- * @returns the omnibridge provider, or null
408
- */
409
119
  export const deriveOmnibridgeProviderFromPlan = (plan) => {
410
120
  const bridgeHop = [...plan.hops].reverse().find((hop) => isBridgeCarrier(hop.edge.carrier));
411
121
  if (bridgeHop === undefined)
412
122
  return null;
413
- // `Array.prototype.find`'s own predicate narrowing does not flow back out
414
- // of the callback into the found element's type — only `isBridgeCarrier`
415
- // called again, directly on the plain `carrier` binding, narrows it here.
416
- // Re-declaring the return type as the real `Provider | null` (rather than
417
- // the wide `string | null` this used to widen to) is what surfaces a
418
- // mismatch at the source the moment a caller narrows further downstream,
419
- // instead of a silent `string` riding all the way to `providerDisplayName`.
420
123
  const { carrier } = bridgeHop.edge;
421
124
  return isBridgeCarrier(carrier) ? carrier : null;
422
125
  };
423
- /**
424
- * The plan's entry chain id — the chain the plan's FIRST hop lands on, when
425
- * that hop is carried by an aggregator (LI.FI, Relay or NEAR Intents); null
426
- * when the plan's first hop IS the omnibridge itself (a direct, same-chain
427
- * crossing — `buildDirectCrossingCandidate`'s shape) or is a PulseX swap.
428
- *
429
- * MATCHES `RouteCandidate.entryChainId`'S EXISTING MEANING throughout the
430
- * candidate-based builders — "the chain the omnibridge leg starts on" — for
431
- * every candidate that carries one, including a candidate whose aggregator
432
- * reaches its OWN destination with no further bridge hop at all (today's
433
- * `makeCandidate` test fixtures set `entryChainId` to the destination chain
434
- * in exactly that case, because it is still where the aggregator's own leg
435
- * lands).
436
- *
437
- * @param plan - the plan to derive from
438
- * @returns the entry chain id, or null for a direct crossing or a swap-only plan
439
- */
440
126
  export const deriveEntryChainIdFromPlan = (plan) => {
441
127
  const firstHop = plan.hops[0];
442
128
  if (!firstHop)
443
129
  return null;
444
130
  return isAggregatorCarrier(firstHop.edge.carrier) ? firstHop.edge.to.chainId : null;
445
131
  };
446
- /**
447
- * The decimal chain ids every INTERMEDIATE hop of the plan passes through,
448
- * origin and destination excluded — `RouteData.viaChainIds`'s own contract.
449
- * Undefined (never an empty array) when the plan has nothing to report, per
450
- * that field's own "ABSENT... on any route with no intermediate chain" doc.
451
- *
452
- * @param plan - the plan to derive from
453
- * @returns the via chain ids in hop order, or undefined
454
- */
455
132
  export const deriveViaChainIdsFromPlan = (plan) => {
456
133
  const intermediateChainIds = plan.hops.slice(0, -1).map((hop) => hop.edge.to.chainId);
457
134
  return intermediateChainIds.length > 0 ? intermediateChainIds : undefined;
458
135
  };
459
- /**
460
- * The plan's `RouteKind`, per the route-search design's section 1: `'direct'`
461
- * when signature 1 IS the omnibridge crossing itself (no aggregator prefix —
462
- * `buildDirectCrossingCandidate`'s shape, and `buildOutboundRouteData`'s
463
- * shape), `'conversion'` when the LAST hop is a PulseX swap
464
- * (`buildConversionRouteData`'s shape), `'aggregated'` otherwise (an
465
- * aggregator carries signature 1, fused with a bridge hop or not —
466
- * `buildNearIntentsTwoStepRouteData`'s shape, and the general
467
- * aggregator-fused shape).
468
- *
469
- * DIVERGES FROM TODAY'S LITERAL OUTPUT FOR ONE CASE, DELIBERATELY: a
470
- * same-chain candidate built by `buildDirectCrossingCandidate` and rendered
471
- * through `buildCandidateRouteData` today always comes back `kind:
472
- * 'aggregated'`, because that builder hard-codes `'aggregated'`
473
- * unconditionally — an artifact of `RouteCandidate.entryChainId` being
474
- * non-nullable, so `buildCandidateRouteData` always assumes an
475
- * aggregator-entry shape even for a candidate with no aggregator leg at all.
476
- * This function instead answers `'direct'` for that exact plan, matching how
477
- * `buildDirectRouteData` (the OTHER, non-candidate 'direct' family) already
478
- * represents the identical real-world same-chain crossing. See this
479
- * module's test file for the specific case.
480
- *
481
- * @param plan - the plan to derive from
482
- * @returns the route's kind
483
- */
484
136
  export const derivePlanKind = (plan) => {
485
137
  const lastHop = plan.hops[plan.hops.length - 1];
486
138
  if (lastHop && lastHop.edge.carrier === 'PulseX')
@@ -490,24 +142,6 @@ export const derivePlanKind = (plan) => {
490
142
  return 'direct';
491
143
  return 'aggregated';
492
144
  };
493
- /**
494
- * Narrows a plan's {@link HopCarrier} (a plain string, since `HopEdge.carrier`
495
- * moved off the closed `ProviderKey` union) down to `RouteData.headlineProviderKey`'s
496
- * own closed {@link ProviderKey} type, for the display/logo lookup tables that
497
- * type exists to protect (`resolveProviderLogoUri` / `resolveProviderDisplayName`
498
- * — see `ProviderKey`'s own doc in `../../providers.js`).
499
- *
500
- * A GIBS PLAN NEVER CARRIES ANYTHING ELSE. Every carrier a `RoutePlan` this
501
- * module renders can name is one gibs itself registered — the three built-in
502
- * aggregators, the omnibridge, PulseX, or a wrap step, which
503
- * {@link deriveHeadlineProviderKeyFromPlan} skips (`routing/carriers/`,
504
- * `packages/ui/src/lib/state/useRouteSearch.ts`) — so this throws rather than
505
- * guessing when it sees anything else: a plan naming an unrecognized carrier
506
- * is a bug in whatever built it, not a display gap to paper over silently.
507
- *
508
- * @param carrier - the plan's headline carrier
509
- * @returns the same value, typed as `ProviderKey`
510
- */
511
145
  const toHeadlineProviderKey = (carrier) => {
512
146
  if (isBridgeCarrier(carrier))
513
147
  return carrier;
@@ -517,48 +151,8 @@ const toHeadlineProviderKey = (carrier) => {
517
151
  return carrier;
518
152
  throw new Error(`toHeadlineProviderKey: unrecognized carrier "${carrier}"`);
519
153
  };
520
- /**
521
- * The plan's headline provider — the omnibridge crossing it out when it
522
- * crosses one, or whichever carrier performs signature 1 when it does not.
523
- * Matches every existing builder's own headline choice: the omnibridge when
524
- * present (`buildCandidateRouteData`, `buildNearIntentsTwoStepRouteData`,
525
- * `buildOutboundRouteData`), the aggregator's own key as the fallback
526
- * (`buildCandidateRouteData`'s no-omnibridge branch).
527
- *
528
- * @param plan - the plan to derive from
529
- * @returns the headline provider key
530
- */
531
154
  export const deriveHeadlineProviderKeyFromPlan = (plan) => toHeadlineProviderKey(deriveOmnibridgeProviderFromPlan(plan) ?? firstHeadlineCarrier(plan));
532
- /**
533
- * The carrier of the first hop that can headline a route. A `wrap` hop
534
- * (native coin to its wrapped token, or back) is a step inside a route, never
535
- * the route itself, so it is skipped. A plan made only of wraps headlines as
536
- * PulseX, the same key the same-chain swap builder gives that route
537
- * (`buildSameChainSwapRouteData`).
538
- *
539
- * @param plan - the plan to read
540
- * @returns the carrier to headline
541
- */
542
155
  const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== WRAP_CARRIER_ID)?.edge.carrier ?? 'PulseX';
543
- /**
544
- * The anchor a two-signature plan leaves the reader holding between its
545
- * first and second groups — `RouteCandidate.plan`'s own TSDoc: "the anchor
546
- * the reader is left holding between signatures... taken from the first hop
547
- * of the second group." Reuses `describeDeferredCrossing`
548
- * (`../../near-intents-route.js`) rather than re-validating the same
549
- * "positive amount, resolved token" rule a second time.
550
- *
551
- * Null when the plan has only one signature group (there is no deferred
552
- * crossing to describe) or the plan is malformed (a signature group naming
553
- * hop indices the plan does not have).
554
- *
555
- * @param plan - the plan to derive from
556
- * @param resolveToken - resolves a graph node to its full display token
557
- * @param anchorUsdValue - the anchor's own carrier's valuation of the
558
- * landing, exactly as `DeferredCrossing.anchorUsdValue` expects — supplied
559
- * by the caller, since a plan carries no United States dollar figures.
560
- * @returns the deferred crossing, or null
561
- */
562
156
  export const deriveDeferredCrossingFromPlan = (plan, resolveToken, anchorUsdValue) => {
563
157
  const secondGroup = plan.signatures[1];
564
158
  const firstGroup = plan.signatures[0];
@@ -578,26 +172,7 @@ export const deriveDeferredCrossingFromPlan = (plan, resolveToken, anchorUsdValu
578
172
  anchorUsdValue,
579
173
  });
580
174
  };
581
- /**
582
- * Whether `hop`'s own figure carries no value at all — an `EstimateBasis`
583
- * (`graph.ts`) of `'unpriced'`: a bare decimal rescale with nothing behind
584
- * it (see that type's own doc). {@link sectionLandingAmount} is what a
585
- * `true` result means for the section's (and the route's) headline figure:
586
- * it is left OUT of `amount` entirely, never shown at all.
587
- */
588
175
  const hopOutcomeIsUnpriced = (hop) => hop.outcome.kind === 'estimate' && hop.outcome.basis === 'unpriced';
589
- /**
590
- * What `hop` reports for `RouteLandingData.estimateBasis` /
591
- * `RouteOutputData.estimateBasis` (`../route-data.ts`): the hop's own
592
- * `EstimateBasis` string when it is a composed/probed figure, `null` for a
593
- * bound quote — and `null` for `'unpriced'` too, per that field's own doc
594
- * ("'unpriced' never reaches here"), since {@link sectionLandingAmount}
595
- * already leaves an unpriced hop's `amount` out entirely rather than
596
- * labelling it.
597
- *
598
- * @param hop - the hop to read
599
- * @returns the estimate basis to report, or null
600
- */
601
176
  const hopEstimateBasis = (hop) => {
602
177
  if (hop.outcome.kind !== 'estimate')
603
178
  return null;
@@ -605,17 +180,6 @@ const hopEstimateBasis = (hop) => {
605
180
  return null;
606
181
  return hop.outcome.basis;
607
182
  };
608
- /**
609
- * A section's landing amount: the caller's explicit override when given
610
- * ({@link PlanSectionPricing.landingAmountOverride}'s own doc), else `null`
611
- * when the section's last hop is `'unpriced'` (see {@link hopOutcomeIsUnpriced}
612
- * — a figure with no value behind it is never shown, marked or not), else
613
- * the hop's own composed delivered amount.
614
- *
615
- * @param pricing - the section's caller-supplied pricing
616
- * @param lastHop - the section's last hop
617
- * @returns the amount to land the section on, or null
618
- */
619
183
  const sectionLandingAmount = (pricing, lastHop) => {
620
184
  if ('landingAmountOverride' in pricing)
621
185
  return pricing.landingAmountOverride ?? null;
@@ -623,26 +187,6 @@ const sectionLandingAmount = (pricing, lastHop) => {
623
187
  return null;
624
188
  return lastHop.outcome.deliveredAmount;
625
189
  };
626
- /**
627
- * Turns a priced {@link RoutePlan} into a raw {@link RouteData} — one section
628
- * per signature group, `deferred` on every group after the first, each
629
- * section's hops and token flow drawn structurally from the plan, and the
630
- * dollar figures overlaid from `inputs.sections`. See this module's head doc
631
- * for why the dollar figures are caller-supplied rather than derived here.
632
- *
633
- * NEVER SHOWS AN UNPRICED HEADLINE FIGURE, AND MARKS EVERY OTHER ESTIMATE AS
634
- * ONE — PHASE 3. A section (and the route's own `output`) whose last hop is
635
- * grounded on `EstimateBasis` `'unpriced'` lands on `amount: null`, whatever
636
- * the caller's own pricing says, per {@link sectionLandingAmount}'s own doc.
637
- * Every OTHER estimate (`'probe-rate'`, `'priced-conversion'`) still shows
638
- * its figure, with `estimateBasis` set to that basis so a reader is never
639
- * shown a composed/probed number as if it were a confirmed quote — see
640
- * `RouteOutputData.estimateBasis`'s own doc (`../route-data.ts`) for the
641
- * full rule this reads off {@link hopEstimateBasis}.
642
- *
643
- * @param inputs - the plan, a token resolver, an id, and per-section pricing
644
- * @returns the raw route data
645
- */
646
190
  export const buildRouteDataFromPlan = (inputs) => {
647
191
  const { plan, resolveToken, routeId, sections, totalCostUsdValue, netValueUsd, etaMinutes } = inputs;
648
192
  if (plan.hops.length === 0) {
@@ -714,25 +258,11 @@ export const buildRouteDataFromPlan = (inputs) => {
714
258
  }),
715
259
  netValueUsd,
716
260
  etaMinutes,
717
- // The single legacy `viaChainId` a not-yet-migrated consumer may still
718
- // read (`RouteOption`, `journeyPlan.ts`'s current `pathKeyFor` — see the
719
- // route-search design's section 1) — the first intermediate chain, when
720
- // there is one. `buildOutboundRouteData` sets this to the chain its one
721
- // bridge hop lands on, which is exactly this plan's first via chain.
722
261
  viaChainId: deriveViaChainIdsFromPlan(plan)?.[0],
723
262
  pathKey: plan.pathKey,
724
263
  viaChainIds: deriveViaChainIdsFromPlan(plan),
725
264
  };
726
265
  };
727
- /**
728
- * Turns a priced {@link RoutePlan} into a {@link RouteCandidate}, filling
729
- * `plan` and deriving `deferredCrossing`, `entryChainId` and
730
- * `omnibridgeProvider` from it exactly as `RouteCandidate.plan`'s own TSDoc
731
- * (`../candidate.ts`) promises.
732
- *
733
- * @param inputs - the plan, a token resolver, an id, and the caller's pricing
734
- * @returns the route candidate
735
- */
736
266
  export const buildRouteCandidateFromPlan = (inputs) => {
737
267
  const { plan, resolveToken, id, inputAmount, paidUsdValue, lastHopDeliveredUsdValue, destinationUsdPrice, omnibridgeFeeAmount = 0n, anchorUsdValue = null, requestedAmount, fees = [], executionDurationSeconds = 0, } = inputs;
738
268
  const firstHop = plan.hops[0];
@@ -760,23 +290,6 @@ export const buildRouteCandidateFromPlan = (inputs) => {
760
290
  executionDurationSeconds,
761
291
  steps: [],
762
292
  fees: [...fees],
763
- // A PRICED-BUT-NOT-RESERVED deposit (`UnreservedDeposit`,
764
- // `../../origin-deposit.js`) — the variant that EXISTS for exactly this
765
- // case: "a route on display... priced with a dry quote, which carries
766
- // figures and no address" (that type's own doc). This candidate exists
767
- // for scoring and for `RouteData`/`RouteCandidate` shape assembly, never
768
- // for a real signature, so it has nothing to reserve.
769
- //
770
- // THIS IS NOT A STAND-IN FOR THE ORIGIN'S REAL ECOSYSTEM. An earlier
771
- // version of this line hard-coded `{ ecosystem: 'evm', ... }`
772
- // unconditionally, which is wrong for any non-Ethereum-Virtual-Machine
773
- // origin a general-search plan can now represent (a Zcash or Solana
774
- // first hop, for example) — a deposit's ecosystem must come from the
775
- // chain, never asserted past it. `UnreservedDeposit` sidesteps the
776
- // question honestly instead of answering it wrongly: it is
777
- // discriminated by `kind`, not by `ecosystem`, and carries no
778
- // ecosystem-specific fields at all, so there is no wrong shape to pick
779
- // regardless of which ecosystem `firstHop.edge.from` names.
780
293
  originDeposit: { kind: 'unreserved', chainId: firstHop.edge.from.chainId },
781
294
  };
782
295
  return {
@@ -794,20 +307,6 @@ export const buildRouteCandidateFromPlan = (inputs) => {
794
307
  plan,
795
308
  };
796
309
  };
797
- /**
798
- * Turns a priced {@link RoutePlan} into {@link RouteScoreInputs} — reusing
799
- * `candidateDeliveredUsd`, `candidatePaidUsd` and `candidateOutsideFeesUsd`
800
- * (`../producers.js`) against the SAME candidate
801
- * {@link buildRouteCandidateFromPlan} builds, exactly as `RouteScoreInputs`'
802
- * own doc in `score.ts` asks: "generalized to the plan's LAST hop and FIRST
803
- * hop respectively... import and reuse them; do not write a second
804
- * valuation."
805
- *
806
- * @param valuation - the plan, a token resolver, an id, and the caller's pricing
807
- * @param gaslessIntermediateChainCount - intermediate chains the connected
808
- * account holds no gas on — supplied by the caller, never computed here
809
- * @returns the plan's score inputs
810
- */
811
310
  export const buildRouteScoreInputsFromPlan = (valuation, gaslessIntermediateChainCount) => {
812
311
  const candidate = buildRouteCandidateFromPlan(valuation);
813
312
  return {
@@ -817,10 +316,6 @@ export const buildRouteScoreInputsFromPlan = (valuation, gaslessIntermediateChai
817
316
  legClaimedDurationsSeconds: valuation.legClaimedDurationsSeconds,
818
317
  readerActionCount: valuation.plan.signatures.length,
819
318
  gaslessIntermediateChainCount,
820
- // PHASE 3 — read directly off the plan's own hops, never re-derived: see
821
- // `planComposedBasis`'s own doc (`graph.ts`) for why this always agrees
822
- // with `planIsRankable`, and `RouteScoreInputs.basis`'s own doc for what
823
- // `scoreRoutePlan` does with it.
824
319
  basis: planComposedBasis(valuation.plan.hops),
825
320
  };
826
321
  };