@gibs/bridge-sdk 1.13.1 → 1.18.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 (228) hide show
  1. package/README.md +295 -18
  2. package/abis/package.json +6 -0
  3. package/bridge-limits/package.json +6 -0
  4. package/chain-info/package.json +6 -0
  5. package/chain-names/package.json +6 -0
  6. package/chains/package.json +6 -0
  7. package/claims/package.json +6 -0
  8. package/config/package.json +6 -0
  9. package/consolidated-quote/package.json +6 -0
  10. package/destination-calldata/package.json +6 -0
  11. package/dist/chain-info.d.ts +1 -1
  12. package/dist/chain-info.js +11 -12
  13. package/dist/chains.d.ts +25 -0
  14. package/dist/chains.js +55 -20
  15. package/dist/cjs/abis.d.ts +1 -0
  16. package/dist/cjs/abis.js +19 -0
  17. package/dist/cjs/bridge-limits.d.ts +103 -0
  18. package/dist/cjs/bridge-limits.js +89 -0
  19. package/dist/cjs/chain-info.d.ts +169 -0
  20. package/dist/cjs/chain-info.js +452 -0
  21. package/dist/cjs/chain-names.d.ts +160 -0
  22. package/dist/cjs/chain-names.js +207 -0
  23. package/dist/cjs/chains.d.ts +90 -0
  24. package/dist/cjs/chains.js +357 -0
  25. package/dist/cjs/claims.d.ts +198 -0
  26. package/dist/cjs/claims.js +182 -0
  27. package/dist/cjs/config.d.ts +255 -0
  28. package/dist/cjs/config.js +569 -0
  29. package/dist/cjs/consolidated-quote.d.ts +648 -0
  30. package/dist/cjs/consolidated-quote.js +1048 -0
  31. package/dist/cjs/destination-calldata.d.ts +118 -0
  32. package/dist/cjs/destination-calldata.js +156 -0
  33. package/dist/cjs/ecosystems.d.ts +217 -0
  34. package/dist/cjs/ecosystems.js +298 -0
  35. package/dist/cjs/execution.d.ts +359 -0
  36. package/dist/cjs/execution.js +495 -0
  37. package/dist/cjs/fee-type.d.ts +10 -0
  38. package/dist/cjs/fee-type.js +10 -0
  39. package/dist/cjs/hyperliquid-execution.d.ts +152 -0
  40. package/dist/cjs/hyperliquid-execution.js +175 -0
  41. package/dist/cjs/image-links.d.ts +117 -0
  42. package/dist/cjs/image-links.js +153 -0
  43. package/dist/cjs/index.d.ts +24 -0
  44. package/dist/cjs/index.js +40 -0
  45. package/dist/cjs/lifi-execution-guard.d.ts +210 -0
  46. package/dist/cjs/lifi-execution-guard.js +409 -0
  47. package/dist/cjs/near-intents-conformance.d.ts +85 -0
  48. package/dist/cjs/near-intents-conformance.js +337 -0
  49. package/dist/cjs/near-intents-route.d.ts +47 -0
  50. package/dist/cjs/near-intents-route.js +303 -0
  51. package/dist/cjs/near-intents.d.ts +524 -0
  52. package/dist/cjs/near-intents.js +1071 -0
  53. package/dist/cjs/origin-deposit.d.ts +326 -0
  54. package/dist/cjs/origin-deposit.js +82 -0
  55. package/dist/cjs/package.json +3 -0
  56. package/dist/cjs/pre-quote.d.ts +198 -0
  57. package/dist/cjs/pre-quote.js +180 -0
  58. package/dist/cjs/prices.d.ts +117 -0
  59. package/dist/cjs/prices.js +224 -0
  60. package/dist/cjs/providers.d.ts +238 -0
  61. package/dist/cjs/providers.js +171 -0
  62. package/dist/cjs/quote-fees.d.ts +102 -0
  63. package/dist/cjs/quote-fees.js +201 -0
  64. package/dist/cjs/referral.d.ts +85 -0
  65. package/dist/cjs/referral.js +85 -0
  66. package/dist/cjs/relay-currencies.d.ts +47 -0
  67. package/dist/cjs/relay-currencies.js +95 -0
  68. package/dist/cjs/relay-depository.d.ts +2 -0
  69. package/dist/cjs/relay-depository.js +85 -0
  70. package/dist/cjs/relay-quote.d.ts +301 -0
  71. package/dist/cjs/relay-quote.js +996 -0
  72. package/dist/cjs/route-steps.d.ts +138 -0
  73. package/dist/cjs/route-steps.js +310 -0
  74. package/dist/cjs/routing/amounts.d.ts +80 -0
  75. package/dist/cjs/routing/amounts.js +126 -0
  76. package/dist/cjs/routing/candidate-route.d.ts +43 -0
  77. package/dist/cjs/routing/candidate-route.js +50 -0
  78. package/dist/cjs/routing/candidate.d.ts +237 -0
  79. package/dist/cjs/routing/candidate.js +100 -0
  80. package/dist/cjs/routing/carrier-provider-key.d.ts +31 -0
  81. package/dist/cjs/routing/carrier-provider-key.js +42 -0
  82. package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
  83. package/dist/cjs/routing/carriers/ask-answer.js +2 -0
  84. package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
  85. package/dist/cjs/routing/carriers/omnibridge.js +459 -0
  86. package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
  87. package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
  88. package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
  89. package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
  90. package/dist/cjs/routing/carriers/pulsex.d.ts +184 -0
  91. package/dist/cjs/routing/carriers/pulsex.js +615 -0
  92. package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
  93. package/dist/cjs/routing/carriers/wrap.js +119 -0
  94. package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
  95. package/dist/cjs/routing/composed-route-quality.js +144 -0
  96. package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
  97. package/dist/cjs/routing/entry-chain-anchor.js +59 -0
  98. package/dist/cjs/routing/format.d.ts +31 -0
  99. package/dist/cjs/routing/format.js +64 -0
  100. package/dist/cjs/routing/index.d.ts +42 -0
  101. package/dist/cjs/routing/index.js +142 -0
  102. package/dist/cjs/routing/producers.d.ts +770 -0
  103. package/dist/cjs/routing/producers.js +1617 -0
  104. package/dist/cjs/routing/providers.d.ts +31 -0
  105. package/dist/cjs/routing/providers.js +48 -0
  106. package/dist/cjs/routing/route-data.d.ts +397 -0
  107. package/dist/cjs/routing/route-data.js +2 -0
  108. package/dist/cjs/routing/search/compose.d.ts +168 -0
  109. package/dist/cjs/routing/search/compose.js +104 -0
  110. package/dist/cjs/routing/search/graph.d.ts +668 -0
  111. package/dist/cjs/routing/search/graph.js +461 -0
  112. package/dist/cjs/routing/search/plan-route-data.d.ts +285 -0
  113. package/dist/cjs/routing/search/plan-route-data.js +871 -0
  114. package/dist/cjs/routing/search/score.d.ts +356 -0
  115. package/dist/cjs/routing/search/score.js +445 -0
  116. package/dist/cjs/routing/swap-transactions.d.ts +95 -0
  117. package/dist/cjs/routing/swap-transactions.js +133 -0
  118. package/dist/cjs/routing/usd-value.d.ts +17 -0
  119. package/dist/cjs/routing/usd-value.js +29 -0
  120. package/dist/cjs/settings.d.ts +785 -0
  121. package/dist/cjs/settings.js +1074 -0
  122. package/dist/cjs/swap-venues.d.ts +43 -0
  123. package/dist/cjs/swap-venues.js +29 -0
  124. package/dist/cjs/ton-connect.d.ts +98 -0
  125. package/dist/cjs/ton-connect.js +206 -0
  126. package/dist/cjs/ton-execution.d.ts +7 -0
  127. package/dist/cjs/ton-execution.js +23 -0
  128. package/dist/cjs/ton.d.ts +37 -0
  129. package/dist/cjs/ton.js +83 -0
  130. package/dist/cjs/tron-execution.d.ts +146 -0
  131. package/dist/cjs/tron-execution.js +248 -0
  132. package/dist/cjs/types.d.ts +51 -0
  133. package/dist/cjs/types.js +2 -0
  134. package/dist/claims.d.ts +198 -0
  135. package/dist/claims.js +140 -0
  136. package/dist/config.d.ts +42 -3
  137. package/dist/config.js +87 -21
  138. package/dist/consolidated-quote.d.ts +109 -15
  139. package/dist/consolidated-quote.js +310 -52
  140. package/dist/execution.d.ts +359 -0
  141. package/dist/execution.js +455 -0
  142. package/dist/hyperliquid-execution.js +2 -3
  143. package/dist/index.d.ts +4 -0
  144. package/dist/index.js +4 -0
  145. package/dist/lifi-execution-guard.d.ts +13 -2
  146. package/dist/lifi-execution-guard.js +4 -0
  147. package/dist/near-intents.js +2 -3
  148. package/dist/pre-quote.d.ts +6 -6
  149. package/dist/pre-quote.js +10 -14
  150. package/dist/prices.d.ts +10 -0
  151. package/dist/prices.js +52 -1
  152. package/dist/providers.d.ts +17 -0
  153. package/dist/providers.js +6 -0
  154. package/dist/referral.d.ts +12 -4
  155. package/dist/referral.js +12 -4
  156. package/dist/relay-depository.d.ts +2 -0
  157. package/dist/relay-depository.js +82 -0
  158. package/dist/relay-quote.js +2 -3
  159. package/dist/routing/candidate.d.ts +3 -3
  160. package/dist/routing/candidate.js +3 -3
  161. package/dist/routing/carrier-provider-key.d.ts +31 -0
  162. package/dist/routing/carrier-provider-key.js +37 -0
  163. package/dist/routing/carriers/ask-answer.d.ts +17 -0
  164. package/dist/routing/carriers/omnibridge.js +23 -21
  165. package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
  166. package/dist/routing/carriers/pulsex-paths.js +92 -0
  167. package/dist/routing/carriers/pulsex-split.d.ts +175 -0
  168. package/dist/routing/carriers/pulsex-split.js +185 -0
  169. package/dist/routing/carriers/pulsex.d.ts +81 -46
  170. package/dist/routing/carriers/pulsex.js +267 -148
  171. package/dist/routing/composed-route-quality.d.ts +28 -6
  172. package/dist/routing/composed-route-quality.js +36 -5
  173. package/dist/routing/entry-chain-anchor.d.ts +1 -1
  174. package/dist/routing/entry-chain-anchor.js +1 -1
  175. package/dist/routing/index.d.ts +11 -3
  176. package/dist/routing/index.js +8 -2
  177. package/dist/routing/producers.js +1 -1
  178. package/dist/routing/providers.d.ts +5 -0
  179. package/dist/routing/providers.js +5 -0
  180. package/dist/routing/route-data.d.ts +47 -5
  181. package/dist/routing/search/graph.d.ts +15 -0
  182. package/dist/routing/search/plan-route-data.d.ts +42 -1
  183. package/dist/routing/search/plan-route-data.js +68 -9
  184. package/dist/routing/search/score.d.ts +3 -0
  185. package/dist/routing/search/score.js +11 -3
  186. package/dist/routing/swap-transactions.d.ts +95 -0
  187. package/dist/routing/swap-transactions.js +127 -0
  188. package/dist/settings.d.ts +94 -1
  189. package/dist/settings.js +95 -3
  190. package/dist/swap-venues.d.ts +43 -0
  191. package/dist/swap-venues.js +24 -0
  192. package/dist/ton-connect.d.ts +98 -0
  193. package/dist/ton-connect.js +200 -0
  194. package/dist/ton-execution.d.ts +4 -114
  195. package/dist/ton-execution.js +4 -244
  196. package/dist/ton.d.ts +37 -0
  197. package/dist/ton.js +65 -0
  198. package/docs/examples/example-a-ether-to-pulse.ts +300 -0
  199. package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
  200. package/docs/examples/shared.ts +261 -0
  201. package/docs/examples/tsconfig.json +18 -0
  202. package/docs/integration-guide.md +617 -0
  203. package/ecosystems/package.json +6 -0
  204. package/execution/package.json +6 -0
  205. package/fee-type/package.json +6 -0
  206. package/hyperliquid-execution/package.json +6 -0
  207. package/image-links/package.json +6 -0
  208. package/lifi-execution-guard/package.json +6 -0
  209. package/llms.txt +75 -10
  210. package/near-intents/package.json +6 -0
  211. package/near-intents-route/package.json +6 -0
  212. package/origin-deposit/package.json +6 -0
  213. package/package.json +817 -10
  214. package/pre-quote/package.json +6 -0
  215. package/prices/package.json +6 -0
  216. package/providers/package.json +6 -0
  217. package/quote-fees/package.json +6 -0
  218. package/referral/package.json +6 -0
  219. package/relay-currencies/package.json +6 -0
  220. package/relay-quote/package.json +6 -0
  221. package/route-steps/package.json +6 -0
  222. package/routing/package.json +6 -0
  223. package/settings/package.json +6 -0
  224. package/ton/package.json +6 -0
  225. package/ton-connect/package.json +6 -0
  226. package/ton-execution/package.json +6 -0
  227. package/tron-execution/package.json +6 -0
  228. package/types/package.json +6 -0
@@ -47,6 +47,26 @@ export declare const COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = 3;
47
47
  * of the reader's money.
48
48
  */
49
49
  export declare const COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = 5;
50
+ /**
51
+ * The end-to-end value GAIN above which a composed route is withdrawn, in
52
+ * percent of what the reader pays.
53
+ *
54
+ * A ROUTE CANNOT MAKE MONEY. Every leg charges a fee, so a route that
55
+ * delivers more value than it takes says one of its figures is wrong. Only a
56
+ * disagreement between the two price feeds can make a gain look real, and the
57
+ * value-loss ceiling already trusts those feeds to within the same five
58
+ * percent. So the bound is symmetric with
59
+ * {@link COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT}.
60
+ *
61
+ * THE DEFECT THIS STOPS. On 2026-10-03 the engine served 0.0002 BNB to HEX at
62
+ * 495,256 HEX, about seven thousand times the rate of the next tier. The
63
+ * value-loss test read that as a loss of minus seven hundred thousand percent
64
+ * and passed it. A PulseX hop rescaled from an upstream live answer keeps the
65
+ * price impact measured at its original, smaller input
66
+ * (`recomposePath` in `@gibs/quotes`'s `search/waves.ts`), so the impact test
67
+ * cannot see an inflated amount either. Only the two ends of the journey can.
68
+ */
69
+ export declare const COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT = 5;
50
70
  /**
51
71
  * The verdict on one composed route.
52
72
  *
@@ -62,18 +82,20 @@ export type ComposedRouteVerdict = {
62
82
  readonly missing: 'price-impact' | 'route-value';
63
83
  } | {
64
84
  readonly state: 'rejected';
65
- readonly reason: 'price-impact' | 'value-loss';
85
+ readonly reason: 'price-impact' | 'value-loss' | 'value-gain';
66
86
  /** The measured figure, in percent, that failed its ceiling. */
67
87
  readonly measuredPercent: number;
68
88
  /** The ceiling it failed. */
69
89
  readonly ceilingPercent: number;
70
90
  };
71
91
  /**
72
- * Judges a composed route against both ceilings.
92
+ * Judges a composed route against its three ceilings.
73
93
  *
74
- * Checks price impact first and value loss second, because impact names the
75
- * specific leg at fault while value loss only says the journey as a whole went
76
- * wrong — so reporting impact when both fail is the more useful answer.
94
+ * Checks price impact first, then value loss, then value gain. Impact names
95
+ * the specific leg at fault, while the two value tests only say the journey as
96
+ * a whole went wrong, so impact is the more useful answer when several fail.
97
+ * In the measured percent of a `'value-gain'` rejection, the gain is a
98
+ * positive number.
77
99
  *
78
100
  * @param options.priceImpactPercent - the swap leg's price impact, from the
79
101
  * estimate's marginal reference quote; null when not yet quoted
@@ -98,6 +120,6 @@ export declare const assessComposedRoute: ({ priceImpactPercent, paidUsdValue, d
98
120
  * pricing" rather than "no route", which a boolean alone could not.
99
121
  *
100
122
  * @param verdict - the verdict from {@link assessComposedRoute}
101
- * @returns true only when the route passed both ceilings
123
+ * @returns true only when the route passed every ceiling
102
124
  */
103
125
  export declare const composedRouteMayBeOffered: (verdict: ComposedRouteVerdict) => boolean;
@@ -48,11 +48,33 @@ export const COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = 3;
48
48
  */
49
49
  export const COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = 5;
50
50
  /**
51
- * Judges a composed route against both ceilings.
51
+ * The end-to-end value GAIN above which a composed route is withdrawn, in
52
+ * percent of what the reader pays.
53
+ *
54
+ * A ROUTE CANNOT MAKE MONEY. Every leg charges a fee, so a route that
55
+ * delivers more value than it takes says one of its figures is wrong. Only a
56
+ * disagreement between the two price feeds can make a gain look real, and the
57
+ * value-loss ceiling already trusts those feeds to within the same five
58
+ * percent. So the bound is symmetric with
59
+ * {@link COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT}.
60
+ *
61
+ * THE DEFECT THIS STOPS. On 2026-10-03 the engine served 0.0002 BNB to HEX at
62
+ * 495,256 HEX, about seven thousand times the rate of the next tier. The
63
+ * value-loss test read that as a loss of minus seven hundred thousand percent
64
+ * and passed it. A PulseX hop rescaled from an upstream live answer keeps the
65
+ * price impact measured at its original, smaller input
66
+ * (`recomposePath` in `@gibs/quotes`'s `search/waves.ts`), so the impact test
67
+ * cannot see an inflated amount either. Only the two ends of the journey can.
68
+ */
69
+ export const COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT = 5;
70
+ /**
71
+ * Judges a composed route against its three ceilings.
52
72
  *
53
- * Checks price impact first and value loss second, because impact names the
54
- * specific leg at fault while value loss only says the journey as a whole went
55
- * wrong — so reporting impact when both fail is the more useful answer.
73
+ * Checks price impact first, then value loss, then value gain. Impact names
74
+ * the specific leg at fault, while the two value tests only say the journey as
75
+ * a whole went wrong, so impact is the more useful answer when several fail.
76
+ * In the measured percent of a `'value-gain'` rejection, the gain is a
77
+ * positive number.
56
78
  *
57
79
  * @param options.priceImpactPercent - the swap leg's price impact, from the
58
80
  * estimate's marginal reference quote; null when not yet quoted
@@ -91,6 +113,15 @@ export const assessComposedRoute = ({ priceImpactPercent, paidUsdValue, delivere
91
113
  ceilingPercent: COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT,
92
114
  };
93
115
  }
116
+ const gainPercent = -lossPercent;
117
+ if (gainPercent > COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT) {
118
+ return {
119
+ state: 'rejected',
120
+ reason: 'value-gain',
121
+ measuredPercent: gainPercent,
122
+ ceilingPercent: COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT,
123
+ };
124
+ }
94
125
  return { state: 'acceptable' };
95
126
  };
96
127
  /**
@@ -103,6 +134,6 @@ export const assessComposedRoute = ({ priceImpactPercent, paidUsdValue, delivere
103
134
  * pricing" rather than "no route", which a boolean alone could not.
104
135
  *
105
136
  * @param verdict - the verdict from {@link assessComposedRoute}
106
- * @returns true only when the route passed both ceilings
137
+ * @returns true only when the route passed every ceiling
107
138
  */
108
139
  export const composedRouteMayBeOffered = (verdict) => verdict.state === 'acceptable';
@@ -74,7 +74,7 @@ export type EntryChainAnchor = {
74
74
  * PulseChain token. A USD Coin pair does not gain an ether anchor here —
75
75
  * ether would land as bridged ether, a different token, needing a different
76
76
  * swap path. That is a different DESTINATION PAIR, and choosing between pairs
77
- * already has a home in `selectAnchorDestinationPair`.
77
+ * is made by the route engine, which holds every destination pair.
78
78
  *
79
79
  * @param options.entryChainToken - the destination pair's entry-chain side
80
80
  * @param options.pulsechainToken - the destination pair's PulseChain side
@@ -15,7 +15,7 @@ import { sameAddress } from '../ecosystems.js';
15
15
  * PulseChain token. A USD Coin pair does not gain an ether anchor here —
16
16
  * ether would land as bridged ether, a different token, needing a different
17
17
  * swap path. That is a different DESTINATION PAIR, and choosing between pairs
18
- * already has a home in `selectAnchorDestinationPair`.
18
+ * is made by the route engine, which holds every destination pair.
19
19
  *
20
20
  * @param options.entryChainToken - the destination pair's entry-chain side
21
21
  * @param options.pulsechainToken - the destination pair's PulseChain side
@@ -9,11 +9,11 @@ export { omnibridgeProviderForEntryChain, pulseXConversionSwapPath } from './pro
9
9
  export { buildDirectCrossingCandidate } from './candidate.js';
10
10
  export type { RouteCandidate, DeferredCrossing, DirectCrossingCandidateInputs } from './candidate.js';
11
11
  export { buildRouteDataForCandidate, routeSignatureCount } from './candidate-route.js';
12
- export { COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT, COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT, assessComposedRoute, composedRouteMayBeOffered, } from './composed-route-quality.js';
12
+ export { COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT, COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT, COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT, assessComposedRoute, composedRouteMayBeOffered, } from './composed-route-quality.js';
13
13
  export type { ComposedRouteVerdict } from './composed-route-quality.js';
14
14
  export { entryChainAnchorsForPair } from './entry-chain-anchor.js';
15
15
  export type { EntryChainAnchor, EntryChainCrossingCall } from './entry-chain-anchor.js';
16
- export type { EstimateBasis, RouteData, RouteHopData, RouteKind, RouteOutputData, RouteSectionData, RouteTokenRef, } from './route-data.js';
16
+ export type { EstimateBasis, RouteData, RouteGasByChain, RouteHopData, RouteKind, RouteOutputData, RouteSectionData, RouteTokenRef, } from './route-data.js';
17
17
  export { usdValueOfAmount } from './usd-value.js';
18
18
  export { formatUsd, formatUsdNullable } from './format.js';
19
19
  export { BRIDGE_SETTLEMENT_ETA_MINUTES, PULSEX_MARGINAL_REFERENCE_DIVISOR, pulsechainChainId, addUsdValues, providerDisplayName, resolveEntryChainName, buildCandidateRouteData, buildConversionRouteData, buildDirectRouteData, buildPulseXSwapEstimateData, buildPulseXSwapSectionData, buildOutboundRouteData, buildSameChainSwapRouteData, candidateDeliveredUsd, candidateNetValueUsd, candidateOutsideFeesUsd, candidatePaidUsd, } from './producers.js';
@@ -24,11 +24,19 @@ export { composableHopBasis, composeRouteDelivery, hopOutputAmount, } from './se
24
24
  export type { ComposableHop, ComposableHopFigure, ComposeRefusalReason, ComposedRouteDelivery, ExchangeRate, } from './search/compose.js';
25
25
  export { GAS_TOPUP_PENALTY_FLOOR_USD, GAS_TOPUP_PENALTY_PPM, SIGNATURE_PENALTY_FLOOR_USD, SIGNATURE_PENALTY_PPM, compareRoutePlansByGasAdjustedDeliveredAmount, compareRoutePlansByScoreAdjustedDeliveredAmount, gasAdjustedDeliveredAmount, scoreRoutePlan, sectionIsFundedByLanding, waitPenaltyPpm, zeroGasCostFor, } from './search/score.js';
26
26
  export type { GasCostFor, GasCostQuote, RouteScoreInputs } from './search/score.js';
27
+ export { WRAP_CARRIER_ID, carrierProviderKey, carrierStepKind } from './carrier-provider-key.js';
28
+ export type { CarrierStepKind } from './carrier-provider-key.js';
27
29
  export { buildRouteCandidateFromPlan, buildRouteDataFromPlan, buildRouteScoreInputsFromPlan, deriveDeferredCrossingFromPlan, deriveEntryChainIdFromPlan, deriveOmnibridgeProviderFromPlan, deriveViaChainIdsFromPlan, derivePlanKind, } from './search/plan-route-data.js';
28
30
  export type { PlanCandidateInputs, PlanRouteDataInputs, PlanSectionPricing, PlanValuation, ResolveNodeToken, } from './search/plan-route-data.js';
29
31
  export { createOmnibridgeCarrier, withOmnibridgeEntryFusing, } from './carriers/omnibridge.js';
30
32
  export type { BridgeablePair, CreateOmnibridgeCarrierOptions, } from './carriers/omnibridge.js';
31
- export { createPulseXCarrier, pulseXPoolPath, pulseXRouterForEdge, pulseXVenues } from './carriers/pulsex.js';
33
+ export { createPulseXCarrier, pulseXRouterForEdge, venueForEdge, pulseXSwapPathForHop, pulseXVenues, pulseXWrappedNativePath, } from './carriers/pulsex.js';
32
34
  export type { CreatePulseXCarrierOptions, PulseXVenue } from './carriers/pulsex.js';
35
+ export { bestPulseXPathQuote, candidatePulseXPaths, pulseXHubTokens } from './carriers/pulsex-paths.js';
36
+ export type { PulseXHubToken, PulseXPathQuote } from './carriers/pulsex-paths.js';
37
+ export { findSwapVenue, resolveSwapVenues } from '../swap-venues.js';
38
+ export type { SwapVenue, SwapVenues } from '../swap-venues.js';
33
39
  export { createWrapCarrier } from './carriers/wrap.js';
34
40
  export type { CarrierAskAnswer, CarrierExactResult } from './carriers/ask-answer.js';
41
+ export { buildPulseXSwapTransaction, buildWrapTransaction, swapMinimumOut } from './swap-transactions.js';
42
+ export type { SwapCallInputs } from './swap-transactions.js';
@@ -8,7 +8,7 @@ export { netDeliveredAmount, restateInDecimals, valueEquivalentAmount } from './
8
8
  export { omnibridgeProviderForEntryChain, pulseXConversionSwapPath } from './providers.js';
9
9
  export { buildDirectCrossingCandidate } from './candidate.js';
10
10
  export { buildRouteDataForCandidate, routeSignatureCount } from './candidate-route.js';
11
- export { COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT, COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT, assessComposedRoute, composedRouteMayBeOffered, } from './composed-route-quality.js';
11
+ export { COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT, COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT, COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT, assessComposedRoute, composedRouteMayBeOffered, } from './composed-route-quality.js';
12
12
  export { entryChainAnchorsForPair } from './entry-chain-anchor.js';
13
13
  export { usdValueOfAmount } from './usd-value.js';
14
14
  export { formatUsd, formatUsdNullable } from './format.js';
@@ -26,6 +26,7 @@ export { BRIDGE_SETTLEMENT_ETA_MINUTES, PULSEX_MARGINAL_REFERENCE_DIVISOR, pulse
26
26
  export { RANKABLE_ESTIMATE_BASES, assetNodeKey, combineComposedPathBasis, compareRoutePlansByDeliveredAmount, groupHopsBySignature, hopComposedBasis, hopEdgeKey, isConfirmedBasis, isRankableBasis, isRateLimitedCarrier, planComposedBasis, planIsConfirmed, planIsRankable, routePlanPathKey, } from './search/graph.js';
27
27
  export { composableHopBasis, composeRouteDelivery, hopOutputAmount, } from './search/compose.js';
28
28
  export { GAS_TOPUP_PENALTY_FLOOR_USD, GAS_TOPUP_PENALTY_PPM, SIGNATURE_PENALTY_FLOOR_USD, SIGNATURE_PENALTY_PPM, compareRoutePlansByGasAdjustedDeliveredAmount, compareRoutePlansByScoreAdjustedDeliveredAmount, gasAdjustedDeliveredAmount, scoreRoutePlan, sectionIsFundedByLanding, waitPenaltyPpm, zeroGasCostFor, } from './search/score.js';
29
+ export { WRAP_CARRIER_ID, carrierProviderKey, carrierStepKind } from './carrier-provider-key.js';
29
30
  // General route search — Phase 1: plan-route-data.ts. Turns a priced
30
31
  // RoutePlan into the same RouteData/RouteCandidate/RouteScoreInputs shapes
31
32
  // today's four hand-written producers emit — see that module's head doc.
@@ -38,5 +39,10 @@ export { buildRouteCandidateFromPlan, buildRouteDataFromPlan, buildRouteScoreInp
38
39
  // `searchRouteWaves` and their types from `@gibs/quotes/search`. Gibs's own
39
40
  // carriers (the omnibridge crossing, PulseX) live in `./carriers/`, below.
40
41
  export { createOmnibridgeCarrier, withOmnibridgeEntryFusing, } from './carriers/omnibridge.js';
41
- export { createPulseXCarrier, pulseXPoolPath, pulseXRouterForEdge, pulseXVenues } from './carriers/pulsex.js';
42
+ export { createPulseXCarrier, pulseXRouterForEdge, venueForEdge, pulseXSwapPathForHop, pulseXVenues, pulseXWrappedNativePath, } from './carriers/pulsex.js';
43
+ export { bestPulseXPathQuote, candidatePulseXPaths, pulseXHubTokens } from './carriers/pulsex-paths.js';
44
+ export { findSwapVenue, resolveSwapVenues } from '../swap-venues.js';
42
45
  export { createWrapCarrier } from './carriers/wrap.js';
46
+ // The same-chain swap and wrap builders. The interface's section signing and
47
+ // the SDK's execution planner (`../execution.ts`) share them.
48
+ export { buildPulseXSwapTransaction, buildWrapTransaction, swapMinimumOut } from './swap-transactions.js';
@@ -1182,7 +1182,7 @@ export const buildOutboundRouteData = (roots) => {
1182
1182
  ...(onward === null ? { etaSeconds: PULSEX_SWAP_ETA_SECONDS } : {}),
1183
1183
  deferred: true,
1184
1184
  deferredNote: onward === null
1185
- ? `Fee is approximate — quoted at ${entryChainName} when you return.`
1185
+ ? `The fee shown is an estimate. You get a new quote at ${entryChainName} when you come back.`
1186
1186
  : `Quoted by ${onward.providerName} at ${entryChainName} when you return.`,
1187
1187
  hops: [deferredHop],
1188
1188
  };
@@ -8,6 +8,11 @@ import { type Provider } from '../config.js';
8
8
  * IS WPLS (no point hopping through WPLS to reach WPLS). The router prices the path
9
9
  * with `getAmountsOut`, so a path with no liquidity simply yields no quote.
10
10
  *
11
+ * A FIXED PATH, NOT THE BEST ONE. The route search's PulseX carrier prices
12
+ * this path alongside others through its hub tokens and signs whichever
13
+ * delivers most (`carriers/pulsex-paths.ts`). This one remains the
14
+ * carrier's asynchronous `ask` path (`pulseXWrappedNativePath`).
15
+ *
11
16
  * @param anchorAddress - the bridged anchor token (the swap input)
12
17
  * @param wantedAddress - the token the user wants (the swap output)
13
18
  * @param wplsAddress - the wrapped-native intermediary (WPLS)
@@ -8,6 +8,11 @@ import { sameAddress } from '../ecosystems.js';
8
8
  * IS WPLS (no point hopping through WPLS to reach WPLS). The router prices the path
9
9
  * with `getAmountsOut`, so a path with no liquidity simply yields no quote.
10
10
  *
11
+ * A FIXED PATH, NOT THE BEST ONE. The route search's PulseX carrier prices
12
+ * this path alongside others through its hub tokens and signs whichever
13
+ * delivers most (`carriers/pulsex-paths.ts`). This one remains the
14
+ * carrier's asynchronous `ask` path (`pulseXWrappedNativePath`).
15
+ *
11
16
  * @param anchorAddress - the bridged anchor token (the swap input)
12
17
  * @param wantedAddress - the token the user wants (the swap output)
13
18
  * @param wplsAddress - the wrapped-native intermediary (WPLS)
@@ -1,6 +1,7 @@
1
1
  import type { Hex } from 'viem';
2
2
  import type { QuoteFee } from '../quote-fees.js';
3
3
  import type { ProviderKey } from '../providers.js';
4
+ import type { CarrierStepKind } from './carrier-provider-key.js';
4
5
  /**
5
6
  * The executing engine family a route runs through.
6
7
  *
@@ -91,7 +92,12 @@ export type RouteHopData = {
91
92
  /** Display name for the hop's provider, e.g. "LI.FI", "PulseChain Bridge". */
92
93
  providerName: string;
93
94
  /** Raw provider key for logo resolution when no `providerLogoUri` is supplied. */
94
- providerKey?: string | null;
95
+ providerKey?: ProviderKey | null;
96
+ /**
97
+ * What the hop does when no provider performs it (a native wrap). Such a hop
98
+ * has a null `providerKey`; the adapter draws its mark from this kind.
99
+ */
100
+ stepKind?: CarrierStepKind | null;
95
101
  /** Quote-supplied provider logo url (aggregator legs); preferred when present. */
96
102
  providerLogoUri?: string | null;
97
103
  /** Human-readable hop title, e.g. "Bridge to PulseChain". */
@@ -110,11 +116,27 @@ export type RouteHopData = {
110
116
  * completes. Null/undefined when the provider named no output token.
111
117
  */
112
118
  toToken?: RouteTokenRef | null;
113
- /** Per-hop fee in United States dollars as a NUMBER, or null when unknown. */
119
+ /** Per-hop fee in United States dollars as a NUMBER, or null when unknown. A search route's figure INCLUDES the signature's network gas (carried on the section's first hop). */
114
120
  feeUsdValue?: number | null;
121
+ /** The part of {@link feeUsdValue} that is not network gas: what the hop costs in value. Absent when the producer states no gas split. */
122
+ feeExcludingGasUsdValue?: number | null;
123
+ /** The network gas of the signature this hop opens, in dollars; null or absent on every hop that opens none, and when gas is unknown. */
124
+ gasUsdValue?: number | null;
115
125
  /** True when this hop runs automatically inside the section's single signature. */
116
126
  automatic?: boolean;
117
127
  };
128
+ /**
129
+ * The network gas a route needs on one signing chain, summed across the
130
+ * signatures it signs there.
131
+ */
132
+ export type RouteGasByChain = {
133
+ /** The chain the signatures broadcast on. */
134
+ chainId: number;
135
+ /** The native coin they burn, in base units (wei), or null when the chain's gas price is unknown. */
136
+ amountWei: bigint | null;
137
+ /** The same gas in dollars, or null when the amount or the native price is unknown. */
138
+ usdValue: number | null;
139
+ };
118
140
  /**
119
141
  * What a signature section's funds LAND IN when that step completes — the stop
120
142
  * the money actually comes to rest at, named in tokens rather than only drawn as
@@ -181,7 +203,9 @@ export type RouteSectionData = {
181
203
  /** Short label naming what this signature does, e.g. "Bridge to PulseChain". */
182
204
  label: string;
183
205
  /** Raw provider key for the service executing this signature, or null. */
184
- providerKey?: string | null;
206
+ providerKey?: ProviderKey | null;
207
+ /** What the signature does when no provider performs it (a native wrap); see {@link RouteHopData.stepKind}. */
208
+ stepKind?: CarrierStepKind | null;
185
209
  /**
186
210
  * The token-icon flow this signature walks, in raw references: the source, then
187
211
  * every INTERMEDIATE stop the funds pass through, then the destination. A
@@ -202,8 +226,12 @@ export type RouteSectionData = {
202
226
  * a cost.
203
227
  */
204
228
  unspent?: RouteUnspentData | null;
205
- /** Section total fee in United States dollars as a NUMBER, or null. */
229
+ /** Section total fee in United States dollars as a NUMBER, or null. A search route's figure INCLUDES this signature's network gas. */
206
230
  feeUsdValue?: number | null;
231
+ /** The part of {@link feeUsdValue} that is not network gas. Absent when the producer states no gas split. */
232
+ feeExcludingGasUsdValue?: number | null;
233
+ /** This signature's network gas in dollars, or null/absent when unknown. */
234
+ gasUsdValue?: number | null;
207
235
  /** Numeric inputs for an optional fee breakdown line (conversion mode). */
208
236
  feePercent?: number | null;
209
237
  /** Price-impact percentage for an optional fee breakdown line (conversion mode). */
@@ -316,8 +344,22 @@ export type RouteData = {
316
344
  sections: RouteSectionData[];
317
345
  /** What the route delivers — used for ranking and the output row. */
318
346
  output: RouteOutputData;
319
- /** Aggregate all-in cost in United States dollars as a NUMBER, or null. */
347
+ /**
348
+ * Aggregate ALL-IN cost in United States dollars as a NUMBER, or null:
349
+ * value lost plus network gas. The ranking reads this figure. For the
350
+ * figure without gas, read {@link costExcludingGasUsdValue}.
351
+ */
320
352
  totalCostUsdValue?: number | null;
353
+ /** {@link totalCostUsdValue} without network gas: the value the reader gives up. Absent when the producer states no gas split. */
354
+ costExcludingGasUsdValue?: number | null;
355
+ /**
356
+ * The network gas the reader pays across every signature, in dollars, or
357
+ * null/absent when no signing chain has a gas reading. Never zero for
358
+ * "unknown".
359
+ */
360
+ gasCostUsdValue?: number | null;
361
+ /** The gas by signing chain, in signing order: what to hold, and in which coin. */
362
+ gasByChain?: RouteGasByChain[];
321
363
  /** The user's net United States dollar outcome; the figure routes are ranked on. */
322
364
  netValueUsd?: number | null;
323
365
  /** Estimated end-to-end duration in minutes, or null when unknown. */
@@ -324,6 +324,14 @@ export type HopOutcome = {
324
324
  * pool reserves (a PulseX swap). Absent means "not measured", never zero.
325
325
  */
326
326
  readonly priceImpactPercent?: number;
327
+ /**
328
+ * The token addresses the carrier's execution passes through, in order,
329
+ * when the carrier chose among several (a PulseX swap path). The figure
330
+ * was priced along exactly this path, and the signer must sign exactly
331
+ * this path — never rebuild one. Opaque to the search, like
332
+ * {@link HopEdge.venue}. Absent when the carrier makes no such choice.
333
+ */
334
+ readonly swapPath?: readonly string[];
327
335
  } | {
328
336
  readonly kind: 'estimate';
329
337
  /** The composed/probed figure, in the hop's `to` node's base units. */
@@ -367,6 +375,13 @@ export type HopOutcome = {
367
375
  * small moves a live upstream quote makes. Absent means "not measured".
368
376
  */
369
377
  readonly priceImpactPercent?: number;
378
+ /**
379
+ * The swap path the hop was priced along when the carrier answered it
380
+ * exactly, carried onto the rescaled estimate like
381
+ * {@link priceImpactPercent}. The rescale changes the amount, not the
382
+ * path, so this is still the path to sign.
383
+ */
384
+ readonly swapPath?: readonly string[];
370
385
  };
371
386
  /**
372
387
  * Whether every hop of `hops` carries a figure a ranking may trust: a live
@@ -1,8 +1,9 @@
1
+ import { type ProviderKey } from '../../providers.js';
1
2
  import type { Provider } from '../../config.js';
2
3
  import type { QuoteFee } from '../../quote-fees.js';
3
4
  import type { Token } from '../../types.js';
4
5
  import type { DeferredCrossing, RouteCandidate } from '../candidate.js';
5
- import type { RouteData, RouteKind } from '../route-data.js';
6
+ import type { RouteData, RouteGasByChain, RouteKind } from '../route-data.js';
6
7
  import type { RouteScoreInputs } from './score.js';
7
8
  import { type AssetNode, type RoutePlan } from './graph.js';
8
9
  /**
@@ -83,6 +84,18 @@ export declare const deriveViaChainIdsFromPlan: (plan: RoutePlan) => number[] |
83
84
  * @returns the route's kind
84
85
  */
85
86
  export declare const derivePlanKind: (plan: RoutePlan) => RouteKind;
87
+ /**
88
+ * The plan's headline provider — the omnibridge crossing it out when it
89
+ * crosses one, or whichever carrier performs signature 1 when it does not.
90
+ * Matches every existing builder's own headline choice: the omnibridge when
91
+ * present (`buildCandidateRouteData`, `buildNearIntentsTwoStepRouteData`,
92
+ * `buildOutboundRouteData`), the aggregator's own key as the fallback
93
+ * (`buildCandidateRouteData`'s no-omnibridge branch).
94
+ *
95
+ * @param plan - the plan to derive from
96
+ * @returns the headline provider key
97
+ */
98
+ export declare const deriveHeadlineProviderKeyFromPlan: (plan: RoutePlan) => ProviderKey;
86
99
  /**
87
100
  * The anchor a two-signature plan leaves the reader holding between its
88
101
  * first and second groups — `RouteCandidate.plan`'s own TSDoc: "the anchor
@@ -116,6 +129,28 @@ export type PlanSectionPricing = {
116
129
  landingUsdValue: number | null;
117
130
  /** This section's own cost, in United States dollars, or null when unpriced. */
118
131
  feeUsdValue: number | null;
132
+ /**
133
+ * Each hop's own share of `feeUsdValue`, one entry per hop of the group in
134
+ * order, or omitted when the caller cannot price every hop.
135
+ *
136
+ * WHY A FUSED SECTION NEEDS THIS. One signature can carry two legs: an
137
+ * aggregator lands on the bridge's entry chain and makes the entry call in
138
+ * the same transaction. With no per-hop figure, a display that splits the
139
+ * section into legs can only put the whole cost on the first leg, so the
140
+ * aggregator wore the bridge's fee and the bridge leg read as free. The
141
+ * caller passes shares that add up to `feeUsdValue`, or nothing.
142
+ */
143
+ hopFeeUsdValues?: readonly (number | null)[];
144
+ /**
145
+ * The gas split of `feeUsdValue`, from a producer that prices network gas
146
+ * apart: `feeExcludingGasUsdValue` is the cost without gas, `gasUsdValue` the
147
+ * signature's gas (null when unknown). `hopFeeExcludingGasUsdValues` splits
148
+ * the first the way `hopFeeUsdValues` splits the all-in figure. The gas sits
149
+ * on the section's first hop.
150
+ */
151
+ feeExcludingGasUsdValue?: number | null;
152
+ gasUsdValue?: number | null;
153
+ hopFeeExcludingGasUsdValues?: readonly (number | null)[];
119
154
  /** The itemized charges behind `feeUsdValue`, passed straight through to `RouteSectionData.fees`. */
120
155
  fees?: readonly QuoteFee[];
121
156
  /** Estimated duration for this signature in minutes, or null/undefined to omit. */
@@ -150,6 +185,12 @@ export type PlanRouteDataInputs = {
150
185
  sections: readonly PlanSectionPricing[];
151
186
  /** The route's aggregate all-in cost in United States dollars, or null/undefined. */
152
187
  totalCostUsdValue?: number | null;
188
+ /** The all-in cost without network gas, or null/undefined. */
189
+ costExcludingGasUsdValue?: number | null;
190
+ /** The route's network gas in dollars, or null/undefined when unknown. */
191
+ gasCostUsdValue?: number | null;
192
+ /** The gas by signing chain, in signing order. */
193
+ gasByChain?: readonly RouteGasByChain[];
153
194
  /** The route's net United States dollar outcome — the figure routes are ranked on. */
154
195
  netValueUsd?: number | null;
155
196
  /** Estimated end-to-end duration in minutes, or null/undefined. */
@@ -1,6 +1,7 @@
1
1
  import * as imageLinks from '../../image-links.js';
2
2
  import { chainDisplayName } from '../../chain-names.js';
3
3
  import { findProviderById } from '../../providers.js';
4
+ import { WRAP_CARRIER_ID, carrierProviderKey, carrierStepKind } from '../carrier-provider-key.js';
4
5
  import { describeDeferredCrossing } from '../../near-intents-route.js';
5
6
  import { candidateDeliveredUsd, candidateOutsideFeesUsd, candidatePaidUsd, pulsechainChainId, providerDisplayName, } from '../producers.js';
6
7
  import { assetNodeKey, planComposedBasis, } from './graph.js';
@@ -229,6 +230,8 @@ const hopProviderName = (carrier) => {
229
230
  return providerDisplayName(carrier);
230
231
  if (carrier === 'PulseX')
231
232
  return 'PulseX';
233
+ if (carrierStepKind(carrier) === 'wrap')
234
+ return 'Wrap';
232
235
  // TOTAL OVER EVERY CARRIER. The engine plans hops for carriers the provider
233
236
  // registry does not hold (a local wrap, a host's own plug-in). Casting such
234
237
  // an id to a provider id and asking the throwing lookup crashed the whole
@@ -349,20 +352,44 @@ const hopIsAutomatic = (hopIndex, plan) => {
349
352
  * @param hopIndex - the hop's index into `plan.hops`
350
353
  * @param plan - the plan the hop belongs to
351
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
352
357
  * @returns the raw hop data
353
358
  */
354
- const toRouteHopData = (hop, hopIndex, plan, resolveToken) => ({
359
+ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue, gas) => ({
355
360
  providerName: hopProviderName(hop.edge.carrier),
356
- providerKey: hop.edge.carrier,
361
+ providerKey: carrierProviderKey(hop.edge.carrier),
362
+ stepKind: carrierStepKind(hop.edge.carrier),
357
363
  title: hopTitle(hop, hopIndex, plan, resolveToken),
358
364
  conversion: hopConversion(hop, hopIndex, plan, resolveToken),
359
365
  fromToken: toRouteTokenRef(hop.edge.from, resolveToken),
360
366
  toToken: toRouteTokenRef(hop.edge.to, resolveToken),
361
- // The per-hop dollar figure is a section-level, caller-supplied input (see
362
- // this module's head doc); a raw hop carries none of its own here.
363
- feeUsdValue: null,
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
+ feeUsdValue,
370
+ ...(gas === null ? {} : gas),
364
371
  automatic: hopIsAutomatic(hopIndex, plan),
365
372
  });
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
+ const groupHopFeeUsdValues = (pricing, group, groupIndex) => {
385
+ const shares = pricing.hopFeeUsdValues;
386
+ if (shares === undefined)
387
+ return group.map(() => null);
388
+ if (shares.length !== group.length) {
389
+ throw new Error(`buildRouteDataFromPlan: section ${groupIndex + 1} has ${group.length} hop(s) but ${shares.length} hop fee(s)`);
390
+ }
391
+ return shares;
392
+ };
366
393
  // ---------------------------------------------------------------------------
367
394
  // Route-level derivations — candidate.ts's promised derivation, made real
368
395
  // ---------------------------------------------------------------------------
@@ -472,7 +499,8 @@ export const derivePlanKind = (plan) => {
472
499
  *
473
500
  * A GIBS PLAN NEVER CARRIES ANYTHING ELSE. Every carrier a `RoutePlan` this
474
501
  * module renders can name is one gibs itself registered — the three built-in
475
- * aggregators, the omnibridge, or PulseX (`routing/carriers/`,
502
+ * aggregators, the omnibridge, PulseX, or a wrap step, which
503
+ * {@link deriveHeadlineProviderKeyFromPlan} skips (`routing/carriers/`,
476
504
  * `packages/ui/src/lib/state/useRouteSearch.ts`) — so this throws rather than
477
505
  * guessing when it sees anything else: a plan naming an unrecognized carrier
478
506
  * is a bug in whatever built it, not a display gap to paper over silently.
@@ -500,7 +528,18 @@ const toHeadlineProviderKey = (carrier) => {
500
528
  * @param plan - the plan to derive from
501
529
  * @returns the headline provider key
502
530
  */
503
- const deriveHeadlineProviderKeyFromPlan = (plan) => toHeadlineProviderKey(deriveOmnibridgeProviderFromPlan(plan) ?? plan.hops[0].edge.carrier);
531
+ 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
+ const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== WRAP_CARRIER_ID)?.edge.carrier ?? 'PulseX';
504
543
  /**
505
544
  * The anchor a two-signature plan leaves the reader holding between its
506
545
  * first and second groups — `RouteCandidate.plan`'s own TSDoc: "the anchor
@@ -618,10 +657,14 @@ export const buildRouteDataFromPlan = (inputs) => {
618
657
  const lastHop = hops[hops.length - 1];
619
658
  const landing = resolveNodeWithAssertedDecimals(lastHop.edge.to, resolveToken);
620
659
  const landingIsUnpriced = hopOutcomeIsUnpriced(lastHop);
660
+ const hopFeeUsdValues = groupHopFeeUsdValues(pricing, group, groupIndex);
661
+ const statesGasSplit = pricing.feeExcludingGasUsdValue !== undefined;
662
+ const hopFeeExcludingGasUsdValues = pricing.hopFeeExcludingGasUsdValues;
621
663
  return {
622
664
  id: `${routeId}-signature-${groupIndex + 1}`,
623
665
  label: sectionLabel(group, groupIndex, plan),
624
- providerKey: hops[0].edge.carrier,
666
+ providerKey: carrierProviderKey(hops[0].edge.carrier),
667
+ stepKind: carrierStepKind(hops[0].edge.carrier),
625
668
  tokenFlow: buildGroupTokenFlow(hops, resolveToken),
626
669
  landing: {
627
670
  amount: sectionLandingAmount(pricing, lastHop),
@@ -631,10 +674,19 @@ export const buildRouteDataFromPlan = (inputs) => {
631
674
  estimateBasis: hopEstimateBasis(lastHop),
632
675
  },
633
676
  feeUsdValue: pricing.feeUsdValue,
677
+ ...(statesGasSplit
678
+ ? { feeExcludingGasUsdValue: pricing.feeExcludingGasUsdValue, gasUsdValue: pricing.gasUsdValue ?? null }
679
+ : {}),
634
680
  fees: pricing.fees ? [...pricing.fees] : undefined,
635
681
  etaMinutes: pricing.etaMinutes,
636
682
  etaSeconds: pricing.etaSeconds,
637
- hops: group.map((hopIndex) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken)),
683
+ hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null, statesGasSplit
684
+ ? {
685
+ feeExcludingGasUsdValue: hopFeeExcludingGasUsdValues?.[positionInGroup] ??
686
+ (group.length === 1 ? (pricing.feeExcludingGasUsdValue ?? null) : null),
687
+ gasUsdValue: positionInGroup === 0 ? (pricing.gasUsdValue ?? null) : null,
688
+ }
689
+ : null)),
638
690
  deferred: groupIndex > 0 ? true : undefined,
639
691
  deferredNote: pricing.deferredNote,
640
692
  };
@@ -653,6 +705,13 @@ export const buildRouteDataFromPlan = (inputs) => {
653
705
  estimateBasis: lastSection.landing?.estimateBasis ?? null,
654
706
  },
655
707
  totalCostUsdValue,
708
+ ...(inputs.costExcludingGasUsdValue === undefined
709
+ ? {}
710
+ : {
711
+ costExcludingGasUsdValue: inputs.costExcludingGasUsdValue,
712
+ gasCostUsdValue: inputs.gasCostUsdValue ?? null,
713
+ gasByChain: [...(inputs.gasByChain ?? [])],
714
+ }),
656
715
  netValueUsd,
657
716
  etaMinutes,
658
717
  // The single legacy `viaChainId` a not-yet-migrated consumer may still
@@ -347,6 +347,9 @@ export declare const gasAdjustedDeliveredAmount: (plan: RoutePlan, gasCostFor: G
347
347
  * own real charges.
348
348
  *
349
349
  * @param gasCostFor - supplies each signature's real cost; see {@link GasCostFor}
350
+ * TIE-BREAK: when the gas-adjusted amounts are equal, the plan with fewer
351
+ * signatures ranks first.
352
+ *
350
353
  * @returns a comparator: negative when `a` ranks ahead of `b`, positive when
351
354
  * `b` ranks ahead of `a`, zero when they tie
352
355
  */
@@ -409,6 +409,9 @@ export const gasAdjustedDeliveredAmount = (plan, gasCostFor) => {
409
409
  * own real charges.
410
410
  *
411
411
  * @param gasCostFor - supplies each signature's real cost; see {@link GasCostFor}
412
+ * TIE-BREAK: when the gas-adjusted amounts are equal, the plan with fewer
413
+ * signatures ranks first.
414
+ *
412
415
  * @returns a comparator: negative when `a` ranks ahead of `b`, positive when
413
416
  * `b` ranks ahead of `a`, zero when they tie
414
417
  */
@@ -421,7 +424,12 @@ export const compareRoutePlansByGasAdjustedDeliveredAmount = (gasCostFor) => (a,
421
424
  return compareRoutePlansByDeliveredAmount(a, b);
422
425
  const adjustedA = gasAdjustedDeliveredAmount(a, gasCostFor);
423
426
  const adjustedB = gasAdjustedDeliveredAmount(b, gasCostFor);
424
- if (adjustedA === adjustedB)
425
- return 0;
426
- return adjustedA > adjustedB ? -1 : 1;
427
+ if (adjustedA !== adjustedB)
428
+ return adjustedA > adjustedB ? -1 : 1;
429
+ // EQUAL FIGURES: FEWER SIGNATURES RANKS FIRST. Two plans that deliver the
430
+ // same amount cost the reader different effort — each signature is one more
431
+ // wallet prompt. Without this the stable sort kept the depth-first walk
432
+ // order, so a three-signature plan could be selected over a two-signature
433
+ // plan showing the same figure.
434
+ return a.signatures.length - b.signatures.length;
427
435
  };