@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.
- package/LICENSE +15 -0
- package/README.md +234 -432
- package/dist/abis.d.ts +1 -0
- package/dist/bridge-limits.d.ts +103 -0
- package/dist/bridge-limits.js +84 -0
- package/dist/chain-info.d.ts +169 -0
- package/dist/chain-info.js +410 -0
- package/dist/chain-names.d.ts +160 -0
- package/dist/chain-names.js +200 -0
- package/dist/chains.d.ts +65 -0
- package/dist/chains.js +284 -0
- package/dist/config.d.ts +216 -0
- package/dist/config.js +493 -0
- package/dist/consolidated-quote.d.ts +554 -0
- package/dist/consolidated-quote.js +773 -0
- package/dist/destination-calldata.d.ts +118 -0
- package/dist/destination-calldata.js +152 -0
- package/dist/ecosystems.d.ts +217 -0
- package/dist/ecosystems.js +288 -0
- package/dist/fee-type.d.ts +10 -0
- package/dist/fee-type.js +9 -0
- package/dist/hyperliquid-execution.d.ts +152 -0
- package/dist/hyperliquid-execution.js +168 -0
- package/dist/image-links.d.ts +117 -0
- package/dist/image-links.js +142 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/lifi-execution-guard.d.ts +199 -0
- package/dist/lifi-execution-guard.js +400 -0
- package/dist/near-intents-conformance.d.ts +85 -0
- package/dist/near-intents-conformance.js +329 -0
- package/dist/near-intents-route.d.ts +47 -0
- package/dist/near-intents-route.js +265 -0
- package/dist/near-intents.d.ts +524 -0
- package/dist/near-intents.js +1055 -0
- package/dist/origin-deposit.d.ts +326 -0
- package/dist/origin-deposit.js +68 -0
- package/dist/pre-quote.d.ts +198 -0
- package/dist/pre-quote.js +175 -0
- package/dist/prices.d.ts +107 -0
- package/dist/prices.js +162 -0
- package/dist/providers.d.ts +221 -0
- package/dist/providers.js +157 -0
- package/dist/quote-fees.d.ts +102 -0
- package/dist/quote-fees.js +195 -0
- package/dist/referral.d.ts +77 -0
- package/dist/referral.js +72 -0
- package/dist/relay-currencies.d.ts +47 -0
- package/dist/relay-currencies.js +89 -0
- package/dist/relay-quote.d.ts +301 -0
- package/dist/relay-quote.js +983 -0
- package/dist/route-steps.d.ts +138 -0
- package/dist/route-steps.js +305 -0
- package/dist/routing/amounts.d.ts +80 -0
- package/dist/routing/amounts.js +120 -0
- package/dist/routing/candidate-route.d.ts +43 -0
- package/dist/routing/candidate-route.js +45 -0
- package/dist/routing/candidate.d.ts +237 -0
- package/dist/routing/candidate.js +96 -0
- package/dist/routing/carriers/ask-answer.d.ts +58 -0
- package/dist/routing/carriers/ask-answer.js +1 -0
- package/dist/routing/carriers/omnibridge.d.ts +137 -0
- package/dist/routing/carriers/omnibridge.js +419 -0
- package/dist/routing/carriers/pulsex.d.ts +111 -0
- package/dist/routing/carriers/pulsex.js +404 -0
- package/dist/routing/carriers/wrap.d.ts +25 -0
- package/dist/routing/carriers/wrap.js +115 -0
- package/dist/routing/composed-route-quality.d.ts +103 -0
- package/dist/routing/composed-route-quality.js +108 -0
- package/dist/routing/entry-chain-anchor.d.ts +88 -0
- package/dist/routing/entry-chain-anchor.js +55 -0
- package/dist/routing/format.d.ts +31 -0
- package/dist/routing/format.js +58 -0
- package/dist/routing/index.d.ts +34 -0
- package/dist/routing/index.js +42 -0
- package/dist/routing/producers.d.ts +770 -0
- package/dist/routing/producers.js +1564 -0
- package/dist/routing/providers.d.ts +26 -0
- package/dist/routing/providers.js +38 -0
- package/dist/routing/route-data.d.ts +355 -0
- package/dist/routing/route-data.js +1 -0
- package/dist/routing/search/compose.d.ts +168 -0
- package/dist/routing/search/compose.js +98 -0
- package/dist/routing/search/graph.d.ts +639 -0
- package/dist/routing/search/graph.js +445 -0
- package/dist/routing/search/plan-route-data.d.ts +244 -0
- package/dist/routing/search/plan-route-data.js +767 -0
- package/dist/routing/search/score.d.ts +324 -0
- package/dist/routing/search/score.js +400 -0
- package/dist/routing/usd-value.d.ts +17 -0
- package/dist/routing/usd-value.js +25 -0
- package/dist/settings.d.ts +692 -0
- package/dist/settings.js +909 -0
- package/dist/ton-execution.d.ts +117 -0
- package/dist/ton-execution.js +247 -0
- package/dist/tron-execution.d.ts +146 -0
- package/dist/tron-execution.js +239 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +1 -0
- package/package.json +203 -23
- package/src/chain-info.test.ts +0 -968
- package/src/chain-info.ts +0 -498
- package/src/chains.test.ts +0 -123
- package/src/chains.ts +0 -88
- package/src/config.test.ts +0 -679
- package/src/config.ts +0 -500
- package/src/fee-type.test.ts +0 -45
- package/src/fee-type.ts +0 -14
- package/src/image-links.test.ts +0 -160
- package/src/image-links.ts +0 -65
- package/src/index.ts +0 -8
- package/src/settings.test.ts +0 -2754
- package/src/settings.ts +0 -875
- package/src/types.ts +0 -48
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -13
- /package/{src/abis.ts → dist/abis.js} +0 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import { type Ecosystem } from '../../ecosystems.js';
|
|
2
|
+
import { type ProviderId } from '../../providers.js';
|
|
3
|
+
import type { EstimateBasis } from '../route-data.js';
|
|
4
|
+
/**
|
|
5
|
+
* PHASE 0 — FOUNDATION ONLY. This module fixes the pure data model for the
|
|
6
|
+
* general route search described in the route-search design (section 1). It
|
|
7
|
+
* introduces no search, no quoting, and no behaviour change: nothing outside
|
|
8
|
+
* this file constructs one of these types yet. Phase 1 (`capabilities.ts`,
|
|
9
|
+
* `enumerate.ts`) is what starts building {@link AssetNode}s, {@link HopEdge}s
|
|
10
|
+
* and {@link RoutePlan}s from live provider data.
|
|
11
|
+
*
|
|
12
|
+
* WHY A GRAPH AT ALL. Today the bridge special-cases each shape a route can
|
|
13
|
+
* take: a same-chain crossing, an aggregator fused with the bridge, a two-step
|
|
14
|
+
* NEAR Intents route, a conversion swap. Modelling every route as a walk over
|
|
15
|
+
* a small graph of {@link AssetNode}s joined by {@link HopEdge}s replaces the
|
|
16
|
+
* special cases with one search over one structure — see the design's
|
|
17
|
+
* section 6, "what it absorbs."
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* One stop in the route graph: a specific token, on a specific chain, in a
|
|
21
|
+
* specific ecosystem. Nodes are the graph's vertices; {@link HopEdge}s are its
|
|
22
|
+
* directed edges.
|
|
23
|
+
*
|
|
24
|
+
* KEYED AS `chainId:address`, not by object identity, so two independently
|
|
25
|
+
* built references to "USD Coin on Ethereum" collapse to the same graph
|
|
26
|
+
* vertex. Use {@link assetNodeKey} to compute that key — never format the
|
|
27
|
+
* template by hand, because the key normalizes address casing per ecosystem
|
|
28
|
+
* (see {@link assetNodeKey}) and a hand-formatted string would silently skip
|
|
29
|
+
* that normalization.
|
|
30
|
+
*
|
|
31
|
+
* WHY A COIN AND ITS WRAPPED FORM ARE NOT MERGED HERE. `AssetNode` names one
|
|
32
|
+
* literal address; native ether (the zero address) and wrapped ether (WETH)
|
|
33
|
+
* are and stay two distinct nodes. The two are equivalent ONLY in one
|
|
34
|
+
* specific circumstance — the next hop is an omnibridge entry call — because
|
|
35
|
+
* the router wraps the coin on the way in and mints the SAME PulseChain token
|
|
36
|
+
* either anchor would have minted (`wrapAndRelayTokens`; see
|
|
37
|
+
* `entryChainAnchorsForPair` in `../entry-chain-anchor.js` and its module doc
|
|
38
|
+
* for the measurements behind this). That equivalence is a property of ONE
|
|
39
|
+
* EDGE (an entry call accepting either anchor), not of the node graph as a
|
|
40
|
+
* whole — a USD Coin pair gains no such alias, and neither does a native
|
|
41
|
+
* coin whose next hop is NOT a bridge entry call. So the aliasing is Phase
|
|
42
|
+
* 1's job, applied when `enumerate.ts` builds edges INTO a bridge entry hop,
|
|
43
|
+
* by trying both anchors from `entryChainAnchorsForPair` rather than by
|
|
44
|
+
* collapsing nodes here. Collapsing it into the node graph itself would make
|
|
45
|
+
* every consumer of `AssetNode` reason about an aliasing rule that applies to
|
|
46
|
+
* only one kind of edge.
|
|
47
|
+
*/
|
|
48
|
+
export type AssetNode = {
|
|
49
|
+
/** Decimal chain id this asset lives on. */
|
|
50
|
+
readonly chainId: number;
|
|
51
|
+
/**
|
|
52
|
+
* The token's on-chain address, in the ecosystem's own notation — hex for
|
|
53
|
+
* an EVM chain, base58 for Solana or Tron, a user-friendly string for The
|
|
54
|
+
* Open Network, and so on. Treat it as opaque; only {@link assetNodeKey}
|
|
55
|
+
* and code that already knows the ecosystem should normalize it.
|
|
56
|
+
*/
|
|
57
|
+
readonly address: string;
|
|
58
|
+
/** Which ecosystem `chainId` belongs to — governs address casing and carrier support. */
|
|
59
|
+
readonly ecosystem: Ecosystem;
|
|
60
|
+
/**
|
|
61
|
+
* The number of decimal places this token's base units carry. A fixed fact
|
|
62
|
+
* about the token, kept on the node so any hop that touches it — most
|
|
63
|
+
* directly `search/compose.ts`, composing a delivered amount across hops —
|
|
64
|
+
* can check two mentions of the SAME node against each other without a
|
|
65
|
+
* second lookup.
|
|
66
|
+
*
|
|
67
|
+
* ADDED IN PHASE 1, FOR `compose.ts`. Composing amounts hop to hop must
|
|
68
|
+
* refuse rather than guess when two hops disagree about a shared token's
|
|
69
|
+
* decimals — two independent provider integrations naming the same token
|
|
70
|
+
* with two different decimal counts is exactly the kind of silent data bug
|
|
71
|
+
* `compose.ts` is required to catch instead of composing through. Carrying
|
|
72
|
+
* `decimals` on the node is what makes that check possible without adding
|
|
73
|
+
* a second, decimals-only lookup structure that could itself drift from
|
|
74
|
+
* this one. Phase 0 shipped {@link AssetNode} without this field because
|
|
75
|
+
* nothing yet constructed one; this is the first module that does.
|
|
76
|
+
*/
|
|
77
|
+
readonly decimals: number;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* The stable, collision-free key for one {@link AssetNode}.
|
|
81
|
+
*
|
|
82
|
+
* Normalizes address casing through the ecosystem registry
|
|
83
|
+
* (`ecosystemById(...).addressCasing`) before keying, so a checksummed and a
|
|
84
|
+
* lower-cased reference to the SAME Ethereum address produce the SAME key —
|
|
85
|
+
* two representations of one asset must key identically, or the search would
|
|
86
|
+
* treat one node as two and silently duplicate work. An ecosystem whose
|
|
87
|
+
* addresses are case-SIGNIFICANT (Solana, Tron, The Open Network, Bitcoin,
|
|
88
|
+
* Zcash — see `ecosystems.ts`) is left exactly as given, because lower-casing
|
|
89
|
+
* a case-significant address can turn it into a different, unrelated one.
|
|
90
|
+
*
|
|
91
|
+
* Collision-free for any two distinct `(chainId, normalizedAddress)` pairs:
|
|
92
|
+
* `chainId` renders as a plain decimal numeral (never containing `:`), so the
|
|
93
|
+
* FIRST `:` in the key always marks the same boundary a caller would expect,
|
|
94
|
+
* and no two distinct pairs can render the same string.
|
|
95
|
+
*
|
|
96
|
+
* @param node - the asset to key
|
|
97
|
+
* @returns the node's graph key, e.g. `"1:0xa0b8...eb48"`
|
|
98
|
+
*/
|
|
99
|
+
export declare const assetNodeKey: (node: AssetNode) => string;
|
|
100
|
+
/**
|
|
101
|
+
* Identifies one carrier that can move funds across a {@link HopEdge}: a
|
|
102
|
+
* plain string, NOT a closed union.
|
|
103
|
+
*
|
|
104
|
+
* FORMERLY `ProviderKey` (`ProviderId | Provider | 'PulseX'`) — a union that
|
|
105
|
+
* baked gibs's own carrier names (`'pulsechain'`, `'tokensex'`, `'PulseX'`)
|
|
106
|
+
* directly into the search engine's data model. The engine (this module,
|
|
107
|
+
* `capabilities.ts`, `enumerate.ts`, `waves.ts` — the latter three now living
|
|
108
|
+
* in `@gibs/quotes`'s `search/` directory) is a plug-in host: it takes
|
|
109
|
+
* whatever carriers it is registered with and names none of them itself. A
|
|
110
|
+
* host names its own carriers by registering objects whose `id` is one of
|
|
111
|
+
* these strings — gibs's own `'pulsechain'`, `'tokensex'` and `'PulseX'`
|
|
112
|
+
* among them, from `routing/carriers/` — and every existing lookup table
|
|
113
|
+
* keyed on a carrier's identity (a logo, a display name) stays keyed on the
|
|
114
|
+
* SAME strings as before; only the TYPE that names them widened from a
|
|
115
|
+
* closed union to `string`.
|
|
116
|
+
*/
|
|
117
|
+
export type CarrierId = string;
|
|
118
|
+
/**
|
|
119
|
+
* @deprecated Use {@link CarrierId}. Kept as an alias so existing imports of
|
|
120
|
+
* this name keep compiling; it is no longer the closed `ProviderKey` union.
|
|
121
|
+
*/
|
|
122
|
+
export type HopCarrier = CarrierId;
|
|
123
|
+
/**
|
|
124
|
+
* Whether `carrier` is one of the three rate-limited aggregators
|
|
125
|
+
* (`ProviderId`) rather than a FREE local read (the omnibridge crossing or a
|
|
126
|
+
* PulseX swap). Free carriers are asked on EVERY wave without counting
|
|
127
|
+
* against `waves.ts`'s own request budget or per-provider concurrency cap —
|
|
128
|
+
* design section 3: "Bridge and PulseX hops are free local reads ... done
|
|
129
|
+
* exactly at any amount."
|
|
130
|
+
*
|
|
131
|
+
* CANONICAL HOME FOR THIS CHECK — PHASE 3. Moved here from `waves.ts` (which
|
|
132
|
+
* re-exports it) because {@link compareRoutePlansByDeliveredAmount}'s own
|
|
133
|
+
* tiebreak among unpriced plans needs it too (see that function's doc): a
|
|
134
|
+
* free-carrier first hop is guaranteed a real answer, a rate-limited one is
|
|
135
|
+
* not, and that distinction belongs on `HopCarrier` itself, not inside the
|
|
136
|
+
* one module that happened to need it first.
|
|
137
|
+
*
|
|
138
|
+
* @param carrier - the carrier to classify
|
|
139
|
+
* @returns true for `'lifi'`, `'relay'` or `'near-intents'`
|
|
140
|
+
*/
|
|
141
|
+
export declare const isRateLimitedCarrier: (carrier: HopCarrier) => carrier is ProviderId;
|
|
142
|
+
/**
|
|
143
|
+
* How a hop's crossing is initiated.
|
|
144
|
+
*
|
|
145
|
+
* `'wallet'` — the reader (or a prior hop's aggregator, on arrival) signs a
|
|
146
|
+
* transaction. `'by-hand'` — the reader sends funds to a deposit address
|
|
147
|
+
* outside any wallet flow this application controls (a Zcash shielded send,
|
|
148
|
+
* for example), which is how a NEAR Intents pay-by-hand origin funds its
|
|
149
|
+
* first hop. See design section 2: "a pay-by-hand origin's first hop must be
|
|
150
|
+
* NEAR."
|
|
151
|
+
*/
|
|
152
|
+
export type HopEdgeOrigin = 'wallet' | 'by-hand';
|
|
153
|
+
/**
|
|
154
|
+
* A known minimum amount this edge has already been refused below.
|
|
155
|
+
*
|
|
156
|
+
* EMPTY ON EVERY EDGE PHASE 0 OR PHASE 1's `enumerate.ts` PRODUCES. Floors
|
|
157
|
+
* are learned from a provider's own refusal — NEAR's `belowChainMinimum`, the
|
|
158
|
+
* bridge's `minPerTx`, a Relay or LI.FI minimum from `quote-refusals.ts` —
|
|
159
|
+
* which only happens once a real request has been made. Phase 1's
|
|
160
|
+
* `waves.ts` is what populates this, feeding a floor learned on one search
|
|
161
|
+
* into the pruning step of the next (design section 2: "a known floor must
|
|
162
|
+
* be at or below the amount"; section 3: "refusals feed a floors cache that
|
|
163
|
+
* prunes later searches"). The type is fixed here because `HopEdge` needs a
|
|
164
|
+
* concrete shape for the field, not because anything writes to it yet.
|
|
165
|
+
*/
|
|
166
|
+
export type HopFloor = {
|
|
167
|
+
/** Why the floor exists, e.g. `"near-chain-minimum"`, `"bridge-min-per-tx"`. */
|
|
168
|
+
readonly reason: string;
|
|
169
|
+
/** The minimum amount this edge accepts, in the edge's `from` node's base units. */
|
|
170
|
+
readonly minimumAmount: bigint;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* One directed edge in the route graph: a single carrier moving funds from
|
|
174
|
+
* one {@link AssetNode} to another.
|
|
175
|
+
*/
|
|
176
|
+
export type HopEdge = {
|
|
177
|
+
/** Which carrier performs this hop. */
|
|
178
|
+
readonly carrier: HopCarrier;
|
|
179
|
+
/** The asset this hop consumes. */
|
|
180
|
+
readonly from: AssetNode;
|
|
181
|
+
/** The asset this hop produces. */
|
|
182
|
+
readonly to: AssetNode;
|
|
183
|
+
/**
|
|
184
|
+
* Whether this hop's carrier performs the NEXT hop's entry call itself on
|
|
185
|
+
* arrival, so the two hops together cost the reader only ONE signature.
|
|
186
|
+
*
|
|
187
|
+
* True only when BOTH hold: this carrier's `executesDestinationCalls` is
|
|
188
|
+
* true (see `../../providers.js`), and the next hop is a bridge entry call
|
|
189
|
+
* — `relayTokens` or `wrapAndRelayTokens` — on the chain this hop lands on.
|
|
190
|
+
* Today that means an LI.FI or Relay leg immediately followed by the
|
|
191
|
+
* omnibridge crossing; every other adjacent pair costs a second signature.
|
|
192
|
+
*
|
|
193
|
+
* NEAR IS ALWAYS FALSE. Its `executesDestinationCalls` is false — it
|
|
194
|
+
* delivers to the reader's own wallet and stops (see the head of
|
|
195
|
+
* `../../providers.js`, the `nearIntentsDescriptor` entry) — so a NEAR hop
|
|
196
|
+
* can never satisfy the first condition, whatever follows it. This is WHY
|
|
197
|
+
* every NEAR Intents route today takes two signatures, and it is also why
|
|
198
|
+
* {@link groupHopsBySignature} needs no special case for NEAR: a hop whose
|
|
199
|
+
* `fusesNext` is always false always ends its own signature group on its
|
|
200
|
+
* own, by the same rule that governs every other hop.
|
|
201
|
+
*
|
|
202
|
+
* LEAVING PULSECHAIN IS ALSO ALWAYS FALSE. The omnibridge's outbound leg
|
|
203
|
+
* delivers the anchor token to the reader's own wallet on the entry chain
|
|
204
|
+
* and stops — it has no executor of its own that could carry an aggregator
|
|
205
|
+
* call forward — so a hop that leaves PulseChain can never fuse with
|
|
206
|
+
* whatever aggregator leg follows it either. This is the existing
|
|
207
|
+
* "outbound with onward hop" case (design section 6): the bridge-out leg
|
|
208
|
+
* and the aggregator that continues from it are always two signatures.
|
|
209
|
+
*/
|
|
210
|
+
readonly fusesNext: boolean;
|
|
211
|
+
/** How this hop's crossing is initiated. */
|
|
212
|
+
readonly origin: HopEdgeOrigin;
|
|
213
|
+
/** Minimum amounts already learned for this edge; empty until a search records a refusal. */
|
|
214
|
+
readonly floors: readonly HopFloor[];
|
|
215
|
+
/**
|
|
216
|
+
* Which of the carrier's own execution venues performs this hop, when the
|
|
217
|
+
* carrier has more than one between the same two assets — opaque to the
|
|
218
|
+
* graph. Two edges that differ only here are two different hops: each is
|
|
219
|
+
* quoted, ranked and signed on its own venue, never on the other's.
|
|
220
|
+
*
|
|
221
|
+
* ABSENT for a carrier with one venue. The carrier that sets it is the one
|
|
222
|
+
* that reads it back (for example, which router signs a swap), so the
|
|
223
|
+
* route search never interprets the value.
|
|
224
|
+
*/
|
|
225
|
+
readonly venue?: string;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* The provider-specific request and outcome for one planned hop.
|
|
229
|
+
*
|
|
230
|
+
* `request` is deliberately `unknown` in Phase 0: each carrier's request is a
|
|
231
|
+
* different shape (`ConsolidatedQuoteRequest` for LI.FI/Relay, NEAR's own
|
|
232
|
+
* pre-quote/quote request, a bare fee-rate read for the bridge, a
|
|
233
|
+
* `getAmountsOut` call for PulseX), and Phase 0 introduces no coupling to any
|
|
234
|
+
* one of them. Phase 1's `waves.ts` is what gives this field a real,
|
|
235
|
+
* carrier-discriminated type once the quoting waves exist to fill it.
|
|
236
|
+
*/
|
|
237
|
+
export type HopRequest = unknown;
|
|
238
|
+
/**
|
|
239
|
+
* PHASE 3 — what GROUNDS an `'estimate'` {@link HopOutcome} (or a composed
|
|
240
|
+
* path figure, see `compose.ts`'s {@link ComposedPathBasis}): `'probe-rate'`
|
|
241
|
+
* (a real provider answered this edge, at some reference amount),
|
|
242
|
+
* `'priced-conversion'` (both endpoints carry a real United States dollar
|
|
243
|
+
* price and the figure converts value through them), or `'unpriced'` (a bare
|
|
244
|
+
* decimal rescale with no value behind it, `restateInDecimals`, `../amounts.js`).
|
|
245
|
+
*
|
|
246
|
+
* DECLARED IN `../route-data.ts`, IMPORTED HERE, NOT REDECLARED — that module
|
|
247
|
+
* is this search engine's lower, display-facing data layer (`RouteData`,
|
|
248
|
+
* `RouteLandingData.estimateBasis`, `RouteOutputData.estimateBasis`), so the
|
|
249
|
+
* union belongs where ITS consumers can read it without depending upward on
|
|
250
|
+
* `search/`; see that module's own doc for the full rule, including why
|
|
251
|
+
* `'unpriced'` never reaches a `RouteLandingData`/`RouteOutputData` field.
|
|
252
|
+
* Re-exported from here because this whole module (and `compose.ts`,
|
|
253
|
+
* `enumerate.ts`, `waves.ts`) already names it relative to `./graph.js` —
|
|
254
|
+
* one declaration, reachable both ways.
|
|
255
|
+
*
|
|
256
|
+
* THIS IS THE TYPE THE ROUTE-SEARCH DESIGN'S OWN RISK LIST WAS MISSING: a
|
|
257
|
+
* decimal-rescaled bigint and a genuinely priced one are indistinguishable BY
|
|
258
|
+
* SHAPE — both are a positive bigint — so nothing downstream could tell one
|
|
259
|
+
* from the other without this field. Shadow mode against the running search
|
|
260
|
+
* found exactly that gap: on three of five test routes the engine's
|
|
261
|
+
* top-ranked path delivered about 276 PLS where today's routing delivers
|
|
262
|
+
* about 23 million PLS, because a hop with no price-feed entry fell back to
|
|
263
|
+
* a bare decimal rescale and nothing marked that number as untrustworthy
|
|
264
|
+
* before it reached the ranking — see {@link RANKABLE_ESTIMATE_BASES}.
|
|
265
|
+
*/
|
|
266
|
+
export type { EstimateBasis };
|
|
267
|
+
/**
|
|
268
|
+
* The bases a ranking may compare a hop's `deliveredAmount` on — every
|
|
269
|
+
* {@link EstimateBasis} except `'unpriced'`. `'unpriced'` is EXCLUDED ON
|
|
270
|
+
* PURPOSE: it names a figure with no value behind it (see that member's own
|
|
271
|
+
* doc), and a ranking that compared on it would be comparing one path's real
|
|
272
|
+
* number against another path's decimal-shifted guess — exactly the defect
|
|
273
|
+
* shadow mode found. A caller checking whether a hop or a whole plan may
|
|
274
|
+
* enter a ranking reads this set (or calls {@link planIsRankable}), never a
|
|
275
|
+
* hand-rolled `!== 'unpriced'` check, so the one rule lives in one place.
|
|
276
|
+
*
|
|
277
|
+
* THIS IS A SEARCH-ORDER RULE, NOT A REPORTING RULE — PHASE 3, SECOND ROUND.
|
|
278
|
+
* `'priced-conversion'` stays in this set on purpose: `enumerate.ts`'s own
|
|
279
|
+
* cut and `waves.ts`'s between-wave top-candidate selection both need SOME
|
|
280
|
+
* way to compare two not-yet-quoted plans, and a price-feed conversion is a
|
|
281
|
+
* far better signal for THAT purpose than no signal at all. It is a
|
|
282
|
+
* DIFFERENT question whether a plan may be handed back to a reader as the
|
|
283
|
+
* winner while still carrying `'priced-conversion'` on any hop — it may not;
|
|
284
|
+
* see {@link isConfirmedBasis} and {@link planIsConfirmed}, the stricter pair
|
|
285
|
+
* `waves.ts`'s own final return step applies, for that rule and the live
|
|
286
|
+
* shadow-mode finding it closes (1 Zcash reported as 131.6 million Wrapped
|
|
287
|
+
* Pulse via `'priced-conversion'`, against a real 33.3 million from the
|
|
288
|
+
* pool's own `getAmountsOut` — a thin-pool price-impact gap `'priced-conversion'`
|
|
289
|
+
* cannot see, because it never asks the pool at all).
|
|
290
|
+
*/
|
|
291
|
+
export declare const RANKABLE_ESTIMATE_BASES: ReadonlySet<EstimateBasis>;
|
|
292
|
+
/**
|
|
293
|
+
* What a planned hop is expected to deliver.
|
|
294
|
+
*
|
|
295
|
+
* `'quote'` — a live, provider-confirmed answer (signature 1 must always be
|
|
296
|
+
* one of these; see design section 3, "only signature 1 must be binding").
|
|
297
|
+
* `'estimate'` — a probed or composed figure for a hop that has not been
|
|
298
|
+
* bindingly quoted yet (every later hop, until the reader returns to it),
|
|
299
|
+
* carrying the {@link EstimateBasis} that grounds it — see that type's own
|
|
300
|
+
* doc for why this field exists and what breaks without it.
|
|
301
|
+
*/
|
|
302
|
+
export type HopOutcome = {
|
|
303
|
+
readonly kind: 'quote';
|
|
304
|
+
/** What the carrier is bound to deliver, in the hop's `to` node's base units. */
|
|
305
|
+
readonly deliveredAmount: bigint;
|
|
306
|
+
/**
|
|
307
|
+
* The least the carrier promises to deliver after its own slippage
|
|
308
|
+
* tolerance, in the same units — present only when a provider's live
|
|
309
|
+
* answer stated one.
|
|
310
|
+
*
|
|
311
|
+
* WHY A SIGNER NEEDS IT. A provider hop that FUSES with the omnibridge
|
|
312
|
+
* entry (`HopEdge.fusesNext`) is signed as an exact-output request, and
|
|
313
|
+
* the entry call it performs embeds a fixed amount. That amount is this
|
|
314
|
+
* minimum — the owner's two-phase rule: the pre-quote's floor becomes
|
|
315
|
+
* the target, and the unspent remainder is the reader's cushion. So the
|
|
316
|
+
* hop that follows a fused one is composed from this figure, never from
|
|
317
|
+
* {@link deliveredAmount}, and the section's signer reads it back to
|
|
318
|
+
* build exactly the request the route was priced for.
|
|
319
|
+
*/
|
|
320
|
+
readonly minimumDeliveredAmount?: bigint;
|
|
321
|
+
} | {
|
|
322
|
+
readonly kind: 'estimate';
|
|
323
|
+
/** The composed/probed figure, in the hop's `to` node's base units. */
|
|
324
|
+
readonly deliveredAmount: bigint;
|
|
325
|
+
/** What grounds this figure — see {@link EstimateBasis}. */
|
|
326
|
+
readonly basis: EstimateBasis;
|
|
327
|
+
/**
|
|
328
|
+
* Non-null when a REAL ask for this hop came back refused rather than
|
|
329
|
+
* simply never being asked (or still in flight) — `waves.ts`'s
|
|
330
|
+
* `recomposePath` is the one place that sets it, from the cached
|
|
331
|
+
* {@link @gibs/bridge-sdk/routing/search/waves!EdgeRefusal}'s own
|
|
332
|
+
* `reason` (or its `kind` when the provider gave no reason). `null` or
|
|
333
|
+
* `undefined` means "not refused": never asked, still pending, or
|
|
334
|
+
* genuinely answered.
|
|
335
|
+
*
|
|
336
|
+
* WHY THIS EXISTS, SEPARATELY FROM `basis`. `basis` says what GROUNDS
|
|
337
|
+
* `deliveredAmount` — where the number came from. It says nothing about
|
|
338
|
+
* whether a real provider has SINCE said this edge cannot carry the
|
|
339
|
+
* plan's amount at all. A hop can carry a perfectly good
|
|
340
|
+
* `'priced-conversion'` basis (a real United States dollar price on
|
|
341
|
+
* both ends) and still be refused — a shadow comparison against the
|
|
342
|
+
* live search found exactly this: a refused hop's price-feed estimate
|
|
343
|
+
* has no fees behind it and kept outranking real quotes, because
|
|
344
|
+
* nothing marked it untrustworthy once the refusal came back. See
|
|
345
|
+
* {@link planIsRankable}, which is where a non-null reason here takes
|
|
346
|
+
* effect.
|
|
347
|
+
*
|
|
348
|
+
* KEPT FOR DISPLAY, NOT HIDDEN LIKE `'unpriced'`. Reusing `'unpriced'`
|
|
349
|
+
* for a refused hop would also blank its `deliveredAmount` from every
|
|
350
|
+
* display that checks `basis === 'unpriced'` (`plan-route-data.ts`'s
|
|
351
|
+
* `hopOutcomeIsUnpriced`) — losing a real, price-feed-grounded number
|
|
352
|
+
* the reader can still usefully see, just not one bridge-sdk may rank
|
|
353
|
+
* a plan on any more.
|
|
354
|
+
*/
|
|
355
|
+
readonly refusalReason?: string | null;
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* Whether every hop of `hops` carries a figure a ranking may trust: a live
|
|
359
|
+
* quote, or an estimate whose {@link EstimateBasis} is in
|
|
360
|
+
* {@link RANKABLE_ESTIMATE_BASES} AND was never refused
|
|
361
|
+
* ({@link HopOutcome.refusalReason} is `null`/`undefined`). False the moment
|
|
362
|
+
* ANY hop is `'unpriced'` — one ungrounded hop taints the whole path's
|
|
363
|
+
* composed figure, because composition multiplies every hop's figure into
|
|
364
|
+
* the next (`compose.ts`), so a single meaningless number anywhere in the
|
|
365
|
+
* chain makes the FINAL delivered amount meaningless too, however real the
|
|
366
|
+
* other hops were.
|
|
367
|
+
*
|
|
368
|
+
* A REFUSED HOP IS UNRANKABLE EVEN WITH A GOOD BASIS. A hop that carries
|
|
369
|
+
* `'priced-conversion'` or `'probe-rate'` still fails this check once a real
|
|
370
|
+
* ask for it came back declined — see {@link HopOutcome.refusalReason}'s own
|
|
371
|
+
* doc for the shadow-mode finding this closes: a refused hop's leftover
|
|
372
|
+
* price-feed estimate has no fees behind it and kept beating real quotes,
|
|
373
|
+
* because a provider's own refusal was never checked before the hop's
|
|
374
|
+
* ORIGINAL basis was trusted again.
|
|
375
|
+
*
|
|
376
|
+
* A plan that fails this check is never dropped by this function — it is the
|
|
377
|
+
* CALLER's job to decide what "not yet known" means for it (excluded from
|
|
378
|
+
* being the ranked winner, but still held back in the list; see
|
|
379
|
+
* `waves.ts`'s and `useRouteSearch.ts`'s own comparators, both of which sort
|
|
380
|
+
* every unrankable plan below every rankable one rather than comparing raw
|
|
381
|
+
* amounts across the two groups).
|
|
382
|
+
*
|
|
383
|
+
* @param hops - the plan's hops
|
|
384
|
+
* @returns true when every hop's figure is trustworthy enough to rank on
|
|
385
|
+
*/
|
|
386
|
+
export declare const planIsRankable: (hops: readonly PlannedHop[]) => boolean;
|
|
387
|
+
/**
|
|
388
|
+
* What grounds a WHOLE COMPOSED PATH's figure — {@link EstimateBasis} widened
|
|
389
|
+
* with `'quoted'`, for the one case a single hop's basis cannot express: every
|
|
390
|
+
* hop in the path was itself a live `'quote'`. `compose.ts`'s
|
|
391
|
+
* {@link @gibs/bridge-sdk/routing/search/compose!ComposedRouteDelivery} carries
|
|
392
|
+
* one of these, computed as the WORST (least-grounded) basis across every hop
|
|
393
|
+
* composed — see {@link combineComposedPathBasis} for why "worst wins" is the
|
|
394
|
+
* only correct combination rule.
|
|
395
|
+
*/
|
|
396
|
+
export type ComposedPathBasis = 'quoted' | EstimateBasis;
|
|
397
|
+
/**
|
|
398
|
+
* Combines two hops' (or two paths') bases into the basis their COMPOSITION
|
|
399
|
+
* deserves: the WORSE of the two, never the better one and never an average.
|
|
400
|
+
* A composed figure is a chain — `hop2`'s output is computed FROM `hop1`'s
|
|
401
|
+
* output — so if either link is `'unpriced'`, the number at the far end
|
|
402
|
+
* carries no value regardless of how solid the other link was. "Worst wins"
|
|
403
|
+
* is the only rule that keeps that true; picking the better basis (or the
|
|
404
|
+
* LAST hop's own basis, which an earlier draft of this reasoning tried) would
|
|
405
|
+
* let one grounded hop launder an ungrounded one earlier in the same chain.
|
|
406
|
+
*
|
|
407
|
+
* @param a - one basis being combined
|
|
408
|
+
* @param b - the other
|
|
409
|
+
* @returns whichever of `a`/`b` a ranking trusts less
|
|
410
|
+
*/
|
|
411
|
+
export declare const combineComposedPathBasis: (a: ComposedPathBasis, b: ComposedPathBasis) => ComposedPathBasis;
|
|
412
|
+
/**
|
|
413
|
+
* Whether a ranking may compare on `basis` — true for every
|
|
414
|
+
* {@link ComposedPathBasis} except `'unpriced'`. The whole-path counterpart to
|
|
415
|
+
* {@link RANKABLE_ESTIMATE_BASES}; see that constant's doc for the rule both
|
|
416
|
+
* enforce.
|
|
417
|
+
*
|
|
418
|
+
* @param basis - the composed path basis to check
|
|
419
|
+
* @returns false only for `'unpriced'`
|
|
420
|
+
*/
|
|
421
|
+
export declare const isRankableBasis: (basis: ComposedPathBasis) => boolean;
|
|
422
|
+
/**
|
|
423
|
+
* Whether a ranking may report `basis` as a WINNER — true only for
|
|
424
|
+
* `'quoted'` and `'probe-rate'`, i.e. {@link COMPOSED_PATH_BASIS_RANK} of 2 or
|
|
425
|
+
* higher. FALSE for `'priced-conversion'`, even though {@link isRankableBasis}
|
|
426
|
+
* accepts it — see that function's own "SEARCH-ORDER RULE, NOT A REPORTING
|
|
427
|
+
* RULE" note for why the two disagree on that one basis, on purpose.
|
|
428
|
+
*
|
|
429
|
+
* THE DEFECT THIS CLOSES. A live shadow comparison found 1 Zcash routed to
|
|
430
|
+
* Pulse (Wrapped) through the Binance Smart Chain omnibridge reported 131.6
|
|
431
|
+
* million Wrapped Pulse on `'priced-conversion'` — a United States dollar
|
|
432
|
+
* price-feed conversion that never asks the destination PulseX pool at all
|
|
433
|
+
* — while the pool's OWN `getAmountsOut` answers 33.3 million for the same
|
|
434
|
+
* size (86,540 per unit at 1 unit, 13,121 per unit at 1,500 units: an 84.8%
|
|
435
|
+
* worse rate at real size). `isRankableBasis` treated the two bases as
|
|
436
|
+
* equally trustworthy, so the price-feed number stood as the reported
|
|
437
|
+
* winner. `'priced-conversion'` IGNORES PRICE IMPACT BY CONSTRUCTION
|
|
438
|
+
* (`buildEstimateHopOutput`, `packages/ui/src/lib/state/useRouteSearch.ts`,
|
|
439
|
+
* converts through a flat United States dollar rate on each side, with no
|
|
440
|
+
* pool, no depth and no size in the computation) — it is not merely
|
|
441
|
+
* unconfirmed, it is blind to the one thing that broke here, so no amount of
|
|
442
|
+
* "it is probably close enough" reasoning can rescue it.
|
|
443
|
+
*
|
|
444
|
+
* WHY `'probe-rate'` IS STILL ACCEPTED HERE. A hop's basis is `'probe-rate'`
|
|
445
|
+
* ONLY WHEN A REAL PROVIDER (or a free local read — `getAmountsOut` included)
|
|
446
|
+
* ANSWERED THAT EXACT EDGE — see {@link HopOutcome}'s own doc and
|
|
447
|
+
* `waves.ts`'s `recomposePath`, whose fallback branch always carries forward
|
|
448
|
+
* a hop's ORIGINAL, pre-network basis (`'priced-conversion'`/`'unpriced'`,
|
|
449
|
+
* NEVER `'probe-rate'`) rather than inventing one. So a `'probe-rate'` hop is
|
|
450
|
+
* always grounded in a real, size-aware answer for SOME amount — the one
|
|
451
|
+
* residual imprecision is that `waves.ts`'s early waves may have asked it at
|
|
452
|
+
* an amount close to, but not exactly, the plan's final fully-propagated
|
|
453
|
+
* figure (see `waves.ts`'s own "confirmation pass" doc for how the final
|
|
454
|
+
* return step narrows that gap for a plan's TOP candidates before this
|
|
455
|
+
* function is ever consulted). A real answer at a slightly-off size still
|
|
456
|
+
* beats a number no provider was ever asked for at any size.
|
|
457
|
+
*
|
|
458
|
+
* @param basis - the composed path basis to check
|
|
459
|
+
* @returns true only for `'quoted'` and `'probe-rate'`
|
|
460
|
+
*/
|
|
461
|
+
export declare const isConfirmedBasis: (basis: ComposedPathBasis) => boolean;
|
|
462
|
+
/**
|
|
463
|
+
* What grounds a WHOLE PLAN's delivered figure, read directly off its hops'
|
|
464
|
+
* own `outcome`s — the WORST (least-grounded) {@link ComposedPathBasis} across
|
|
465
|
+
* every hop, folded with {@link combineComposedPathBasis} exactly as
|
|
466
|
+
* `compose.ts`'s `composeRouteDelivery` folds one while a path is being
|
|
467
|
+
* actively composed.
|
|
468
|
+
*
|
|
469
|
+
* USE THIS ONCE A PLAN'S HOPS ALREADY CARRY THEIR FINAL `outcome`S — a
|
|
470
|
+
* finished `enumerate.ts`/`waves.ts` plan — rather than re-deriving a fresh
|
|
471
|
+
* `ComposedRouteDelivery` from scratch. `buildRouteScoreInputsFromPlan`
|
|
472
|
+
* (`plan-route-data.ts`) is the first caller, feeding `scoreRoutePlan`
|
|
473
|
+
* (`score.ts`) the one figure it needs to refuse ever scoring an unpriced
|
|
474
|
+
* plan as if its `deliveredUsd` meant anything.
|
|
475
|
+
*
|
|
476
|
+
* AGREES WITH {@link planIsRankable} BY CONSTRUCTION, NEVER BY COINCIDENCE:
|
|
477
|
+
* `isRankableBasis(planComposedBasis(hops)) === planIsRankable(hops)` for
|
|
478
|
+
* every plan, because "worst wins" and "true unless any hop is unpriced" are
|
|
479
|
+
* the same rule read two ways — see `graph.test.ts`'s own test pinning that
|
|
480
|
+
* equivalence so the two can never quietly drift apart.
|
|
481
|
+
*
|
|
482
|
+
* A REFUSED HOP FOLDS AS `'unpriced'`, WHATEVER ITS OWN `basis` SAYS — the
|
|
483
|
+
* same rule {@link planIsRankable} applies, read through
|
|
484
|
+
* {@link combineComposedPathBasis}'s "worst wins": a hop a real provider has
|
|
485
|
+
* declined is worse-grounded than any {@link EstimateBasis}, refused or not.
|
|
486
|
+
* This is WHY {@link isRankableBasis}`(planComposedBasis(hops)) === planIsRankable(hops)`
|
|
487
|
+
* still holds for every plan — see this function's own "AGREES WITH..." note
|
|
488
|
+
* below.
|
|
489
|
+
*
|
|
490
|
+
* @param hops - the plan's hops
|
|
491
|
+
* @returns the plan's composed basis; `'quoted'` (the fold's identity value)
|
|
492
|
+
* for an empty plan
|
|
493
|
+
*/
|
|
494
|
+
export declare const planComposedBasis: (hops: readonly PlannedHop[]) => ComposedPathBasis;
|
|
495
|
+
/**
|
|
496
|
+
* The {@link ComposedPathBasis} one hop contributes to
|
|
497
|
+
* {@link planComposedBasis}'s fold: `'quoted'` for a live quote, `'unpriced'`
|
|
498
|
+
* for a REFUSED estimate regardless of its own `basis` (see
|
|
499
|
+
* {@link planComposedBasis}'s own "REFUSED HOP" note), else the estimate's
|
|
500
|
+
* own `basis`.
|
|
501
|
+
*
|
|
502
|
+
* EXPORTED FOR `waves.ts`'s OWN "confirmation pass" — the final step,
|
|
503
|
+
* described on {@link isConfirmedBasis}'s own doc, that upgrades a
|
|
504
|
+
* TOP-ranked plan's still-estimated free-carrier hops to a real quote before
|
|
505
|
+
* the search returns. That pass needs the SAME per-hop fold this function
|
|
506
|
+
* already applies (a hop a real provider has since refused is no better
|
|
507
|
+
* grounded than one that was never asked) to decide WHICH hops still need
|
|
508
|
+
* upgrading, so it reuses this rather than re-deriving the fold by hand.
|
|
509
|
+
*/
|
|
510
|
+
export declare const hopComposedBasis: (hop: PlannedHop) => ComposedPathBasis;
|
|
511
|
+
/**
|
|
512
|
+
* Whether every hop of `hops` carries a CONFIRMED figure — read straight off
|
|
513
|
+
* {@link planComposedBasis} through {@link isConfirmedBasis}, so this agrees
|
|
514
|
+
* with that function BY CONSTRUCTION, never by coincidence, exactly as
|
|
515
|
+
* {@link planIsRankable} agrees with {@link isRankableBasis} (see that pair's
|
|
516
|
+
* own "AGREES WITH" note above). STRICTER than {@link planIsRankable}: every
|
|
517
|
+
* confirmed plan is rankable, but a rankable plan carrying `'priced-conversion'`
|
|
518
|
+
* on any hop is not confirmed — see {@link isConfirmedBasis}'s own doc for why.
|
|
519
|
+
*
|
|
520
|
+
* USE THIS TO DECIDE WHAT A SEARCH MAY REPORT AS A WINNER. `waves.ts`'s own
|
|
521
|
+
* final return step is the one caller that PARTITIONS on this (confirmed
|
|
522
|
+
* plans ahead of unconfirmed ones, each group's own search-order preserved)
|
|
523
|
+
* — see that module's "confirmation pass" doc. NEVER use this to prune or
|
|
524
|
+
* rank DURING search: `planIsRankable` is still the correct predicate for
|
|
525
|
+
* `enumerate.ts`'s cut and `waves.ts`'s between-wave top-candidate
|
|
526
|
+
* selection, so `'priced-conversion'` keeps doing its job there exactly as
|
|
527
|
+
* before — see {@link RANKABLE_ESTIMATE_BASES}'s own "SEARCH-ORDER RULE, NOT
|
|
528
|
+
* A REPORTING RULE" note.
|
|
529
|
+
*
|
|
530
|
+
* @param hops - the plan's hops
|
|
531
|
+
* @returns true when every hop's figure is confirmed by a real ask
|
|
532
|
+
*/
|
|
533
|
+
export declare const planIsConfirmed: (hops: readonly PlannedHop[]) => boolean;
|
|
534
|
+
/** One hop of a {@link RoutePlan}: the edge it travels, its request, and its outcome. */
|
|
535
|
+
export type PlannedHop = {
|
|
536
|
+
/** The graph edge this hop travels. */
|
|
537
|
+
readonly edge: HopEdge;
|
|
538
|
+
/** The provider-specific request that would produce (or produced) {@link outcome}. */
|
|
539
|
+
readonly request: HopRequest;
|
|
540
|
+
/** What this hop delivers — a binding quote, or an estimate awaiting one. */
|
|
541
|
+
readonly outcome: HopOutcome;
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* One complete, priced-or-estimated path through the route graph, from the
|
|
545
|
+
* reader's origin asset to their destination asset.
|
|
546
|
+
*/
|
|
547
|
+
export type RoutePlan = {
|
|
548
|
+
/** The plan's hops, first to last. */
|
|
549
|
+
readonly hops: readonly PlannedHop[];
|
|
550
|
+
/**
|
|
551
|
+
* The plan's hops grouped into signatures, as hop INDICES into {@link hops}.
|
|
552
|
+
* Build this with {@link groupHopsBySignature} rather than by hand — see its
|
|
553
|
+
* doc for the one rule it applies.
|
|
554
|
+
*/
|
|
555
|
+
readonly signatures: readonly (readonly number[])[];
|
|
556
|
+
/**
|
|
557
|
+
* The plan's stable identity, for deduplication and for `pathKeyFor` in
|
|
558
|
+
* `journeyPlan.ts` to prefer over its current entry-chain-only key (design
|
|
559
|
+
* section 1). Build this with {@link routePlanPathKey}, never by hand.
|
|
560
|
+
*/
|
|
561
|
+
readonly pathKey: string;
|
|
562
|
+
};
|
|
563
|
+
/**
|
|
564
|
+
* The stable, collision-free key for one {@link HopEdge}.
|
|
565
|
+
*
|
|
566
|
+
* The {@link HopEdge.venue}, when present, is a fourth segment: two venues
|
|
567
|
+
* between the same assets are two edges, so they must never share a key (a
|
|
568
|
+
* path deduplication or an answer cache would otherwise merge them). An edge
|
|
569
|
+
* with no venue keeps its three-segment key unchanged.
|
|
570
|
+
*
|
|
571
|
+
* @param edge - the edge to key
|
|
572
|
+
* @returns the edge's key, built from its carrier, its two node keys, and its venue when it has one
|
|
573
|
+
*/
|
|
574
|
+
export declare const hopEdgeKey: (edge: HopEdge) => string;
|
|
575
|
+
/**
|
|
576
|
+
* The stable, collision-free key for a whole path — the value
|
|
577
|
+
* {@link RoutePlan.pathKey} must hold.
|
|
578
|
+
*
|
|
579
|
+
* Two DIFFERENT hop sequences never produce the same key, and the SAME
|
|
580
|
+
* sequence always produces the same key, because each hop's key is
|
|
581
|
+
* length-prefixed before joining (see {@link netstringEncode}). This is what
|
|
582
|
+
* makes the key usable for deduplicating plans found by different search
|
|
583
|
+
* paths that happen to walk the same edges.
|
|
584
|
+
*
|
|
585
|
+
* @param hops - the plan's hops, first to last
|
|
586
|
+
* @returns the path's key
|
|
587
|
+
*/
|
|
588
|
+
export declare const routePlanPathKey: (hops: readonly PlannedHop[]) => string;
|
|
589
|
+
/**
|
|
590
|
+
* Groups a plan's hops into signature groups, as hop indices.
|
|
591
|
+
*
|
|
592
|
+
* THE ONE RULE: a hop whose PREDECESSOR fused it (`fusesNext` was true on the
|
|
593
|
+
* hop before it) joins that predecessor's group; every other hop — including
|
|
594
|
+
* the first — starts a new one. A pay-by-hand hop needs no special case
|
|
595
|
+
* either: {@link HopEdge.origin} only says how a hop is FUNDED, not whether it
|
|
596
|
+
* groups with what follows, and design section 1 is explicit that a
|
|
597
|
+
* pay-by-hand deposit "counts as one reader action but needs no wallet
|
|
598
|
+
* signature" — a fact for the ACTION count Phase 1's `score.ts` will compute
|
|
599
|
+
* (design section 4, the `A` term), not a fact about signature grouping.
|
|
600
|
+
*
|
|
601
|
+
* @param hops - the plan's hops, first to last
|
|
602
|
+
* @returns the hop index groups, in signature order — the outer array's
|
|
603
|
+
* length is the plan's signature count
|
|
604
|
+
*/
|
|
605
|
+
export declare const groupHopsBySignature: (hops: readonly PlannedHop[]) => readonly (readonly number[])[];
|
|
606
|
+
/**
|
|
607
|
+
* THE ONE PLACE THIS COMPARATOR IS WRITTEN. Every rankable plan
|
|
608
|
+
* ({@link planIsRankable}) sorts ahead of every unrankable one, whatever its
|
|
609
|
+
* raw `deliveredAmount` claims to be — a QUOTABLE plan never loses its slot
|
|
610
|
+
* to one that is not, at the enumeration cut or anywhere else this
|
|
611
|
+
* comparator runs. Within the same rankability group: two RANKABLE plans
|
|
612
|
+
* compare on the larger last-hop delivered amount, a real signal; two
|
|
613
|
+
* UNRANKABLE plans compare with {@link compareUnrankablePlans} instead, per
|
|
614
|
+
* that function's own doc, because `deliveredAmount` carries no signal for
|
|
615
|
+
* that group at all. Bigint-safe throughout for the rankable branch — never
|
|
616
|
+
* widens to `Number`, which would lose precision on a large delivered amount
|
|
617
|
+
* long before the comparison needed it to.
|
|
618
|
+
*
|
|
619
|
+
* IMPORTED, NEVER RESTATED — PHASE 3's OWN CLOSE-THE-GAP RULE. Three
|
|
620
|
+
* independent call sites need exactly this rule: `enumerate.ts`'s own
|
|
621
|
+
* default (its top-N keep, applied to freshly enumerated plans — the SAME
|
|
622
|
+
* cut that decides which plans even reach `waves.ts`'s request budget),
|
|
623
|
+
* `waves.ts`'s default (its between-wave re-ranking), and
|
|
624
|
+
* `packages/ui/src/lib/state/useRouteSearch.ts`'s final sort. Before this
|
|
625
|
+
* function existed, two of those three each carried their own hand-written
|
|
626
|
+
* copy of the identical rule — a DRY violation that is also a correctness
|
|
627
|
+
* risk: a future edit to one copy (say, widening `RANKABLE_ESTIMATE_BASES`)
|
|
628
|
+
* could silently fail to reach the other, reopening the exact defect shadow
|
|
629
|
+
* mode measured (an unpriced 276 PLS outranking a priced 23 million PLS).
|
|
630
|
+
* Importing this one function everywhere a plan-to-plan comparison happens
|
|
631
|
+
* is what makes that drift structurally impossible rather than merely
|
|
632
|
+
* against convention.
|
|
633
|
+
*
|
|
634
|
+
* @param a - one plan being compared
|
|
635
|
+
* @param b - the other
|
|
636
|
+
* @returns negative when `a` ranks ahead of `b`, positive when `b` ranks
|
|
637
|
+
* ahead of `a`, zero when they tie
|
|
638
|
+
*/
|
|
639
|
+
export declare const compareRoutePlansByDeliveredAmount: (a: RoutePlan, b: RoutePlan) => number;
|