@gibs/bridge-sdk 1.18.0 → 1.20.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/README.md +33 -0
- package/dist/abis.js +0 -2
- package/dist/bridge-limits.js +0 -38
- package/dist/chain-info.js +0 -116
- package/dist/chain-names.js +0 -151
- package/dist/chains.js +0 -198
- package/dist/cjs/abis.js +0 -2
- package/dist/cjs/bridge-limits.js +0 -38
- package/dist/cjs/chain-info.js +0 -116
- package/dist/cjs/chain-names.js +0 -151
- package/dist/cjs/chains.js +0 -198
- package/dist/cjs/claims.js +0 -60
- package/dist/cjs/config.js +1 -208
- package/dist/cjs/consolidated-quote.js +2 -464
- package/dist/cjs/destination-calldata.js +0 -71
- package/dist/cjs/ecosystems.js +0 -183
- package/dist/cjs/execution.js +0 -92
- package/dist/cjs/fee-type.js +0 -1
- package/dist/cjs/hyperliquid-execution.js +0 -98
- package/dist/cjs/image-links.js +0 -108
- package/dist/cjs/lifi-execution-guard.js +65 -172
- package/dist/cjs/near-intents-conformance.js +0 -110
- package/dist/cjs/near-intents-route.d.ts +13 -0
- package/dist/cjs/near-intents-route.js +7 -140
- package/dist/cjs/near-intents.js +0 -562
- package/dist/cjs/origin-deposit.js +0 -56
- package/dist/cjs/pre-quote.js +0 -80
- package/dist/cjs/prices.js +0 -113
- package/dist/cjs/providers.js +0 -83
- package/dist/cjs/quote-fees.js +0 -80
- package/dist/cjs/referral.js +0 -75
- package/dist/cjs/relay-currencies.js +0 -31
- package/dist/cjs/relay-depository.js +52 -77
- package/dist/cjs/relay-quote.js +0 -394
- package/dist/cjs/route-steps.js +0 -100
- package/dist/cjs/routing/amounts.js +0 -83
- package/dist/cjs/routing/candidate-route.d.ts +46 -1
- package/dist/cjs/routing/candidate-route.js +12 -40
- package/dist/cjs/routing/candidate.js +0 -46
- package/dist/cjs/routing/carrier-provider-key.js +0 -27
- package/dist/cjs/routing/carriers/omnibridge.js +0 -120
- package/dist/cjs/routing/carriers/pulsex-paths.js +0 -57
- package/dist/cjs/routing/carriers/pulsex-split.js +0 -92
- package/dist/cjs/routing/carriers/pulsex.js +0 -279
- package/dist/cjs/routing/carriers/wrap.js +0 -57
- package/dist/cjs/routing/composed-route-quality.js +0 -95
- package/dist/cjs/routing/entry-chain-anchor.js +0 -27
- package/dist/cjs/routing/format.js +0 -50
- package/dist/cjs/routing/index.d.ts +1 -1
- package/dist/cjs/routing/index.js +3 -30
- package/dist/cjs/routing/producers.js +0 -854
- package/dist/cjs/routing/providers.js +0 -27
- package/dist/cjs/routing/search/compose.js +0 -56
- package/dist/cjs/routing/search/graph.js +0 -364
- package/dist/cjs/routing/search/plan-route-data.js +5 -510
- package/dist/cjs/routing/search/score.js +0 -344
- package/dist/cjs/routing/swap-transactions.js +0 -74
- package/dist/cjs/routing/usd-value.js +0 -12
- package/dist/cjs/settings.js +1 -456
- package/dist/cjs/swap-venues.js +0 -17
- package/dist/cjs/ton-connect.js +0 -82
- package/dist/cjs/ton-execution.js +0 -6
- package/dist/cjs/ton.js +0 -42
- package/dist/cjs/tron-execution.js +0 -145
- package/dist/cjs/types.d.ts +8 -0
- package/dist/claims.js +0 -60
- package/dist/config.js +1 -211
- package/dist/consolidated-quote.js +2 -464
- package/dist/destination-calldata.js +0 -71
- package/dist/ecosystems.js +0 -183
- package/dist/execution.js +0 -92
- package/dist/fee-type.js +0 -2
- package/dist/hyperliquid-execution.js +0 -98
- package/dist/image-links.js +0 -108
- package/dist/lifi-execution-guard.js +65 -172
- package/dist/near-intents-conformance.js +0 -110
- package/dist/near-intents-route.d.ts +13 -0
- package/dist/near-intents-route.js +5 -139
- package/dist/near-intents.js +0 -562
- package/dist/origin-deposit.js +0 -56
- package/dist/pre-quote.js +0 -80
- package/dist/prices.js +0 -113
- package/dist/providers.js +0 -83
- package/dist/quote-fees.js +0 -80
- package/dist/referral.js +0 -75
- package/dist/relay-currencies.js +0 -31
- package/dist/relay-depository.js +52 -77
- package/dist/relay-quote.js +0 -394
- package/dist/route-steps.js +0 -100
- package/dist/routing/amounts.js +0 -83
- package/dist/routing/candidate-route.d.ts +46 -1
- package/dist/routing/candidate-route.js +10 -39
- package/dist/routing/candidate.js +0 -46
- package/dist/routing/carrier-provider-key.js +0 -27
- package/dist/routing/carriers/omnibridge.js +0 -120
- package/dist/routing/carriers/pulsex-paths.js +0 -57
- package/dist/routing/carriers/pulsex-split.js +0 -92
- package/dist/routing/carriers/pulsex.js +0 -279
- package/dist/routing/carriers/wrap.js +0 -57
- package/dist/routing/composed-route-quality.js +0 -95
- package/dist/routing/entry-chain-anchor.js +0 -27
- package/dist/routing/format.js +0 -50
- package/dist/routing/index.d.ts +1 -1
- package/dist/routing/index.js +1 -29
- package/dist/routing/producers.js +0 -854
- package/dist/routing/providers.js +0 -27
- package/dist/routing/search/compose.js +0 -56
- package/dist/routing/search/graph.js +0 -364
- package/dist/routing/search/plan-route-data.js +6 -511
- package/dist/routing/search/score.js +0 -344
- package/dist/routing/swap-transactions.js +0 -74
- package/dist/routing/usd-value.js +0 -12
- package/dist/settings.js +1 -456
- package/dist/swap-venues.js +0 -17
- package/dist/ton-connect.js +0 -82
- package/dist/ton-execution.js +0 -6
- package/dist/ton.js +0 -42
- package/dist/tron-execution.js +0 -145
- package/dist/types.d.ts +8 -0
- package/package.json +3 -3
|
@@ -34,236 +34,53 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.buildSameChainSwapRouteData = exports.buildPulseXSwapEstimateData = exports.buildConversionRouteData = exports.buildPulseXSwapSectionData = exports.buildOutboundRouteData = exports.buildDirectRouteData = exports.buildCandidateRouteData = exports.candidateNetValueUsd = exports.candidateOutsideFeesUsd = exports.candidateDeliveredUsd = exports.candidatePaidUsd = exports.usdNumber = exports.resolveEntryChainName = exports.providerDisplayName = exports.subtractUsdValues = exports.addUsdValues = exports.omnibridgeProviderDisplayName = exports.pulsechainChainId = exports.BRIDGE_SETTLEMENT_ETA_MINUTES = exports.PULSEX_MARGINAL_REFERENCE_DIVISOR = void 0;
|
|
37
|
-
/**
|
|
38
|
-
* Pure route-producer helpers and constants shared by every route builder.
|
|
39
|
-
* Lives in the software development kit so the builders can relocate here in
|
|
40
|
-
* subsequent tasks without pulling user-interface dependencies into the kit.
|
|
41
|
-
*
|
|
42
|
-
* All helpers are framework-agnostic: no React, no Svelte, no display formatters.
|
|
43
|
-
*/
|
|
44
37
|
const config_js_1 = require("../config.js");
|
|
45
38
|
const chain_names_js_1 = require("../chain-names.js");
|
|
46
39
|
const usd_value_js_1 = require("./usd-value.js");
|
|
47
40
|
const imageLinks = __importStar(require("../image-links.js"));
|
|
48
41
|
const format_js_1 = require("./format.js");
|
|
49
42
|
const viem_1 = require("viem");
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Constants
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
/**
|
|
54
|
-
* The PulseX swap fee in basis points (hundredths of a percent). The deferred
|
|
55
|
-
* outbound leg swaps the bridged intermediate token into the wanted token on the
|
|
56
|
-
* entry chain at this rate, so the deferred section's fee is APPROXIMATE — the
|
|
57
|
-
* binding quote is only known when the user returns. 29 basis points is roughly
|
|
58
|
-
* 0.29%.
|
|
59
|
-
*/
|
|
60
43
|
const PULSEX_SWAP_FEE_BASIS_POINTS = 29n;
|
|
61
|
-
/** The denominator that turns {@link PULSEX_SWAP_FEE_BASIS_POINTS} into a fraction. */
|
|
62
44
|
const BASIS_POINTS_DENOMINATOR = 10000n;
|
|
63
|
-
/**
|
|
64
|
-
* Divisor for the MARGINAL reference swap quote. A swap's price impact cannot be
|
|
65
|
-
* read from the trade quote alone — `getAmountsOut(fullInput)` already folds the
|
|
66
|
-
* impact into its output, and the PulseChain USD prices are themselves PulseX
|
|
67
|
-
* pool quotes, so differencing them cancels the impact out (it vanishes to
|
|
68
|
-
* $0.00). Instead the estimate quotes a TINY reference input — `1/this` of the
|
|
69
|
-
* trade — whose own impact is negligible, giving the near-spot marginal rate. The
|
|
70
|
-
* gap between that marginal rate and the full trade's realized rate IS the price
|
|
71
|
-
* impact (the swap fee rides in BOTH quotes, so it cancels and the gap is impact
|
|
72
|
-
* alone). 1/10000 of the trade is small enough to be effectively marginal yet
|
|
73
|
-
* large enough that its output does not round away.
|
|
74
|
-
*/
|
|
75
45
|
exports.PULSEX_MARGINAL_REFERENCE_DIVISOR = 10000n;
|
|
76
|
-
/**
|
|
77
|
-
* The approximate wall-clock wait for an omnibridge crossing to settle before a
|
|
78
|
-
* DEFERRED swap leg can be signed — the same ~20 minutes whether the swap happens
|
|
79
|
-
* on the entry chain (outbound PulseChain → Ethereum/Binance Smart Chain) or on
|
|
80
|
-
* PulseChain (conversion route: bridge an anchor in, then swap on PulseX).
|
|
81
|
-
* Surfaced compactly as `~20 min` in the deferred section head, mirroring how
|
|
82
|
-
* every other section shows its estimate, rather than spelled out inside the
|
|
83
|
-
* deferred note.
|
|
84
|
-
*/
|
|
85
46
|
exports.BRIDGE_SETTLEMENT_ETA_MINUTES = 20;
|
|
86
|
-
/**
|
|
87
|
-
* The approximate wall-clock duration of the SWAP itself, in seconds — a PulseX
|
|
88
|
-
* swap (or an entry-chain swap) settles in roughly ten seconds once signed. This
|
|
89
|
-
* is the swap leg's OWN time, distinct from the ~20-minute crossing it waits
|
|
90
|
-
* behind ({@link BRIDGE_SETTLEMENT_ETA_MINUTES}): the wait belongs on the BRIDGE
|
|
91
|
-
* leg, the near-instant swap on the SWAP leg, so each section reads honestly
|
|
92
|
-
* ("~20 min" to cross, then "~10 sec" to swap) rather than stamping the crossing's
|
|
93
|
-
* twenty minutes onto the swap.
|
|
94
|
-
*/
|
|
95
47
|
const PULSEX_SWAP_ETA_SECONDS = 10;
|
|
96
|
-
/** Decimal chain id for PulseChain — the shared destination of every inbound route. */
|
|
97
48
|
exports.pulsechainChainId = Number(config_js_1.Chains.PLS);
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// Private backing maps
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
/**
|
|
102
|
-
* The fully spelled-out display name of each omnibridge provider, keyed by the
|
|
103
|
-
* raw provider identifier the builders carry. These are the headline names the
|
|
104
|
-
* route strip shows as the route's bridge.
|
|
105
|
-
*
|
|
106
|
-
* Typed `Record<Provider, string>`, exhaustive over the two real omnibridge
|
|
107
|
-
* crossings, so a third one added to `Providers` without a name here is a
|
|
108
|
-
* compile error rather than a silent gap in {@link omnibridgeProviderDisplayName}.
|
|
109
|
-
*/
|
|
110
49
|
const omnibridgeProviderDisplayNames = {
|
|
111
50
|
[config_js_1.Providers.PULSECHAIN]: 'PulseChain Bridge',
|
|
112
51
|
[config_js_1.Providers.TOKENSEX]: 'Tokens Express Bridge',
|
|
113
52
|
};
|
|
114
|
-
/**
|
|
115
|
-
* Whether `value` genuinely names an omnibridge crossing, narrowing it to
|
|
116
|
-
* {@link Provider}.
|
|
117
|
-
*
|
|
118
|
-
* @param value - a string that may or may not be a real omnibridge provider
|
|
119
|
-
* @returns whether `value` is one of the two registered crossings
|
|
120
|
-
*/
|
|
121
53
|
const isOmnibridgeProvider = (value) => value === config_js_1.Providers.PULSECHAIN || value === config_js_1.Providers.TOKENSEX;
|
|
122
|
-
/**
|
|
123
|
-
* The display name for an omnibridge crossing, fully spelled out. Narrowed to
|
|
124
|
-
* exactly {@link Provider} — the two real crossings — rather than any string,
|
|
125
|
-
* so passing an aggregator's `ProviderId` (or anything else) is a COMPILE
|
|
126
|
-
* ERROR here, not a silently wrong name.
|
|
127
|
-
*
|
|
128
|
-
* PREFER THIS OVER {@link providerDisplayName} wherever the caller already
|
|
129
|
-
* holds a genuine `Provider`. `providerDisplayName` exists only because some
|
|
130
|
-
* callers (`RouteCandidate.omnibridgeProvider`, `RouteData.headlineProviderKey`)
|
|
131
|
-
* are typed `string` at their own source and cannot be narrowed from here.
|
|
132
|
-
*
|
|
133
|
-
* @param provider - a real omnibridge provider
|
|
134
|
-
* @returns the spelled-out provider name
|
|
135
|
-
*/
|
|
136
54
|
const omnibridgeProviderDisplayName = (provider) => omnibridgeProviderDisplayNames[provider];
|
|
137
55
|
exports.omnibridgeProviderDisplayName = omnibridgeProviderDisplayName;
|
|
138
|
-
/**
|
|
139
|
-
* The fully spelled-out display name of an omnibridge ENTRY chain, keyed by its
|
|
140
|
-
* decimal chain id. Acronyms are spelled out rather than read from viem's terse
|
|
141
|
-
* `name`. Ethereum and Binance Smart Chain are the only omnibridge entry points;
|
|
142
|
-
* any other chain id has no entry and {@link resolveEntryChainName} returns null.
|
|
143
|
-
*/
|
|
144
56
|
const entryChainDisplayName = {
|
|
145
57
|
[Number(config_js_1.Chains.ETH)]: 'Ethereum',
|
|
146
58
|
[Number(config_js_1.Chains.BNB)]: 'Binance Smart Chain',
|
|
147
59
|
};
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
// Helpers
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
/**
|
|
152
|
-
* Adds two nullable United States dollar NUMBERS. A null operand contributes
|
|
153
|
-
* nothing; two nulls yield null so an all-unpriced route keeps a null cost rather
|
|
154
|
-
* than a misleading 0.
|
|
155
|
-
*
|
|
156
|
-
* This sums the RAW values and leaves the single cent-rounding to the display
|
|
157
|
-
* adapter (round(a + b)). It deliberately differs from the pre-split
|
|
158
|
-
* `sumFormattedUsd`, which summed two already-cent-rounded strings
|
|
159
|
-
* (round(a) + round(b)); the two can diverge by a cent when both operands carry
|
|
160
|
-
* sub-cent fractions. Rounding the total once is the more accurate result and
|
|
161
|
-
* keeps this helper free of any display formatter, so the raw producers stay
|
|
162
|
-
* relocatable. See the "sums the raw total once" test for the locked behavior.
|
|
163
|
-
*
|
|
164
|
-
* @param first - the first dollar value, or null/undefined when absent
|
|
165
|
-
* @param second - the second dollar value, or null/undefined when absent
|
|
166
|
-
* @returns the sum, or null when both operands are absent
|
|
167
|
-
*/
|
|
168
60
|
const addUsdValues = (first, second) => {
|
|
169
61
|
if (first == null && second == null)
|
|
170
62
|
return null;
|
|
171
63
|
return (first ?? 0) + (second ?? 0);
|
|
172
64
|
};
|
|
173
65
|
exports.addUsdValues = addUsdValues;
|
|
174
|
-
/**
|
|
175
|
-
* Subtracts a cost from a dollar value on the SAME basis — the operation every
|
|
176
|
-
* producer uses to derive what a leg delivers from what entered it.
|
|
177
|
-
*
|
|
178
|
-
* Unlike {@link addUsdValues} this is deliberately NOT symmetric about null: an
|
|
179
|
-
* unknown starting value cannot be reduced, so it stays unknown, while an unknown
|
|
180
|
-
* cost is treated as no cost. That asymmetry is what keeps a route with one
|
|
181
|
-
* unpriced leg from reporting a delivered value it never established.
|
|
182
|
-
*
|
|
183
|
-
* @param value - the dollar value entering the leg, or null/undefined when unknown
|
|
184
|
-
* @param cost - the leg's dollar cost, or null/undefined when unpriced
|
|
185
|
-
* @returns the value less the cost, or null when the starting value is unknown
|
|
186
|
-
*/
|
|
187
66
|
const subtractUsdValues = (value, cost) => {
|
|
188
67
|
if (value == null)
|
|
189
68
|
return null;
|
|
190
69
|
return value - (cost ?? 0);
|
|
191
70
|
};
|
|
192
71
|
exports.subtractUsdValues = subtractUsdValues;
|
|
193
|
-
/**
|
|
194
|
-
* The display name for an omnibridge crossing, accepting a raw `string` rather
|
|
195
|
-
* than {@link Provider}.
|
|
196
|
-
*
|
|
197
|
-
* PREFER {@link omnibridgeProviderDisplayName} INSTEAD wherever the caller
|
|
198
|
-
* already holds a genuine `Provider` — that one is a compile error for
|
|
199
|
-
* anything else. This wide version exists only for callers whose OWN field is
|
|
200
|
-
* typed `string` at its source and so cannot be narrowed here:
|
|
201
|
-
* `RouteCandidate.omnibridgeProvider` and `RouteData.headlineProviderKey`
|
|
202
|
-
* (`routing/candidate.ts`, `routing/route-data.ts`).
|
|
203
|
-
*
|
|
204
|
-
* KEPT DELIBERATELY NON-THROWING, UNLIKE {@link providerById}. The obvious
|
|
205
|
-
* next step — throw on an unrecognized key, the way `providerById` does for
|
|
206
|
-
* an aggregator — was tried and reverted: `toRouteView.ts`'s headline
|
|
207
|
-
* resolution calls this with `RouteData.headlineProviderKey`, a `string` this
|
|
208
|
-
* repository does not otherwise guarantee is a real provider of ANY kind, and
|
|
209
|
-
* an existing, deliberate test (`toRouteView.test.ts`, "headline falls back
|
|
210
|
-
* to 'Omnibridge' for an unknown headline provider key") requires that a
|
|
211
|
-
* genuinely-unrecognized key render a generic label rather than crash the
|
|
212
|
-
* whole route card. A NEAR Intents leg being mislabeled "Omnibridge" was a
|
|
213
|
-
* real defect (see `near-intents-route.ts`), fixed there by calling the
|
|
214
|
-
* RIGHT function (`providerById(id).label`) instead of this one — not by
|
|
215
|
-
* making this one less forgiving of input nobody validates before it arrives.
|
|
216
|
-
*
|
|
217
|
-
* `'PulseX'` IS RECOGNIZED HERE TOO, unlike an aggregator `ProviderId`, and for
|
|
218
|
-
* a different reason than the omnibridge case: it needs no registry lookup —
|
|
219
|
-
* there is exactly one PulseX, the label IS the key — so there is nothing an
|
|
220
|
-
* aggregator-style `providerById(id).label` call would add. A same-chain
|
|
221
|
-
* PulseX swap route (`RouteData.kind === 'swap'`) has NO omnibridge leg at
|
|
222
|
-
* all, so falling back to "Omnibridge" for it would name a bridge that never
|
|
223
|
-
* ran.
|
|
224
|
-
*
|
|
225
|
-
* @param provider - the raw provider key (e.g. `'pulsechain'`, `'PulseX'`)
|
|
226
|
-
* @returns the spelled-out provider name, or "Omnibridge" when unregistered
|
|
227
|
-
*/
|
|
228
72
|
const providerDisplayName = (provider) => {
|
|
229
73
|
if (provider === 'PulseX')
|
|
230
74
|
return 'PulseX';
|
|
231
75
|
return isOmnibridgeProvider(provider) ? (0, exports.omnibridgeProviderDisplayName)(provider) : 'Omnibridge';
|
|
232
76
|
};
|
|
233
77
|
exports.providerDisplayName = providerDisplayName;
|
|
234
|
-
/**
|
|
235
|
-
* The fully spelled-out display name of an omnibridge entry chain, or null when
|
|
236
|
-
* the chain id is null or is not one of the two entry points (Ethereum, Binance
|
|
237
|
-
* Smart Chain). A null result makes route builders return null, so an
|
|
238
|
-
* unrecognised destination leaves the strip empty rather than naming a chain it
|
|
239
|
-
* cannot bridge to.
|
|
240
|
-
*
|
|
241
|
-
* @param entryChainId - the decimal entry chain id, or null for direct routes
|
|
242
|
-
* @returns the spelled-out chain name, or null
|
|
243
|
-
*/
|
|
244
78
|
const resolveEntryChainName = (entryChainId) => {
|
|
245
79
|
if (entryChainId === null)
|
|
246
80
|
return null;
|
|
247
81
|
return entryChainDisplayName[entryChainId] ?? null;
|
|
248
82
|
};
|
|
249
83
|
exports.resolveEntryChainName = resolveEntryChainName;
|
|
250
|
-
/**
|
|
251
|
-
* Parses one of the aggregator's own United States dollar figures —
|
|
252
|
-
* `fromAmountUsd` or `toAmountUsd` on a candidate's quote.
|
|
253
|
-
*
|
|
254
|
-
* `Number('')` IS `0`, NOT "UNKNOWN", and both the LI.FI and Relay quote
|
|
255
|
-
* parsers can hand this module a blank amount string. Read with a bare
|
|
256
|
-
* `Number()`, a blank `fromAmountUsd` priced a route's origin leg at zero
|
|
257
|
-
* cost — one measured case showed a route reporting a net gain of +$273 that
|
|
258
|
-
* had in fact cost real money, and led the strip on the strength of it — and
|
|
259
|
-
* a blank `toAmountUsd` would do the same to what a direct route (no
|
|
260
|
-
* omnibridge leg) reports as delivered. A dollar figure only means something
|
|
261
|
-
* when it is a positive, finite number: blank text, non-numeric text, zero,
|
|
262
|
-
* and a negative number are all treated as UNPRICED rather than free.
|
|
263
|
-
*
|
|
264
|
-
* @param raw - the provider's amount string, or null/undefined when absent
|
|
265
|
-
* @returns the parsed value, or null when the field cannot be trusted
|
|
266
|
-
*/
|
|
267
84
|
const usdNumber = (raw) => {
|
|
268
85
|
if (raw == null || raw.trim() === '')
|
|
269
86
|
return null;
|
|
@@ -271,74 +88,18 @@ const usdNumber = (raw) => {
|
|
|
271
88
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
272
89
|
};
|
|
273
90
|
exports.usdNumber = usdNumber;
|
|
274
|
-
/**
|
|
275
|
-
* What the user pays for a candidate's origin leg, in United States dollars, as
|
|
276
|
-
* the provider itself valued it (`fromAmountUsd`). This is the candidate's SOURCE
|
|
277
|
-
* BASIS: every dollar figure the route reports is derived from it, so the costs
|
|
278
|
-
* shown and the value delivered can never drift onto different price sources.
|
|
279
|
-
*
|
|
280
|
-
* @param candidate - the priced route candidate
|
|
281
|
-
* @returns the amount paid in United States dollars, or null when the provider gave none
|
|
282
|
-
*/
|
|
283
|
-
/**
|
|
284
|
-
* Exported so `search/plan-route-data.ts` can generalize this SAME valuation
|
|
285
|
-
* to a general-search plan's first hop, per the route-search design's
|
|
286
|
-
* `RouteScoreInputs` doc ("import and reuse them; do not write a second
|
|
287
|
-
* valuation") — a candidate-shaped module boundary, not a second formula.
|
|
288
|
-
*/
|
|
289
91
|
const candidatePaidUsd = (candidate) => (0, exports.usdNumber)(candidate.quote.fromAmountUsd);
|
|
290
92
|
exports.candidatePaidUsd = candidatePaidUsd;
|
|
291
|
-
/**
|
|
292
|
-
* The cost of a candidate, in United States dollars, as the difference between
|
|
293
|
-
* what the user pays for the aggregator leg ({@link candidatePaidUsd}) and what the
|
|
294
|
-
* delivered tokens are worth. Returns null when the provider gave no
|
|
295
|
-
* `fromAmountUsd` or the delivered tokens are unpriced — the fee then shows only
|
|
296
|
-
* what it can. The figure is clamped at zero because a fleeting price-source
|
|
297
|
-
* skew should never render a negative fee.
|
|
298
|
-
*
|
|
299
|
-
* @param candidate - the priced route candidate
|
|
300
|
-
* @returns the fee in United States dollars, or null when either side is unpriced
|
|
301
|
-
*/
|
|
302
93
|
const candidateFeeUsd = (candidate) => {
|
|
303
94
|
const paid = (0, exports.candidatePaidUsd)(candidate);
|
|
304
95
|
if (paid === null)
|
|
305
96
|
return null;
|
|
306
|
-
// Charges levied outside `fromAmount` are part of the cost even though the
|
|
307
|
-
// difference between paid and delivered cannot see them.
|
|
308
97
|
const outside = (0, exports.candidateOutsideFeesUsd)(candidate);
|
|
309
98
|
const delivered = (0, exports.candidateDeliveredUsd)(candidate);
|
|
310
99
|
if (delivered === null)
|
|
311
100
|
return null;
|
|
312
101
|
return Math.max(0, paid + outside - delivered);
|
|
313
102
|
};
|
|
314
|
-
/**
|
|
315
|
-
* What a candidate's delivered tokens are worth, in United States dollars.
|
|
316
|
-
*
|
|
317
|
-
* TWO SOURCES, AND WHICH ONE APPLIES IS DECIDED BY WHETHER A SECOND LEG
|
|
318
|
-
* FOLLOWS — never by which one happens to be present.
|
|
319
|
-
*
|
|
320
|
-
* A route with no omnibridge leg ends where the provider drops the funds, so
|
|
321
|
-
* the provider valued the very tokens the reader receives, in the same answer
|
|
322
|
-
* and on the same feed as `fromAmountUsd`. The difference between the two is
|
|
323
|
-
* then the route's cost and nothing else. This is the case that had no price at
|
|
324
|
-
* all: the destination chain is not one the bridge's own price sources cover,
|
|
325
|
-
* so `destinationUsdPrice` arrived null and every direct route reported no fee.
|
|
326
|
-
*
|
|
327
|
-
* A route that continues through the omnibridge must NOT use it. The provider's
|
|
328
|
-
* delivered side there is the entry chain's tokens, handed to the mediator —
|
|
329
|
-
* not what finally lands on PulseChain, which is what `deliveredAmount` counts.
|
|
330
|
-
* Those two differ by the omnibridge's own fee, so reading the provider's
|
|
331
|
-
* figure would report a crossing as free.
|
|
332
|
-
*
|
|
333
|
-
* Exported because the ranking must value a route on the SAME basis the route
|
|
334
|
-
* states. The ranker used to price the delivered tokens against
|
|
335
|
-
* `destinationUsdPrice` alone, which is null on every direct route, so every
|
|
336
|
-
* direct route was unpriced and the ranker fell back to an order that could not
|
|
337
|
-
* see what each route delivers.
|
|
338
|
-
*
|
|
339
|
-
* @param candidate - the priced route candidate
|
|
340
|
-
* @returns the delivered value in United States dollars, or null when unpriced
|
|
341
|
-
*/
|
|
342
103
|
const candidateDeliveredUsd = (candidate) => {
|
|
343
104
|
if (candidate.omnibridgeProvider === null) {
|
|
344
105
|
const quoted = (0, exports.usdNumber)(candidate.quote.toAmountUsd);
|
|
@@ -354,34 +115,7 @@ const candidateDeliveredUsd = (candidate) => {
|
|
|
354
115
|
});
|
|
355
116
|
};
|
|
356
117
|
exports.candidateDeliveredUsd = candidateDeliveredUsd;
|
|
357
|
-
/**
|
|
358
|
-
* The share of the requested amount below which an unspent remainder is treated
|
|
359
|
-
* as dust and not reported, in basis points (hundredths of a percent). Ten basis
|
|
360
|
-
* points is 0.1%.
|
|
361
|
-
*
|
|
362
|
-
* A backward-solved quote lands a handful of base units away from the amount it
|
|
363
|
-
* was asked for even when it consumes essentially all of it — the control case
|
|
364
|
-
* measured eleven wei left over on a five-hundredths-of-an-ether transfer. A
|
|
365
|
-
* remainder that small is rounding, not money the user gets to keep, and naming
|
|
366
|
-
* it would add a line to every route that says nothing.
|
|
367
|
-
*/
|
|
368
118
|
const UNSPENT_MATERIAL_BASIS_POINTS = 10n;
|
|
369
|
-
/**
|
|
370
|
-
* The origin tokens a candidate's quote does NOT consume — the difference between
|
|
371
|
-
* what the user typed and what the backward-solved quote turned out to need.
|
|
372
|
-
*
|
|
373
|
-
* Valued from the candidate's OWN source basis (`fromAmountUsd` divided across
|
|
374
|
-
* `fromAmount`) rather than from a separate price feed, for the same reason every
|
|
375
|
-
* other figure on the route is: two feeds disagree, and a remainder priced against
|
|
376
|
-
* a different one would reopen the gap this whole model exists to close.
|
|
377
|
-
*
|
|
378
|
-
* Returns null when the requested amount is unknown, when the quote consumed all
|
|
379
|
-
* of it, or when the remainder is below {@link UNSPENT_MATERIAL_BASIS_POINTS} —
|
|
380
|
-
* a route that spends what it was given says nothing about unspent tokens.
|
|
381
|
-
*
|
|
382
|
-
* @param candidate - the priced route candidate
|
|
383
|
-
* @returns the unspent remainder, or null when there is none worth naming
|
|
384
|
-
*/
|
|
385
119
|
const candidateUnspent = (candidate) => {
|
|
386
120
|
const requested = candidate.requestedAmount;
|
|
387
121
|
if (requested == null || requested <= 0n)
|
|
@@ -394,9 +128,6 @@ const candidateUnspent = (candidate) => {
|
|
|
394
128
|
return null;
|
|
395
129
|
}
|
|
396
130
|
const paidUsd = (0, exports.candidatePaidUsd)(candidate);
|
|
397
|
-
// The remainder's value is the paid basis scaled by the ratio of the two
|
|
398
|
-
// amounts, which is exact regardless of the origin token's decimals and needs
|
|
399
|
-
// no per-token price at all.
|
|
400
131
|
const usdValue = paidUsd === null
|
|
401
132
|
? null
|
|
402
133
|
: (paidUsd * Number((0, viem_1.formatUnits)(remainder, candidate.originToken.decimals))) /
|
|
@@ -408,86 +139,22 @@ const candidateUnspent = (candidate) => {
|
|
|
408
139
|
usdValue: usdValue !== null && Number.isFinite(usdValue) ? usdValue : null,
|
|
409
140
|
};
|
|
410
141
|
};
|
|
411
|
-
/**
|
|
412
|
-
* A token address shortened for display, for example `0x1234…5678`. Preserves
|
|
413
|
-
* short addresses that are already readable without truncation.
|
|
414
|
-
*
|
|
415
|
-
* @param address - the full token address
|
|
416
|
-
* @returns the shortened address string
|
|
417
|
-
*/
|
|
418
142
|
const shortenAddress = (address) => address.length > 11 ? `${address.slice(0, 6)}…${address.slice(-4)}` : address;
|
|
419
|
-
/**
|
|
420
|
-
* The token label for a step endpoint: the provider-supplied SYMBOL when present
|
|
421
|
-
* (for example "USDC"), otherwise a SHORTENED address. Never the raw full address,
|
|
422
|
-
* which is unreadable in the hop conversion line.
|
|
423
|
-
*
|
|
424
|
-
* @param symbol - the token symbol, or null when not supplied by the provider
|
|
425
|
-
* @param address - the token's full on-chain address, used as fallback
|
|
426
|
-
* @returns the label to display for this endpoint
|
|
427
|
-
*/
|
|
428
143
|
const stepTokenLabel = (symbol, address) => symbol ?? shortenAddress(address);
|
|
429
|
-
/**
|
|
430
|
-
* The swap conversion label for a normalized aggregator route step — the
|
|
431
|
-
* "from token → to token" string shown in the hop's conversion line. Returns null
|
|
432
|
-
* when the step carries no token addresses (Relay steps are empty; the hop then
|
|
433
|
-
* shows no conversion clause).
|
|
434
|
-
*
|
|
435
|
-
* @param step - one normalized aggregator route step
|
|
436
|
-
* @returns the conversion string, or null when address data is absent
|
|
437
|
-
*/
|
|
438
144
|
const stepConversion = (step) => {
|
|
439
145
|
if (!step.fromToken || !step.toToken)
|
|
440
146
|
return null;
|
|
441
147
|
return `${stepTokenLabel(step.fromTokenSymbol, step.fromToken)} → ${stepTokenLabel(step.toTokenSymbol, step.toToken)}`;
|
|
442
148
|
};
|
|
443
|
-
/**
|
|
444
|
-
* A fee rate as percent text: 3e15 reads "0.3%". Exact, because it scales the
|
|
445
|
-
* integer and lets `formatUnits` place the point, rather than going through a
|
|
446
|
-
* floating point number that prints 2.9699999999999998.
|
|
447
|
-
*/
|
|
448
149
|
const rateAsPercentText = (rate) => `${(0, viem_1.formatUnits)(rate * 100n, 18)}%`;
|
|
449
|
-
/** A label, with the rate beside it when the rate is known. */
|
|
450
150
|
const labelWithRate = (label, rate) => rate == null ? label : `${label} (${rateAsPercentText(rate)})`;
|
|
451
|
-
/** A dollar value as the cent string a {@link QuoteFee} carries, or null when unpriced. */
|
|
452
151
|
const feeAmountUsdText = (value) => value === null ? null : (0, format_js_1.roundUsdToCents)(value).toFixed(2);
|
|
453
|
-
/**
|
|
454
|
-
* The token the bridge takes its own fee in.
|
|
455
|
-
*
|
|
456
|
-
* The fee manager lives on the home mediator, on PulseChain, and the pathway
|
|
457
|
-
* says which side of the crossing that is. Leaving PulseChain it is `'from'`:
|
|
458
|
-
* the home mediator takes the fee from the tokens it receives, before it sends
|
|
459
|
-
* the message. Arriving it is `'to'`: the home mediator takes the fee from the
|
|
460
|
-
* tokens it releases. So the fee is always in the PulseChain side's token.
|
|
461
|
-
*
|
|
462
|
-
* @returns the fee's token, or null when the bridge key names no pathway
|
|
463
|
-
*/
|
|
464
152
|
const bridgeFeeToken = ({ bridgeKey, sourceToken, arrivingToken, }) => {
|
|
465
|
-
// Not production-only: the testnet pathways live in a second map, and a
|
|
466
|
-
// mainnet key finds its own pathway in the first map either way.
|
|
467
153
|
const crossing = (0, config_js_1.pathway)(bridgeKey, false);
|
|
468
154
|
if (!crossing)
|
|
469
155
|
return null;
|
|
470
156
|
return crossing.feeManager === 'from' ? sourceToken : arrivingToken;
|
|
471
157
|
};
|
|
472
|
-
/**
|
|
473
|
-
* The cost of one omnibridge crossing: the bridge's own fee plus the Gibs
|
|
474
|
-
* delivery fee, both valued on the route's source basis.
|
|
475
|
-
*
|
|
476
|
-
* A PARTIAL SUM IS NEVER A COST. When either fee is unknown the figure is null,
|
|
477
|
-
* because the known half alone would read as the whole cost and understate it.
|
|
478
|
-
* A delivery the reader did not ask for is not unknown: it is a real zero.
|
|
479
|
-
*
|
|
480
|
-
* The delivery fee is in the ARRIVING token: the destination router takes it
|
|
481
|
-
* from the value that reaches it.
|
|
482
|
-
*
|
|
483
|
-
* EVERY AMOUNT IS COUNTED IN SOURCE BASE UNITS, because the settings cascade
|
|
484
|
-
* derives both fees from `amountToBridge`, which is. So both are valued with the
|
|
485
|
-
* source token's decimals and price, the same basis the route's delivered value
|
|
486
|
-
* uses. The bridge keeps a token's decimals when it creates the counterpart, so
|
|
487
|
-
* that count is also the count in the arriving token each charge names.
|
|
488
|
-
*
|
|
489
|
-
* @returns the crossing's cost and the charges behind it
|
|
490
|
-
*/
|
|
491
158
|
const crossingCost = ({ bridgeKey, feeRoots, sourceToken, arrivingToken, sourceUsdPrice, }) => {
|
|
492
159
|
const [provider] = bridgeKey;
|
|
493
160
|
const { bridgeFeeAmount, bridgeFeeRate, deliveryFee } = feeRoots;
|
|
@@ -531,25 +198,11 @@ const crossingCost = ({ bridgeKey, feeRoots, sourceToken, arrivingToken, sourceU
|
|
|
531
198
|
: bridgeFeeUsdValue + deliveryFeeUsdValue;
|
|
532
199
|
return { feeUsdValue, fees: [...bridgeFees, ...deliveryFees] };
|
|
533
200
|
};
|
|
534
|
-
/** A token as the fee token a {@link QuoteFee} names, on the chain it is presented on. */
|
|
535
201
|
const toFeeToken = (token, chainId) => ({
|
|
536
202
|
symbol: token.symbol,
|
|
537
203
|
decimals: token.decimals,
|
|
538
204
|
chainId,
|
|
539
205
|
});
|
|
540
|
-
/**
|
|
541
|
-
* The raw token reference for one end of a normalized aggregator step — the shape
|
|
542
|
-
* an intermediate stop is rendered from.
|
|
543
|
-
*
|
|
544
|
-
* The provider's own token image wins when present. When it supplies none, the
|
|
545
|
-
* logo is left UNSET for a `0x` address so the adapter derives it from the image
|
|
546
|
-
* service, and set to null for a non-Ethereum address (Bitcoin, Solana, Tron, The
|
|
547
|
-
* Open Network), which the image service cannot key by a checksummed address.
|
|
548
|
-
* Decimals are omitted: an intermediate stop formats no amount.
|
|
549
|
-
*
|
|
550
|
-
* @param endpoint - one end of a normalized step
|
|
551
|
-
* @returns the raw token reference for that end
|
|
552
|
-
*/
|
|
553
206
|
const stepTokenRef = (endpoint) => ({
|
|
554
207
|
symbol: endpoint.symbol ?? shortenAddress(endpoint.address),
|
|
555
208
|
address: endpoint.address,
|
|
@@ -557,25 +210,18 @@ const stepTokenRef = (endpoint) => ({
|
|
|
557
210
|
logoUri: endpoint.logoUri ?? (endpoint.address.startsWith('0x') ? undefined : null),
|
|
558
211
|
chainLogoUri: imageLinks.networkOrPseudo(endpoint.chainId),
|
|
559
212
|
});
|
|
560
|
-
/** The token a step consumes, as a raw reference. */
|
|
561
213
|
const stepFromTokenRef = (step) => stepTokenRef({
|
|
562
214
|
address: step.fromToken,
|
|
563
215
|
symbol: step.fromTokenSymbol,
|
|
564
216
|
chainId: step.fromChainId,
|
|
565
217
|
logoUri: step.fromTokenLogoUri,
|
|
566
218
|
});
|
|
567
|
-
/** The token a step produces, as a raw reference — the stop it lands the funds on. */
|
|
568
219
|
const stepToTokenRef = (step) => stepTokenRef({
|
|
569
220
|
address: step.toToken,
|
|
570
221
|
symbol: step.toTokenSymbol,
|
|
571
222
|
chainId: step.toChainId,
|
|
572
223
|
logoUri: step.toTokenLogoUri,
|
|
573
224
|
});
|
|
574
|
-
/**
|
|
575
|
-
* True when two references name the same token on the same chain. Hex addresses
|
|
576
|
-
* compare case-insensitively (providers disagree on checksum casing); anything
|
|
577
|
-
* else compares exactly, because a base58 address is genuinely case-sensitive.
|
|
578
|
-
*/
|
|
579
225
|
const isSameTokenRef = (left, right) => {
|
|
580
226
|
if (left.chainId !== right.chainId)
|
|
581
227
|
return false;
|
|
@@ -585,20 +231,6 @@ const isSameTokenRef = (left, right) => {
|
|
|
585
231
|
return false;
|
|
586
232
|
return left.address.toLowerCase() === right.address.toLowerCase();
|
|
587
233
|
};
|
|
588
|
-
/**
|
|
589
|
-
* The token flow a set of hops walks: the origin, then the token each hop
|
|
590
|
-
* PRODUCES. Every entry except the first is a stop the funds actually reach, and
|
|
591
|
-
* the last hop's output is the destination — so the intermediate stops are exactly
|
|
592
|
-
* the hops minus the last one.
|
|
593
|
-
*
|
|
594
|
-
* A hop that names no output token contributes nothing (the flow still reaches the
|
|
595
|
-
* destination through the hops that do), and a hop that produces the token the
|
|
596
|
-
* flow is already holding is collapsed rather than drawn twice.
|
|
597
|
-
*
|
|
598
|
-
* @param origin - the token the route starts from
|
|
599
|
-
* @param hops - the hops the signature performs, first to last
|
|
600
|
-
* @returns the ordered token flow, origin first and destination last
|
|
601
|
-
*/
|
|
602
234
|
const deriveTokenFlow = (origin, hops) => hops.reduce((flow, hop) => {
|
|
603
235
|
const produced = hop.toToken;
|
|
604
236
|
if (!produced)
|
|
@@ -608,24 +240,6 @@ const deriveTokenFlow = (origin, hops) => hops.reduce((flow, hop) => {
|
|
|
608
240
|
return flow;
|
|
609
241
|
return [...flow, produced];
|
|
610
242
|
}, [origin]);
|
|
611
|
-
/**
|
|
612
|
-
* Everything a candidate is charged that sits OUTSIDE `fromAmount`, in United
|
|
613
|
-
* States dollars.
|
|
614
|
-
*
|
|
615
|
-
* THIS REPLACES A PER-STEP GAS SUM that could only ever see part of the picture.
|
|
616
|
-
* That sum read `step.gasUsd`, so it was ZERO for every Relay quote — Relay
|
|
617
|
-
* publishes no per-leg breakdown — and for LI.FI it counted the network gas
|
|
618
|
-
* while missing the larger charge: a LayerZero fee of about 1.37 dollars marked
|
|
619
|
-
* `included: false`, paid on top in native currency. Reading the quote's own
|
|
620
|
-
* normalized fee list catches both, and cannot double-count the per-step gas
|
|
621
|
-
* against the aggregate the provider reports.
|
|
622
|
-
*
|
|
623
|
-
* A charge whose dollar value the provider did not give is skipped rather than
|
|
624
|
-
* guessed. It is still named in the breakdown the reader can open.
|
|
625
|
-
*
|
|
626
|
-
* Exported for the same reason {@link candidatePaidUsd} is — `search/plan-route-data.ts`
|
|
627
|
-
* reuses this exact valuation rather than restating it for a plan's last hop.
|
|
628
|
-
*/
|
|
629
243
|
const candidateOutsideFeesUsd = (candidate) => {
|
|
630
244
|
const fromQuote = candidate.quote.fees.reduce((total, fee) => {
|
|
631
245
|
if (fee.includedInFromAmount) {
|
|
@@ -634,11 +248,6 @@ const candidateOutsideFeesUsd = (candidate) => {
|
|
|
634
248
|
const value = Number(fee.amountUsd);
|
|
635
249
|
return Number.isFinite(value) ? total + value : total;
|
|
636
250
|
}, 0);
|
|
637
|
-
// A provider that reports its network cost once, for the whole route, gives a
|
|
638
|
-
// gas row here and that row is the whole of it. One that reports gas only per
|
|
639
|
-
// leg gives no such row, and the per-step figures are then the only record of
|
|
640
|
-
// it — counting them as well when the row IS present would bill the same gas
|
|
641
|
-
// twice, so this is a fallback and never an addition.
|
|
642
251
|
const reportsGasOnce = candidate.quote.fees.some((fee) => !fee.includedInFromAmount && fee.kind === 'gas');
|
|
643
252
|
if (reportsGasOnce) {
|
|
644
253
|
return fromQuote;
|
|
@@ -646,25 +255,6 @@ const candidateOutsideFeesUsd = (candidate) => {
|
|
|
646
255
|
return fromQuote + candidate.quote.steps.reduce((total, step) => total + (step.gasUsd ?? 0), 0);
|
|
647
256
|
};
|
|
648
257
|
exports.candidateOutsideFeesUsd = candidateOutsideFeesUsd;
|
|
649
|
-
/**
|
|
650
|
-
* The user's NET dollar outcome for a candidate: the value delivered minus
|
|
651
|
-
* everything paid to reach it. Three cost components:
|
|
652
|
-
*
|
|
653
|
-
* - the origin tokens spent (`quote.fromAmountUsd`);
|
|
654
|
-
* - every charge paid on top ({@link candidateOutsideFeesUsd}), NOT part of
|
|
655
|
-
* `fromAmount` — the sender's own network gas, and any provider fee the
|
|
656
|
-
* quote marked as charged outside the amount;
|
|
657
|
-
* - the omnibridge CROSSING fee, already netted into `deliveredAmount`.
|
|
658
|
-
*
|
|
659
|
-
* The delivered value comes from {@link candidateDeliveredUsd}, the same figure
|
|
660
|
-
* the route's own dollar value and cost are built from. This is the one ranking
|
|
661
|
-
* rule: the route strip orders by it, so the route the reader sees costing least
|
|
662
|
-
* is the route that leads.
|
|
663
|
-
*
|
|
664
|
-
* @param candidate - the priced route candidate
|
|
665
|
-
* @returns the net outcome in United States dollars, or null when the leg or
|
|
666
|
-
* the delivered tokens are unpriced
|
|
667
|
-
*/
|
|
668
258
|
const candidateNetValueUsd = (candidate) => {
|
|
669
259
|
const paid = (0, exports.candidatePaidUsd)(candidate);
|
|
670
260
|
if (paid === null)
|
|
@@ -675,41 +265,8 @@ const candidateNetValueUsd = (candidate) => {
|
|
|
675
265
|
return delivered - paid - (0, exports.candidateOutsideFeesUsd)(candidate);
|
|
676
266
|
};
|
|
677
267
|
exports.candidateNetValueUsd = candidateNetValueUsd;
|
|
678
|
-
/**
|
|
679
|
-
* Builds the RAW, data-only {@link RouteData} for one priced aggregator candidate
|
|
680
|
-
* — the framework-agnostic half of the candidate path, carrying NUMBERS, raw
|
|
681
|
-
* token references, and raw provider keys ONLY. The display wrapper runs this
|
|
682
|
-
* through `toRouteView` to resolve every logo, name, formatted string, and the
|
|
683
|
-
* structural id.
|
|
684
|
-
*
|
|
685
|
-
* The candidate is a single signature (the aggregator swap auto-calls the
|
|
686
|
-
* omnibridge), so it produces exactly one {@link RouteSectionData} whose hops are
|
|
687
|
-
* the aggregator legs the provider reported (Relay reports none) followed by the
|
|
688
|
-
* omnibridge crossing leg. Each hop carries its OWN numeric fee so the user sees
|
|
689
|
-
* where cost accrues.
|
|
690
|
-
*
|
|
691
|
-
* @param candidate - one priced route candidate
|
|
692
|
-
* @returns the raw, data-only route for the candidate
|
|
693
|
-
*/
|
|
694
268
|
const buildCandidateRouteData = (candidate) => {
|
|
695
269
|
const feeUsdValue = candidateFeeUsd(candidate);
|
|
696
|
-
// THE PROVIDER'S OWN FIGURE, IN ITS OWN UNIT, CARRIED THROUGH UNROUNDED.
|
|
697
|
-
//
|
|
698
|
-
// This used to keep only `Math.ceil(seconds / 60)`, and that one expression
|
|
699
|
-
// erased the difference the reader most needed to see. Measured live on Base
|
|
700
|
-
// to Polygon: LI.FI claimed 29 seconds and Relay claimed 3, and both rendered
|
|
701
|
-
// "~1 min". A reader comparing them had no way to tell a route that settles
|
|
702
|
-
// before they look away from one that claims ten times the wait — so the
|
|
703
|
-
// strip handed the choice to a 0.05% difference in delivered tokens, which is
|
|
704
|
-
// nine cents on a hundred-and-twenty-six-dollar transfer.
|
|
705
|
-
//
|
|
706
|
-
// The section therefore carries the seconds and the view picks the unit (see
|
|
707
|
-
// `sectionWaitLabel` in the route strip). `etaMinutes` stays beside it for the
|
|
708
|
-
// route-level field and for every consumer that still reads minutes.
|
|
709
|
-
//
|
|
710
|
-
// It is the provider's CLAIM and nothing more. One live route ran about
|
|
711
|
-
// seventy-five times its own estimate, which is why the strip labels this the
|
|
712
|
-
// provider's estimate rather than presenting it as a fact.
|
|
713
270
|
const etaSeconds = candidate.quote.executionDurationSeconds > 0 ? candidate.quote.executionDurationSeconds : null;
|
|
714
271
|
const etaMinutes = candidate.quote.executionDurationSeconds > 0
|
|
715
272
|
? Math.ceil(candidate.quote.executionDurationSeconds / 60)
|
|
@@ -720,79 +277,38 @@ const buildCandidateRouteData = (candidate) => {
|
|
|
720
277
|
` entryChainId=${candidate.entryChainId}` +
|
|
721
278
|
` omnibridgeProvider=${candidate.omnibridgeProvider}` +
|
|
722
279
|
` quote.fromAmountUsd=${candidate.quote.fromAmountUsd}` +
|
|
723
|
-
// BOTH delivered-value sources, because which one applies depends on
|
|
724
|
-
// whether a second leg follows — naming only one sends whoever reads
|
|
725
|
-
// this to the wrong place. See `candidateDeliveredUsd`.
|
|
726
280
|
` quote.toAmountUsd=${candidate.quote.toAmountUsd ?? null}` +
|
|
727
281
|
` destinationUsdPrice=${candidate.destinationUsdPrice}`);
|
|
728
282
|
}
|
|
729
283
|
const { originToken, destinationToken } = candidate;
|
|
730
|
-
// Token logos are pre-resolved here via the image service (which lives in the
|
|
731
|
-
// software development kit, so the producer stays framework-agnostic), passing
|
|
732
|
-
// the FULL token so its own `logoURI` is preferred over the address-derived
|
|
733
|
-
// fallback — matching the pre-split `buildCandidateTokenFlow` exactly.
|
|
734
284
|
const originNode = {
|
|
735
285
|
symbol: originToken.symbol,
|
|
736
286
|
address: originToken.address,
|
|
737
287
|
chainId: originToken.chainId,
|
|
738
288
|
decimals: originToken.decimals,
|
|
739
|
-
// `tokenImageOrChainFallback` — see the identical note on `destinationNode`
|
|
740
|
-
// below; the same "address not resolvable yet" case applies to either end.
|
|
741
289
|
logoUri: imageLinks.tokenImageOrChainFallback(originToken),
|
|
742
|
-
// A pay-by-hand origin (Zcash) carries a local, invented chain id
|
|
743
|
-
// `imageLinks.network` cannot look up by number — see `networkOrPseudo`.
|
|
744
290
|
chainLogoUri: imageLinks.networkOrPseudo(originToken.chainId),
|
|
745
291
|
};
|
|
746
|
-
// Read up front, before the node below, so the node itself never claims
|
|
747
|
-
// PulseChain for a candidate that does not enter the omnibridge. A candidate
|
|
748
|
-
// whose omnibridge leg is absent (NEAR Intents, or any aggregator delivering
|
|
749
|
-
// straight to its own destination chain) lands on `destinationToken.chainId`
|
|
750
|
-
// — Base, in the case that pinned this — and a node hardcoded to PulseChain
|
|
751
|
-
// named the wrong chain for every stop that reused it, including the
|
|
752
|
-
// token-flow fallback below.
|
|
753
292
|
const omnibridgeProviderKey = candidate.omnibridgeProvider;
|
|
754
|
-
// THE CHAIN THIS SIGNATURE ACTUALLY ENDS ON. The label was the constant
|
|
755
|
-
// 'Bridge to PulseChain', true of every route that existed when it was
|
|
756
|
-
// written. A route the omnibridge does not carry ends somewhere else
|
|
757
|
-
// entirely, and titled that way the tracker named a chain the funds never
|
|
758
|
-
// touch — over a transfer to Base that had already landed.
|
|
759
293
|
const landingChainId = omnibridgeProviderKey === null ? destinationToken.chainId : exports.pulsechainChainId;
|
|
760
294
|
const destinationNode = {
|
|
761
295
|
symbol: destinationToken.symbol,
|
|
762
296
|
address: destinationToken.address,
|
|
763
297
|
chainId: landingChainId,
|
|
764
298
|
decimals: destinationToken.decimals,
|
|
765
|
-
// `tokenImageOrChainFallback`, NOT `image` — a not-yet-bridged token's
|
|
766
|
-
// address can still be unresolved when this producer draws the DELIVERED
|
|
767
|
-
// node (see the repository's own "Not-Yet-Bridged Tokens" gotcha), and
|
|
768
|
-
// `image` either throws on it or silently draws nothing. See that
|
|
769
|
-
// helper's own note.
|
|
770
299
|
logoUri: imageLinks.tokenImageOrChainFallback(destinationToken),
|
|
771
|
-
// `networkOrPseudo`, matching `originNode` — a destination is not known to
|
|
772
|
-
// ever be a deposit-address pseudo chain today, but the badge helper is the
|
|
773
|
-
// one every other node in this builder goes through, and there is no reason
|
|
774
|
-
// this one should be the exception that 404s if that ever changes.
|
|
775
300
|
chainLogoUri: imageLinks.networkOrPseudo(landingChainId),
|
|
776
301
|
};
|
|
777
302
|
const aggregatorHops = candidate.quote.steps.map((step, index) => ({
|
|
778
303
|
providerName: step.label,
|
|
779
|
-
// The aggregator step carries its tool's real logo url straight from the quote
|
|
780
|
-
// — raw data, not a derived view concern, so it rides on the raw hop.
|
|
781
304
|
providerLogoUri: step.providerLogoUri,
|
|
782
305
|
title: step.label,
|
|
783
306
|
conversion: stepConversion(step),
|
|
784
|
-
// The tokens this leg moves between, structurally — the section's token flow is
|
|
785
|
-
// derived from these, so every stop the funds make is one the provider named.
|
|
786
307
|
fromToken: stepFromTokenRef(step),
|
|
787
308
|
toToken: stepToTokenRef(step),
|
|
788
309
|
feeUsdValue: step.feeUsd,
|
|
789
|
-
// The first hop is the user's single signature; later hops run inside it.
|
|
790
310
|
automatic: index > 0,
|
|
791
311
|
}));
|
|
792
|
-
// NO BRIDGE LEG MEANS NO BRIDGE HOP. A candidate the omnibridge does not
|
|
793
|
-
// carry ends where the aggregator leaves it, so the crossing hop below simply
|
|
794
|
-
// does not exist — appending one would draw a step nobody performs and name a
|
|
795
|
-
// bridge that is not involved.
|
|
796
312
|
const omnibridgeHop = omnibridgeProviderKey === null
|
|
797
313
|
? null
|
|
798
314
|
: {
|
|
@@ -800,8 +316,6 @@ const buildCandidateRouteData = (candidate) => {
|
|
|
800
316
|
providerKey: omnibridgeProviderKey,
|
|
801
317
|
title: 'Bridge to PulseChain',
|
|
802
318
|
conversion: `→ ${candidate.destinationToken.symbol}`,
|
|
803
|
-
// The crossing consumes whatever the last aggregator leg produced — the anchor
|
|
804
|
-
// token on the entry chain — or the origin token when there were no legs.
|
|
805
319
|
fromToken: aggregatorHops[aggregatorHops.length - 1]?.toToken ?? originNode,
|
|
806
320
|
toToken: destinationNode,
|
|
807
321
|
feeUsdValue: (0, usd_value_js_1.usdValueOfAmount)({
|
|
@@ -809,38 +323,17 @@ const buildCandidateRouteData = (candidate) => {
|
|
|
809
323
|
decimals: candidate.destinationToken.decimals,
|
|
810
324
|
price: candidate.destinationUsdPrice,
|
|
811
325
|
}),
|
|
812
|
-
// The omnibridge call is always auto-called by the aggregator leg before it.
|
|
813
326
|
automatic: aggregatorHops.length > 0,
|
|
814
327
|
};
|
|
815
328
|
const hops = omnibridgeHop === null ? aggregatorHops : [...aggregatorHops, omnibridgeHop];
|
|
816
|
-
// Origin, then every stop the funds reach on the way — NOT a collapsed pair. The
|
|
817
|
-
// last hop is the crossing, so the flow still ends on the destination, EXCEPT
|
|
818
|
-
// when the provider itemised no legs at all (Relay and NEAR Intents both report
|
|
819
|
-
// zero `quote.steps`) and there is no omnibridge hop to supply one either — a
|
|
820
|
-
// candidate delivering straight to its own destination chain. `deriveTokenFlow`
|
|
821
|
-
// then has nothing to reduce over and stops at the origin, which is the exact
|
|
822
|
-
// defect that showed a NEAR Intents route to Base USD Coin as "At ETH on
|
|
823
|
-
// Ethereum": the pill drew the one-and-only flow node it was given, and that
|
|
824
|
-
// node had never left the origin. The route's own `destinationToken` is known
|
|
825
|
-
// regardless of what got itemised, so it is appended here whenever the flow
|
|
826
|
-
// does not already end on it — deduplicated, so an itemised leg that DID reach
|
|
827
|
-
// the destination is never drawn twice.
|
|
828
329
|
const rawTokenFlow = deriveTokenFlow(originNode, hops);
|
|
829
330
|
const lastStop = rawTokenFlow[rawTokenFlow.length - 1];
|
|
830
331
|
const tokenFlow = lastStop && isSameTokenRef(lastStop, destinationNode) ? rawTokenFlow : [...rawTokenFlow, destinationNode];
|
|
831
332
|
const section = {
|
|
832
|
-
// The section id stays a stable string key derived from the candidate id —
|
|
833
|
-
// section ids are within-route keys and do not need to be structural hashes.
|
|
834
333
|
id: `${candidate.id}-signature-1`,
|
|
835
334
|
label: `Bridge to ${(0, chain_names_js_1.chainDisplayName)(landingChainId)}`,
|
|
836
|
-
// The signature is executed by the cross-chain AGGREGATOR (LI.FI / Relay) that
|
|
837
|
-
// carries the bridge leg — that is the provider to surface on this section, not
|
|
838
|
-
// the omnibridge headline.
|
|
839
335
|
providerKey: candidate.quote.provider,
|
|
840
336
|
tokenFlow,
|
|
841
|
-
// What this signature leaves the user holding on PulseChain. Valued on the
|
|
842
|
-
// same source basis as the route's output so the step's landing figure and
|
|
843
|
-
// the route's delivered figure are the same number, not two derivations of it.
|
|
844
337
|
landing: {
|
|
845
338
|
amount: candidate.deliveredAmount,
|
|
846
339
|
decimals: candidate.destinationToken.decimals,
|
|
@@ -849,8 +342,6 @@ const buildCandidateRouteData = (candidate) => {
|
|
|
849
342
|
},
|
|
850
343
|
unspent: candidateUnspent(candidate),
|
|
851
344
|
feeUsdValue,
|
|
852
|
-
// The itemization behind `feeUsdValue`, plus the charges that difference
|
|
853
|
-
// cannot see: anything the provider levies outside `fromAmount`.
|
|
854
345
|
fees: candidate.quote.fees,
|
|
855
346
|
etaMinutes,
|
|
856
347
|
etaSeconds,
|
|
@@ -858,26 +349,13 @@ const buildCandidateRouteData = (candidate) => {
|
|
|
858
349
|
};
|
|
859
350
|
return {
|
|
860
351
|
kind: 'aggregated',
|
|
861
|
-
// The entry chain this aggregator route crosses through (Ethereum or Binance
|
|
862
|
-
// Smart Chain), carried explicitly so Bridge.tsx can link the selected route
|
|
863
|
-
// back to its quoted entry candidate without string-parsing the id.
|
|
864
352
|
entryChainId: candidate.entryChainId,
|
|
865
|
-
// The route's headline provider is the omnibridge it crosses on — or, when
|
|
866
|
-
// it crosses none, the aggregator that carries the whole journey. A route
|
|
867
|
-
// has to be named after something that actually performs it.
|
|
868
353
|
headlineProviderKey: omnibridgeProviderKey ?? candidate.quote.provider,
|
|
869
354
|
sections: [section],
|
|
870
355
|
output: {
|
|
871
356
|
amount: candidate.deliveredAmount,
|
|
872
357
|
decimals: candidate.destinationToken.decimals,
|
|
873
358
|
symbol: candidate.destinationToken.symbol,
|
|
874
|
-
// Valued on the SOURCE basis — what was paid, less this route's cost — NOT by
|
|
875
|
-
// re-pricing the delivered token against its own feed. The two differ whenever
|
|
876
|
-
// the destination asset trades away from its origin-chain counterpart, and that
|
|
877
|
-
// difference used to land silently in this figure: the page then stated the
|
|
878
|
-
// route's cost twice and the two never agreed. Deriving it keeps the fee clamp
|
|
879
|
-
// above from opening a gap too. Falls back to the destination feed only when
|
|
880
|
-
// the provider gave no `fromAmountUsd`, so an unpriced leg still shows what it can.
|
|
881
359
|
usdValue: (0, exports.subtractUsdValues)((0, exports.candidatePaidUsd)(candidate), feeUsdValue) ??
|
|
882
360
|
(0, usd_value_js_1.usdValueOfAmount)({
|
|
883
361
|
balance: candidate.deliveredAmount,
|
|
@@ -885,37 +363,17 @@ const buildCandidateRouteData = (candidate) => {
|
|
|
885
363
|
price: candidate.destinationUsdPrice,
|
|
886
364
|
}),
|
|
887
365
|
},
|
|
888
|
-
// The aggregate cost is the single section's spread (this route is one
|
|
889
|
-
// signature), so it equals the section fee; null when the leg is unpriced.
|
|
890
366
|
totalCostUsdValue: feeUsdValue,
|
|
891
|
-
// The net dollar outcome this route is ranked on (delivered value minus origin
|
|
892
|
-
// tokens paid, network gas, and the omnibridge crossing fee).
|
|
893
367
|
netValueUsd: (0, exports.candidateNetValueUsd)(candidate),
|
|
894
368
|
etaMinutes,
|
|
895
369
|
};
|
|
896
370
|
};
|
|
897
371
|
exports.buildCandidateRouteData = buildCandidateRouteData;
|
|
898
|
-
// ---------------------------------------------------------------------------
|
|
899
|
-
// Producer: direct route
|
|
900
|
-
// ---------------------------------------------------------------------------
|
|
901
|
-
/**
|
|
902
|
-
* Builds the ALWAYS-PRESENT direct omnibridge route from the current outbound
|
|
903
|
-
* settings, so the strip is never empty for a resolvable pathway.
|
|
904
|
-
*
|
|
905
|
-
* The direct route is a SINGLE SIGNATURE — the user signs the omnibridge call
|
|
906
|
-
* once — so it is exactly ONE {@link RouteSectionData}. Returns null only when
|
|
907
|
-
* the pathway is NOT resolvable (a missing input or output asset).
|
|
908
|
-
*
|
|
909
|
-
* @param roots - the resolved settings + price + logo roots (see {@link DirectRouteRoots})
|
|
910
|
-
* @returns the single direct {@link RouteData}, or null when unresolvable
|
|
911
|
-
*/
|
|
912
372
|
const buildDirectRouteData = (roots) => {
|
|
913
373
|
const { bridgeKey, assetIn, assetOut, estimatedAmountOut, assetInUsdPrice, assetOutUsdPrice, assetInLogoUri, assetOutLogoUri, fromChainLogoUri, toChainLogoUri, advanced, } = roots;
|
|
914
374
|
if (!assetIn || !assetOut)
|
|
915
375
|
return null;
|
|
916
376
|
const [provider, fromChain, toChain] = bridgeKey;
|
|
917
|
-
// The bridge's own fee plus the delivery fee, valued on the source basis; null
|
|
918
|
-
// when either is unknown or the input asset is unpriced.
|
|
919
377
|
const { feeUsdValue, fees } = crossingCost({
|
|
920
378
|
bridgeKey,
|
|
921
379
|
feeRoots: roots,
|
|
@@ -925,30 +383,15 @@ const buildDirectRouteData = (roots) => {
|
|
|
925
383
|
});
|
|
926
384
|
const hop = {
|
|
927
385
|
providerName: (0, exports.omnibridgeProviderDisplayName)(provider),
|
|
928
|
-
// The direct route's provider is the omnibridge it crosses on; the adapter
|
|
929
|
-
// resolves its logo from this key.
|
|
930
386
|
providerKey: provider,
|
|
931
387
|
title: (0, exports.omnibridgeProviderDisplayName)(provider),
|
|
932
388
|
conversion: `${assetIn.symbol} → ${assetOut.symbol}`,
|
|
933
389
|
feeUsdValue,
|
|
934
|
-
// Delivery auto-calls the completion on the PulseChain side, so the user's
|
|
935
|
-
// single signature covers the whole leg.
|
|
936
390
|
automatic: true,
|
|
937
391
|
};
|
|
938
392
|
const section = {
|
|
939
393
|
id: `direct-${provider}-${fromChain}-signature-1`,
|
|
940
|
-
// NAMED FROM `toChain`, NOT HARD-CODED. A direct crossing runs both ways —
|
|
941
|
-
// a foreign chain into PulseChain, or PulseChain out to a foreign chain —
|
|
942
|
-
// and a literal "Bridge to PulseChain" on every direct route named the
|
|
943
|
-
// wrong destination on every outbound crossing: the label read "Bridge to
|
|
944
|
-
// PulseChain" while the arrival stage it produces (derived correctly, from
|
|
945
|
-
// the section's own last token-flow node — see `tracker/trackerView.ts`'s
|
|
946
|
-
// `sectionArrivalStage`) correctly read "Arrive on Ethereum" for the exact
|
|
947
|
-
// same signature. One route, two destinations stated for it.
|
|
948
394
|
label: `Bridge to ${(0, chain_names_js_1.chainDisplayName)(Number(toChain))}`,
|
|
949
|
-
// The direct section carries NO section provider (matching the pre-split
|
|
950
|
-
// builder): the route strip falls back to the hop's provider for the badge,
|
|
951
|
-
// and the structural id stays identical.
|
|
952
395
|
providerKey: null,
|
|
953
396
|
tokenFlow: [
|
|
954
397
|
{
|
|
@@ -968,9 +411,6 @@ const buildDirectRouteData = (roots) => {
|
|
|
968
411
|
chainLogoUri: toChainLogoUri,
|
|
969
412
|
},
|
|
970
413
|
],
|
|
971
|
-
// The direct crossing IS the whole route, so what this step lands in is what
|
|
972
|
-
// the route delivers — priced on the source basis for the same reason the
|
|
973
|
-
// output is (see the output note below).
|
|
974
414
|
landing: {
|
|
975
415
|
amount: estimatedAmountOut,
|
|
976
416
|
decimals: assetOut.decimals,
|
|
@@ -982,22 +422,13 @@ const buildDirectRouteData = (roots) => {
|
|
|
982
422
|
}),
|
|
983
423
|
},
|
|
984
424
|
feeUsdValue,
|
|
985
|
-
// Who takes what: the bridge, then the delivery service.
|
|
986
425
|
fees,
|
|
987
|
-
// The direct crossing waits on the SAME omnibridge settlement as the
|
|
988
|
-
// outbound route's own "Bridge to {entry chain}" section
|
|
989
|
-
// ({@link buildOutboundRouteData}), which quotes this identical constant for
|
|
990
|
-
// the identical wait. Leaving it null here was the one crossing section that
|
|
991
|
-
// never got it, so the strip and the tracker both had no duration to show for
|
|
992
|
-
// the most common route in the product.
|
|
993
426
|
etaMinutes: exports.BRIDGE_SETTLEMENT_ETA_MINUTES,
|
|
994
427
|
hops: [hop],
|
|
995
428
|
advanced,
|
|
996
429
|
};
|
|
997
430
|
return {
|
|
998
431
|
kind: 'direct',
|
|
999
|
-
// The direct omnibridge route IS the crossing — it does not pivot through a
|
|
1000
|
-
// distinct aggregator entry chain — so `entryChainId` is null.
|
|
1001
432
|
entryChainId: null,
|
|
1002
433
|
headlineProviderKey: provider,
|
|
1003
434
|
sections: [section],
|
|
@@ -1005,66 +436,19 @@ const buildDirectRouteData = (roots) => {
|
|
|
1005
436
|
amount: estimatedAmountOut,
|
|
1006
437
|
decimals: assetOut.decimals,
|
|
1007
438
|
symbol: assetOut.symbol,
|
|
1008
|
-
// Priced with the SOURCE asset's price, deliberately, even though the amount is
|
|
1009
|
-
// counted in the destination token's units. A direct crossing hands back the
|
|
1010
|
-
// same asset on another chain, and the bridged counterpart routinely trades at
|
|
1011
|
-
// a discount to it — pricing the two sides from their own feeds made a
|
|
1012
|
-
// zero-fee crossing report a loss (100 USDC in, 100 USDC out, "$100.03" paid
|
|
1013
|
-
// and "$99.42" received). The delivered COUNT still comes from the destination
|
|
1014
|
-
// token's decimals; only the basis is shared. The destination's own price
|
|
1015
|
-
// remains the fallback so a route whose SOURCE side is unpriced still shows a
|
|
1016
|
-
// value rather than nothing.
|
|
1017
439
|
usdValue: (0, usd_value_js_1.usdValueOfAmount)({
|
|
1018
440
|
balance: estimatedAmountOut,
|
|
1019
441
|
decimals: assetOut.decimals,
|
|
1020
442
|
price: assetInUsdPrice ?? assetOutUsdPrice ?? null,
|
|
1021
443
|
}),
|
|
1022
444
|
},
|
|
1023
|
-
// The direct route is a single signature, so its aggregate cost is that one
|
|
1024
|
-
// section's fee: the bridge's own fee plus delivery. Null when either is unknown.
|
|
1025
445
|
totalCostUsdValue: feeUsdValue,
|
|
1026
|
-
// THE SAME BASIS AGGREGATED ROUTES ARE RANKED ON: delivered value less what
|
|
1027
|
-
// was paid. The delivered amount is what is sent less both fees, and both
|
|
1028
|
-
// sides are priced on the source token, so the difference is exactly the
|
|
1029
|
-
// negated cost. It leaves out the sender's own network gas, which this
|
|
1030
|
-
// route does not know; an aggregated route counts it when its quote does.
|
|
1031
446
|
netValueUsd: feeUsdValue === null ? null : -feeUsdValue,
|
|
1032
|
-
// The direct route is the only option for now, so it is always the best.
|
|
1033
447
|
isBest: true,
|
|
1034
448
|
deltaPercent: null,
|
|
1035
449
|
};
|
|
1036
450
|
};
|
|
1037
451
|
exports.buildDirectRouteData = buildDirectRouteData;
|
|
1038
|
-
/**
|
|
1039
|
-
* Builds the TWO-SIGNATURE OUTBOUND raw {@link RouteData} from PulseChain, across
|
|
1040
|
-
* an omnibridge entry chain (Ethereum or Binance Smart Chain), to wherever the
|
|
1041
|
-
* second signature lands.
|
|
1042
|
-
*
|
|
1043
|
-
* The route is exactly TWO {@link RouteSectionData}s:
|
|
1044
|
-
* - SECTION 1 ("Bridge to {entry chain}") — the omnibridge out.
|
|
1045
|
-
* - SECTION 2 ("Complete at {final chain}") — DEFERRED. Signed only after section
|
|
1046
|
-
* 1's crossing settles, and carrying no binding quote until then.
|
|
1047
|
-
*
|
|
1048
|
-
* SECTION 2 HAS TWO SHAPES, and `roots.onward` is what picks between them.
|
|
1049
|
-
* Without it the deferred leg is a swap that stays on the entry chain: the
|
|
1050
|
-
* existing behaviour, down to its approximate ~0.29% PulseX fee. With it the
|
|
1051
|
-
* deferred leg is an onward BRIDGE to `onward.chainId`, carried by the named
|
|
1052
|
-
* provider, and it states NO fee — see the fee note in the body for why quoting
|
|
1053
|
-
* the swap constant there would be a fabricated number.
|
|
1054
|
-
*
|
|
1055
|
-
* WHY THE FIRST LEG CANNOT WIDEN THE SAME WAY. The bridged message carries
|
|
1056
|
-
* `(token, receiver, value, data)` and no chain id, and a mediator's counterpart
|
|
1057
|
-
* is wired in at deploy time, so the omnibridge's destination is structural
|
|
1058
|
-
* rather than a parameter. The reach comes from composing a second signature on
|
|
1059
|
-
* the far side, which is what this builder draws.
|
|
1060
|
-
*
|
|
1061
|
-
* Returns null when `assetIn`, `intermediateToken`, or `finalToken` is missing,
|
|
1062
|
-
* when the crossing's destination is not one of the two omnibridge entry chains,
|
|
1063
|
-
* or when `onward.chainId` disagrees with the final token's own `chainId`.
|
|
1064
|
-
*
|
|
1065
|
-
* @param roots - the resolved settings + price + logo roots (see {@link OutboundRouteRoots})
|
|
1066
|
-
* @returns the two-section raw outbound {@link RouteData}, or null when unresolvable
|
|
1067
|
-
*/
|
|
1068
452
|
const buildOutboundRouteData = (roots) => {
|
|
1069
453
|
const { bridgeKey, assetIn, intermediateToken, finalToken, estimatedAmountOut, assetInUsdPrice, intermediateUsdPrice, assetInLogoUri, assetOutLogoUri, finalTokenLogoUri, fromChainLogoUri, intermediateChainLogoUri, onward = null, } = roots;
|
|
1070
454
|
if (!assetIn || !intermediateToken || !finalToken)
|
|
@@ -1074,21 +458,10 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1074
458
|
const entryChainName = (0, exports.resolveEntryChainName)(entryChainId);
|
|
1075
459
|
if (entryChainName === null)
|
|
1076
460
|
return null;
|
|
1077
|
-
// A TOKEN DOES NOT CARRY A CHAIN, so the caller states the onward chain and the
|
|
1078
|
-
// token states its own, and the two must agree. Reading one from the other is
|
|
1079
|
-
// the cross-chain address mistake this codebase has already paid for: an origin
|
|
1080
|
-
// address asked for on the destination chain fails silently, and succeeds by
|
|
1081
|
-
// coincidence when some unrelated contract answers at the same address.
|
|
1082
|
-
// Disagreement here means one of the two is wrong and there is no way to tell
|
|
1083
|
-
// which, so the route is not drawn at all.
|
|
1084
461
|
if (onward !== null && onward.chainId !== finalToken.chainId)
|
|
1085
462
|
return null;
|
|
1086
|
-
// Where the SECOND signature leaves the user. Same as the crossing's landing
|
|
1087
|
-
// when the deferred leg is a swap; the onward chain when it is a bridge.
|
|
1088
463
|
const finalChainId = onward === null ? entryChainId : onward.chainId;
|
|
1089
464
|
const finalChainName = onward === null ? entryChainName : onward.chainName;
|
|
1090
|
-
// SECTION 1 fee: the crossing's bridge fee plus its delivery fee, priced in the
|
|
1091
|
-
// source token. Null when either is unknown.
|
|
1092
465
|
const { feeUsdValue: sectionOneFeeValue, fees: sectionOneFees } = crossingCost({
|
|
1093
466
|
bridgeKey,
|
|
1094
467
|
feeRoots: roots,
|
|
@@ -1096,16 +469,6 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1096
469
|
arrivingToken: toFeeToken(intermediateToken, entryChainId),
|
|
1097
470
|
sourceUsdPrice: assetInUsdPrice,
|
|
1098
471
|
});
|
|
1099
|
-
// SECTION 2's swap fee is APPROXIMATE: PulseX takes ~0.29% of the swapped value,
|
|
1100
|
-
// applied here to the bridged intermediate amount.
|
|
1101
|
-
//
|
|
1102
|
-
// AN ONWARD HOP GETS NO FIGURE AT ALL, and that is the honest answer rather
|
|
1103
|
-
// than a gap. `PULSEX_SWAP_FEE_BASIS_POINTS` prices a PulseX swap; an
|
|
1104
|
-
// aggregator bridge to another chain charges its own spread plus that chain's
|
|
1105
|
-
// gas, and neither is 0.29%. Reusing the constant would print a number that
|
|
1106
|
-
// looks measured and is not, beside section 1's fee, which really is measured.
|
|
1107
|
-
// The aggregator quotes the leg when the user returns to sign it, and until
|
|
1108
|
-
// then null is what we know.
|
|
1109
472
|
const sectionTwoFeeValue = onward !== null
|
|
1110
473
|
? null
|
|
1111
474
|
: (0, usd_value_js_1.usdValueOfAmount)({
|
|
@@ -1119,8 +482,6 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1119
482
|
title: (0, exports.omnibridgeProviderDisplayName)(provider),
|
|
1120
483
|
conversion: `${assetIn.symbol} → ${intermediateToken.symbol}`,
|
|
1121
484
|
feeUsdValue: sectionOneFeeValue,
|
|
1122
|
-
// Delivery auto-completes on the entry chain, so the single signature covers
|
|
1123
|
-
// the whole omnibridge leg.
|
|
1124
485
|
automatic: true,
|
|
1125
486
|
};
|
|
1126
487
|
const sectionOne = {
|
|
@@ -1144,9 +505,6 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1144
505
|
chainLogoUri: intermediateChainLogoUri,
|
|
1145
506
|
},
|
|
1146
507
|
],
|
|
1147
|
-
// The crossing lands the user in the INTERMEDIATE token on the entry chain —
|
|
1148
|
-
// the whole point of naming a step's landing, since this is exactly the stop
|
|
1149
|
-
// a two-signature route leaves you sitting at while you wait to sign again.
|
|
1150
508
|
landing: {
|
|
1151
509
|
amount: estimatedAmountOut,
|
|
1152
510
|
decimals: intermediateToken.decimals,
|
|
@@ -1158,16 +516,10 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1158
516
|
}),
|
|
1159
517
|
},
|
|
1160
518
|
feeUsdValue: sectionOneFeeValue,
|
|
1161
|
-
// Who takes what on the crossing: the bridge, then the delivery service.
|
|
1162
519
|
fees: sectionOneFees,
|
|
1163
|
-
// The omnibridge crossing out is the ~20-minute wait the deferred swap sits
|
|
1164
|
-
// behind, so the wait is shown HERE, on the crossing leg.
|
|
1165
520
|
etaMinutes: exports.BRIDGE_SETTLEMENT_ETA_MINUTES,
|
|
1166
521
|
hops: [bridgeHop],
|
|
1167
522
|
};
|
|
1168
|
-
// SECTION 2 — DEFERRED. Signed only after section 1 settles. A swap on the entry
|
|
1169
|
-
// chain (approximate ~0.29% fee), or an onward bridge carried by a named
|
|
1170
|
-
// aggregator (no fee stated until it is quoted).
|
|
1171
523
|
const deferredHop = onward === null
|
|
1172
524
|
? {
|
|
1173
525
|
providerName: 'Swap',
|
|
@@ -1177,9 +529,6 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1177
529
|
automatic: false,
|
|
1178
530
|
}
|
|
1179
531
|
: {
|
|
1180
|
-
// THE SERVICE THAT WILL ACTUALLY CARRY IT. "Swap" names PulseX, and a
|
|
1181
|
-
// reader who saw it beside a Base destination would be told the wrong
|
|
1182
|
-
// thing about who holds their funds for the second leg.
|
|
1183
532
|
providerName: onward.providerName,
|
|
1184
533
|
title: `Bridge to ${onward.chainName}`,
|
|
1185
534
|
conversion: `${intermediateToken.symbol} → ${finalToken.symbol}`,
|
|
@@ -1202,17 +551,11 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1202
551
|
symbol: finalToken.symbol,
|
|
1203
552
|
address: finalToken.address,
|
|
1204
553
|
decimals: finalToken.decimals,
|
|
1205
|
-
// THE LINE THIS WHOLE CHANGE IS ABOUT. Pinned to `entryChainId`, the
|
|
1206
|
-
// deferred leg could only ever be a swap where the crossing landed, and
|
|
1207
|
-
// the route strip drew a Base token wearing Ethereum's badge.
|
|
1208
554
|
chainId: finalChainId,
|
|
1209
555
|
logoUri: finalTokenLogoUri,
|
|
1210
556
|
chainLogoUri: onward === null ? intermediateChainLogoUri : onward.chainLogoUri,
|
|
1211
557
|
},
|
|
1212
558
|
],
|
|
1213
|
-
// The deferred swap names the token it will land in but NOT an amount: it is
|
|
1214
|
-
// quoted only when the user returns, and stating a figure now would present an
|
|
1215
|
-
// estimate with the same weight as the crossing's real one.
|
|
1216
559
|
landing: {
|
|
1217
560
|
amount: null,
|
|
1218
561
|
decimals: finalToken.decimals,
|
|
@@ -1220,13 +563,6 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1220
563
|
usdValue: null,
|
|
1221
564
|
},
|
|
1222
565
|
feeUsdValue: sectionTwoFeeValue,
|
|
1223
|
-
// The swap itself is near-instant (~10 seconds); the ~20-minute wait it sits
|
|
1224
|
-
// behind is shown on section 1 (the crossing).
|
|
1225
|
-
//
|
|
1226
|
-
// AN ONWARD HOP STATES NO ESTIMATE, for the same reason it states no fee. Ten
|
|
1227
|
-
// seconds is how long a PulseX swap takes. How long an aggregator takes to
|
|
1228
|
-
// carry value to another chain depends on the pair and on the route it picks,
|
|
1229
|
-
// so the figure would be borrowed from a different operation.
|
|
1230
566
|
...(onward === null ? { etaSeconds: PULSEX_SWAP_ETA_SECONDS } : {}),
|
|
1231
567
|
deferred: true,
|
|
1232
568
|
deferredNote: onward === null
|
|
@@ -1235,72 +571,37 @@ const buildOutboundRouteData = (roots) => {
|
|
|
1235
571
|
hops: [deferredHop],
|
|
1236
572
|
};
|
|
1237
573
|
return {
|
|
1238
|
-
// 'direct' EVEN WHEN THE SECOND LEG IS AN AGGREGATOR, and this is a decision
|
|
1239
|
-
// rather than a leftover. `kind` is what the interface dispatches EXECUTION
|
|
1240
|
-
// on: `executesViaConsolidated` sends a 'direct' route through the omnibridge
|
|
1241
|
-
// sender and everything else through the aggregator sender. The only
|
|
1242
|
-
// signature this route asks for now is section 1 — a plain omnibridge
|
|
1243
|
-
// crossing out of PulseChain — so the omnibridge sender is the correct one.
|
|
1244
|
-
// Calling it 'aggregated' would hand the button to a sender holding no quote
|
|
1245
|
-
// for it, because the onward leg is not quoted until the user returns and
|
|
1246
|
-
// starts a fresh route from the entry chain.
|
|
1247
574
|
kind: 'direct',
|
|
1248
575
|
entryChainId: null,
|
|
1249
|
-
// The route is shown crossing VIA the chain it bridges to. Already correct
|
|
1250
|
-
// for an onward hop: PulseChain to Base really does travel via Ethereum.
|
|
1251
576
|
viaChainId: entryChainId,
|
|
1252
577
|
headlineProviderKey: provider,
|
|
1253
578
|
sections: [sectionOne, sectionTwo],
|
|
1254
579
|
output: {
|
|
1255
|
-
// The final amount is quoted on return (deferred leg) — unknown at display time.
|
|
1256
580
|
amount: null,
|
|
1257
581
|
decimals: finalToken.decimals,
|
|
1258
582
|
symbol: finalToken.symbol,
|
|
1259
583
|
usdValue: null,
|
|
1260
584
|
},
|
|
1261
|
-
// Aggregate cost is section 1's crossing fees plus section 2's APPROXIMATE swap
|
|
1262
|
-
// fee (~0.29%). Section 2 drops out when it states no figure (an onward hop
|
|
1263
|
-
// is quoted only on return). Section 1 cannot: it is the signature the reader
|
|
1264
|
-
// makes now, so without its cost there is no total, only the swap's guess.
|
|
1265
585
|
totalCostUsdValue: sectionOneFeeValue === null ? null : (0, exports.addUsdValues)(sectionOneFeeValue, sectionTwoFeeValue),
|
|
1266
586
|
isBest: true,
|
|
1267
587
|
deltaPercent: null,
|
|
1268
588
|
};
|
|
1269
589
|
};
|
|
1270
590
|
exports.buildOutboundRouteData = buildOutboundRouteData;
|
|
1271
|
-
/**
|
|
1272
|
-
* Builds a single composable raw {@link RouteSectionData} for a swap on PulseX,
|
|
1273
|
-
* the decentralized exchange on PulseChain. A composing route appends this to its
|
|
1274
|
-
* `sections` array. Both tokens live on PulseChain (chain id 369), so both
|
|
1275
|
-
* token-flow nodes carry the PulseChain chain badge.
|
|
1276
|
-
*
|
|
1277
|
-
* The section carries raw numeric fields (`feePercent`, `priceImpactPercent`,
|
|
1278
|
-
* `feeUsdValue`); the adapter (`toRouteView`) formats them into the display
|
|
1279
|
-
* breakdown string.
|
|
1280
|
-
*
|
|
1281
|
-
* @param roots - the section's identity, tokens, pricing, and logos
|
|
1282
|
-
* @returns one raw PulseX swap {@link RouteSectionData}
|
|
1283
|
-
*/
|
|
1284
591
|
const buildPulseXSwapSectionData = (roots) => {
|
|
1285
592
|
const { id, fromToken, toToken, feeUsdValue = null, priceImpactPercent = null, landedAmount = null, landedUsdValue = null, etaMinutes = null, etaSeconds = null, fromTokenLogoUri, toTokenLogoUri, pulsechainChainLogoUri, deferred = false, advanced = null, } = roots;
|
|
1286
593
|
const swapHop = {
|
|
1287
594
|
providerName: 'PulseX',
|
|
1288
|
-
// Resolve the PulseX logo via the provider key in the adapter; the raw hop
|
|
1289
|
-
// carries only the key, not the url.
|
|
1290
595
|
providerKey: 'PulseX',
|
|
1291
596
|
title: `Swap to ${toToken.symbol}`,
|
|
1292
597
|
conversion: `${fromToken.symbol} → ${toToken.symbol}`,
|
|
1293
598
|
feeUsdValue,
|
|
1294
|
-
// A standalone PulseX swap is the user's own signature, not an automatic hop.
|
|
1295
599
|
automatic: false,
|
|
1296
600
|
};
|
|
1297
601
|
const tokenFlow = [
|
|
1298
602
|
{ symbol: fromToken.symbol, address: fromToken.address, chainId: exports.pulsechainChainId, decimals: fromToken.decimals, logoUri: fromTokenLogoUri, chainLogoUri: pulsechainChainLogoUri },
|
|
1299
603
|
{ symbol: toToken.symbol, address: toToken.address, chainId: exports.pulsechainChainId, decimals: toToken.decimals, logoUri: toTokenLogoUri, chainLogoUri: pulsechainChainLogoUri },
|
|
1300
604
|
];
|
|
1301
|
-
// The swap section always shows the flat PulseX fee in its breakdown; the price
|
|
1302
|
-
// impact clause is added by the adapter only when the impact is present and
|
|
1303
|
-
// non-trivial. Round the impact to two decimals and drop it when it is <= 0.
|
|
1304
605
|
const roundedImpact = priceImpactPercent === null || Number(priceImpactPercent.toFixed(2)) <= 0
|
|
1305
606
|
? null
|
|
1306
607
|
: Number(priceImpactPercent.toFixed(2));
|
|
@@ -1309,8 +610,6 @@ const buildPulseXSwapSectionData = (roots) => {
|
|
|
1309
610
|
label: 'Swap on PulseX',
|
|
1310
611
|
providerKey: 'PulseX',
|
|
1311
612
|
tokenFlow,
|
|
1312
|
-
// The swap is the last step of whatever composes it, so what it lands in is
|
|
1313
|
-
// what the user ends up holding.
|
|
1314
613
|
landing: {
|
|
1315
614
|
amount: landedAmount,
|
|
1316
615
|
decimals: toToken.decimals,
|
|
@@ -1318,177 +617,64 @@ const buildPulseXSwapSectionData = (roots) => {
|
|
|
1318
617
|
usdValue: landedUsdValue,
|
|
1319
618
|
},
|
|
1320
619
|
feeUsdValue,
|
|
1321
|
-
// 0.29 — the flat PulseX fee percentage, kept in lockstep with the basis-point
|
|
1322
|
-
// constant so the breakdown line tracks the rate the estimate applies.
|
|
1323
620
|
feePercent: Number(PULSEX_SWAP_FEE_BASIS_POINTS) / 100,
|
|
1324
621
|
priceImpactPercent: roundedImpact,
|
|
1325
622
|
etaMinutes,
|
|
1326
623
|
etaSeconds,
|
|
1327
624
|
hops: [swapHop],
|
|
1328
|
-
// A PulseX swap is normally an immediate signature; deferred only when it sits
|
|
1329
|
-
// behind a prior section's crossing.
|
|
1330
625
|
deferred: deferred ? true : undefined,
|
|
1331
|
-
// NO `bridgeFrom`/`bridgeTo` — a same-chain swap crosses no bridge, and
|
|
1332
|
-
// leaving them unset (rather than null) keeps the disclosure from naming a
|
|
1333
|
-
// mediator that never runs. `destinationRouter` is the PulseX router
|
|
1334
|
-
// itself here, not a delivery leg inside a crossing — see the field's own
|
|
1335
|
-
// note on `RouteSectionData`.
|
|
1336
626
|
advanced: advanced === null ? undefined : advanced,
|
|
1337
627
|
};
|
|
1338
628
|
};
|
|
1339
629
|
exports.buildPulseXSwapSectionData = buildPulseXSwapSectionData;
|
|
1340
|
-
/**
|
|
1341
|
-
* Composes a raw {@link RouteData} for the conversion route: bridge a liquid anchor
|
|
1342
|
-
* token onto PulseChain, then a DEFERRED PulseX swap to the wanted token.
|
|
1343
|
-
*
|
|
1344
|
-
* Operates entirely at the raw data level — no display formatting. The caller runs
|
|
1345
|
-
* the result through `toRouteView` to get the display-ready `RouteOption`.
|
|
1346
|
-
*
|
|
1347
|
-
* Per the owner's two locked decisions:
|
|
1348
|
-
*
|
|
1349
|
-
* - **LOCKED DECISION 1 — normalise via the liquid anchor.** When the omnibridge
|
|
1350
|
-
* cannot deliver the wanted token directly (the wanted token has not yet been
|
|
1351
|
-
* bridged, or its canonical foreign-chain pair is illiquid), the rails bridge a
|
|
1352
|
-
* well-known liquid anchor token instead and a PulseX swap leg converts it on
|
|
1353
|
-
* PulseChain. The {@link ConversionRouteComposition.baseData} is the raw route
|
|
1354
|
-
* that delivers that anchor; this function appends the conversion leg. The two
|
|
1355
|
-
* combined sections represent one complete user journey: bridge to the anchor,
|
|
1356
|
-
* then swap the anchor to the wanted token.
|
|
1357
|
-
*
|
|
1358
|
-
* - **LOCKED DECISION 2 — the swap is a deferred second signature.** The PulseX
|
|
1359
|
-
* swap can only be signed AFTER the PulseChain Bridge lands the anchor on
|
|
1360
|
-
* PulseChain — the funds must arrive before the user can authorise the swap. The
|
|
1361
|
-
* appended section therefore carries `deferred: true` and an ESTIMATED fee that
|
|
1362
|
-
* firms up on arrival (price impact and pool depth may shift in the ~20 minutes
|
|
1363
|
-
* the crossing takes). Do not collapse these into one signature.
|
|
1364
|
-
*
|
|
1365
|
-
* The composed route:
|
|
1366
|
-
* - keeps the base route's provider and entry chain (`entryChainId` / `via`) — it
|
|
1367
|
-
* is the same PulseChain Bridge crossing with a swap bolted on at the
|
|
1368
|
-
* destination;
|
|
1369
|
-
* - takes a DISTINCT identifier (`<baseRouteId>-pulsex-swap`) so React keys and
|
|
1370
|
-
* selection never collide with the bare anchor-delivery route;
|
|
1371
|
-
* - re-drives the output to the wanted token: the estimate's delivered amount, its
|
|
1372
|
-
* United States dollar value, and the wanted token's symbol (the bare anchor
|
|
1373
|
-
* route would otherwise claim the anchor as the delivered token);
|
|
1374
|
-
* - sums the swap's total cost (fee + price impact) into `totalCostUsdValue` so
|
|
1375
|
-
* the aggregate cost is honest — aggregator spread + omnibridge crossing +
|
|
1376
|
-
* PulseX swap — and subtracts it from `netValueUsd`.
|
|
1377
|
-
*
|
|
1378
|
-
* @param composition - the base route data, swap tokens, estimate, logos, and the
|
|
1379
|
-
* injected base route id (computed by the caller via `toRouteView`)
|
|
1380
|
-
* @returns the composed raw two-section conversion route (bridge, then deferred PulseX swap)
|
|
1381
|
-
*/
|
|
1382
630
|
const buildConversionRouteData = (composition) => {
|
|
1383
631
|
const { baseData, baseRouteId, anchorToken, wantedToken, estimate, anchorTokenLogoUri, wantedTokenLogoUri, pulsechainChainLogoUri, } = composition;
|
|
1384
|
-
// The anchor bridges the two legs: it is BOTH the base route's delivered node and
|
|
1385
|
-
// the swap's input node. Reuse the delivered node's pre-resolved logo so the same
|
|
1386
|
-
// token shows ONE icon across the boundary; fall back to the threaded anchor logo.
|
|
1387
632
|
const deliveredAnchorNode = baseData.sections.at(-1)?.tokenFlow.at(-1);
|
|
1388
633
|
const boundaryAnchorLogoUri = deliveredAnchorNode?.logoUri ?? anchorTokenLogoUri;
|
|
1389
634
|
const swapSection = (0, exports.buildPulseXSwapSectionData)({
|
|
1390
635
|
id: `${baseRouteId}-pulsex-swap`,
|
|
1391
636
|
fromToken: anchorToken,
|
|
1392
637
|
toToken: wantedToken,
|
|
1393
|
-
// The section cost is the swap's TOTAL value loss (fee + price impact) so the
|
|
1394
|
-
// route reconciles: input value − each step's cost = delivered output.
|
|
1395
638
|
feeUsdValue: estimate.costUsdValue,
|
|
1396
639
|
priceImpactPercent: estimate.priceImpactPercent,
|
|
1397
|
-
// The swap lands the user in the wanted token. Its value is the bridge leg's
|
|
1398
|
-
// landing less what the swap costs — the same subtraction the route's output
|
|
1399
|
-
// uses, so the last step's landing figure and the route's delivered figure
|
|
1400
|
-
// cannot drift apart.
|
|
1401
640
|
landedAmount: estimate.outputAmount,
|
|
1402
641
|
landedUsdValue: (0, exports.subtractUsdValues)(baseData.output.usdValue, estimate.costUsdValue),
|
|
1403
|
-
// The swap is near-instant (~10s); the ~20-minute wait sits on the crossing below.
|
|
1404
642
|
etaSeconds: PULSEX_SWAP_ETA_SECONDS,
|
|
1405
643
|
fromTokenLogoUri: boundaryAnchorLogoUri,
|
|
1406
644
|
toTokenLogoUri: wantedTokenLogoUri,
|
|
1407
645
|
pulsechainChainLogoUri,
|
|
1408
|
-
// The bridge must land on PulseChain before the swap can be signed.
|
|
1409
646
|
deferred: true,
|
|
1410
647
|
});
|
|
1411
|
-
// The ~20-minute crossing wait belongs on the section that LANDS THE FUNDS ON
|
|
1412
|
-
// PULSECHAIN, replacing whatever entry-chain-only estimate that section carried.
|
|
1413
|
-
//
|
|
1414
|
-
// THE INDEX IS NOT A PROXY HERE, and the difference is worth stating because
|
|
1415
|
-
// this project has been bitten by one. A positional "last section" stood in for
|
|
1416
|
-
// "the section Gibs delivers" elsewhere and broke the moment a deferred leg
|
|
1417
|
-
// appeared. This is the other case: a base route is BUILT to end on PulseChain —
|
|
1418
|
-
// that is what makes it a base a swap can be appended to — so its final section
|
|
1419
|
-
// IS the landing section by construction, for a one-signature base and for the
|
|
1420
|
-
// two-signature NEAR Intents base alike. The name says which fact is meant, so a
|
|
1421
|
-
// future base that ends somewhere else fails a test rather than a reader.
|
|
1422
648
|
const pulsechainLandingSectionIndex = baseData.sections.length - 1;
|
|
1423
649
|
const bridgeSections = baseData.sections.map((section, index) => index === pulsechainLandingSectionIndex
|
|
1424
650
|
? { ...section, etaMinutes: exports.BRIDGE_SETTLEMENT_ETA_MINUTES, etaSeconds: null }
|
|
1425
651
|
: section);
|
|
1426
652
|
return {
|
|
1427
653
|
...baseData,
|
|
1428
|
-
// The conversion route crosses the aggregator entry chain then swaps on PulseX.
|
|
1429
654
|
kind: 'conversion',
|
|
1430
|
-
// Carry the aggregator entry chain so Bridge.tsx can link back to the candidate.
|
|
1431
655
|
entryChainId: baseData.entryChainId ?? null,
|
|
1432
656
|
sections: [...bridgeSections, swapSection],
|
|
1433
657
|
output: {
|
|
1434
658
|
amount: estimate.outputAmount,
|
|
1435
659
|
decimals: wantedToken.decimals,
|
|
1436
660
|
symbol: wantedToken.symbol,
|
|
1437
|
-
// The wanted token's COUNT comes from the swap quote, but its value continues
|
|
1438
|
-
// the base route's basis: what the anchor was worth on arrival, less the swap's
|
|
1439
|
-
// cost. Re-pricing the wanted token against its own feed — which is what this
|
|
1440
|
-
// used to do — sets the figure by the router's realized rate instead, and that
|
|
1441
|
-
// rate routinely disagrees with the ratio of the two feeds by about a dollar on
|
|
1442
|
-
// a hundred. The route then appeared to deliver more than its own cost rows allowed.
|
|
1443
661
|
usdValue: (0, exports.subtractUsdValues)(baseData.output.usdValue, estimate.costUsdValue),
|
|
1444
662
|
},
|
|
1445
|
-
// Aggregate cost = base cost + the swap's total loss; null only when both unknown.
|
|
1446
663
|
totalCostUsdValue: (0, exports.addUsdValues)(baseData.totalCostUsdValue, estimate.costUsdValue),
|
|
1447
|
-
// Net = base net − the swap's total loss; null when either is unknown.
|
|
1448
664
|
netValueUsd: baseData.netValueUsd == null || estimate.costUsdValue == null
|
|
1449
665
|
? null
|
|
1450
666
|
: baseData.netValueUsd - estimate.costUsdValue,
|
|
1451
667
|
};
|
|
1452
668
|
};
|
|
1453
669
|
exports.buildConversionRouteData = buildConversionRouteData;
|
|
1454
|
-
/**
|
|
1455
|
-
* Derives a {@link PulseXSwapEstimateData} — the raw NUMERIC form of the PulseX
|
|
1456
|
-
* swap estimate — from a PulseX `getAmountsOut` quote.
|
|
1457
|
-
*
|
|
1458
|
-
* Returns raw numbers rather than formatted currency strings, so the conversion
|
|
1459
|
-
* composition can flow them straight into a {@link RouteData} without re-parsing
|
|
1460
|
-
* currency strings.
|
|
1461
|
-
*
|
|
1462
|
-
* Returns null when the quote is absent or empty (the router reverted or has not
|
|
1463
|
-
* loaded) — the composing route then has no swap leg to append.
|
|
1464
|
-
*
|
|
1465
|
-
* @param params.amountsOut - the router's amounts-out array, or null when unquoted
|
|
1466
|
-
* @param params.inputAmount - the anchor amount entering the swap, in its base units
|
|
1467
|
-
* @param params.inputToken - the anchor token (the swap input)
|
|
1468
|
-
* @param params.inputUsdPrice - the anchor token's per-token dollar price, or null
|
|
1469
|
-
* @param params.outputToken - the wanted token (the swap output)
|
|
1470
|
-
* @param params.outputUsdPrice - the wanted token's per-token dollar price, or null
|
|
1471
|
-
* @param params.referenceAmountsOut - marginal reference quote amounts-out, or null
|
|
1472
|
-
* @param params.referenceInputAmount - the reference quote's input amount
|
|
1473
|
-
* @returns the derived estimate with numeric fields, or null when the quote yields no output amount
|
|
1474
|
-
*/
|
|
1475
670
|
const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inputUsdPrice, outputToken, outputUsdPrice, referenceAmountsOut = null, referenceInputAmount = 0n, }) => {
|
|
1476
671
|
if (!amountsOut || amountsOut.length === 0)
|
|
1477
672
|
return null;
|
|
1478
673
|
const outputAmount = amountsOut[amountsOut.length - 1];
|
|
1479
674
|
if (typeof outputAmount !== 'bigint')
|
|
1480
675
|
return null;
|
|
1481
|
-
// The swap fee is APPROXIMATE: PulseX takes a flat ~0.29% of the trade. Apply that
|
|
1482
|
-
// rate to the swapped value, preferring the input (anchor) side and falling back
|
|
1483
|
-
// to the output (wanted) side, so the fee still shows when only ONE side is priced.
|
|
1484
676
|
const feeUsdValue = (0, usd_value_js_1.usdValueOfAmount)({ balance: (inputAmount * PULSEX_SWAP_FEE_BASIS_POINTS) / BASIS_POINTS_DENOMINATOR, decimals: inputToken.decimals, price: inputUsdPrice }) ??
|
|
1485
677
|
(0, usd_value_js_1.usdValueOfAmount)({ balance: (outputAmount * PULSEX_SWAP_FEE_BASIS_POINTS) / BASIS_POINTS_DENOMINATOR, decimals: outputToken.decimals, price: outputUsdPrice });
|
|
1486
|
-
// PRICE IMPACT comes from the MARGINAL reference quote, NOT from differencing the
|
|
1487
|
-
// two PulseChain USD prices: those prices are themselves PulseX pool quotes, so
|
|
1488
|
-
// value-in minus value-out cancels the fee and impact to ~$0 (the bug this
|
|
1489
|
-
// replaces). Instead compare the full trade's realized rate against the tiny
|
|
1490
|
-
// reference quote's near-spot rate. The swap fee rides in BOTH `getAmountsOut`
|
|
1491
|
-
// results, so it cancels in the ratio and the gap is price impact ALONE.
|
|
1492
678
|
const marginalRate = (() => {
|
|
1493
679
|
if (!referenceAmountsOut || referenceAmountsOut.length === 0)
|
|
1494
680
|
return null;
|
|
@@ -1505,18 +691,11 @@ const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inpu
|
|
|
1505
691
|
})();
|
|
1506
692
|
const inputWhole = Number((0, viem_1.formatUnits)(inputAmount, inputToken.decimals));
|
|
1507
693
|
const outputWhole = Number((0, viem_1.formatUnits)(outputAmount, outputToken.decimals));
|
|
1508
|
-
// The output the trade WOULD deliver at the marginal (near-spot) rate with no
|
|
1509
|
-
// price impact — fee still applied, since the marginal rate carries it too.
|
|
1510
694
|
const idealOutputWhole = marginalRate !== null ? inputWhole * marginalRate : null;
|
|
1511
|
-
// The impact loss in OUTPUT-token units: what the marginal rate promised minus
|
|
1512
|
-
// what the curve actually delivered. The fee is in both, so this is impact only.
|
|
1513
695
|
const impactLossOutput = idealOutputWhole !== null ? Math.max(0, idealOutputWhole - outputWhole) : null;
|
|
1514
696
|
const priceImpactPercent = impactLossOutput !== null && idealOutputWhole !== null && idealOutputWhole > 0
|
|
1515
697
|
? Math.max(0, (impactLossOutput / idealOutputWhole) * 100)
|
|
1516
698
|
: null;
|
|
1517
|
-
// Value the impact loss in dollars — preferring the output token's price, falling
|
|
1518
|
-
// back to the input side via the marginal rate so the impact still shows when
|
|
1519
|
-
// only the anchor is priced.
|
|
1520
699
|
const impactUsd = (() => {
|
|
1521
700
|
if (impactLossOutput === null)
|
|
1522
701
|
return null;
|
|
@@ -1527,9 +706,6 @@ const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inpu
|
|
|
1527
706
|
}
|
|
1528
707
|
return null;
|
|
1529
708
|
})();
|
|
1530
|
-
// Total cost is the flat fee PLUS the price impact — both real, neither cancelled.
|
|
1531
|
-
// Round the fee to cents the SAME way the pre-split builder did (it summed the
|
|
1532
|
-
// already-formatted fee string), so the total cost is identical to the cent.
|
|
1533
709
|
const feeRounded = feeUsdValue === null ? null : (0, format_js_1.roundUsdToCents)(feeUsdValue);
|
|
1534
710
|
const costUsdValue = feeRounded !== null && Number.isFinite(feeRounded)
|
|
1535
711
|
? feeRounded + (impactUsd ?? 0)
|
|
@@ -1549,33 +725,6 @@ const buildPulseXSwapEstimateData = ({ amountsOut, inputAmount, inputToken, inpu
|
|
|
1549
725
|
};
|
|
1550
726
|
};
|
|
1551
727
|
exports.buildPulseXSwapEstimateData = buildPulseXSwapEstimateData;
|
|
1552
|
-
/**
|
|
1553
|
-
* Composes a raw {@link RouteData} for a SAME-CHAIN PulseX swap: PulseChain on
|
|
1554
|
-
* both sides of the selection, one signature, no crossing at all.
|
|
1555
|
-
*
|
|
1556
|
-
* WHY THIS IS ITS OWN PRODUCER, RATHER THAN `buildConversionRouteData` WITH NO
|
|
1557
|
-
* BASE. The conversion route APPENDS a swap section to a bridge route that
|
|
1558
|
-
* already delivered the anchor onto PulseChain; its cost, its `entryChainId`,
|
|
1559
|
-
* and its "deferred" swap all inherit from that base. A same-chain swap has no
|
|
1560
|
-
* base — the reader already holds the input token on PulseChain before
|
|
1561
|
-
* pressing anything — so the section is signed IMMEDIATELY (`deferred` stays
|
|
1562
|
-
* false, the {@link buildPulseXSwapSectionData} default) and the route has no
|
|
1563
|
-
* `entryChainId` at all, exactly like the direct omnibridge route it sits
|
|
1564
|
-
* beside in the strip.
|
|
1565
|
-
*
|
|
1566
|
-
* NOT DEFERRED, AND THAT IS LOAD-BEARING FOR THE TRACKER. `deferred` means "the
|
|
1567
|
-
* reader has not yet signed this" (see `sectionProgress.ts`); a same-chain swap
|
|
1568
|
-
* is signed in the very press that builds it, so marking it deferred would tell
|
|
1569
|
-
* the tracker a signature is still owed for a transaction already broadcast.
|
|
1570
|
-
* What DOES need care downstream is the opposite failure the tracker's own
|
|
1571
|
-
* `routeChainSequence` can produce for a route whose every stop shares one
|
|
1572
|
-
* chain id — see `routeProgress.ts`'s `reachedChainIndex` for the fix that
|
|
1573
|
-
* keeps a same-chain route from reading "delivered" the instant it is merely
|
|
1574
|
-
* broadcast.
|
|
1575
|
-
*
|
|
1576
|
-
* @param roots - the route's identity, tokens, pricing, and logos
|
|
1577
|
-
* @returns the one-section same-chain swap {@link RouteData}
|
|
1578
|
-
*/
|
|
1579
728
|
const buildSameChainSwapRouteData = (roots) => {
|
|
1580
729
|
const { id, fromToken, toToken, estimate, inputUsdValue, fromTokenLogoUri, toTokenLogoUri, pulsechainChainLogoUri, advanced, } = roots;
|
|
1581
730
|
const swapSection = (0, exports.buildPulseXSwapSectionData)({
|
|
@@ -1590,13 +739,11 @@ const buildSameChainSwapRouteData = (roots) => {
|
|
|
1590
739
|
fromTokenLogoUri,
|
|
1591
740
|
toTokenLogoUri,
|
|
1592
741
|
pulsechainChainLogoUri,
|
|
1593
|
-
// Signed immediately — see the function doc above for why this must stay false.
|
|
1594
742
|
deferred: false,
|
|
1595
743
|
advanced,
|
|
1596
744
|
});
|
|
1597
745
|
return {
|
|
1598
746
|
kind: 'swap',
|
|
1599
|
-
// No aggregator entry chain: the whole journey is one signature on PulseChain.
|
|
1600
747
|
entryChainId: null,
|
|
1601
748
|
headlineProviderKey: 'PulseX',
|
|
1602
749
|
sections: [swapSection],
|
|
@@ -1610,7 +757,6 @@ const buildSameChainSwapRouteData = (roots) => {
|
|
|
1610
757
|
netValueUsd: inputUsdValue == null || estimate.costUsdValue == null
|
|
1611
758
|
? null
|
|
1612
759
|
: inputUsdValue - estimate.costUsdValue,
|
|
1613
|
-
// The swap's own near-instant duration; there is no crossing wait to add.
|
|
1614
760
|
etaMinutes: null,
|
|
1615
761
|
};
|
|
1616
762
|
};
|