@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
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compareRoutePlansByGasAdjustedDeliveredAmount = exports.gasAdjustedDeliveredAmount = exports.sectionIsFundedByLanding = exports.zeroGasCostFor = exports.scoreRoutePlan = exports.compareRoutePlansByScoreAdjustedDeliveredAmount = exports.GAS_TOPUP_PENALTY_FLOOR_USD = exports.GAS_TOPUP_PENALTY_PPM = exports.SIGNATURE_PENALTY_FLOOR_USD = exports.SIGNATURE_PENALTY_PPM = exports.waitPenaltyPpm = void 0;
4
+ const ecosystems_js_1 = require("../../ecosystems.js");
5
+ const graph_js_1 = require("./graph.js");
6
+ /**
7
+ * PHASE 0 — FOUNDATION ONLY. Today this module holds exactly one function,
8
+ * {@link waitPenaltyPpm}, moved here unchanged from
9
+ * `packages/ui/src/lib/stores/consolidated-quote.ts` (that file now imports
10
+ * and re-exports it, so every existing caller keeps working). Phase 1's
11
+ * `score.ts` work (design section 4) generalizes `candidateNetValueUsd` and
12
+ * `netValueAfterWaitPenalty` from `../producers.js` and
13
+ * `packages/ui/src/lib/state/useRouteOptions.ts` into one `scoreRoutePlan`
14
+ * function that lives here beside it.
15
+ *
16
+ * WHY THIS MOVES TO `bridge-sdk` AT ALL. `waitPenaltyPpm` is a pure function
17
+ * of a number — it has no dependency on React, a store, or any
18
+ * user-interface concern — and `scoreRoutePlan` will need it to score a
19
+ * `RoutePlan` (`../graph.js`), which is a `bridge-sdk` type. Leaving the
20
+ * function in the user-interface package would make `bridge-sdk`'s own
21
+ * scoring logic import FROM the application that is supposed to consume it.
22
+ */
23
+ /**
24
+ * The share of the transfer a full minute of extra waiting is charged, in
25
+ * parts per million — 5,000 ppm is half of one percent.
26
+ *
27
+ * WHERE THE NUMBER COMES FROM. The owner's own crossing: LI.FI (through
28
+ * squid) claimed twenty seconds and Relay claimed two, and the strip
29
+ * preferred LI.FI because it delivered 0.075% more — nine cents on a
30
+ * hundred and twenty-six dollars. The transfer was still unsettled
31
+ * twenty-five minutes later. A second live measurement on Base to Polygon:
32
+ * twenty-nine seconds against three, for 0.0545%. Both are the same shape —
33
+ * a few cents of advantage bought with a ten-fold increase in the wait —
34
+ * and under this rate both go the other way: at eighteen seconds of extra
35
+ * claimed wait a route must deliver 0.15% more to win, and at twenty-six
36
+ * seconds it must deliver 0.217% more. Neither does.
37
+ *
38
+ * The rate is also calibrated NOT to override a real price advantage. A
39
+ * route that is a full minute slower still wins on 0.6% more delivered,
40
+ * which on a hundred-and-twenty-six-dollar transfer is seventy-six cents —
41
+ * a figure worth waiting a minute for, where nine cents is not.
42
+ */
43
+ const WAIT_PENALTY_PPM_PER_MINUTE = 5_000;
44
+ /** The wait beyond which extra claimed seconds stop adding to the penalty. */
45
+ const WAIT_PENALTY_CEILING_SECONDS = 60;
46
+ /**
47
+ * What a quote's CLAIMED duration costs it in the ranking, as a share of the
48
+ * transfer in parts per million. It ramps linearly to
49
+ * {@link WAIT_PENALTY_PPM_PER_MINUTE} at {@link WAIT_PENALTY_CEILING_SECONDS}
50
+ * and stops there.
51
+ *
52
+ * THE DURATION IS A CLAIM AND IS TREATED AS ONE. The route that prompted
53
+ * this ran about seventy-five times its provider's own estimate, so the
54
+ * figure cannot be trusted as a measurement of anything. It is still the
55
+ * only signal there is, and it is informative in one direction: a provider
56
+ * that says it will take longer is not going to turn out faster, and a
57
+ * claimed wait understates the real one far more often than it overstates
58
+ * it. So the penalty is used to demand a real price advantage before ANY
59
+ * extra claimed wait is accepted — never to predict a settlement time, and
60
+ * never to produce a number shown to the reader. Nothing on screen is
61
+ * adjusted by this; only the order is.
62
+ *
63
+ * The ceiling is what keeps the rule from becoming a speed preference. Past
64
+ * a minute the penalty stops growing, so a route that is genuinely worth
65
+ * waiting for still wins on the strength of its price.
66
+ *
67
+ * @param durationSeconds - the provider's own claimed execution duration
68
+ * @returns the penalty as parts per million of the transfer, 0 for a missing claim
69
+ */
70
+ const waitPenaltyPpm = (durationSeconds) => {
71
+ if (typeof durationSeconds !== 'number' || !Number.isFinite(durationSeconds))
72
+ return 0;
73
+ if (durationSeconds <= 0)
74
+ return 0;
75
+ const rampedSeconds = Math.min(durationSeconds, WAIT_PENALTY_CEILING_SECONDS);
76
+ return Math.round((WAIT_PENALTY_PPM_PER_MINUTE * rampedSeconds) / WAIT_PENALTY_CEILING_SECONDS);
77
+ };
78
+ exports.waitPenaltyPpm = waitPenaltyPpm;
79
+ // ---------------------------------------------------------------------------
80
+ // scoreRoutePlan — the one maximization function
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * The share of the transfer charged for EACH reader action beyond the first,
84
+ * in parts per million — see {@link signaturePenaltyUsd}.
85
+ *
86
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Unlike {@link WAIT_PENALTY_PPM_PER_MINUTE},
87
+ * which is calibrated against two logged, live crossings, no comparable
88
+ * signature-count measurement exists yet. 2,500 ppm (0.25% of the delivered
89
+ * amount) is a starting point sized to sit BELOW the wait penalty's own
90
+ * ceiling (5,000 ppm) — a second signature should cost less than the worst
91
+ * wait penalty already accepted, since a signature is a moment of the
92
+ * reader's attention, not twenty-five minutes of theirs. The owner is meant
93
+ * to calibrate this against real reader behavior once it ships.
94
+ */
95
+ exports.SIGNATURE_PENALTY_PPM = 2_500;
96
+ /**
97
+ * SUPERSEDED FOR THE SEARCH ENGINE'S OWN RANKING — see
98
+ * {@link compareRoutePlansByGasAdjustedDeliveredAmount}, below, which
99
+ * `waves.ts` now uses in place of {@link compareRoutePlansByScoreAdjustedDeliveredAmount}
100
+ * once real gas data exists. A flat parts-per-million guess cannot tell a
101
+ * cheap chain from an expensive one, and it charges nothing for gas at all —
102
+ * the owner's own instruction: "it's custom for each provider ... gather the
103
+ * logic around how much a user is about to be charged." This constant, and
104
+ * {@link GAS_TOPUP_PENALTY_PPM} below, are KEPT UNCHANGED because
105
+ * {@link scoreRoutePlan} and every existing United States dollar-based
106
+ * caller of it (`plan-route-data.ts`, `producers.ts`, the user interface's
107
+ * own single-candidate scoring) still read them — migrating those callers to
108
+ * a real gas-cost model is FLAGGED HERE as follow-up work outside this
109
+ * module's own owned scope, not done silently in this change.
110
+ */
111
+ /**
112
+ * The minimum a signature is ever charged, in United States dollars — see
113
+ * {@link signaturePenaltyUsd}.
114
+ *
115
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Without a floor, {@link SIGNATURE_PENALTY_PPM}
116
+ * charges almost nothing on a small transfer (0.25% of ten dollars is two and
117
+ * a half cents), which would let a route add a whole extra signature for a
118
+ * fraction of a cent. One dollar is a guess at what a reader's own time and
119
+ * attention for one extra wallet prompt is worth, floored so it cannot be
120
+ * bought away by a large delivered amount either — the floor and the rate are
121
+ * a `max`, so whichever is larger applies. The owner is meant to calibrate
122
+ * this once real reader behavior is observed.
123
+ */
124
+ exports.SIGNATURE_PENALTY_FLOOR_USD = 1;
125
+ /**
126
+ * The share of the transfer charged for EACH intermediate chain where the
127
+ * reader holds no gas, in parts per million — see {@link gasTopUpPenaltyUsd}.
128
+ *
129
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Sized larger than
130
+ * {@link SIGNATURE_PENALTY_PPM} because a gas top-up is not a moment of
131
+ * attention like a signature — it is a SEPARATE errand: acquire the
132
+ * intermediate chain's native coin, by another bridge or a purchase, before
133
+ * the planned hop can even be signed. That errand risks stranding the
134
+ * transfer mid-route if the reader does not complete it, which the
135
+ * "stranded funds" risk in the route-search design's section 10 names
136
+ * directly. The owner is meant to calibrate this once real reader behavior
137
+ * is observed.
138
+ */
139
+ exports.GAS_TOPUP_PENALTY_PPM = 10_000;
140
+ /**
141
+ * The minimum a gas top-up is ever charged, in United States dollars — see
142
+ * {@link gasTopUpPenaltyUsd}.
143
+ *
144
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Set higher than
145
+ * {@link SIGNATURE_PENALTY_FLOOR_USD} for the same reason
146
+ * {@link GAS_TOPUP_PENALTY_PPM} is: this penalty stands in for a separate
147
+ * errand with its own real cost (buying gas on a chain the reader was not
148
+ * already using), not a moment of attention. The owner is meant to calibrate
149
+ * this once real reader behavior is observed.
150
+ */
151
+ exports.GAS_TOPUP_PENALTY_FLOOR_USD = 3;
152
+ /**
153
+ * What one extra reader action (a signature or a pay-by-hand deposit) costs
154
+ * in the ranking, in United States dollars: the larger of
155
+ * {@link SIGNATURE_PENALTY_FLOOR_USD} and {@link SIGNATURE_PENALTY_PPM} of
156
+ * the delivered amount. See both constants' docs for where the numbers come
157
+ * from and that they are proposals, not measurements.
158
+ *
159
+ * @param deliveredUsd - the route's delivered value in United States dollars
160
+ * @returns the penalty for one extra action, always positive
161
+ */
162
+ const signaturePenaltyUsd = (deliveredUsd) => Math.max(exports.SIGNATURE_PENALTY_FLOOR_USD, (deliveredUsd * exports.SIGNATURE_PENALTY_PPM) / 1_000_000);
163
+ /**
164
+ * What one intermediate chain with no reader gas costs in the ranking, in
165
+ * United States dollars: the larger of {@link GAS_TOPUP_PENALTY_FLOOR_USD}
166
+ * and {@link GAS_TOPUP_PENALTY_PPM} of the delivered amount. See both
167
+ * constants' docs for where the numbers come from and that they are
168
+ * proposals, not measurements.
169
+ *
170
+ * @param deliveredUsd - the route's delivered value in United States dollars
171
+ * @returns the penalty for one gasless intermediate chain, always positive
172
+ */
173
+ const gasTopUpPenaltyUsd = (deliveredUsd) => Math.max(exports.GAS_TOPUP_PENALTY_FLOOR_USD, (deliveredUsd * exports.GAS_TOPUP_PENALTY_PPM) / 1_000_000);
174
+ /**
175
+ * The one maximization function a route search ranks every plan by —
176
+ * generalizing today's `candidateNetValueUsd` and `netValueAfterWaitPenalty`
177
+ * (`../producers.js` and `packages/ui/src/lib/state/useRouteOptions.ts`)
178
+ * from a single quoted candidate to a whole {@link RouteScoreInputs} plan, per
179
+ * the route-search design's section 4:
180
+ *
181
+ * ```
182
+ * score = D − P − F
183
+ * − Σ_legs D·waitPenaltyPpm(claimedSeconds_leg)/1e6
184
+ * − (A − 1)·max($1, D·SIGNATURE_PENALTY_PPM/1e6)
185
+ * − G·max($3, D·GAS_TOPUP_PENALTY_PPM/1e6)
186
+ * ```
187
+ *
188
+ * A HIGHER SCORE IS BETTER. A route search picks the plan with the largest
189
+ * `scoreRoutePlan` result, exactly as today's ranking picks the candidate
190
+ * with the largest `netValueAfterWaitPenalty`.
191
+ *
192
+ * THE WAIT PENALTY IS UNCHANGED, JUST SUMMED ACROSS LEGS. Today's single-quote
193
+ * penalty is `deliveredUsd * waitPenaltyPpm(oneClaimedDuration) / 1e6`, one
194
+ * term. A plan can carry several hops, each with its own claimed duration, so
195
+ * this sums that same per-unit charge over every hop in
196
+ * {@link RouteScoreInputs.legClaimedDurationsSeconds} — still against the
197
+ * PLAN'S total delivered value, not each leg's own, because the reader is
198
+ * charged for the whole route's wait, not a fraction of it per leg.
199
+ *
200
+ * WHAT IS NEW: THE ACTION AND GAS-TOP-UP TERMS. Today's ranking only ever
201
+ * compared plans that cost one signature, so it had nothing to charge for a
202
+ * second one. A general search can find a plan needing two or three actions,
203
+ * or one that strands the reader without gas on an intermediate chain — see
204
+ * `signaturePenaltyUsd` and `gasTopUpPenaltyUsd` for what each extra action
205
+ * or gasless chain costs, and {@link SIGNATURE_PENALTY_PPM} /
206
+ * {@link GAS_TOPUP_PENALTY_PPM} for where the rates come from.
207
+ *
208
+ * WHY `(A − 1)` AND NOT `A`. Every plan needs at least one action to execute
209
+ * at all — signing or depositing is not itself a mark against a route, it is
210
+ * what EXECUTES it. Only actions beyond the first are extra friction the
211
+ * reader would not have paid on a one-action alternative, so only those are
212
+ * charged.
213
+ *
214
+ * REFUSES AN UNPRICED PLAN BY SCORING IT NEGATIVE INFINITY — PHASE 3. When
215
+ * {@link RouteScoreInputs.basis} is `'unpriced'`, `deliveredUsd` is a bare
216
+ * decimal rescale with no value behind it (see `EstimateBasis`'s own doc,
217
+ * `graph.ts`), and no arithmetic performed on it below would make it
218
+ * trustworthy. Returning `Number.NEGATIVE_INFINITY` rather than, say, `-1` or
219
+ * `0` is what makes this impossible to sidestep BY ACCIDENT: an ordinary `>`
220
+ * comparison, a plain numeric `.sort()`, or a `Math.max` over several plans'
221
+ * scores all already put an unpriced plan last WITHOUT the caller having to
222
+ * remember to check {@link RouteScoreInputs.basis} first — the same defence
223
+ * `compareRoutePlansByDeliveredAmount` (`graph.ts`) applies explicitly to a
224
+ * plan-to-plan comparison, applied here so a caller who reaches for
225
+ * `scoreRoutePlan` directly, for a single plan's score, gets the same
226
+ * protection for free.
227
+ *
228
+ * @param inputs - the plan's already-priced figures; see {@link RouteScoreInputs}
229
+ * @returns the plan's score — higher is better, and the units are United
230
+ * States dollars throughout; `Number.NEGATIVE_INFINITY` for an unpriced plan
231
+ */
232
+ // ---------------------------------------------------------------------------
233
+ // compareRoutePlansByScoreAdjustedDeliveredAmount — ranking that charges for
234
+ // an extra signature, without needing a United States dollar price
235
+ // ---------------------------------------------------------------------------
236
+ /** `SIGNATURE_PENALTY_PPM`'s denominator: parts per million, as a bigint. */
237
+ const PPM_DENOMINATOR = 1000000n;
238
+ /**
239
+ * `plan`'s last-hop `deliveredAmount`, discounted by {@link SIGNATURE_PENALTY_PPM}
240
+ * for every signature beyond the first — proportionally, in the delivered
241
+ * token's own base units, entirely in bigint arithmetic.
242
+ *
243
+ * WHY ONLY THE PROPORTIONAL SHARE, NEVER {@link SIGNATURE_PENALTY_FLOOR_USD}.
244
+ * The floor is denominated in United States dollars; converting a base-unit
245
+ * `deliveredAmount` into dollars needs a live price, which a `RoutePlan`
246
+ * (`graph.ts`) does not carry — it is a bigint amount in one token's own
247
+ * units, nothing more. `scoreRoutePlan` still applies the full formula,
248
+ * floor included, once a caller (`plan-route-data.ts`) has priced a plan into
249
+ * `RouteScoreInputs`. This function is the version usable at the STRUCTURAL
250
+ * ranking sites — `enumerate.ts`'s cut and `waves.ts`'s between-wave
251
+ * re-ranking — which only ever see the plan's own hops, not a price. See
252
+ * {@link compareRoutePlansByScoreAdjustedDeliveredAmount}'s own doc for why a
253
+ * proportional-only penalty is still enough to settle the case it exists for.
254
+ *
255
+ * @param plan - the plan to discount
256
+ * @returns the discounted delivered amount, floored at zero
257
+ */
258
+ const signaturePenaltyAdjustedDeliveredAmount = (plan) => {
259
+ const deliveredAmount = plan.hops[plan.hops.length - 1]?.outcome.deliveredAmount ?? 0n;
260
+ const extraActionCount = Math.max(0, plan.signatures.length - 1);
261
+ const penaltyPpm = BigInt(extraActionCount) * BigInt(exports.SIGNATURE_PENALTY_PPM);
262
+ const retainedPpm = penaltyPpm >= PPM_DENOMINATOR ? 0n : PPM_DENOMINATOR - penaltyPpm;
263
+ return (deliveredAmount * retainedPpm) / PPM_DENOMINATOR;
264
+ };
265
+ /**
266
+ * Orders two RANKABLE plans (both pass `graph.ts`'s `planIsRankable`) by
267
+ * their {@link signaturePenaltyAdjustedDeliveredAmount} rather than their raw
268
+ * `deliveredAmount` — the STRUCTURAL counterpart to `graph.ts`'s
269
+ * {@link @gibs/bridge-sdk/routing/search/graph!compareRoutePlansByDeliveredAmount},
270
+ * reusing every one of that function's rules (rankable beats unrankable,
271
+ * `compareUnrankablePlans`'s tiebreak among two unrankable plans) and adding
272
+ * exactly one more: within two RANKABLE plans, a plan needing more
273
+ * signatures than another must clear {@link SIGNATURE_PENALTY_PPM} of
274
+ * "extra" delivered value per extra signature before it is allowed to win,
275
+ * not merely `> 0`.
276
+ *
277
+ * THE DEFECT THIS CLOSES. `SIGNATURE_PENALTY_PPM` and `GAS_TOPUP_PENALTY_PPM`
278
+ * existed in this module as constants `scoreRoutePlan` charges — but nothing
279
+ * in the actual ranking path called `scoreRoutePlan` at all:
280
+ * `enumerate.ts`'s cut, `waves.ts`'s between-wave re-ranking, and
281
+ * `useRouteSearch.ts`'s final sort all imported `graph.ts`'s plain
282
+ * delivered-amount comparator directly, so a route needing a second
283
+ * signature for a fractional gain (measured: 0.006% more, needing one more
284
+ * wallet prompt) always won outright. Sorting on
285
+ * {@link signaturePenaltyAdjustedDeliveredAmount} instead is what makes that
286
+ * lose to the one-signature alternative: the extra signature's proportional
287
+ * cost (0.25% per {@link SIGNATURE_PENALTY_PPM}) outweighs a 0.006% edge by
288
+ * roughly forty times.
289
+ *
290
+ * WHY NOT `GAS_TOPUP_PENALTY_PPM` HERE TOO. A gasless-intermediate-chain
291
+ * charge needs to know whether the connected account holds gas on a chain in
292
+ * the middle of the plan — a live wallet balance read `RoutePlan` has no way
293
+ * to name. Applying it here would require guessing, which this comparator
294
+ * refuses to do; `GAS_TOPUP_PENALTY_PPM` is still applied wherever a caller
295
+ * has that balance and reaches for `scoreRoutePlan` directly (`plan-route-data.ts`
296
+ * onward). Left as an OWNER DECISION whether a structural ranking site should
297
+ * ever get gas-balance context of its own.
298
+ *
299
+ * @param a - one plan being compared
300
+ * @param b - the other
301
+ * @returns negative when `a` ranks ahead of `b`, positive when `b` ranks
302
+ * ahead of `a`, zero when they tie
303
+ */
304
+ const compareRoutePlansByScoreAdjustedDeliveredAmount = (a, b) => {
305
+ const rankableA = (0, graph_js_1.planIsRankable)(a.hops);
306
+ const rankableB = (0, graph_js_1.planIsRankable)(b.hops);
307
+ if (rankableA !== rankableB)
308
+ return rankableA ? -1 : 1;
309
+ if (!rankableA)
310
+ return (0, graph_js_1.compareRoutePlansByDeliveredAmount)(a, b);
311
+ const adjustedA = signaturePenaltyAdjustedDeliveredAmount(a);
312
+ const adjustedB = signaturePenaltyAdjustedDeliveredAmount(b);
313
+ if (adjustedA === adjustedB)
314
+ return 0;
315
+ return adjustedA > adjustedB ? -1 : 1;
316
+ };
317
+ exports.compareRoutePlansByScoreAdjustedDeliveredAmount = compareRoutePlansByScoreAdjustedDeliveredAmount;
318
+ const scoreRoutePlan = (inputs) => {
319
+ if (!(0, graph_js_1.isRankableBasis)(inputs.basis)) {
320
+ return Number.NEGATIVE_INFINITY;
321
+ }
322
+ const totalWaitPenaltyUsd = inputs.legClaimedDurationsSeconds.reduce((total, claimedSeconds) => total + (inputs.deliveredUsd * (0, exports.waitPenaltyPpm)(claimedSeconds)) / 1_000_000, 0);
323
+ const extraActionCount = Math.max(0, inputs.readerActionCount - 1);
324
+ const totalSignaturePenaltyUsd = extraActionCount * signaturePenaltyUsd(inputs.deliveredUsd);
325
+ const totalGasTopUpPenaltyUsd = inputs.gaslessIntermediateChainCount * gasTopUpPenaltyUsd(inputs.deliveredUsd);
326
+ return (inputs.deliveredUsd -
327
+ inputs.paidUsd -
328
+ inputs.outsideFeesUsd -
329
+ totalWaitPenaltyUsd -
330
+ totalSignaturePenaltyUsd -
331
+ totalGasTopUpPenaltyUsd);
332
+ };
333
+ exports.scoreRoutePlan = scoreRoutePlan;
334
+ /**
335
+ * A PLACEHOLDER {@link GasCostFor} that charges nothing — preserves the
336
+ * behaviour of ranking on raw delivered amount for a caller that has not yet
337
+ * wired a real gas-price and native-price source. NOT A PRODUCTION DEFAULT:
338
+ * unlike {@link WAIT_PENALTY_PPM_PER_MINUTE}'s two logged, live crossings, no
339
+ * per-chain gas figure here would be a measurement — it would be a guess
340
+ * dressed up as one, exactly what Rule 13 of this codebase's own working
341
+ * principles warns against. A host should always supply its own
342
+ * {@link GasCostFor} once it has gas-price and native-price feeds to build
343
+ * one from; this export exists only so `waves.ts`'s own default comparator
344
+ * has something total to call when nobody has.
345
+ */
346
+ const zeroGasCostFor = () => ({ gasAmount: 0n, refuelAmount: 0n });
347
+ exports.zeroGasCostFor = zeroGasCostFor;
348
+ /**
349
+ * Whether the section at `sectionIndex` starts with native coin already in
350
+ * the reader's hand: the hop that ends the PREVIOUS section delivers the
351
+ * chain's native coin, so the reader arrives holding gas for this section's
352
+ * own signature and needs no top-up there, whatever their balance was.
353
+ *
354
+ * Always false for the first section — nothing lands before it, so its gas
355
+ * can only come from the reader's own balance.
356
+ *
357
+ * @param plan - the plan the section belongs to
358
+ * @param sectionIndex - 0-based index into `plan.signatures`
359
+ * @returns true when the previous section lands native coin on this
360
+ * section's chain
361
+ */
362
+ const sectionIsFundedByLanding = (plan, sectionIndex) => {
363
+ if (sectionIndex < 1)
364
+ return false;
365
+ const previousGroup = plan.signatures[sectionIndex - 1];
366
+ const landingHop = plan.hops[previousGroup?.[previousGroup.length - 1] ?? -1];
367
+ if (landingHop === undefined)
368
+ return false;
369
+ return (0, ecosystems_js_1.isNativeAsset)(landingHop.edge.to.address, landingHop.edge.to.ecosystem);
370
+ };
371
+ exports.sectionIsFundedByLanding = sectionIsFundedByLanding;
372
+ /**
373
+ * `plan`'s last-hop `deliveredAmount`, minus the REAL cost of every
374
+ * signature it needs — `gasCostFor` charged ONCE per signature group
375
+ * (`plan.signatures`, built by `groupHopsBySignature`), on the chain and
376
+ * carrier of that group's FIRST hop, which is the hop the reader actually
377
+ * signs (or deposits by hand) for that group. A group's refuel cost is
378
+ * dropped when the previous group lands native coin
379
+ * ({@link sectionIsFundedByLanding}). Floored at zero: a plan whose
380
+ * gas costs exceed its own delivered amount is worth nothing, never negative.
381
+ *
382
+ * @param plan - the plan to charge
383
+ * @param gasCostFor - supplies each signature's real cost; see {@link GasCostFor}
384
+ * @returns the gas-adjusted delivered amount, in the plan's destination token's base units
385
+ */
386
+ const gasAdjustedDeliveredAmount = (plan, gasCostFor) => {
387
+ const deliveredAmount = plan.hops[plan.hops.length - 1]?.outcome.deliveredAmount ?? 0n;
388
+ const totalGasCost = plan.signatures.reduce((total, group, sectionIndex) => {
389
+ const firstHopIndex = group[0];
390
+ const firstHop = firstHopIndex === undefined ? undefined : plan.hops[firstHopIndex];
391
+ if (firstHop === undefined)
392
+ return total;
393
+ const quote = gasCostFor(firstHop.edge.from.chainId, firstHop.edge.carrier);
394
+ const refuelAmount = (0, exports.sectionIsFundedByLanding)(plan, sectionIndex) ? 0n : quote.refuelAmount;
395
+ return total + quote.gasAmount + refuelAmount;
396
+ }, 0n);
397
+ const netAmount = deliveredAmount - totalGasCost;
398
+ return netAmount < 0n ? 0n : netAmount;
399
+ };
400
+ exports.gasAdjustedDeliveredAmount = gasAdjustedDeliveredAmount;
401
+ /**
402
+ * Orders two plans by {@link gasAdjustedDeliveredAmount} rather than raw
403
+ * `deliveredAmount` or {@link signaturePenaltyAdjustedDeliveredAmount}'s flat
404
+ * ppm guess — the structural ranking `waves.ts` uses once Round A/B have real
405
+ * gas data to charge, replacing {@link SIGNATURE_PENALTY_PPM}/
406
+ * {@link GAS_TOPUP_PENALTY_PPM} at that one site per the owner's own
407
+ * instruction (see both constants' updated docs). A CURRIED FACTORY, not a
408
+ * plain comparator, because the real cost this needs — {@link GasCostFor} —
409
+ * is caller-supplied and cannot be a module-level default the way
410
+ * {@link compareRoutePlansByScoreAdjustedDeliveredAmount} is; call it once
411
+ * per search with the caller's own `gasCostFor`, then pass the returned
412
+ * comparator wherever a plan-to-plan comparison is needed.
413
+ *
414
+ * KEEPS THE SAME RANKABLE-BEFORE-UNRANKABLE RULE `compareRoutePlansByScoreAdjustedDeliveredAmount`
415
+ * ALREADY APPLIES, unchanged, including its `compareRoutePlansByDeliveredAmount`
416
+ * tiebreak among two unrankable plans — only the tiebreak among two RANKABLE
417
+ * plans changes, from a proportional signature-count discount to the route's
418
+ * own real charges.
419
+ *
420
+ * @param gasCostFor - supplies each signature's real cost; see {@link GasCostFor}
421
+ * TIE-BREAK: when the gas-adjusted amounts are equal, the plan with fewer
422
+ * signatures ranks first.
423
+ *
424
+ * @returns a comparator: negative when `a` ranks ahead of `b`, positive when
425
+ * `b` ranks ahead of `a`, zero when they tie
426
+ */
427
+ const compareRoutePlansByGasAdjustedDeliveredAmount = (gasCostFor) => (a, b) => {
428
+ const rankableA = (0, graph_js_1.planIsRankable)(a.hops);
429
+ const rankableB = (0, graph_js_1.planIsRankable)(b.hops);
430
+ if (rankableA !== rankableB)
431
+ return rankableA ? -1 : 1;
432
+ if (!rankableA)
433
+ return (0, graph_js_1.compareRoutePlansByDeliveredAmount)(a, b);
434
+ const adjustedA = (0, exports.gasAdjustedDeliveredAmount)(a, gasCostFor);
435
+ const adjustedB = (0, exports.gasAdjustedDeliveredAmount)(b, gasCostFor);
436
+ if (adjustedA !== adjustedB)
437
+ return adjustedA > adjustedB ? -1 : 1;
438
+ // EQUAL FIGURES: FEWER SIGNATURES RANKS FIRST. Two plans that deliver the
439
+ // same amount cost the reader different effort — each signature is one more
440
+ // wallet prompt. Without this the stable sort kept the depth-first walk
441
+ // order, so a three-signature plan could be selected over a two-signature
442
+ // plan showing the same figure.
443
+ return a.signatures.length - b.signatures.length;
444
+ };
445
+ exports.compareRoutePlansByGasAdjustedDeliveredAmount = compareRoutePlansByGasAdjustedDeliveredAmount;
@@ -0,0 +1,95 @@
1
+ import { type Hex } from 'viem';
2
+ import { type SignedLeg } from '../settings.js';
3
+ import type { SwapVenues } from '../swap-venues.js';
4
+ import type { HopEdge, PlannedHop } from './search/graph.js';
5
+ /**
6
+ * The same-chain, one-signature transaction shape every sender in this file
7
+ * builds: a target contract, a native-coin value, and encoded calldata.
8
+ */
9
+ export type SwapCallInputs = {
10
+ readonly to: Hex;
11
+ readonly value: bigint;
12
+ readonly data: Hex;
13
+ };
14
+ /**
15
+ * Builds a same-chain PulseX swap transaction for one planned route-search
16
+ * hop. The interface's section signing (`packages/ui` `state/sectionSigning.ts`)
17
+ * and the SDK's execution planner (`execution.ts`) both call it, so there is
18
+ * exactly one place that turns "swap this hop" into calldata.
19
+ *
20
+ * SIGNS THE PATH THE HOP WAS PRICED ON. The PulseX carrier picks the best of
21
+ * many paths through its hub tokens and records the winner on the hop's
22
+ * outcome; `pulseXSwapPathForHop` (`carriers/pulsex.ts`) reads it back
23
+ * and checks it fits the hop's edge. This never rebuilds a path: a rebuilt
24
+ * path is a quote on one route and a signature on another. A hop that
25
+ * records no path is refused. REUSES `pulseXSwapTransactionInputs` (`settings.ts`)
26
+ * for the encoding, never re-implements it: that function already carries the
27
+ * `swapRefusal` guard (refuses to build a call for a leg the screen is not
28
+ * looking at) and the native-in/native-out/token-token branching.
29
+ *
30
+ * SIGNS ON THE EDGE'S OWN ROUTER. A PulseX edge names the deployment it was
31
+ * quoted on (`venueForEdge`: V1 or V2, each with its own pools). The
32
+ * caller's `routerAddress` must be that same router — it is also the router
33
+ * the caller approved to spend the input token — or nothing is built. A V2
34
+ * quote signed on V1 would trade against a different pool than the one the
35
+ * reader was shown.
36
+ *
37
+ * @param options - the hop to swap, the amount and slippage-adjusted
38
+ * minimum, who receives the output, the signing deadline, what the caller
39
+ * believes this signature does (`signedLeg`), the chain it executes on, the
40
+ * resolved router address, and the swap venues the router must belong to
41
+ * (the defaults when omitted)
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 declare const buildPulseXSwapTransaction: (options: {
48
+ readonly hop: Pick<PlannedHop, "edge" | "outcome">;
49
+ readonly amountIn: bigint;
50
+ readonly minAmountOut: bigint;
51
+ readonly recipient: Hex | null;
52
+ readonly deadline: bigint;
53
+ readonly signedLeg: SignedLeg | null;
54
+ readonly chainId: number | null;
55
+ readonly routerAddress: Hex | null;
56
+ /** The venues the edge's router must belong to. Omitted means the defaults. */
57
+ readonly venues?: SwapVenues;
58
+ }) => SwapCallInputs | null;
59
+ /**
60
+ * Builds a native-coin wrap/unwrap transaction for one route-search graph
61
+ * edge — PLS<->WPLS on PulseChain, ETH<->WETH on Ethereum, or the same pair
62
+ * on any other chain `config.ts`'s `nativeAssetOut` names one for.
63
+ *
64
+ * A WRAP IS NOT A SWAP CALL. There is no PulseX pool for native-versus-wrapped
65
+ * — see `routing/carriers/wrap.ts`'s own doc — so this never touches the
66
+ * PulseX router: wrapping sends `amountIn` native coin to the wrapped token's
67
+ * own `deposit()`; unwrapping calls `withdraw(amountIn)` on it with zero
68
+ * value. The `edge` names which direction: its `from`/`to` addresses are
69
+ * checked against the zero-address sentinel, never assumed from a caller
70
+ * flag, so a swapped/malformed edge is refused rather than guessed at.
71
+ *
72
+ * @param options - the edge to wrap or unwrap, and the amount in the SOURCE
73
+ * token's base units (native units wrapping, wrapped units unwrapping —
74
+ * native and wrapped share decimals on every chain this carrier covers)
75
+ * @returns the wrap/unwrap transaction inputs, or null when `amountIn` is not
76
+ * positive or the edge is not exactly one native/wrapped pair
77
+ */
78
+ export declare const buildWrapTransaction: (options: {
79
+ readonly edge: HopEdge;
80
+ readonly amountIn: bigint;
81
+ }) => SwapCallInputs | null;
82
+ /**
83
+ * The slippage floor a swap signs: the quoted output less the slippage
84
+ * allowance, rounded down. The interface's engine signing and the SDK's
85
+ * execution planner both read it, so the two never sign different floors for
86
+ * the same quote.
87
+ *
88
+ * @param options.quotedAmount - the hop's quoted output, in the output token's base units
89
+ * @param options.slippageBasisPoints - the allowance, in basis points (100 is 1%)
90
+ * @returns the minimum output the swap accepts
91
+ */
92
+ export declare const swapMinimumOut: ({ quotedAmount, slippageBasisPoints, }: {
93
+ readonly quotedAmount: bigint;
94
+ readonly slippageBasisPoints: bigint;
95
+ }) => bigint;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.swapMinimumOut = exports.buildWrapTransaction = exports.buildPulseXSwapTransaction = void 0;
4
+ const viem_1 = require("viem");
5
+ const settings_js_1 = require("../settings.js");
6
+ const config_js_1 = require("../config.js");
7
+ const pulsex_js_1 = require("./carriers/pulsex.js");
8
+ /**
9
+ * The minimal WETH9-style wrap/unwrap interface: `deposit()` mints the
10
+ * wrapped token for the native coin sent with the call, `withdraw(uint256)`
11
+ * burns wrapped balance and returns native coin. Every wrapped-native
12
+ * contract this application bridges to exposes exactly this pair — see
13
+ * `carriers/wrap.ts`, whose own doc records the selectors as confirmed
14
+ * present on WPLS by bytecode inspection (2026-09-24). Kept local rather than
15
+ * added to `@gibs/abis`: the interface is two standard, unchanging selectors
16
+ * rather than a contract-specific ABI.
17
+ */
18
+ const wrapAbi = (0, viem_1.parseAbi)(['function deposit() payable', 'function withdraw(uint256 amount)']);
19
+ /**
20
+ * Builds a same-chain PulseX swap transaction for one planned route-search
21
+ * hop. The interface's section signing (`packages/ui` `state/sectionSigning.ts`)
22
+ * and the SDK's execution planner (`execution.ts`) both call it, so there is
23
+ * exactly one place that turns "swap this hop" into calldata.
24
+ *
25
+ * SIGNS THE PATH THE HOP WAS PRICED ON. The PulseX carrier picks the best of
26
+ * many paths through its hub tokens and records the winner on the hop's
27
+ * outcome; `pulseXSwapPathForHop` (`carriers/pulsex.ts`) reads it back
28
+ * and checks it fits the hop's edge. This never rebuilds a path: a rebuilt
29
+ * path is a quote on one route and a signature on another. A hop that
30
+ * records no path is refused. REUSES `pulseXSwapTransactionInputs` (`settings.ts`)
31
+ * for the encoding, never re-implements it: that function already carries the
32
+ * `swapRefusal` guard (refuses to build a call for a leg the screen is not
33
+ * looking at) and the native-in/native-out/token-token branching.
34
+ *
35
+ * SIGNS ON THE EDGE'S OWN ROUTER. A PulseX edge names the deployment it was
36
+ * quoted on (`venueForEdge`: V1 or V2, each with its own pools). The
37
+ * caller's `routerAddress` must be that same router — it is also the router
38
+ * the caller approved to spend the input token — or nothing is built. A V2
39
+ * quote signed on V1 would trade against a different pool than the one the
40
+ * reader was shown.
41
+ *
42
+ * @param options - the hop to swap, the amount and slippage-adjusted
43
+ * minimum, who receives the output, the signing deadline, what the caller
44
+ * believes this signature does (`signedLeg`), the chain it executes on, the
45
+ * resolved router address, and the swap venues the router must belong to
46
+ * (the defaults when omitted)
47
+ * @returns the swap transaction inputs, or null when the hop records no
48
+ * PulseX swap path, names no known router, `routerAddress` is not the
49
+ * edge's own router, the leg on screen disagrees, or any of
50
+ * `pulseXSwapTransactionInputs`'s own preconditions are unmet
51
+ */
52
+ const buildPulseXSwapTransaction = (options) => {
53
+ const { hop, amountIn, minAmountOut, recipient, deadline, signedLeg, chainId, routerAddress } = options;
54
+ const { edge } = hop;
55
+ const swapPath = (0, pulsex_js_1.pulseXSwapPathForHop)(hop);
56
+ if (swapPath === null) {
57
+ return null;
58
+ }
59
+ const quotedRouter = (0, pulsex_js_1.venueForEdge)(options.venues ?? config_js_1.defaultSwapVenues, edge)?.router ?? null;
60
+ if (quotedRouter === null || routerAddress === null || !(0, viem_1.isAddressEqual)(quotedRouter, routerAddress)) {
61
+ return null;
62
+ }
63
+ return (0, settings_js_1.pulseXSwapTransactionInputs)({
64
+ signedLeg,
65
+ chainId,
66
+ routerAddress,
67
+ fromTokenAddress: edge.from.address,
68
+ toTokenAddress: edge.to.address,
69
+ swapPath,
70
+ amountIn,
71
+ minAmountOut,
72
+ recipient,
73
+ deadline,
74
+ });
75
+ };
76
+ exports.buildPulseXSwapTransaction = buildPulseXSwapTransaction;
77
+ /**
78
+ * Builds a native-coin wrap/unwrap transaction for one route-search graph
79
+ * edge — PLS<->WPLS on PulseChain, ETH<->WETH on Ethereum, or the same pair
80
+ * on any other chain `config.ts`'s `nativeAssetOut` names one for.
81
+ *
82
+ * A WRAP IS NOT A SWAP CALL. There is no PulseX pool for native-versus-wrapped
83
+ * — see `routing/carriers/wrap.ts`'s own doc — so this never touches the
84
+ * PulseX router: wrapping sends `amountIn` native coin to the wrapped token's
85
+ * own `deposit()`; unwrapping calls `withdraw(amountIn)` on it with zero
86
+ * value. The `edge` names which direction: its `from`/`to` addresses are
87
+ * checked against the zero-address sentinel, never assumed from a caller
88
+ * flag, so a swapped/malformed edge is refused rather than guessed at.
89
+ *
90
+ * @param options - the edge to wrap or unwrap, and the amount in the SOURCE
91
+ * token's base units (native units wrapping, wrapped units unwrapping —
92
+ * native and wrapped share decimals on every chain this carrier covers)
93
+ * @returns the wrap/unwrap transaction inputs, or null when `amountIn` is not
94
+ * positive or the edge is not exactly one native/wrapped pair
95
+ */
96
+ const buildWrapTransaction = (options) => {
97
+ const { edge, amountIn } = options;
98
+ if (amountIn <= 0n) {
99
+ return null;
100
+ }
101
+ const fromIsNative = edge.from.address.toLowerCase() === viem_1.zeroAddress;
102
+ const toIsNative = edge.to.address.toLowerCase() === viem_1.zeroAddress;
103
+ // Exactly one side must be the native sentinel — a token/token pair, or a
104
+ // native/native no-op, is not a wrap edge at all.
105
+ if (fromIsNative === toIsNative) {
106
+ return null;
107
+ }
108
+ if (fromIsNative) {
109
+ return {
110
+ to: edge.to.address,
111
+ value: amountIn,
112
+ data: (0, viem_1.encodeFunctionData)({ abi: wrapAbi, functionName: 'deposit', args: [] }),
113
+ };
114
+ }
115
+ return {
116
+ to: edge.from.address,
117
+ value: 0n,
118
+ data: (0, viem_1.encodeFunctionData)({ abi: wrapAbi, functionName: 'withdraw', args: [amountIn] }),
119
+ };
120
+ };
121
+ exports.buildWrapTransaction = buildWrapTransaction;
122
+ /**
123
+ * The slippage floor a swap signs: the quoted output less the slippage
124
+ * allowance, rounded down. The interface's engine signing and the SDK's
125
+ * execution planner both read it, so the two never sign different floors for
126
+ * the same quote.
127
+ *
128
+ * @param options.quotedAmount - the hop's quoted output, in the output token's base units
129
+ * @param options.slippageBasisPoints - the allowance, in basis points (100 is 1%)
130
+ * @returns the minimum output the swap accepts
131
+ */
132
+ const swapMinimumOut = ({ quotedAmount, slippageBasisPoints, }) => (quotedAmount * (10000n - slippageBasisPoints)) / 10000n;
133
+ exports.swapMinimumOut = swapMinimumOut;