@gibs/bridge-sdk 1.13.1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +244 -18
  2. package/abis/package.json +6 -0
  3. package/bridge-limits/package.json +6 -0
  4. package/chain-info/package.json +6 -0
  5. package/chain-names/package.json +6 -0
  6. package/chains/package.json +6 -0
  7. package/claims/package.json +6 -0
  8. package/config/package.json +6 -0
  9. package/consolidated-quote/package.json +6 -0
  10. package/destination-calldata/package.json +6 -0
  11. package/dist/chain-info.d.ts +1 -1
  12. package/dist/chain-info.js +11 -12
  13. package/dist/chains.d.ts +25 -0
  14. package/dist/chains.js +55 -20
  15. package/dist/cjs/abis.d.ts +1 -0
  16. package/dist/cjs/abis.js +19 -0
  17. package/dist/cjs/bridge-limits.d.ts +103 -0
  18. package/dist/cjs/bridge-limits.js +89 -0
  19. package/dist/cjs/chain-info.d.ts +169 -0
  20. package/dist/cjs/chain-info.js +452 -0
  21. package/dist/cjs/chain-names.d.ts +160 -0
  22. package/dist/cjs/chain-names.js +207 -0
  23. package/dist/cjs/chains.d.ts +90 -0
  24. package/dist/cjs/chains.js +357 -0
  25. package/dist/cjs/claims.d.ts +198 -0
  26. package/dist/cjs/claims.js +182 -0
  27. package/dist/cjs/config.d.ts +240 -0
  28. package/dist/cjs/config.js +537 -0
  29. package/dist/cjs/consolidated-quote.d.ts +648 -0
  30. package/dist/cjs/consolidated-quote.js +1048 -0
  31. package/dist/cjs/destination-calldata.d.ts +118 -0
  32. package/dist/cjs/destination-calldata.js +156 -0
  33. package/dist/cjs/ecosystems.d.ts +217 -0
  34. package/dist/cjs/ecosystems.js +298 -0
  35. package/dist/cjs/execution.d.ts +346 -0
  36. package/dist/cjs/execution.js +468 -0
  37. package/dist/cjs/fee-type.d.ts +10 -0
  38. package/dist/cjs/fee-type.js +10 -0
  39. package/dist/cjs/hyperliquid-execution.d.ts +152 -0
  40. package/dist/cjs/hyperliquid-execution.js +175 -0
  41. package/dist/cjs/image-links.d.ts +117 -0
  42. package/dist/cjs/image-links.js +153 -0
  43. package/dist/cjs/index.d.ts +23 -0
  44. package/dist/cjs/index.js +39 -0
  45. package/dist/cjs/lifi-execution-guard.d.ts +210 -0
  46. package/dist/cjs/lifi-execution-guard.js +409 -0
  47. package/dist/cjs/near-intents-conformance.d.ts +85 -0
  48. package/dist/cjs/near-intents-conformance.js +337 -0
  49. package/dist/cjs/near-intents-route.d.ts +47 -0
  50. package/dist/cjs/near-intents-route.js +303 -0
  51. package/dist/cjs/near-intents.d.ts +524 -0
  52. package/dist/cjs/near-intents.js +1071 -0
  53. package/dist/cjs/origin-deposit.d.ts +326 -0
  54. package/dist/cjs/origin-deposit.js +82 -0
  55. package/dist/cjs/package.json +3 -0
  56. package/dist/cjs/pre-quote.d.ts +198 -0
  57. package/dist/cjs/pre-quote.js +180 -0
  58. package/dist/cjs/prices.d.ts +107 -0
  59. package/dist/cjs/prices.js +172 -0
  60. package/dist/cjs/providers.d.ts +238 -0
  61. package/dist/cjs/providers.js +171 -0
  62. package/dist/cjs/quote-fees.d.ts +102 -0
  63. package/dist/cjs/quote-fees.js +201 -0
  64. package/dist/cjs/referral.d.ts +85 -0
  65. package/dist/cjs/referral.js +85 -0
  66. package/dist/cjs/relay-currencies.d.ts +47 -0
  67. package/dist/cjs/relay-currencies.js +95 -0
  68. package/dist/cjs/relay-depository.d.ts +2 -0
  69. package/dist/cjs/relay-depository.js +85 -0
  70. package/dist/cjs/relay-quote.d.ts +301 -0
  71. package/dist/cjs/relay-quote.js +996 -0
  72. package/dist/cjs/route-steps.d.ts +138 -0
  73. package/dist/cjs/route-steps.js +310 -0
  74. package/dist/cjs/routing/amounts.d.ts +80 -0
  75. package/dist/cjs/routing/amounts.js +126 -0
  76. package/dist/cjs/routing/candidate-route.d.ts +43 -0
  77. package/dist/cjs/routing/candidate-route.js +50 -0
  78. package/dist/cjs/routing/candidate.d.ts +237 -0
  79. package/dist/cjs/routing/candidate.js +100 -0
  80. package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
  81. package/dist/cjs/routing/carriers/ask-answer.js +2 -0
  82. package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
  83. package/dist/cjs/routing/carriers/omnibridge.js +459 -0
  84. package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
  85. package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
  86. package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
  87. package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
  88. package/dist/cjs/routing/carriers/pulsex.d.ts +159 -0
  89. package/dist/cjs/routing/carriers/pulsex.js +565 -0
  90. package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
  91. package/dist/cjs/routing/carriers/wrap.js +119 -0
  92. package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
  93. package/dist/cjs/routing/composed-route-quality.js +144 -0
  94. package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
  95. package/dist/cjs/routing/entry-chain-anchor.js +59 -0
  96. package/dist/cjs/routing/format.d.ts +31 -0
  97. package/dist/cjs/routing/format.js +64 -0
  98. package/dist/cjs/routing/index.d.ts +38 -0
  99. package/dist/cjs/routing/index.js +134 -0
  100. package/dist/cjs/routing/producers.d.ts +770 -0
  101. package/dist/cjs/routing/producers.js +1617 -0
  102. package/dist/cjs/routing/providers.d.ts +31 -0
  103. package/dist/cjs/routing/providers.js +48 -0
  104. package/dist/cjs/routing/route-data.d.ts +355 -0
  105. package/dist/cjs/routing/route-data.js +2 -0
  106. package/dist/cjs/routing/search/compose.d.ts +168 -0
  107. package/dist/cjs/routing/search/compose.js +104 -0
  108. package/dist/cjs/routing/search/graph.d.ts +668 -0
  109. package/dist/cjs/routing/search/graph.js +461 -0
  110. package/dist/cjs/routing/search/plan-route-data.d.ts +269 -0
  111. package/dist/cjs/routing/search/plan-route-data.js +848 -0
  112. package/dist/cjs/routing/search/score.d.ts +356 -0
  113. package/dist/cjs/routing/search/score.js +445 -0
  114. package/dist/cjs/routing/swap-transactions.d.ts +91 -0
  115. package/dist/cjs/routing/swap-transactions.js +131 -0
  116. package/dist/cjs/routing/usd-value.d.ts +17 -0
  117. package/dist/cjs/routing/usd-value.js +29 -0
  118. package/dist/cjs/settings.d.ts +785 -0
  119. package/dist/cjs/settings.js +1074 -0
  120. package/dist/cjs/ton-connect.d.ts +98 -0
  121. package/dist/cjs/ton-connect.js +206 -0
  122. package/dist/cjs/ton-execution.d.ts +7 -0
  123. package/dist/cjs/ton-execution.js +23 -0
  124. package/dist/cjs/ton.d.ts +37 -0
  125. package/dist/cjs/ton.js +83 -0
  126. package/dist/cjs/tron-execution.d.ts +146 -0
  127. package/dist/cjs/tron-execution.js +248 -0
  128. package/dist/cjs/types.d.ts +51 -0
  129. package/dist/cjs/types.js +2 -0
  130. package/dist/claims.d.ts +198 -0
  131. package/dist/claims.js +140 -0
  132. package/dist/config.d.ts +24 -0
  133. package/dist/config.js +39 -5
  134. package/dist/consolidated-quote.d.ts +109 -15
  135. package/dist/consolidated-quote.js +310 -52
  136. package/dist/execution.d.ts +346 -0
  137. package/dist/execution.js +428 -0
  138. package/dist/hyperliquid-execution.js +2 -3
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +3 -0
  141. package/dist/lifi-execution-guard.d.ts +13 -2
  142. package/dist/lifi-execution-guard.js +4 -0
  143. package/dist/near-intents.js +2 -3
  144. package/dist/pre-quote.d.ts +6 -6
  145. package/dist/pre-quote.js +10 -14
  146. package/dist/providers.d.ts +17 -0
  147. package/dist/providers.js +6 -0
  148. package/dist/referral.d.ts +12 -4
  149. package/dist/referral.js +12 -4
  150. package/dist/relay-depository.d.ts +2 -0
  151. package/dist/relay-depository.js +82 -0
  152. package/dist/relay-quote.js +2 -3
  153. package/dist/routing/candidate.d.ts +3 -3
  154. package/dist/routing/candidate.js +3 -3
  155. package/dist/routing/carriers/ask-answer.d.ts +17 -0
  156. package/dist/routing/carriers/omnibridge.js +23 -21
  157. package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
  158. package/dist/routing/carriers/pulsex-paths.js +92 -0
  159. package/dist/routing/carriers/pulsex-split.d.ts +175 -0
  160. package/dist/routing/carriers/pulsex-split.js +185 -0
  161. package/dist/routing/carriers/pulsex.d.ts +31 -21
  162. package/dist/routing/carriers/pulsex.js +141 -71
  163. package/dist/routing/composed-route-quality.d.ts +28 -6
  164. package/dist/routing/composed-route-quality.js +36 -5
  165. package/dist/routing/entry-chain-anchor.d.ts +1 -1
  166. package/dist/routing/entry-chain-anchor.js +1 -1
  167. package/dist/routing/index.d.ts +6 -2
  168. package/dist/routing/index.js +6 -2
  169. package/dist/routing/producers.js +1 -1
  170. package/dist/routing/providers.d.ts +5 -0
  171. package/dist/routing/providers.js +5 -0
  172. package/dist/routing/search/graph.d.ts +15 -0
  173. package/dist/routing/search/plan-route-data.d.ts +25 -0
  174. package/dist/routing/search/plan-route-data.js +43 -7
  175. package/dist/routing/search/score.d.ts +3 -0
  176. package/dist/routing/search/score.js +11 -3
  177. package/dist/routing/swap-transactions.d.ts +91 -0
  178. package/dist/routing/swap-transactions.js +125 -0
  179. package/dist/settings.d.ts +94 -1
  180. package/dist/settings.js +95 -3
  181. package/dist/ton-connect.d.ts +98 -0
  182. package/dist/ton-connect.js +200 -0
  183. package/dist/ton-execution.d.ts +4 -114
  184. package/dist/ton-execution.js +4 -244
  185. package/dist/ton.d.ts +37 -0
  186. package/dist/ton.js +65 -0
  187. package/docs/examples/example-a-ether-to-pulse.ts +300 -0
  188. package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
  189. package/docs/examples/shared.ts +261 -0
  190. package/docs/examples/tsconfig.json +18 -0
  191. package/docs/integration-guide.md +617 -0
  192. package/ecosystems/package.json +6 -0
  193. package/execution/package.json +6 -0
  194. package/fee-type/package.json +6 -0
  195. package/hyperliquid-execution/package.json +6 -0
  196. package/image-links/package.json +6 -0
  197. package/lifi-execution-guard/package.json +6 -0
  198. package/llms.txt +75 -10
  199. package/near-intents/package.json +6 -0
  200. package/near-intents-route/package.json +6 -0
  201. package/origin-deposit/package.json +6 -0
  202. package/package.json +817 -10
  203. package/pre-quote/package.json +6 -0
  204. package/prices/package.json +6 -0
  205. package/providers/package.json +6 -0
  206. package/quote-fees/package.json +6 -0
  207. package/referral/package.json +6 -0
  208. package/relay-currencies/package.json +6 -0
  209. package/relay-quote/package.json +6 -0
  210. package/route-steps/package.json +6 -0
  211. package/routing/package.json +6 -0
  212. package/settings/package.json +6 -0
  213. package/ton/package.json +6 -0
  214. package/ton-connect/package.json +6 -0
  215. package/ton-execution/package.json +6 -0
  216. package/tron-execution/package.json +6 -0
  217. package/types/package.json +6 -0
@@ -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,7 +9,7 @@ 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';
@@ -28,7 +28,11 @@ export { buildRouteCandidateFromPlan, buildRouteDataFromPlan, buildRouteScoreInp
28
28
  export type { PlanCandidateInputs, PlanRouteDataInputs, PlanSectionPricing, PlanValuation, ResolveNodeToken, } from './search/plan-route-data.js';
29
29
  export { createOmnibridgeCarrier, withOmnibridgeEntryFusing, } from './carriers/omnibridge.js';
30
30
  export type { BridgeablePair, CreateOmnibridgeCarrierOptions, } from './carriers/omnibridge.js';
31
- export { createPulseXCarrier, pulseXPoolPath, pulseXRouterForEdge, pulseXVenues } from './carriers/pulsex.js';
31
+ export { createPulseXCarrier, pulseXRouterForEdge, pulseXSwapPathForHop, pulseXVenues, pulseXWrappedNativePath, } from './carriers/pulsex.js';
32
32
  export type { CreatePulseXCarrierOptions, PulseXVenue } from './carriers/pulsex.js';
33
+ export { bestPulseXPathQuote, candidatePulseXPaths, pulseXHubTokens } from './carriers/pulsex-paths.js';
34
+ export type { PulseXHubToken, PulseXPathQuote } from './carriers/pulsex-paths.js';
33
35
  export { createWrapCarrier } from './carriers/wrap.js';
34
36
  export type { CarrierAskAnswer, CarrierExactResult } from './carriers/ask-answer.js';
37
+ export { buildPulseXSwapTransaction, buildWrapTransaction, swapMinimumOut } from './swap-transactions.js';
38
+ 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';
@@ -38,5 +38,9 @@ export { buildRouteCandidateFromPlan, buildRouteDataFromPlan, buildRouteScoreInp
38
38
  // `searchRouteWaves` and their types from `@gibs/quotes/search`. Gibs's own
39
39
  // carriers (the omnibridge crossing, PulseX) live in `./carriers/`, below.
40
40
  export { createOmnibridgeCarrier, withOmnibridgeEntryFusing, } from './carriers/omnibridge.js';
41
- export { createPulseXCarrier, pulseXPoolPath, pulseXRouterForEdge, pulseXVenues } from './carriers/pulsex.js';
41
+ export { createPulseXCarrier, pulseXRouterForEdge, pulseXSwapPathForHop, pulseXVenues, pulseXWrappedNativePath, } from './carriers/pulsex.js';
42
+ export { bestPulseXPathQuote, candidatePulseXPaths, pulseXHubTokens } from './carriers/pulsex-paths.js';
42
43
  export { createWrapCarrier } from './carriers/wrap.js';
44
+ // The same-chain swap and wrap builders. The interface's section signing and
45
+ // the SDK's execution planner (`../execution.ts`) share them.
46
+ 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)
@@ -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,3 +1,4 @@
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';
@@ -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,18 @@ 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)[];
119
144
  /** The itemized charges behind `feeUsdValue`, passed straight through to `RouteSectionData.fees`. */
120
145
  fees?: readonly QuoteFee[];
121
146
  /** Estimated duration for this signature in minutes, or null/undefined to omit. */
@@ -349,20 +349,41 @@ const hopIsAutomatic = (hopIndex, plan) => {
349
349
  * @param hopIndex - the hop's index into `plan.hops`
350
350
  * @param plan - the plan the hop belongs to
351
351
  * @param resolveToken - resolves a graph node to its full display token
352
+ * @param feeUsdValue - the hop's own share of its section's cost, or null
352
353
  * @returns the raw hop data
353
354
  */
354
- const toRouteHopData = (hop, hopIndex, plan, resolveToken) => ({
355
+ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue) => ({
355
356
  providerName: hopProviderName(hop.edge.carrier),
356
357
  providerKey: hop.edge.carrier,
357
358
  title: hopTitle(hop, hopIndex, plan, resolveToken),
358
359
  conversion: hopConversion(hop, hopIndex, plan, resolveToken),
359
360
  fromToken: toRouteTokenRef(hop.edge.from, resolveToken),
360
361
  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,
362
+ // The per-hop dollar figure is a caller-supplied input (see this module's
363
+ // head doc); the caller passes it in, never derived here.
364
+ feeUsdValue,
364
365
  automatic: hopIsAutomatic(hopIndex, plan),
365
366
  });
367
+ /**
368
+ * The per-hop shares a section's pricing names, checked against the group
369
+ * they describe.
370
+ *
371
+ * @param pricing - the section's caller-supplied pricing
372
+ * @param group - the signature group's hop indices
373
+ * @param groupIndex - the group's index, for the error message
374
+ * @returns one share per hop, null where none was given
375
+ * @throws when the caller passed a share list of the wrong length — a share
376
+ * matched to the wrong hop is a figure on the wrong leg
377
+ */
378
+ const groupHopFeeUsdValues = (pricing, group, groupIndex) => {
379
+ const shares = pricing.hopFeeUsdValues;
380
+ if (shares === undefined)
381
+ return group.map(() => null);
382
+ if (shares.length !== group.length) {
383
+ throw new Error(`buildRouteDataFromPlan: section ${groupIndex + 1} has ${group.length} hop(s) but ${shares.length} hop fee(s)`);
384
+ }
385
+ return shares;
386
+ };
366
387
  // ---------------------------------------------------------------------------
367
388
  // Route-level derivations — candidate.ts's promised derivation, made real
368
389
  // ---------------------------------------------------------------------------
@@ -472,7 +493,8 @@ export const derivePlanKind = (plan) => {
472
493
  *
473
494
  * A GIBS PLAN NEVER CARRIES ANYTHING ELSE. Every carrier a `RoutePlan` this
474
495
  * module renders can name is one gibs itself registered — the three built-in
475
- * aggregators, the omnibridge, or PulseX (`routing/carriers/`,
496
+ * aggregators, the omnibridge, PulseX, or a wrap step, which
497
+ * {@link deriveHeadlineProviderKeyFromPlan} skips (`routing/carriers/`,
476
498
  * `packages/ui/src/lib/state/useRouteSearch.ts`) — so this throws rather than
477
499
  * guessing when it sees anything else: a plan naming an unrecognized carrier
478
500
  * is a bug in whatever built it, not a display gap to paper over silently.
@@ -480,6 +502,8 @@ export const derivePlanKind = (plan) => {
480
502
  * @param carrier - the plan's headline carrier
481
503
  * @returns the same value, typed as `ProviderKey`
482
504
  */
505
+ /** The carrier id of the native-coin wrap step (`routing/carriers/wrap.ts`). */
506
+ const WRAP_CARRIER = 'wrap';
483
507
  const toHeadlineProviderKey = (carrier) => {
484
508
  if (isBridgeCarrier(carrier))
485
509
  return carrier;
@@ -500,7 +524,18 @@ const toHeadlineProviderKey = (carrier) => {
500
524
  * @param plan - the plan to derive from
501
525
  * @returns the headline provider key
502
526
  */
503
- const deriveHeadlineProviderKeyFromPlan = (plan) => toHeadlineProviderKey(deriveOmnibridgeProviderFromPlan(plan) ?? plan.hops[0].edge.carrier);
527
+ export const deriveHeadlineProviderKeyFromPlan = (plan) => toHeadlineProviderKey(deriveOmnibridgeProviderFromPlan(plan) ?? firstHeadlineCarrier(plan));
528
+ /**
529
+ * The carrier of the first hop that can headline a route. A `wrap` hop
530
+ * (native coin to its wrapped token, or back) is a step inside a route, never
531
+ * the route itself, so it is skipped. A plan made only of wraps headlines as
532
+ * PulseX, the same key the same-chain swap builder gives that route
533
+ * (`buildSameChainSwapRouteData`).
534
+ *
535
+ * @param plan - the plan to read
536
+ * @returns the carrier to headline
537
+ */
538
+ const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== WRAP_CARRIER)?.edge.carrier ?? 'PulseX';
504
539
  /**
505
540
  * The anchor a two-signature plan leaves the reader holding between its
506
541
  * first and second groups — `RouteCandidate.plan`'s own TSDoc: "the anchor
@@ -618,6 +653,7 @@ export const buildRouteDataFromPlan = (inputs) => {
618
653
  const lastHop = hops[hops.length - 1];
619
654
  const landing = resolveNodeWithAssertedDecimals(lastHop.edge.to, resolveToken);
620
655
  const landingIsUnpriced = hopOutcomeIsUnpriced(lastHop);
656
+ const hopFeeUsdValues = groupHopFeeUsdValues(pricing, group, groupIndex);
621
657
  return {
622
658
  id: `${routeId}-signature-${groupIndex + 1}`,
623
659
  label: sectionLabel(group, groupIndex, plan),
@@ -634,7 +670,7 @@ export const buildRouteDataFromPlan = (inputs) => {
634
670
  fees: pricing.fees ? [...pricing.fees] : undefined,
635
671
  etaMinutes: pricing.etaMinutes,
636
672
  etaSeconds: pricing.etaSeconds,
637
- hops: group.map((hopIndex) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken)),
673
+ hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null)),
638
674
  deferred: groupIndex > 0 ? true : undefined,
639
675
  deferredNote: pricing.deferredNote,
640
676
  };
@@ -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
  };
@@ -0,0 +1,91 @@
1
+ import { type Hex } from 'viem';
2
+ import { type SignedLeg } from '../settings.js';
3
+ import type { HopEdge, PlannedHop } from './search/graph.js';
4
+ /**
5
+ * The same-chain, one-signature transaction shape every sender in this file
6
+ * builds: a target contract, a native-coin value, and encoded calldata.
7
+ */
8
+ export type SwapCallInputs = {
9
+ readonly to: Hex;
10
+ readonly value: bigint;
11
+ readonly data: Hex;
12
+ };
13
+ /**
14
+ * Builds a same-chain PulseX swap transaction for one planned route-search
15
+ * hop. The interface's section signing (`packages/ui` `state/sectionSigning.ts`)
16
+ * and the SDK's execution planner (`execution.ts`) both call it, so there is
17
+ * exactly one place that turns "swap this hop" into calldata.
18
+ *
19
+ * SIGNS THE PATH THE HOP WAS PRICED ON. The PulseX carrier picks the best of
20
+ * many paths through its hub tokens and records the winner on the hop's
21
+ * outcome; `pulseXSwapPathForHop` (`carriers/pulsex.ts`) reads it back
22
+ * and checks it fits the hop's edge. This never rebuilds a path: a rebuilt
23
+ * path is a quote on one route and a signature on another. A hop that
24
+ * records no path is refused. REUSES `pulseXSwapTransactionInputs` (`settings.ts`)
25
+ * for the encoding, never re-implements it: that function already carries the
26
+ * `swapRefusal` guard (refuses to build a call for a leg the screen is not
27
+ * looking at) and the native-in/native-out/token-token branching.
28
+ *
29
+ * SIGNS ON THE EDGE'S OWN ROUTER. A PulseX edge names the deployment it was
30
+ * quoted on (`pulseXRouterForEdge`: V1 or V2, each with its own pools). The
31
+ * caller's `routerAddress` must be that same router — it is also the router
32
+ * the caller approved to spend the input token — or nothing is built. A V2
33
+ * quote signed on V1 would trade against a different pool than the one the
34
+ * reader was shown.
35
+ *
36
+ * @param options - the hop to swap, the amount and slippage-adjusted
37
+ * minimum, who receives the output, the signing deadline, what the caller
38
+ * believes this signature does (`signedLeg`), the chain it executes on, and
39
+ * the resolved router address
40
+ * @returns the swap transaction inputs, or null when the hop records no
41
+ * PulseX swap path, names no known router, `routerAddress` is not the
42
+ * edge's own router, the leg on screen disagrees, or any of
43
+ * `pulseXSwapTransactionInputs`'s own preconditions are unmet
44
+ */
45
+ export declare const buildPulseXSwapTransaction: (options: {
46
+ readonly hop: Pick<PlannedHop, "edge" | "outcome">;
47
+ readonly amountIn: bigint;
48
+ readonly minAmountOut: bigint;
49
+ readonly recipient: Hex | null;
50
+ readonly deadline: bigint;
51
+ readonly signedLeg: SignedLeg | null;
52
+ readonly chainId: number | null;
53
+ readonly routerAddress: Hex | null;
54
+ }) => SwapCallInputs | null;
55
+ /**
56
+ * Builds a native-coin wrap/unwrap transaction for one route-search graph
57
+ * edge — PLS<->WPLS on PulseChain, ETH<->WETH on Ethereum, or the same pair
58
+ * on any other chain `config.ts`'s `nativeAssetOut` names one for.
59
+ *
60
+ * A WRAP IS NOT A SWAP CALL. There is no PulseX pool for native-versus-wrapped
61
+ * — see `routing/carriers/wrap.ts`'s own doc — so this never touches the
62
+ * PulseX router: wrapping sends `amountIn` native coin to the wrapped token's
63
+ * own `deposit()`; unwrapping calls `withdraw(amountIn)` on it with zero
64
+ * value. The `edge` names which direction: its `from`/`to` addresses are
65
+ * checked against the zero-address sentinel, never assumed from a caller
66
+ * flag, so a swapped/malformed edge is refused rather than guessed at.
67
+ *
68
+ * @param options - the edge to wrap or unwrap, and the amount in the SOURCE
69
+ * token's base units (native units wrapping, wrapped units unwrapping —
70
+ * native and wrapped share decimals on every chain this carrier covers)
71
+ * @returns the wrap/unwrap transaction inputs, or null when `amountIn` is not
72
+ * positive or the edge is not exactly one native/wrapped pair
73
+ */
74
+ export declare const buildWrapTransaction: (options: {
75
+ readonly edge: HopEdge;
76
+ readonly amountIn: bigint;
77
+ }) => SwapCallInputs | null;
78
+ /**
79
+ * The slippage floor a swap signs: the quoted output less the slippage
80
+ * allowance, rounded down. The interface's engine signing and the SDK's
81
+ * execution planner both read it, so the two never sign different floors for
82
+ * the same quote.
83
+ *
84
+ * @param options.quotedAmount - the hop's quoted output, in the output token's base units
85
+ * @param options.slippageBasisPoints - the allowance, in basis points (100 is 1%)
86
+ * @returns the minimum output the swap accepts
87
+ */
88
+ export declare const swapMinimumOut: ({ quotedAmount, slippageBasisPoints, }: {
89
+ readonly quotedAmount: bigint;
90
+ readonly slippageBasisPoints: bigint;
91
+ }) => bigint;
@@ -0,0 +1,125 @@
1
+ import { encodeFunctionData, isAddressEqual, parseAbi, zeroAddress } from 'viem';
2
+ import { pulseXSwapTransactionInputs } from '../settings.js';
3
+ import { pulseXRouterForEdge, pulseXSwapPathForHop } from './carriers/pulsex.js';
4
+ /**
5
+ * The minimal WETH9-style wrap/unwrap interface: `deposit()` mints the
6
+ * wrapped token for the native coin sent with the call, `withdraw(uint256)`
7
+ * burns wrapped balance and returns native coin. Every wrapped-native
8
+ * contract this application bridges to exposes exactly this pair — see
9
+ * `carriers/wrap.ts`, whose own doc records the selectors as confirmed
10
+ * present on WPLS by bytecode inspection (2026-09-24). Kept local rather than
11
+ * added to `@gibs/abis`: the interface is two standard, unchanging selectors
12
+ * rather than a contract-specific ABI.
13
+ */
14
+ const wrapAbi = parseAbi(['function deposit() payable', 'function withdraw(uint256 amount)']);
15
+ /**
16
+ * Builds a same-chain PulseX swap transaction for one planned route-search
17
+ * hop. The interface's section signing (`packages/ui` `state/sectionSigning.ts`)
18
+ * and the SDK's execution planner (`execution.ts`) both call it, so there is
19
+ * exactly one place that turns "swap this hop" into calldata.
20
+ *
21
+ * SIGNS THE PATH THE HOP WAS PRICED ON. The PulseX carrier picks the best of
22
+ * many paths through its hub tokens and records the winner on the hop's
23
+ * outcome; `pulseXSwapPathForHop` (`carriers/pulsex.ts`) reads it back
24
+ * and checks it fits the hop's edge. This never rebuilds a path: a rebuilt
25
+ * path is a quote on one route and a signature on another. A hop that
26
+ * records no path is refused. REUSES `pulseXSwapTransactionInputs` (`settings.ts`)
27
+ * for the encoding, never re-implements it: that function already carries the
28
+ * `swapRefusal` guard (refuses to build a call for a leg the screen is not
29
+ * looking at) and the native-in/native-out/token-token branching.
30
+ *
31
+ * SIGNS ON THE EDGE'S OWN ROUTER. A PulseX edge names the deployment it was
32
+ * quoted on (`pulseXRouterForEdge`: V1 or V2, each with its own pools). The
33
+ * caller's `routerAddress` must be that same router — it is also the router
34
+ * the caller approved to spend the input token — or nothing is built. A V2
35
+ * quote signed on V1 would trade against a different pool than the one the
36
+ * reader was shown.
37
+ *
38
+ * @param options - the hop to swap, the amount and slippage-adjusted
39
+ * minimum, who receives the output, the signing deadline, what the caller
40
+ * believes this signature does (`signedLeg`), the chain it executes on, and
41
+ * the resolved router address
42
+ * @returns the swap transaction inputs, or null when the hop records no
43
+ * PulseX swap path, names no known router, `routerAddress` is not the
44
+ * edge's own router, the leg on screen disagrees, or any of
45
+ * `pulseXSwapTransactionInputs`'s own preconditions are unmet
46
+ */
47
+ export const buildPulseXSwapTransaction = (options) => {
48
+ const { hop, amountIn, minAmountOut, recipient, deadline, signedLeg, chainId, routerAddress } = options;
49
+ const { edge } = hop;
50
+ const swapPath = pulseXSwapPathForHop(hop);
51
+ if (swapPath === null) {
52
+ return null;
53
+ }
54
+ const quotedRouter = pulseXRouterForEdge(edge);
55
+ if (quotedRouter === null || routerAddress === null || !isAddressEqual(quotedRouter, routerAddress)) {
56
+ return null;
57
+ }
58
+ return pulseXSwapTransactionInputs({
59
+ signedLeg,
60
+ chainId,
61
+ routerAddress,
62
+ fromTokenAddress: edge.from.address,
63
+ toTokenAddress: edge.to.address,
64
+ swapPath,
65
+ amountIn,
66
+ minAmountOut,
67
+ recipient,
68
+ deadline,
69
+ });
70
+ };
71
+ /**
72
+ * Builds a native-coin wrap/unwrap transaction for one route-search graph
73
+ * edge — PLS<->WPLS on PulseChain, ETH<->WETH on Ethereum, or the same pair
74
+ * on any other chain `config.ts`'s `nativeAssetOut` names one for.
75
+ *
76
+ * A WRAP IS NOT A SWAP CALL. There is no PulseX pool for native-versus-wrapped
77
+ * — see `routing/carriers/wrap.ts`'s own doc — so this never touches the
78
+ * PulseX router: wrapping sends `amountIn` native coin to the wrapped token's
79
+ * own `deposit()`; unwrapping calls `withdraw(amountIn)` on it with zero
80
+ * value. The `edge` names which direction: its `from`/`to` addresses are
81
+ * checked against the zero-address sentinel, never assumed from a caller
82
+ * flag, so a swapped/malformed edge is refused rather than guessed at.
83
+ *
84
+ * @param options - the edge to wrap or unwrap, and the amount in the SOURCE
85
+ * token's base units (native units wrapping, wrapped units unwrapping —
86
+ * native and wrapped share decimals on every chain this carrier covers)
87
+ * @returns the wrap/unwrap transaction inputs, or null when `amountIn` is not
88
+ * positive or the edge is not exactly one native/wrapped pair
89
+ */
90
+ export const buildWrapTransaction = (options) => {
91
+ const { edge, amountIn } = options;
92
+ if (amountIn <= 0n) {
93
+ return null;
94
+ }
95
+ const fromIsNative = edge.from.address.toLowerCase() === zeroAddress;
96
+ const toIsNative = edge.to.address.toLowerCase() === zeroAddress;
97
+ // Exactly one side must be the native sentinel — a token/token pair, or a
98
+ // native/native no-op, is not a wrap edge at all.
99
+ if (fromIsNative === toIsNative) {
100
+ return null;
101
+ }
102
+ if (fromIsNative) {
103
+ return {
104
+ to: edge.to.address,
105
+ value: amountIn,
106
+ data: encodeFunctionData({ abi: wrapAbi, functionName: 'deposit', args: [] }),
107
+ };
108
+ }
109
+ return {
110
+ to: edge.from.address,
111
+ value: 0n,
112
+ data: encodeFunctionData({ abi: wrapAbi, functionName: 'withdraw', args: [amountIn] }),
113
+ };
114
+ };
115
+ /**
116
+ * The slippage floor a swap signs: the quoted output less the slippage
117
+ * allowance, rounded down. The interface's engine signing and the SDK's
118
+ * execution planner both read it, so the two never sign different floors for
119
+ * the same quote.
120
+ *
121
+ * @param options.quotedAmount - the hop's quoted output, in the output token's base units
122
+ * @param options.slippageBasisPoints - the allowance, in basis points (100 is 1%)
123
+ * @returns the minimum output the swap accepts
124
+ */
125
+ export const swapMinimumOut = ({ quotedAmount, slippageBasisPoints, }) => (quotedAmount * (10000n - slippageBasisPoints)) / 10000n;