@gibs/bridge-sdk 1.12.0 → 1.13.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 (117) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +234 -432
  3. package/dist/abis.d.ts +1 -0
  4. package/dist/bridge-limits.d.ts +103 -0
  5. package/dist/bridge-limits.js +84 -0
  6. package/dist/chain-info.d.ts +169 -0
  7. package/dist/chain-info.js +410 -0
  8. package/dist/chain-names.d.ts +160 -0
  9. package/dist/chain-names.js +200 -0
  10. package/dist/chains.d.ts +65 -0
  11. package/dist/chains.js +284 -0
  12. package/dist/config.d.ts +216 -0
  13. package/dist/config.js +493 -0
  14. package/dist/consolidated-quote.d.ts +554 -0
  15. package/dist/consolidated-quote.js +773 -0
  16. package/dist/destination-calldata.d.ts +118 -0
  17. package/dist/destination-calldata.js +152 -0
  18. package/dist/ecosystems.d.ts +217 -0
  19. package/dist/ecosystems.js +288 -0
  20. package/dist/fee-type.d.ts +10 -0
  21. package/dist/fee-type.js +9 -0
  22. package/dist/hyperliquid-execution.d.ts +152 -0
  23. package/dist/hyperliquid-execution.js +168 -0
  24. package/dist/image-links.d.ts +117 -0
  25. package/dist/image-links.js +142 -0
  26. package/dist/index.d.ts +20 -0
  27. package/dist/index.js +20 -0
  28. package/dist/lifi-execution-guard.d.ts +199 -0
  29. package/dist/lifi-execution-guard.js +400 -0
  30. package/dist/near-intents-conformance.d.ts +85 -0
  31. package/dist/near-intents-conformance.js +329 -0
  32. package/dist/near-intents-route.d.ts +47 -0
  33. package/dist/near-intents-route.js +265 -0
  34. package/dist/near-intents.d.ts +524 -0
  35. package/dist/near-intents.js +1055 -0
  36. package/dist/origin-deposit.d.ts +326 -0
  37. package/dist/origin-deposit.js +68 -0
  38. package/dist/pre-quote.d.ts +198 -0
  39. package/dist/pre-quote.js +175 -0
  40. package/dist/prices.d.ts +107 -0
  41. package/dist/prices.js +162 -0
  42. package/dist/providers.d.ts +221 -0
  43. package/dist/providers.js +157 -0
  44. package/dist/quote-fees.d.ts +102 -0
  45. package/dist/quote-fees.js +195 -0
  46. package/dist/referral.d.ts +77 -0
  47. package/dist/referral.js +72 -0
  48. package/dist/relay-currencies.d.ts +47 -0
  49. package/dist/relay-currencies.js +89 -0
  50. package/dist/relay-quote.d.ts +301 -0
  51. package/dist/relay-quote.js +983 -0
  52. package/dist/route-steps.d.ts +138 -0
  53. package/dist/route-steps.js +305 -0
  54. package/dist/routing/amounts.d.ts +80 -0
  55. package/dist/routing/amounts.js +120 -0
  56. package/dist/routing/candidate-route.d.ts +43 -0
  57. package/dist/routing/candidate-route.js +45 -0
  58. package/dist/routing/candidate.d.ts +237 -0
  59. package/dist/routing/candidate.js +96 -0
  60. package/dist/routing/carriers/ask-answer.d.ts +58 -0
  61. package/dist/routing/carriers/ask-answer.js +1 -0
  62. package/dist/routing/carriers/omnibridge.d.ts +137 -0
  63. package/dist/routing/carriers/omnibridge.js +419 -0
  64. package/dist/routing/carriers/pulsex.d.ts +111 -0
  65. package/dist/routing/carriers/pulsex.js +404 -0
  66. package/dist/routing/carriers/wrap.d.ts +25 -0
  67. package/dist/routing/carriers/wrap.js +115 -0
  68. package/dist/routing/composed-route-quality.d.ts +103 -0
  69. package/dist/routing/composed-route-quality.js +108 -0
  70. package/dist/routing/entry-chain-anchor.d.ts +88 -0
  71. package/dist/routing/entry-chain-anchor.js +55 -0
  72. package/dist/routing/format.d.ts +31 -0
  73. package/dist/routing/format.js +58 -0
  74. package/dist/routing/index.d.ts +34 -0
  75. package/dist/routing/index.js +42 -0
  76. package/dist/routing/producers.d.ts +770 -0
  77. package/dist/routing/producers.js +1564 -0
  78. package/dist/routing/providers.d.ts +26 -0
  79. package/dist/routing/providers.js +38 -0
  80. package/dist/routing/route-data.d.ts +355 -0
  81. package/dist/routing/route-data.js +1 -0
  82. package/dist/routing/search/compose.d.ts +168 -0
  83. package/dist/routing/search/compose.js +98 -0
  84. package/dist/routing/search/graph.d.ts +639 -0
  85. package/dist/routing/search/graph.js +445 -0
  86. package/dist/routing/search/plan-route-data.d.ts +244 -0
  87. package/dist/routing/search/plan-route-data.js +767 -0
  88. package/dist/routing/search/score.d.ts +324 -0
  89. package/dist/routing/search/score.js +400 -0
  90. package/dist/routing/usd-value.d.ts +17 -0
  91. package/dist/routing/usd-value.js +25 -0
  92. package/dist/settings.d.ts +692 -0
  93. package/dist/settings.js +909 -0
  94. package/dist/ton-execution.d.ts +117 -0
  95. package/dist/ton-execution.js +247 -0
  96. package/dist/tron-execution.d.ts +146 -0
  97. package/dist/tron-execution.js +239 -0
  98. package/dist/types.d.ts +51 -0
  99. package/dist/types.js +1 -0
  100. package/package.json +203 -23
  101. package/src/chain-info.test.ts +0 -968
  102. package/src/chain-info.ts +0 -498
  103. package/src/chains.test.ts +0 -123
  104. package/src/chains.ts +0 -88
  105. package/src/config.test.ts +0 -679
  106. package/src/config.ts +0 -500
  107. package/src/fee-type.test.ts +0 -45
  108. package/src/fee-type.ts +0 -14
  109. package/src/image-links.test.ts +0 -160
  110. package/src/image-links.ts +0 -65
  111. package/src/index.ts +0 -8
  112. package/src/settings.test.ts +0 -2754
  113. package/src/settings.ts +0 -875
  114. package/src/types.ts +0 -48
  115. package/tsconfig.json +0 -17
  116. package/vitest.config.ts +0 -13
  117. /package/{src/abis.ts → dist/abis.js} +0 -0
@@ -0,0 +1,400 @@
1
+ import { compareRoutePlansByDeliveredAmount, planIsRankable, isRankableBasis, } from './graph.js';
2
+ /**
3
+ * PHASE 0 — FOUNDATION ONLY. Today this module holds exactly one function,
4
+ * {@link waitPenaltyPpm}, moved here unchanged from
5
+ * `packages/ui/src/lib/stores/consolidated-quote.ts` (that file now imports
6
+ * and re-exports it, so every existing caller keeps working). Phase 1's
7
+ * `score.ts` work (design section 4) generalizes `candidateNetValueUsd` and
8
+ * `netValueAfterWaitPenalty` from `../producers.js` and
9
+ * `packages/ui/src/lib/state/useRouteOptions.ts` into one `scoreRoutePlan`
10
+ * function that lives here beside it.
11
+ *
12
+ * WHY THIS MOVES TO `bridge-sdk` AT ALL. `waitPenaltyPpm` is a pure function
13
+ * of a number — it has no dependency on React, a store, or any
14
+ * user-interface concern — and `scoreRoutePlan` will need it to score a
15
+ * `RoutePlan` (`../graph.js`), which is a `bridge-sdk` type. Leaving the
16
+ * function in the user-interface package would make `bridge-sdk`'s own
17
+ * scoring logic import FROM the application that is supposed to consume it.
18
+ */
19
+ /**
20
+ * The share of the transfer a full minute of extra waiting is charged, in
21
+ * parts per million — 5,000 ppm is half of one percent.
22
+ *
23
+ * WHERE THE NUMBER COMES FROM. The owner's own crossing: LI.FI (through
24
+ * squid) claimed twenty seconds and Relay claimed two, and the strip
25
+ * preferred LI.FI because it delivered 0.075% more — nine cents on a
26
+ * hundred and twenty-six dollars. The transfer was still unsettled
27
+ * twenty-five minutes later. A second live measurement on Base to Polygon:
28
+ * twenty-nine seconds against three, for 0.0545%. Both are the same shape —
29
+ * a few cents of advantage bought with a ten-fold increase in the wait —
30
+ * and under this rate both go the other way: at eighteen seconds of extra
31
+ * claimed wait a route must deliver 0.15% more to win, and at twenty-six
32
+ * seconds it must deliver 0.217% more. Neither does.
33
+ *
34
+ * The rate is also calibrated NOT to override a real price advantage. A
35
+ * route that is a full minute slower still wins on 0.6% more delivered,
36
+ * which on a hundred-and-twenty-six-dollar transfer is seventy-six cents —
37
+ * a figure worth waiting a minute for, where nine cents is not.
38
+ */
39
+ const WAIT_PENALTY_PPM_PER_MINUTE = 5_000;
40
+ /** The wait beyond which extra claimed seconds stop adding to the penalty. */
41
+ const WAIT_PENALTY_CEILING_SECONDS = 60;
42
+ /**
43
+ * What a quote's CLAIMED duration costs it in the ranking, as a share of the
44
+ * transfer in parts per million. It ramps linearly to
45
+ * {@link WAIT_PENALTY_PPM_PER_MINUTE} at {@link WAIT_PENALTY_CEILING_SECONDS}
46
+ * and stops there.
47
+ *
48
+ * THE DURATION IS A CLAIM AND IS TREATED AS ONE. The route that prompted
49
+ * this ran about seventy-five times its provider's own estimate, so the
50
+ * figure cannot be trusted as a measurement of anything. It is still the
51
+ * only signal there is, and it is informative in one direction: a provider
52
+ * that says it will take longer is not going to turn out faster, and a
53
+ * claimed wait understates the real one far more often than it overstates
54
+ * it. So the penalty is used to demand a real price advantage before ANY
55
+ * extra claimed wait is accepted — never to predict a settlement time, and
56
+ * never to produce a number shown to the reader. Nothing on screen is
57
+ * adjusted by this; only the order is.
58
+ *
59
+ * The ceiling is what keeps the rule from becoming a speed preference. Past
60
+ * a minute the penalty stops growing, so a route that is genuinely worth
61
+ * waiting for still wins on the strength of its price.
62
+ *
63
+ * @param durationSeconds - the provider's own claimed execution duration
64
+ * @returns the penalty as parts per million of the transfer, 0 for a missing claim
65
+ */
66
+ export const waitPenaltyPpm = (durationSeconds) => {
67
+ if (typeof durationSeconds !== 'number' || !Number.isFinite(durationSeconds))
68
+ return 0;
69
+ if (durationSeconds <= 0)
70
+ return 0;
71
+ const rampedSeconds = Math.min(durationSeconds, WAIT_PENALTY_CEILING_SECONDS);
72
+ return Math.round((WAIT_PENALTY_PPM_PER_MINUTE * rampedSeconds) / WAIT_PENALTY_CEILING_SECONDS);
73
+ };
74
+ // ---------------------------------------------------------------------------
75
+ // scoreRoutePlan — the one maximization function
76
+ // ---------------------------------------------------------------------------
77
+ /**
78
+ * The share of the transfer charged for EACH reader action beyond the first,
79
+ * in parts per million — see {@link signaturePenaltyUsd}.
80
+ *
81
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Unlike {@link WAIT_PENALTY_PPM_PER_MINUTE},
82
+ * which is calibrated against two logged, live crossings, no comparable
83
+ * signature-count measurement exists yet. 2,500 ppm (0.25% of the delivered
84
+ * amount) is a starting point sized to sit BELOW the wait penalty's own
85
+ * ceiling (5,000 ppm) — a second signature should cost less than the worst
86
+ * wait penalty already accepted, since a signature is a moment of the
87
+ * reader's attention, not twenty-five minutes of theirs. The owner is meant
88
+ * to calibrate this against real reader behavior once it ships.
89
+ */
90
+ export const SIGNATURE_PENALTY_PPM = 2_500;
91
+ /**
92
+ * SUPERSEDED FOR THE SEARCH ENGINE'S OWN RANKING — see
93
+ * {@link compareRoutePlansByGasAdjustedDeliveredAmount}, below, which
94
+ * `waves.ts` now uses in place of {@link compareRoutePlansByScoreAdjustedDeliveredAmount}
95
+ * once real gas data exists. A flat parts-per-million guess cannot tell a
96
+ * cheap chain from an expensive one, and it charges nothing for gas at all —
97
+ * the owner's own instruction: "it's custom for each provider ... gather the
98
+ * logic around how much a user is about to be charged." This constant, and
99
+ * {@link GAS_TOPUP_PENALTY_PPM} below, are KEPT UNCHANGED because
100
+ * {@link scoreRoutePlan} and every existing United States dollar-based
101
+ * caller of it (`plan-route-data.ts`, `producers.ts`, the user interface's
102
+ * own single-candidate scoring) still read them — migrating those callers to
103
+ * a real gas-cost model is FLAGGED HERE as follow-up work outside this
104
+ * module's own owned scope, not done silently in this change.
105
+ */
106
+ /**
107
+ * The minimum a signature is ever charged, in United States dollars — see
108
+ * {@link signaturePenaltyUsd}.
109
+ *
110
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Without a floor, {@link SIGNATURE_PENALTY_PPM}
111
+ * charges almost nothing on a small transfer (0.25% of ten dollars is two and
112
+ * a half cents), which would let a route add a whole extra signature for a
113
+ * fraction of a cent. One dollar is a guess at what a reader's own time and
114
+ * attention for one extra wallet prompt is worth, floored so it cannot be
115
+ * bought away by a large delivered amount either — the floor and the rate are
116
+ * a `max`, so whichever is larger applies. The owner is meant to calibrate
117
+ * this once real reader behavior is observed.
118
+ */
119
+ export const SIGNATURE_PENALTY_FLOOR_USD = 1;
120
+ /**
121
+ * The share of the transfer charged for EACH intermediate chain where the
122
+ * reader holds no gas, in parts per million — see {@link gasTopUpPenaltyUsd}.
123
+ *
124
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Sized larger than
125
+ * {@link SIGNATURE_PENALTY_PPM} because a gas top-up is not a moment of
126
+ * attention like a signature — it is a SEPARATE errand: acquire the
127
+ * intermediate chain's native coin, by another bridge or a purchase, before
128
+ * the planned hop can even be signed. That errand risks stranding the
129
+ * transfer mid-route if the reader does not complete it, which the
130
+ * "stranded funds" risk in the route-search design's section 10 names
131
+ * directly. The owner is meant to calibrate this once real reader behavior
132
+ * is observed.
133
+ */
134
+ export const GAS_TOPUP_PENALTY_PPM = 10_000;
135
+ /**
136
+ * The minimum a gas top-up is ever charged, in United States dollars — see
137
+ * {@link gasTopUpPenaltyUsd}.
138
+ *
139
+ * THIS IS A PROPOSAL, NOT A MEASUREMENT. Set higher than
140
+ * {@link SIGNATURE_PENALTY_FLOOR_USD} for the same reason
141
+ * {@link GAS_TOPUP_PENALTY_PPM} is: this penalty stands in for a separate
142
+ * errand with its own real cost (buying gas on a chain the reader was not
143
+ * already using), not a moment of attention. The owner is meant to calibrate
144
+ * this once real reader behavior is observed.
145
+ */
146
+ export const GAS_TOPUP_PENALTY_FLOOR_USD = 3;
147
+ /**
148
+ * What one extra reader action (a signature or a pay-by-hand deposit) costs
149
+ * in the ranking, in United States dollars: the larger of
150
+ * {@link SIGNATURE_PENALTY_FLOOR_USD} and {@link SIGNATURE_PENALTY_PPM} of
151
+ * the delivered amount. See both constants' docs for where the numbers come
152
+ * from and that they are proposals, not measurements.
153
+ *
154
+ * @param deliveredUsd - the route's delivered value in United States dollars
155
+ * @returns the penalty for one extra action, always positive
156
+ */
157
+ const signaturePenaltyUsd = (deliveredUsd) => Math.max(SIGNATURE_PENALTY_FLOOR_USD, (deliveredUsd * SIGNATURE_PENALTY_PPM) / 1_000_000);
158
+ /**
159
+ * What one intermediate chain with no reader gas costs in the ranking, in
160
+ * United States dollars: the larger of {@link GAS_TOPUP_PENALTY_FLOOR_USD}
161
+ * and {@link GAS_TOPUP_PENALTY_PPM} of the delivered amount. See both
162
+ * constants' docs for where the numbers come from and that they are
163
+ * proposals, not measurements.
164
+ *
165
+ * @param deliveredUsd - the route's delivered value in United States dollars
166
+ * @returns the penalty for one gasless intermediate chain, always positive
167
+ */
168
+ const gasTopUpPenaltyUsd = (deliveredUsd) => Math.max(GAS_TOPUP_PENALTY_FLOOR_USD, (deliveredUsd * GAS_TOPUP_PENALTY_PPM) / 1_000_000);
169
+ /**
170
+ * The one maximization function a route search ranks every plan by —
171
+ * generalizing today's `candidateNetValueUsd` and `netValueAfterWaitPenalty`
172
+ * (`../producers.js` and `packages/ui/src/lib/state/useRouteOptions.ts`)
173
+ * from a single quoted candidate to a whole {@link RouteScoreInputs} plan, per
174
+ * the route-search design's section 4:
175
+ *
176
+ * ```
177
+ * score = D − P − F
178
+ * − Σ_legs D·waitPenaltyPpm(claimedSeconds_leg)/1e6
179
+ * − (A − 1)·max($1, D·SIGNATURE_PENALTY_PPM/1e6)
180
+ * − G·max($3, D·GAS_TOPUP_PENALTY_PPM/1e6)
181
+ * ```
182
+ *
183
+ * A HIGHER SCORE IS BETTER. A route search picks the plan with the largest
184
+ * `scoreRoutePlan` result, exactly as today's ranking picks the candidate
185
+ * with the largest `netValueAfterWaitPenalty`.
186
+ *
187
+ * THE WAIT PENALTY IS UNCHANGED, JUST SUMMED ACROSS LEGS. Today's single-quote
188
+ * penalty is `deliveredUsd * waitPenaltyPpm(oneClaimedDuration) / 1e6`, one
189
+ * term. A plan can carry several hops, each with its own claimed duration, so
190
+ * this sums that same per-unit charge over every hop in
191
+ * {@link RouteScoreInputs.legClaimedDurationsSeconds} — still against the
192
+ * PLAN'S total delivered value, not each leg's own, because the reader is
193
+ * charged for the whole route's wait, not a fraction of it per leg.
194
+ *
195
+ * WHAT IS NEW: THE ACTION AND GAS-TOP-UP TERMS. Today's ranking only ever
196
+ * compared plans that cost one signature, so it had nothing to charge for a
197
+ * second one. A general search can find a plan needing two or three actions,
198
+ * or one that strands the reader without gas on an intermediate chain — see
199
+ * `signaturePenaltyUsd` and `gasTopUpPenaltyUsd` for what each extra action
200
+ * or gasless chain costs, and {@link SIGNATURE_PENALTY_PPM} /
201
+ * {@link GAS_TOPUP_PENALTY_PPM} for where the rates come from.
202
+ *
203
+ * WHY `(A − 1)` AND NOT `A`. Every plan needs at least one action to execute
204
+ * at all — signing or depositing is not itself a mark against a route, it is
205
+ * what EXECUTES it. Only actions beyond the first are extra friction the
206
+ * reader would not have paid on a one-action alternative, so only those are
207
+ * charged.
208
+ *
209
+ * REFUSES AN UNPRICED PLAN BY SCORING IT NEGATIVE INFINITY — PHASE 3. When
210
+ * {@link RouteScoreInputs.basis} is `'unpriced'`, `deliveredUsd` is a bare
211
+ * decimal rescale with no value behind it (see `EstimateBasis`'s own doc,
212
+ * `graph.ts`), and no arithmetic performed on it below would make it
213
+ * trustworthy. Returning `Number.NEGATIVE_INFINITY` rather than, say, `-1` or
214
+ * `0` is what makes this impossible to sidestep BY ACCIDENT: an ordinary `>`
215
+ * comparison, a plain numeric `.sort()`, or a `Math.max` over several plans'
216
+ * scores all already put an unpriced plan last WITHOUT the caller having to
217
+ * remember to check {@link RouteScoreInputs.basis} first — the same defence
218
+ * `compareRoutePlansByDeliveredAmount` (`graph.ts`) applies explicitly to a
219
+ * plan-to-plan comparison, applied here so a caller who reaches for
220
+ * `scoreRoutePlan` directly, for a single plan's score, gets the same
221
+ * protection for free.
222
+ *
223
+ * @param inputs - the plan's already-priced figures; see {@link RouteScoreInputs}
224
+ * @returns the plan's score — higher is better, and the units are United
225
+ * States dollars throughout; `Number.NEGATIVE_INFINITY` for an unpriced plan
226
+ */
227
+ // ---------------------------------------------------------------------------
228
+ // compareRoutePlansByScoreAdjustedDeliveredAmount — ranking that charges for
229
+ // an extra signature, without needing a United States dollar price
230
+ // ---------------------------------------------------------------------------
231
+ /** `SIGNATURE_PENALTY_PPM`'s denominator: parts per million, as a bigint. */
232
+ const PPM_DENOMINATOR = 1000000n;
233
+ /**
234
+ * `plan`'s last-hop `deliveredAmount`, discounted by {@link SIGNATURE_PENALTY_PPM}
235
+ * for every signature beyond the first — proportionally, in the delivered
236
+ * token's own base units, entirely in bigint arithmetic.
237
+ *
238
+ * WHY ONLY THE PROPORTIONAL SHARE, NEVER {@link SIGNATURE_PENALTY_FLOOR_USD}.
239
+ * The floor is denominated in United States dollars; converting a base-unit
240
+ * `deliveredAmount` into dollars needs a live price, which a `RoutePlan`
241
+ * (`graph.ts`) does not carry — it is a bigint amount in one token's own
242
+ * units, nothing more. `scoreRoutePlan` still applies the full formula,
243
+ * floor included, once a caller (`plan-route-data.ts`) has priced a plan into
244
+ * `RouteScoreInputs`. This function is the version usable at the STRUCTURAL
245
+ * ranking sites — `enumerate.ts`'s cut and `waves.ts`'s between-wave
246
+ * re-ranking — which only ever see the plan's own hops, not a price. See
247
+ * {@link compareRoutePlansByScoreAdjustedDeliveredAmount}'s own doc for why a
248
+ * proportional-only penalty is still enough to settle the case it exists for.
249
+ *
250
+ * @param plan - the plan to discount
251
+ * @returns the discounted delivered amount, floored at zero
252
+ */
253
+ const signaturePenaltyAdjustedDeliveredAmount = (plan) => {
254
+ const deliveredAmount = plan.hops[plan.hops.length - 1]?.outcome.deliveredAmount ?? 0n;
255
+ const extraActionCount = Math.max(0, plan.signatures.length - 1);
256
+ const penaltyPpm = BigInt(extraActionCount) * BigInt(SIGNATURE_PENALTY_PPM);
257
+ const retainedPpm = penaltyPpm >= PPM_DENOMINATOR ? 0n : PPM_DENOMINATOR - penaltyPpm;
258
+ return (deliveredAmount * retainedPpm) / PPM_DENOMINATOR;
259
+ };
260
+ /**
261
+ * Orders two RANKABLE plans (both pass `graph.ts`'s `planIsRankable`) by
262
+ * their {@link signaturePenaltyAdjustedDeliveredAmount} rather than their raw
263
+ * `deliveredAmount` — the STRUCTURAL counterpart to `graph.ts`'s
264
+ * {@link @gibs/bridge-sdk/routing/search/graph!compareRoutePlansByDeliveredAmount},
265
+ * reusing every one of that function's rules (rankable beats unrankable,
266
+ * `compareUnrankablePlans`'s tiebreak among two unrankable plans) and adding
267
+ * exactly one more: within two RANKABLE plans, a plan needing more
268
+ * signatures than another must clear {@link SIGNATURE_PENALTY_PPM} of
269
+ * "extra" delivered value per extra signature before it is allowed to win,
270
+ * not merely `> 0`.
271
+ *
272
+ * THE DEFECT THIS CLOSES. `SIGNATURE_PENALTY_PPM` and `GAS_TOPUP_PENALTY_PPM`
273
+ * existed in this module as constants `scoreRoutePlan` charges — but nothing
274
+ * in the actual ranking path called `scoreRoutePlan` at all:
275
+ * `enumerate.ts`'s cut, `waves.ts`'s between-wave re-ranking, and
276
+ * `useRouteSearch.ts`'s final sort all imported `graph.ts`'s plain
277
+ * delivered-amount comparator directly, so a route needing a second
278
+ * signature for a fractional gain (measured: 0.006% more, needing one more
279
+ * wallet prompt) always won outright. Sorting on
280
+ * {@link signaturePenaltyAdjustedDeliveredAmount} instead is what makes that
281
+ * lose to the one-signature alternative: the extra signature's proportional
282
+ * cost (0.25% per {@link SIGNATURE_PENALTY_PPM}) outweighs a 0.006% edge by
283
+ * roughly forty times.
284
+ *
285
+ * WHY NOT `GAS_TOPUP_PENALTY_PPM` HERE TOO. A gasless-intermediate-chain
286
+ * charge needs to know whether the connected account holds gas on a chain in
287
+ * the middle of the plan — a live wallet balance read `RoutePlan` has no way
288
+ * to name. Applying it here would require guessing, which this comparator
289
+ * refuses to do; `GAS_TOPUP_PENALTY_PPM` is still applied wherever a caller
290
+ * has that balance and reaches for `scoreRoutePlan` directly (`plan-route-data.ts`
291
+ * onward). Left as an OWNER DECISION whether a structural ranking site should
292
+ * ever get gas-balance context of its own.
293
+ *
294
+ * @param a - one plan being compared
295
+ * @param b - the other
296
+ * @returns negative when `a` ranks ahead of `b`, positive when `b` ranks
297
+ * ahead of `a`, zero when they tie
298
+ */
299
+ export const compareRoutePlansByScoreAdjustedDeliveredAmount = (a, b) => {
300
+ const rankableA = planIsRankable(a.hops);
301
+ const rankableB = planIsRankable(b.hops);
302
+ if (rankableA !== rankableB)
303
+ return rankableA ? -1 : 1;
304
+ if (!rankableA)
305
+ return compareRoutePlansByDeliveredAmount(a, b);
306
+ const adjustedA = signaturePenaltyAdjustedDeliveredAmount(a);
307
+ const adjustedB = signaturePenaltyAdjustedDeliveredAmount(b);
308
+ if (adjustedA === adjustedB)
309
+ return 0;
310
+ return adjustedA > adjustedB ? -1 : 1;
311
+ };
312
+ export const scoreRoutePlan = (inputs) => {
313
+ if (!isRankableBasis(inputs.basis)) {
314
+ return Number.NEGATIVE_INFINITY;
315
+ }
316
+ const totalWaitPenaltyUsd = inputs.legClaimedDurationsSeconds.reduce((total, claimedSeconds) => total + (inputs.deliveredUsd * waitPenaltyPpm(claimedSeconds)) / 1_000_000, 0);
317
+ const extraActionCount = Math.max(0, inputs.readerActionCount - 1);
318
+ const totalSignaturePenaltyUsd = extraActionCount * signaturePenaltyUsd(inputs.deliveredUsd);
319
+ const totalGasTopUpPenaltyUsd = inputs.gaslessIntermediateChainCount * gasTopUpPenaltyUsd(inputs.deliveredUsd);
320
+ return (inputs.deliveredUsd -
321
+ inputs.paidUsd -
322
+ inputs.outsideFeesUsd -
323
+ totalWaitPenaltyUsd -
324
+ totalSignaturePenaltyUsd -
325
+ totalGasTopUpPenaltyUsd);
326
+ };
327
+ /**
328
+ * A PLACEHOLDER {@link GasCostFor} that charges nothing — preserves the
329
+ * behaviour of ranking on raw delivered amount for a caller that has not yet
330
+ * wired a real gas-price and native-price source. NOT A PRODUCTION DEFAULT:
331
+ * unlike {@link WAIT_PENALTY_PPM_PER_MINUTE}'s two logged, live crossings, no
332
+ * per-chain gas figure here would be a measurement — it would be a guess
333
+ * dressed up as one, exactly what Rule 13 of this codebase's own working
334
+ * principles warns against. A host should always supply its own
335
+ * {@link GasCostFor} once it has gas-price and native-price feeds to build
336
+ * one from; this export exists only so `waves.ts`'s own default comparator
337
+ * has something total to call when nobody has.
338
+ */
339
+ export const zeroGasCostFor = () => ({ gasAmount: 0n, refuelAmount: 0n });
340
+ /**
341
+ * `plan`'s last-hop `deliveredAmount`, minus the REAL cost of every
342
+ * signature it needs — `gasCostFor` charged ONCE per signature group
343
+ * (`plan.signatures`, built by `groupHopsBySignature`), on the chain and
344
+ * carrier of that group's FIRST hop, which is the hop the reader actually
345
+ * signs (or deposits by hand) for that group. Floored at zero: a plan whose
346
+ * gas costs exceed its own delivered amount is worth nothing, never negative.
347
+ *
348
+ * @param plan - the plan to charge
349
+ * @param gasCostFor - supplies each signature's real cost; see {@link GasCostFor}
350
+ * @returns the gas-adjusted delivered amount, in the plan's destination token's base units
351
+ */
352
+ export const gasAdjustedDeliveredAmount = (plan, gasCostFor) => {
353
+ const deliveredAmount = plan.hops[plan.hops.length - 1]?.outcome.deliveredAmount ?? 0n;
354
+ const totalGasCost = plan.signatures.reduce((total, group) => {
355
+ const firstHopIndex = group[0];
356
+ const firstHop = firstHopIndex === undefined ? undefined : plan.hops[firstHopIndex];
357
+ if (firstHop === undefined)
358
+ return total;
359
+ const quote = gasCostFor(firstHop.edge.from.chainId, firstHop.edge.carrier);
360
+ return total + quote.gasAmount + quote.refuelAmount;
361
+ }, 0n);
362
+ const netAmount = deliveredAmount - totalGasCost;
363
+ return netAmount < 0n ? 0n : netAmount;
364
+ };
365
+ /**
366
+ * Orders two plans by {@link gasAdjustedDeliveredAmount} rather than raw
367
+ * `deliveredAmount` or {@link signaturePenaltyAdjustedDeliveredAmount}'s flat
368
+ * ppm guess — the structural ranking `waves.ts` uses once Round A/B have real
369
+ * gas data to charge, replacing {@link SIGNATURE_PENALTY_PPM}/
370
+ * {@link GAS_TOPUP_PENALTY_PPM} at that one site per the owner's own
371
+ * instruction (see both constants' updated docs). A CURRIED FACTORY, not a
372
+ * plain comparator, because the real cost this needs — {@link GasCostFor} —
373
+ * is caller-supplied and cannot be a module-level default the way
374
+ * {@link compareRoutePlansByScoreAdjustedDeliveredAmount} is; call it once
375
+ * per search with the caller's own `gasCostFor`, then pass the returned
376
+ * comparator wherever a plan-to-plan comparison is needed.
377
+ *
378
+ * KEEPS THE SAME RANKABLE-BEFORE-UNRANKABLE RULE `compareRoutePlansByScoreAdjustedDeliveredAmount`
379
+ * ALREADY APPLIES, unchanged, including its `compareRoutePlansByDeliveredAmount`
380
+ * tiebreak among two unrankable plans — only the tiebreak among two RANKABLE
381
+ * plans changes, from a proportional signature-count discount to the route's
382
+ * own real charges.
383
+ *
384
+ * @param gasCostFor - supplies each signature's real cost; see {@link GasCostFor}
385
+ * @returns a comparator: negative when `a` ranks ahead of `b`, positive when
386
+ * `b` ranks ahead of `a`, zero when they tie
387
+ */
388
+ export const compareRoutePlansByGasAdjustedDeliveredAmount = (gasCostFor) => (a, b) => {
389
+ const rankableA = planIsRankable(a.hops);
390
+ const rankableB = planIsRankable(b.hops);
391
+ if (rankableA !== rankableB)
392
+ return rankableA ? -1 : 1;
393
+ if (!rankableA)
394
+ return compareRoutePlansByDeliveredAmount(a, b);
395
+ const adjustedA = gasAdjustedDeliveredAmount(a, gasCostFor);
396
+ const adjustedB = gasAdjustedDeliveredAmount(b, gasCostFor);
397
+ if (adjustedA === adjustedB)
398
+ return 0;
399
+ return adjustedA > adjustedB ? -1 : 1;
400
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The United States dollar value of a base-unit token amount at a given price, as
3
+ * a NUMBER — the numeric core of the user interface's `formatFiatValue` without the
4
+ * currency string. Returns null when the token is unpriced (`price` is null) or the
5
+ * computed value is not finite, mirroring the "return null for invalid state"
6
+ * convention so callers can fall back.
7
+ *
8
+ * @param params.balance - the amount in base units
9
+ * @param params.decimals - the token's decimals
10
+ * @param params.price - the token's United States dollar price, or null when unpriced
11
+ * @returns the dollar value as a number, or null
12
+ */
13
+ export declare const usdValueOfAmount: (params: {
14
+ balance: bigint;
15
+ decimals: number;
16
+ price: number | null;
17
+ }) => number | null;
@@ -0,0 +1,25 @@
1
+ import { formatUnits } from 'viem';
2
+ /**
3
+ * The United States dollar value of a base-unit token amount at a given price, as
4
+ * a NUMBER — the numeric core of the user interface's `formatFiatValue` without the
5
+ * currency string. Returns null when the token is unpriced (`price` is null) or the
6
+ * computed value is not finite, mirroring the "return null for invalid state"
7
+ * convention so callers can fall back.
8
+ *
9
+ * @param params.balance - the amount in base units
10
+ * @param params.decimals - the token's decimals
11
+ * @param params.price - the token's United States dollar price, or null when unpriced
12
+ * @returns the dollar value as a number, or null
13
+ */
14
+ export const usdValueOfAmount = (params) => {
15
+ const { balance, decimals, price } = params;
16
+ if (price === null)
17
+ return null;
18
+ const whole = Number(formatUnits(balance, decimals));
19
+ if (!Number.isFinite(whole))
20
+ return null;
21
+ const value = whole * price;
22
+ if (!Number.isFinite(value))
23
+ return null;
24
+ return value;
25
+ };