@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.
Files changed (120) hide show
  1. package/README.md +33 -0
  2. package/dist/abis.js +0 -2
  3. package/dist/bridge-limits.js +0 -38
  4. package/dist/chain-info.js +0 -116
  5. package/dist/chain-names.js +0 -151
  6. package/dist/chains.js +0 -198
  7. package/dist/cjs/abis.js +0 -2
  8. package/dist/cjs/bridge-limits.js +0 -38
  9. package/dist/cjs/chain-info.js +0 -116
  10. package/dist/cjs/chain-names.js +0 -151
  11. package/dist/cjs/chains.js +0 -198
  12. package/dist/cjs/claims.js +0 -60
  13. package/dist/cjs/config.js +1 -208
  14. package/dist/cjs/consolidated-quote.js +2 -464
  15. package/dist/cjs/destination-calldata.js +0 -71
  16. package/dist/cjs/ecosystems.js +0 -183
  17. package/dist/cjs/execution.js +0 -92
  18. package/dist/cjs/fee-type.js +0 -1
  19. package/dist/cjs/hyperliquid-execution.js +0 -98
  20. package/dist/cjs/image-links.js +0 -108
  21. package/dist/cjs/lifi-execution-guard.js +65 -172
  22. package/dist/cjs/near-intents-conformance.js +0 -110
  23. package/dist/cjs/near-intents-route.d.ts +13 -0
  24. package/dist/cjs/near-intents-route.js +7 -140
  25. package/dist/cjs/near-intents.js +0 -562
  26. package/dist/cjs/origin-deposit.js +0 -56
  27. package/dist/cjs/pre-quote.js +0 -80
  28. package/dist/cjs/prices.js +0 -113
  29. package/dist/cjs/providers.js +0 -83
  30. package/dist/cjs/quote-fees.js +0 -80
  31. package/dist/cjs/referral.js +0 -75
  32. package/dist/cjs/relay-currencies.js +0 -31
  33. package/dist/cjs/relay-depository.js +52 -77
  34. package/dist/cjs/relay-quote.js +0 -394
  35. package/dist/cjs/route-steps.js +0 -100
  36. package/dist/cjs/routing/amounts.js +0 -83
  37. package/dist/cjs/routing/candidate-route.d.ts +46 -1
  38. package/dist/cjs/routing/candidate-route.js +12 -40
  39. package/dist/cjs/routing/candidate.js +0 -46
  40. package/dist/cjs/routing/carrier-provider-key.js +0 -27
  41. package/dist/cjs/routing/carriers/omnibridge.js +0 -120
  42. package/dist/cjs/routing/carriers/pulsex-paths.js +0 -57
  43. package/dist/cjs/routing/carriers/pulsex-split.js +0 -92
  44. package/dist/cjs/routing/carriers/pulsex.js +0 -279
  45. package/dist/cjs/routing/carriers/wrap.js +0 -57
  46. package/dist/cjs/routing/composed-route-quality.js +0 -95
  47. package/dist/cjs/routing/entry-chain-anchor.js +0 -27
  48. package/dist/cjs/routing/format.js +0 -50
  49. package/dist/cjs/routing/index.d.ts +1 -1
  50. package/dist/cjs/routing/index.js +3 -30
  51. package/dist/cjs/routing/producers.js +0 -854
  52. package/dist/cjs/routing/providers.js +0 -27
  53. package/dist/cjs/routing/search/compose.js +0 -56
  54. package/dist/cjs/routing/search/graph.js +0 -364
  55. package/dist/cjs/routing/search/plan-route-data.js +5 -510
  56. package/dist/cjs/routing/search/score.js +0 -344
  57. package/dist/cjs/routing/swap-transactions.js +0 -74
  58. package/dist/cjs/routing/usd-value.js +0 -12
  59. package/dist/cjs/settings.js +1 -456
  60. package/dist/cjs/swap-venues.js +0 -17
  61. package/dist/cjs/ton-connect.js +0 -82
  62. package/dist/cjs/ton-execution.js +0 -6
  63. package/dist/cjs/ton.js +0 -42
  64. package/dist/cjs/tron-execution.js +0 -145
  65. package/dist/cjs/types.d.ts +8 -0
  66. package/dist/claims.js +0 -60
  67. package/dist/config.js +1 -211
  68. package/dist/consolidated-quote.js +2 -464
  69. package/dist/destination-calldata.js +0 -71
  70. package/dist/ecosystems.js +0 -183
  71. package/dist/execution.js +0 -92
  72. package/dist/fee-type.js +0 -2
  73. package/dist/hyperliquid-execution.js +0 -98
  74. package/dist/image-links.js +0 -108
  75. package/dist/lifi-execution-guard.js +65 -172
  76. package/dist/near-intents-conformance.js +0 -110
  77. package/dist/near-intents-route.d.ts +13 -0
  78. package/dist/near-intents-route.js +5 -139
  79. package/dist/near-intents.js +0 -562
  80. package/dist/origin-deposit.js +0 -56
  81. package/dist/pre-quote.js +0 -80
  82. package/dist/prices.js +0 -113
  83. package/dist/providers.js +0 -83
  84. package/dist/quote-fees.js +0 -80
  85. package/dist/referral.js +0 -75
  86. package/dist/relay-currencies.js +0 -31
  87. package/dist/relay-depository.js +52 -77
  88. package/dist/relay-quote.js +0 -394
  89. package/dist/route-steps.js +0 -100
  90. package/dist/routing/amounts.js +0 -83
  91. package/dist/routing/candidate-route.d.ts +46 -1
  92. package/dist/routing/candidate-route.js +10 -39
  93. package/dist/routing/candidate.js +0 -46
  94. package/dist/routing/carrier-provider-key.js +0 -27
  95. package/dist/routing/carriers/omnibridge.js +0 -120
  96. package/dist/routing/carriers/pulsex-paths.js +0 -57
  97. package/dist/routing/carriers/pulsex-split.js +0 -92
  98. package/dist/routing/carriers/pulsex.js +0 -279
  99. package/dist/routing/carriers/wrap.js +0 -57
  100. package/dist/routing/composed-route-quality.js +0 -95
  101. package/dist/routing/entry-chain-anchor.js +0 -27
  102. package/dist/routing/format.js +0 -50
  103. package/dist/routing/index.d.ts +1 -1
  104. package/dist/routing/index.js +1 -29
  105. package/dist/routing/producers.js +0 -854
  106. package/dist/routing/providers.js +0 -27
  107. package/dist/routing/search/compose.js +0 -56
  108. package/dist/routing/search/graph.js +0 -364
  109. package/dist/routing/search/plan-route-data.js +6 -511
  110. package/dist/routing/search/score.js +0 -344
  111. package/dist/routing/swap-transactions.js +0 -74
  112. package/dist/routing/usd-value.js +0 -12
  113. package/dist/settings.js +1 -456
  114. package/dist/swap-venues.js +0 -17
  115. package/dist/ton-connect.js +0 -82
  116. package/dist/ton-execution.js +0 -6
  117. package/dist/ton.js +0 -42
  118. package/dist/tron-execution.js +0 -145
  119. package/dist/types.d.ts +8 -0
  120. package/package.json +3 -3
@@ -9,60 +9,18 @@ import { pulseXConversionSwapPath } from '../providers.js';
9
9
  import { PULSEX_MARGINAL_REFERENCE_DIVISOR, pulsechainChainId } from '../producers.js';
10
10
  import { assetNodeKey } from '../search/graph.js';
11
11
  import { bestPulseXPathQuote, candidatePulseXPaths, pulseXHubTokens } from './pulsex-paths.js';
12
- /**
13
- * PulseX (the PulseChain-native decentralized exchange, used here for a
14
- * conversion swap leg) as a plug-in `Carrier` for `@gibs/quotes`'s
15
- * route-search engine. GIBS-SPECIFIC — see `docs/quote-service.md`'s
16
- * "Carriers" section: "never built in ... PulseX." Registered from
17
- * `packages/ui/src/lib/state/useRouteSearch.ts` / `route-search-quotes.ts`.
18
- *
19
- * See `omnibridge.ts`'s head doc for why this returns a plain object shaped
20
- * like `Carrier` rather than importing that type.
21
- */
22
- /** The hub addresses every candidate path on PulseChain is built from — see {@link pulseXHubTokens}. */
23
12
  const hubAddresses = pulseXHubTokens.map((hub) => hub.address);
24
- /**
25
- * The wrapped-native token a chain's swaps route through, or undefined when
26
- * this package does not know one for the chain. Without it a path on that
27
- * chain can only be direct.
28
- *
29
- * @param chainId - the numeric chain id
30
- */
31
13
  const wrappedNativeFor = (chainId) => {
32
14
  const wrapped = nativeAssetOut[toChain(chainId)];
33
15
  return wrapped === undefined ? undefined : getAddress(wrapped);
34
16
  };
35
- /**
36
- * The hub tokens a path on `chainId` may pass through. PulseChain has the
37
- * full list; any other chain has only its wrapped-native token, because this
38
- * package knows no other hubs.
39
- */
40
17
  const hubsFor = (chainId) => {
41
18
  if (chainId === pulsechainChainId)
42
19
  return hubAddresses;
43
20
  const wrapped = wrappedNativeFor(chainId);
44
21
  return wrapped === undefined ? [] : [wrapped];
45
22
  };
46
- /**
47
- * Every PulseX deployment on PulseChain, V1 first: the PulseChain entry of
48
- * {@link defaultSwapVenues}. Frozen. It lists the DEFAULTS only — a carrier
49
- * built with its own `venues` offers those instead.
50
- */
51
23
  export const pulseXVenues = defaultSwapVenues[pulsechainChainId] ?? [];
52
- /**
53
- * The {@link SwapVenue} a swap edge runs on: the one its figure was quoted
54
- * against. Reads the edge's own {@link HopEdge.venue}; an edge with none runs
55
- * on the FIRST venue of its chain (V1 for PulseX) — every swap edge built
56
- * before venues existed was quoted and signed there, and the same-chain swap
57
- * sender's own hand-built edge still is.
58
- *
59
- * NEVER FALLS BACK TO ANOTHER CHAIN OR ANOTHER ROUTER. An edge that names a
60
- * router `venues` does not list gets null, so the caller can refuse it.
61
- *
62
- * @param venues - the venues the caller allows
63
- * @param edge - the edge to sign; `from` names the chain, and defaults to PulseChain when absent
64
- * @returns the venue, or null when the edge is not a swap edge or names a router that is not in `venues`
65
- */
66
24
  export const venueForEdge = (venues, edge) => {
67
25
  if (edge.carrier !== 'PulseX')
68
26
  return null;
@@ -71,50 +29,8 @@ export const venueForEdge = (venues, edge) => {
71
29
  return venues[chainId]?.[0] ?? null;
72
30
  return findSwapVenue(venues, chainId, edge.venue);
73
31
  };
74
- /**
75
- * The router a PulseX edge must be signed on, judged against the DEFAULT
76
- * venues only. Prefer {@link venueForEdge} with the venues your host passes.
77
- *
78
- * @param edge - the edge to sign
79
- * @returns the router, or null when the edge is not a swap edge or names a router that is not a default
80
- */
81
32
  export const pulseXRouterForEdge = (edge) => venueForEdge(defaultSwapVenues, edge)?.router ?? null;
82
- /**
83
- * A node's own address for a pool lookup: native (the zero address) is not a
84
- * token the router or its factory ever holds a pool for — only the chain's
85
- * wrapped-native token is — so a native node resolves to that wrapped token
86
- * before it ever reaches {@link pulseXConversionSwapPath}. Every other node
87
- * is unchanged. A chain with no known wrapped-native token leaves a native
88
- * node as it is; it has no pool, so no path prices.
89
- *
90
- * MIRRORS `useRouteSearch.ts`'s `quotePathAddress` (the UI's async `ask`
91
- * already resolves native this way through that function) — this is the
92
- * SAME rule, restated here because `prepare`/`exact` never go through the
93
- * UI's `readAmountOut` at all; see {@link pulseXCandidatePaths}'s own doc.
94
- *
95
- * @param node - the node to resolve
96
- * @returns the node's own address, or its chain's wrapped-native address for a native node
97
- */
98
33
  const poolTokenAddress = (node) => sameAddress(node.address, zeroAddress, 'evm') ? (wrappedNativeFor(node.chainId) ?? zeroAddress) : node.address;
99
- /**
100
- * The fixed Wrapped-PLS path from `from` to `to` — `[from, WPLS, to]`, or
101
- * `[from, to]` when one side is WPLS — or null when both ends resolve to the
102
- * same pool token (PLS<->WPLS is the wrap carrier's job).
103
- *
104
- * NOT THE PATH A HOP IS SIGNED ALONG. The carrier's own `exact` picks the
105
- * best of many paths ({@link candidatePulseXPaths}) and records the winner on
106
- * the hop's outcome; the signer reads it back with {@link pulseXSwapPathForHop}.
107
- * This fixed path serves only the carrier's asynchronous `ask` — the
108
- * interface's `getAmountsOut` read, which the route search never calls for a
109
- * PulseX hop, because the search only asks aggregators live.
110
- *
111
- * Native PLS resolves to WPLS first ({@link poolTokenAddress}), so a path
112
- * never starts or ends on the zero address, which has no pool.
113
- *
114
- * @param from - the hop's origin node
115
- * @param to - the hop's destination node
116
- * @returns the fixed path, or null when there is nothing for PulseX to swap
117
- */
118
34
  export const pulseXWrappedNativePath = (from, to) => {
119
35
  const fromAddress = poolTokenAddress(from);
120
36
  const toAddress = poolTokenAddress(to);
@@ -123,38 +39,16 @@ export const pulseXWrappedNativePath = (from, to) => {
123
39
  const wrappedNative = wrappedNativeFor(from.chainId);
124
40
  return wrappedNative === undefined ? [fromAddress, toAddress] : pulseXConversionSwapPath(fromAddress, toAddress, wrappedNative);
125
41
  };
126
- /**
127
- * Every path a PulseX hop from `from` to `to` may be priced along, or none
128
- * when both ends resolve to the same pool token. EVERY PULSEX PRICE AND
129
- * POOL READ IN THIS FILE GOES THROUGH THIS ONE FUNCTION, so `prepare` reads
130
- * exactly the pools `exact` will look up, and native PLS resolves to WPLS
131
- * the same way in both ({@link poolTokenAddress} — a path that started on
132
- * the zero address once left every native-PLS hop unpriced).
133
- *
134
- * @param from - the hop's origin node
135
- * @param to - the hop's destination node
136
- * @returns the candidate paths, direct first; empty when there is nothing to swap
137
- */
138
42
  const pulseXCandidatePaths = (from, to) => {
139
43
  const fromAddress = poolTokenAddress(from);
140
44
  const toAddress = poolTokenAddress(to);
141
45
  if (sameAddress(fromAddress, toAddress, 'evm'))
142
46
  return [];
143
47
  const wrappedNative = wrappedNativeFor(from.chainId);
144
- // No known wrapped-native token: the direct pool is the only path.
145
48
  if (wrappedNative === undefined)
146
49
  return [[fromAddress, toAddress]];
147
50
  return candidatePulseXPaths({ from: fromAddress, to: toAddress, wrappedNative, hubs: hubsFor(from.chainId) });
148
51
  };
149
- /**
150
- * Whether a swap venue in `venues` can carry funds directly from `from` to
151
- * `to`: both must be Ethereum-Virtual-Machine tokens on the SAME chain, that
152
- * chain must have at least one venue, and the two must resolve to different
153
- * pool tokens, so there is at least one path to price.
154
- *
155
- * NO TOKEN ALLOWLIST IS CONSULTED, on purpose: whether a pool exists is the
156
- * reserve read's question, answered in `prepare`, not this one's.
157
- */
158
52
  const pulseXCarries = (venues, from, to) => {
159
53
  if (from.ecosystem !== 'evm' || to.ecosystem !== 'evm')
160
54
  return false;
@@ -164,21 +58,6 @@ const pulseXCarries = (venues, from, to) => {
164
58
  return false;
165
59
  return pulseXCandidatePaths(from, to).length > 0;
166
60
  };
167
- /**
168
- * The swap path a planned PulseX hop must be signed along: the path its
169
- * outcome records ({@link HopOutcome.swapPath}), which is the path the
170
- * carrier's `exact` priced the figure on. NEVER REBUILT HERE — rebuilding a
171
- * path at signing time is how a quote on one path becomes a signature on
172
- * another.
173
- *
174
- * The recorded path is checked against the hop's own edge before it is
175
- * returned: it must start at the edge's input pool token, end at its output
176
- * pool token, hold only addresses, and name no token twice. A path that fails
177
- * any check is refused (null) rather than signed.
178
- *
179
- * @param hop - the planned hop to sign
180
- * @returns the path to sign, or null when the hop is not a PulseX hop, records no path, or records one that does not fit its edge
181
- */
182
61
  export const pulseXSwapPathForHop = (hop) => {
183
62
  if (hop.edge.carrier !== 'PulseX')
184
63
  return null;
@@ -196,42 +75,11 @@ export const pulseXSwapPathForHop = (hop) => {
196
75
  return null;
197
76
  return path;
198
77
  };
199
- /**
200
- * PulseX's own constant-product swap fee in basis points (29 of 10000, a
201
- * numerator of 9971) — the default for {@link amountOutAlongPath}; a
202
- * venue carries its own `feeBasisPoints`. The SAME `getAmountOut` formula every
203
- * Uniswap-V2-style router uses: `amountOut = floor(amountIn * feeNumerator *
204
- * reserveOut / (reserveIn * feeDenominator + amountIn * feeNumerator))`.
205
- *
206
- * MEASURED, NOT ASSUMED — see `uniV2Factories`'s own doc in `config.ts` for
207
- * the full account. Reading live reserves from both PulseX router
208
- * deployments (V1 `0x98bf93…`, V2 `0x165C34…`) and back-solving this ratio
209
- * against each router's own `getAmountsOut`, on a one-hop and a two-hop
210
- * path, gave the IDENTICAL ratio — `9971/10000` — for both, to the base
211
- * unit. One constant serves both, and `pulsex-carrier.fork.test.ts` pins
212
- * this claim against live chain data rather than trusting the arithmetic
213
- * alone.
214
- */
215
78
  const PULSEX_FEE_BASIS_POINTS = 29;
216
- /** Basis points in a whole: the denominator of every venue's fee. */
217
79
  const BASIS_POINTS_DENOMINATOR = 10000n;
218
- /** The minimal read-only factory ABI `prepare` needs — just enough to resolve a pool's address from its two tokens. */
219
80
  const univ2FactoryAbi = parseAbi([
220
81
  'function getPair(address tokenA, address tokenB) external view returns (address pair)',
221
82
  ]);
222
- /**
223
- * The Uniswap-V2-style constant-product `getAmountOut`, applied with
224
- * the venue's fee. Reproduces the router's own on-chain formula
225
- * exactly — see `pulsex-carrier.fork.test.ts` — so PRICE IMPACT IS ALREADY
226
- * IN HERE: this is the same nonlinear function the router evaluates, not a
227
- * flat-fee approximation with an impact correction bolted on afterward.
228
- *
229
- * @param amountIn - the input amount, in `reserveIn`'s token's base units
230
- * @param reserveIn - the pool's reserve of the input token
231
- * @param reserveOut - the pool's reserve of the output token
232
- * @param feeBasisPoints - the venue's swap fee, in basis points
233
- * @returns the output amount, in `reserveOut`'s token's base units; 0 when either reserve is 0
234
- */
235
83
  const constantProductAmountOut = (amountIn, reserveIn, reserveOut, feeBasisPoints) => {
236
84
  if (reserveIn <= 0n || reserveOut <= 0n || amountIn <= 0n)
237
85
  return 0n;
@@ -240,37 +88,11 @@ const constantProductAmountOut = (amountIn, reserveIn, reserveOut, feeBasisPoint
240
88
  const denominator = reserveIn * BASIS_POINTS_DENOMINATOR + amountInWithFee;
241
89
  return numerator / denominator;
242
90
  };
243
- /**
244
- * What `amountIn` delivers after crossing every pool of a path in order,
245
- * chaining {@link constantProductAmountOut}.
246
- *
247
- * @param amountIn - the amount entering the first pool
248
- * @param steps - the pools in order of travel
249
- * @param feeBasisPoints - the venue's swap fee in basis points; PulseX's 29 when omitted
250
- * @returns the amount leaving the last pool; 0 when any pool cannot pay out
251
- */
252
91
  export const amountOutAlongPath = (amountIn, steps, feeBasisPoints = PULSEX_FEE_BASIS_POINTS) => steps.reduce((runningAmount, step) => constantProductAmountOut(runningAmount, step.reserveIn, step.reserveOut, feeBasisPoints), amountIn);
253
- /**
254
- * How far a trade's executed rate falls below the marginal (near-spot) rate,
255
- * in percent. The marginal rate is a tiny reference trade through the same
256
- * pools (`amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR`). The swap fee rides
257
- * in both rates, so it cancels and the gap is price impact alone. A path
258
- * through several pools composes by itself: the reference trade crosses the
259
- * same pools in the same order.
260
- *
261
- * impact = (1 - (amountOut / amountIn) / (referenceAmountOut / referenceAmountIn)) * 100
262
- *
263
- * @param options.amountIn - the full trade's input
264
- * @param options.amountOut - what the full trade delivers
265
- * @param options.referenceAmountIn - the reference trade's input
266
- * @param options.referenceAmountOut - what the reference trade delivers
267
- * @returns the impact in percent, never below 0; null when a reference figure is zero (impact cannot be measured)
268
- */
269
92
  export const priceImpactAgainstMarginal = (options) => {
270
93
  const { amountIn, amountOut, referenceAmountIn, referenceAmountOut } = options;
271
94
  if (amountIn <= 0n || referenceAmountIn <= 0n || referenceAmountOut <= 0n)
272
95
  return null;
273
- // The output the trade would deliver at the marginal rate with no impact.
274
96
  const idealAmountOut = (referenceAmountOut * amountIn) / referenceAmountIn;
275
97
  if (idealAmountOut <= 0n)
276
98
  return null;
@@ -279,26 +101,12 @@ export const priceImpactAgainstMarginal = (options) => {
279
101
  const MILLIONTHS = 1000000n;
280
102
  return Number(((idealAmountOut - amountOut) * MILLIONTHS * 100n) / idealAmountOut) / Number(MILLIONTHS);
281
103
  };
282
- /** The stable key one pool is stored and looked up under — the two token addresses, lower-cased and sorted, so either query order finds the same entry. */
283
104
  const poolKey = (tokenA, tokenB) => {
284
105
  const [lower, higher] = [tokenA.toLowerCase(), tokenB.toLowerCase()].sort();
285
106
  return `${lower}|${higher}`;
286
107
  };
287
- /** The key one venue's pool is stored under: the same two tokens on V1 and on V2 are two different pools with two different reserves. */
288
108
  const venuePoolKey = (venue, tokenA, tokenB) => `${venue.chainId}|${venue.factory.toLowerCase()}|${poolKey(tokenA, tokenB)}`;
289
- /** Every consecutive (tokenIn, tokenOut) hop a PulseX swap path implies. */
290
109
  const hopsAlongPath = (path) => path.slice(0, -1).map((tokenIn, index) => [tokenIn, path[index + 1]]);
291
- /**
292
- * The pools a path crosses on one venue, each read in the direction of
293
- * travel, or null when any of them was never read — `getPair` found no pool,
294
- * or the read failed. A path with a missing pool cannot be priced, exactly as
295
- * the router's own `getAmountsOut` would revert on it.
296
- *
297
- * @param options.venue - the deployment whose pools the path trades against
298
- * @param options.path - the tokens, in order
299
- * @param options.reservesByPoolKey - every pool `prepare` read, keyed by {@link venuePoolKey}
300
- * @returns the steps in order, or null when a pool is missing
301
- */
302
110
  const stepsAlongPath = (options) => {
303
111
  const { venue, path, reservesByPoolKey } = options;
304
112
  const steps = [];
@@ -314,35 +122,6 @@ const stepsAlongPath = (options) => {
314
122
  }
315
123
  return steps;
316
124
  };
317
- /**
318
- * Batch-reads reserves for every pool `nodes`' candidate PulseX paths touch,
319
- * on EVERY venue in `venues`, once, so {@link createPulseXCarrier}'s `exact`
320
- * can answer every hop SYNCHRONOUSLY afterward.
321
- *
322
- * EACH VENUE IS READ THROUGH ITS OWN FACTORY. The same two tokens usually
323
- * have a pool on V1 and another on V2, with different reserves; the two are
324
- * stored apart ({@link venuePoolKey}) and never blended. `PULSEX_FEE_NUMERATOR`
325
- * is proven identical for both deployments (see its own doc), so the formula
326
- * is shared and only the reserves differ.
327
- *
328
- * TWO MULTICALLS ON PULSECHAIN, WHATEVER THE VENUE COUNT: `getPair` must
329
- * resolve each pool's address before `getReserves` can be read from it, so
330
- * the second read cannot join the first — but every venue's `getPair` rides
331
- * the first multicall and every pool's `getReserves` rides the second.
332
- *
333
- * THE HUB SEARCH WIDENS EACH READ, NOT THEIR NUMBER. Every pool any candidate
334
- * path crosses is read here ({@link pulseXCandidatePaths}), so `exact` can
335
- * price every path with no further call. Measured 2026-10-06: an 8-token
336
- * PulseChain universe asked 84 entries across the two reads (the fixed
337
- * Wrapped-PLS path asked about 16). The shared multicall helper splits a read
338
- * past its request-size budget into sequential posts; `getPair` crosses that
339
- * line at about 11 pool tokens (198 entries over two venues).
340
- *
341
- * @param nodes - every node the search might branch through
342
- * @param venues - the venues this carrier offers
343
- * @param clientForChain - see {@link CreatePulseXCarrierOptions}'s own field
344
- * @returns every pool this universe touches, keyed by {@link venuePoolKey} — pools with no `getPair` result are simply absent, not an error
345
- */
346
125
  const preparePulseXReserves = async ({ nodes, venues, clientForChain, }) => {
347
126
  const reservesByPoolKey = new Map();
348
127
  const chainIds = Object.keys(venues)
@@ -355,12 +134,6 @@ const preparePulseXReserves = async ({ nodes, venues, clientForChain, }) => {
355
134
  }
356
135
  return reservesByPoolKey;
357
136
  };
358
- /**
359
- * One chain's share of {@link preparePulseXReserves}: two multicalls, the
360
- * `getPair` reads and then the `getReserves` reads, over every venue of the
361
- * chain. A chain with no client, or with no chain description to multicall
362
- * against, reads nothing.
363
- */
364
137
  const preparePulseXReservesOnChain = async ({ nodes, chainId, venues, clientForChain, }) => {
365
138
  const reservesByPoolKey = new Map();
366
139
  const client = clientForChain(chainId);
@@ -405,10 +178,6 @@ const preparePulseXReservesOnChain = async ({ nodes, chainId, venues, clientForC
405
178
  const pairAddressByPoolKey = new Map();
406
179
  poolQueries.forEach((query, index) => {
407
180
  const pairAddress = factoryResults[index];
408
- // A decoded address is exactly 42 characters; a failed `allowFailure`
409
- // call's undecoded revert bytes are almost never that length — see
410
- // `omnibridge.ts`'s identical guard for why `typeof` alone cannot tell
411
- // success from failure for an address-returning call.
412
181
  if (typeof pairAddress === 'string' && pairAddress.length === 42 && pairAddress !== zeroAddress) {
413
182
  pairAddressByPoolKey.set(query.key, getAddress(pairAddress));
414
183
  }
@@ -433,23 +202,10 @@ const preparePulseXReservesOnChain = async ({ nodes, chainId, venues, clientForC
433
202
  const [reserve0, reserve1] = result;
434
203
  if (typeof reserve0 !== 'bigint' || typeof reserve1 !== 'bigint')
435
204
  return;
436
- // Standard Uniswap-V2-style factories sort a pair's tokens before
437
- // creating it, so `token0` is always the LEXICOGRAPHICALLY LOWER
438
- // address — confirmed against live PulseX pools 2026-09-24 (both V1 and
439
- // V2), never re-derived with an extra `token0()` read.
440
205
  reservesByPoolKey.set(key, { reserveOfLowerAddress: reserve0, reserveOfHigherAddress: reserve1 });
441
206
  });
442
207
  return reservesByPoolKey;
443
208
  };
444
- /**
445
- * The venues a carrier offers: every venue in `venues`, or only the routers
446
- * the host names. A named router that is in no venue is a configuration
447
- * error, thrown at construction rather than offered silently.
448
- *
449
- * @param venues - the resolved venues
450
- * @param routers - the routers the host can sign on, or undefined for all
451
- * @returns the venues, in their original order
452
- */
453
209
  const selectPulseXVenues = (venues, routers) => {
454
210
  if (routers === undefined)
455
211
  return venues;
@@ -461,18 +217,9 @@ const selectPulseXVenues = (venues, routers) => {
461
217
  }
462
218
  return Object.freeze(Object.fromEntries(Object.entries(venues).map(([chainId, list]) => [chainId, list.filter(named)])));
463
219
  };
464
- /**
465
- * Builds the PulseX conversion swap as a plug-in carrier.
466
- *
467
- * @param options - see {@link CreatePulseXCarrierOptions}
468
- * @returns the carrier — `id: 'PulseX'`
469
- */
470
220
  export const createPulseXCarrier = (options) => {
471
221
  const { readAmountOut, clientForChain } = options;
472
222
  const venues = selectPulseXVenues(resolveSwapVenues(options.venues), options.routers);
473
- // ONE EDGE PER VENUE. Each names its router as its `venue`, so the search
474
- // quotes and ranks V1 and V2 as two separate hops and the better one wins
475
- // on its own figure — and the winner is signed on the router it names.
476
223
  const edgesFrom = (from, to) => {
477
224
  if (assetNodeKey(from) === assetNodeKey(to))
478
225
  return [];
@@ -496,17 +243,10 @@ export const createPulseXCarrier = (options) => {
496
243
  return {
497
244
  ok: true,
498
245
  deliveredAmount: amountOut,
499
- // getAmountsOut is exact for the CURRENT block; no slippage tolerance is
500
- // modelled here, so the guaranteed figure is left equal to the expected
501
- // one, matching the omnibridge carrier's own convention.
502
246
  minimumDeliveredAmount: amountOut,
503
247
  claimedDurationSeconds: 0,
504
248
  };
505
249
  };
506
- // Populated by `prepare`, read by `exact` — see `omnibridge.ts`'s identical
507
- // field for why a mutable closure here is safe (client-side,
508
- // one-search-at-a-time; PulseX is likewise "never built into" the shared
509
- // display-quote service).
510
250
  let reservesByPoolKey = new Map();
511
251
  const prepare = async (nodes) => {
512
252
  if (clientForChain === undefined)
@@ -516,31 +256,19 @@ export const createPulseXCarrier = (options) => {
516
256
  const exact = (edge, amountIn) => {
517
257
  if (!pulseXCarries(venues, edge.from, edge.to))
518
258
  return null;
519
- // Quoted on the edge's OWN venue — the router it will be signed on — and
520
- // only on a venue this carrier offers.
521
259
  const venue = venueForEdge(venues, edge);
522
260
  if (venue === null)
523
261
  return null;
524
- // EVERY CANDIDATE PATH IS PRICED, AND THE BEST ONE WINS. A path with a
525
- // pool `prepare` never found is left out — the same as a router call
526
- // that would revert — and a path that pays out nothing is never picked.
527
262
  const quotes = pulseXCandidatePaths(edge.from, edge.to).flatMap((path) => {
528
263
  const steps = stepsAlongPath({ venue, path, reservesByPoolKey });
529
264
  return steps === null ? [] : [{ path, steps, deliveredAmount: amountOutAlongPath(amountIn, steps, venue.feeBasisPoints) }];
530
265
  });
531
- // No path had every pool read — a structural non-answer, not a refusal
532
- // (`exact`'s own doc: `null` means "nothing to say"). NOTE THIS DOES NOT
533
- // FALL BACK TO AN ESTIMATE: a carrier with `exact` defined never yields
534
- // an `'estimate'` outcome (see `enumerate.ts`'s `resolveHopOutcome`,
535
- // branch 1) — `null` here PRUNES the hop entirely, the same as a refusal.
536
266
  if (quotes.length === 0)
537
267
  return null;
538
268
  const best = bestPulseXPathQuote(quotes);
539
269
  if (best === null) {
540
270
  return { outcome: 'refused', refusal: { kind: 'no-quote', reason: 'no liquidity on this PulseX path', minimumAmount: null } };
541
271
  }
542
- // Price impact is measured on the CHOSEN path's own pools, with the same
543
- // reference trade as before.
544
272
  const referenceAmountIn = amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR;
545
273
  const priceImpactPercent = priceImpactAgainstMarginal({
546
274
  amountIn,
@@ -548,10 +276,6 @@ export const createPulseXCarrier = (options) => {
548
276
  referenceAmountIn,
549
277
  referenceAmountOut: amountOutAlongPath(referenceAmountIn, best.steps, venue.feeBasisPoints),
550
278
  });
551
- // The path rides on the answer, so the signer signs exactly what was
552
- // priced (`pulseXSwapPathForHop`). Impact is left off (not zero) when the
553
- // reference trade rounds away: an unknown impact must stay unknown so the
554
- // quality gate withholds the plan.
555
279
  const delivered = { outcome: 'delivered', deliveredAmount: best.deliveredAmount, swapPath: best.path };
556
280
  return priceImpactPercent === null ? delivered : { ...delivered, priceImpactPercent };
557
281
  };
@@ -562,11 +286,8 @@ export const createPulseXCarrier = (options) => {
562
286
  prepare,
563
287
  exact,
564
288
  isRateLimited: false,
565
- // A same-chain swap requires the reader's own wallet signature — never
566
- // a by-hand deposit.
567
289
  depositMode: 'wallet-signed',
568
290
  sender: 'swap',
569
- // A same-chain swap settles with the transaction itself.
570
291
  settlementSeconds: 0,
571
292
  };
572
293
  };
@@ -1,48 +1,6 @@
1
1
  import { getAddress, zeroAddress } from 'viem';
2
2
  import { nativeAssetOut, toChain } from '../../config.js';
3
3
  import { assetNodeKey } from '../search/graph.js';
4
- /**
5
- * Native-coin wrap/unwrap (PLS <-> WPLS on PulseChain, and the same pair on
6
- * every other Ethereum-Virtual-Machine chain the search graph touches — ETH
7
- * <-> WETH, BNB <-> WBNB) as a plug-in `Carrier` for `@gibs/quotes`'s
8
- * route-search engine. GIBS-SPECIFIC in the sense `omnibridge.ts`/`pulsex.ts`
9
- * are (see `omnibridge.ts`'s head doc for why this returns a plain object
10
- * shaped like `Carrier` rather than importing that type) — though the
11
- * carrier itself names no chain: it derives every wrap pair from
12
- * `config.ts`'s own `nativeAssetOut` map, so a chain added there gains a
13
- * wrap edge with no change here.
14
- *
15
- * WHY THIS IS A SEPARATE CARRIER FROM PulseX. `pulsex.ts`'s own
16
- * `readPulseXAmountOut` (via `pulseXSwapPathForEdge` in
17
- * `packages/ui/src/lib/state/useRouteSearch.ts`) quotes native Pulse AS
18
- * Wrapped Pulse — both map to the same `quotePathAddress` — so a PLS<->WPLS
19
- * pair collapses to "no swap to quote" and PulseX has never carried it.
20
- * There is no PulseX POOL for native-versus-wrapped; it is a 1:1 contract
21
- * call (`deposit()`/`withdraw(uint256)`), not a trade, so it needed its own
22
- * carrier rather than a PulseX special case.
23
- *
24
- * SENDER: `'swap'`, THE SAME NAME `pulsex.ts` USES — the reader's own
25
- * connected wallet signs a `deposit()`/`withdraw(uint256)` call on the
26
- * wrapped token's own contract (WETH9-style; confirmed present on WPLS by
27
- * bytecode selector 2026-09-24), exactly the wallet a same-chain PulseX swap
28
- * already uses. WHETHER `settings.ts`'s EXISTING swap-transaction builder
29
- * (`pulseXSwapTransactionInputs`) CAN BUILD THIS CALL: NO, not without a
30
- * change there — that builder only ever targets a PulseX ROUTER address and
31
- * encodes `swapExact*` calldata; a wrap needs `to` = the wrapped token
32
- * itself and `data` = plain `deposit()`/`withdraw(amount)` calldata, which
33
- * is a different (and simpler) shape. Sharing the `'swap'` sender name says
34
- * "the same wallet, the same signing step in the flow" — it does not claim
35
- * the existing builder already emits this calldata. Building that call is
36
- * `settings.ts`'s job, out of this carrier's scope; this module answers only
37
- * the route-search question ("what does this edge deliver, exactly").
38
- */
39
- /**
40
- * Whether this carrier can wrap/unwrap directly between `from` and `to`:
41
- * same Ethereum-Virtual-Machine chain, and the two addresses are EXACTLY the
42
- * zero-address/wrapped-native pair `config.ts`'s `nativeAssetOut` names for
43
- * that chain — nothing else, so a plain token pair that merely happens to
44
- * share a chain is never mistaken for a wrap.
45
- */
46
4
  const wrapCarries = (from, to) => {
47
5
  if (from.ecosystem !== 'evm' || to.ecosystem !== 'evm')
48
6
  return false;
@@ -62,22 +20,11 @@ const wrapCarries = (from, to) => {
62
20
  return fromAddress === wrapped;
63
21
  return false;
64
22
  };
65
- /** Every wrap/unwrap edge is worth exactly `amountIn`, in `edge.to`'s base units — native and wrapped share the same decimals on every chain this carrier has ever seen (18, both sides, everywhere `nativeAssetOut` names). */
66
23
  const wrapExact = (edge, amountIn) => {
67
24
  if (!wrapCarries(edge.from, edge.to))
68
25
  return null;
69
26
  return { outcome: 'delivered', deliveredAmount: amountIn };
70
27
  };
71
- /**
72
- * Builds the native wrap/unwrap carrier as a plug-in.
73
- *
74
- * NO OPTIONS, NO `prepare`. There is no local read to batch — a wrap has no
75
- * minimum, no maximum, no fee rate and no reserve to be wrong about; it is a
76
- * pure 1:1 identity over every chain `config.ts` names a wrapped-native
77
- * token for. `exact` alone is total over every edge this carrier offers.
78
- *
79
- * @returns the carrier — `id: 'wrap'`
80
- */
81
28
  export const createWrapCarrier = () => {
82
29
  const edgesFrom = (from, to) => {
83
30
  if (assetNodeKey(from) === assetNodeKey(to))
@@ -94,7 +41,6 @@ export const createWrapCarrier = () => {
94
41
  return {
95
42
  ok: true,
96
43
  deliveredAmount: result.deliveredAmount,
97
- // 1:1, always — there is no slippage to guarantee against.
98
44
  minimumDeliveredAmount: result.deliveredAmount,
99
45
  claimedDurationSeconds: 0,
100
46
  };
@@ -105,11 +51,8 @@ export const createWrapCarrier = () => {
105
51
  ask,
106
52
  exact: wrapExact,
107
53
  isRateLimited: false,
108
- // A `deposit()`/`withdraw()` call requires the reader's own wallet
109
- // signature — never a by-hand deposit.
110
54
  depositMode: 'wallet-signed',
111
55
  sender: 'swap',
112
- // A same-chain call settles with the transaction itself.
113
56
  settlementSeconds: 0,
114
57
  };
115
58
  };
@@ -1,89 +1,6 @@
1
- /**
2
- * Whether a composed multi-signature route is good enough to offer at all.
3
- *
4
- * WHY A ROUTE NEEDS A GATE THAT A SINGLE CROSSING DOES NOT. A one-signature
5
- * route that turns out badly costs the reader one transaction and they still
6
- * hold something they chose. A composed route — an aggregator leg, then an
7
- * omnibridge crossing, then a swap — strands them between signatures: after
8
- * the first they hold an anchor token on a chain they did not ask for, and the
9
- * only way out is to pay again. So a cheap first leg followed by a bad swap is
10
- * worse than the route it replaced, and the difference has to be found BEFORE
11
- * the route is listed rather than after the reader has signed.
12
- *
13
- * NOTHING HERE COSTS A REQUEST. Both figures the verdict reads are already in
14
- * hand when a composer runs: the swap's price impact comes from the on-chain
15
- * `getAmountsOut` pair the estimate already fetched, and the two dollar values
16
- * come from the route's own basis. The gate is free, which is why it can run
17
- * on every route rather than only on suspicious ones.
18
- */
19
- /**
20
- * The price impact above which a composed route is withdrawn, in percent.
21
- *
22
- * MEASURED RATHER THAN CHOSEN. Across the anchor and size combinations swept
23
- * on 2026-09-21 — bridged wrapped ether, USD Coin, DAI and USDT into wrapped
24
- * PulseChain coin, at roughly forty and one hundred and forty United States
25
- * dollars — the impact ran from 0.008 percent to 0.037 percent, with bridged
26
- * USDT the one outlier at 0.34 to 0.37 percent. Three percent is two orders of
27
- * magnitude above the normal case and an order above the worst observed, so it
28
- * cannot fire on an ordinary route; and it is still far below the point where
29
- * a reader is quietly losing real money on a leg they cannot undo.
30
- *
31
- * See `docs/near-intents-conversion-composition.md` for the full sweep.
32
- */
33
1
  export const COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = 3;
34
- /**
35
- * The end-to-end value loss above which a composed route is withdrawn, in
36
- * percent of what the reader pays.
37
- *
38
- * A SECOND TEST BECAUSE THE FIRST CAN READ FINE ON A BROKEN POOL. Price impact
39
- * is measured against a marginal reference quote taken from the same pool, so
40
- * a pool that is uniformly mispriced — a stale pair, a pair holding almost
41
- * nothing — reports a small impact while paying out far less than the tokens
42
- * going in are worth. Only comparing the two ends of the journey catches that.
43
- *
44
- * Measured whole-journey loss on the same sweep ran from 0.81 to 1.36 percent,
45
- * of which the swap fee alone is 0.29. Five percent leaves every observed
46
- * route untouched and still stops a route that is quietly eating a twentieth
47
- * of the reader's money.
48
- */
49
2
  export const COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = 5;
50
- /**
51
- * The end-to-end value GAIN above which a composed route is withdrawn, in
52
- * percent of what the reader pays.
53
- *
54
- * A ROUTE CANNOT MAKE MONEY. Every leg charges a fee, so a route that
55
- * delivers more value than it takes says one of its figures is wrong. Only a
56
- * disagreement between the two price feeds can make a gain look real, and the
57
- * value-loss ceiling already trusts those feeds to within the same five
58
- * percent. So the bound is symmetric with
59
- * {@link COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT}.
60
- *
61
- * THE DEFECT THIS STOPS. On 2026-10-03 the engine served 0.0002 BNB to HEX at
62
- * 495,256 HEX, about seven thousand times the rate of the next tier. The
63
- * value-loss test read that as a loss of minus seven hundred thousand percent
64
- * and passed it. A PulseX hop rescaled from an upstream live answer keeps the
65
- * price impact measured at its original, smaller input
66
- * (`recomposePath` in `@gibs/quotes`'s `search/waves.ts`), so the impact test
67
- * cannot see an inflated amount either. Only the two ends of the journey can.
68
- */
69
3
  export const COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT = 5;
70
- /**
71
- * Judges a composed route against its three ceilings.
72
- *
73
- * Checks price impact first, then value loss, then value gain. Impact names
74
- * the specific leg at fault, while the two value tests only say the journey as
75
- * a whole went wrong, so impact is the more useful answer when several fail.
76
- * In the measured percent of a `'value-gain'` rejection, the gain is a
77
- * positive number.
78
- *
79
- * @param options.priceImpactPercent - the swap leg's price impact, from the
80
- * estimate's marginal reference quote; null when not yet quoted
81
- * @param options.paidUsdValue - what the reader spends at the origin, in
82
- * United States dollars; null when unpriced
83
- * @param options.deliveredUsdValue - what the route delivers, valued on the
84
- * SAME basis as `paidUsdValue`; null when unpriced
85
- * @returns the verdict
86
- */
87
4
  export const assessComposedRoute = ({ priceImpactPercent, paidUsdValue, deliveredUsdValue, }) => {
88
5
  if (typeof priceImpactPercent !== 'number' || !Number.isFinite(priceImpactPercent)) {
89
6
  return { state: 'unknown', missing: 'price-impact' };
@@ -124,16 +41,4 @@ export const assessComposedRoute = ({ priceImpactPercent, paidUsdValue, delivere
124
41
  }
125
42
  return { state: 'acceptable' };
126
43
  };
127
- /**
128
- * Whether a composed route may be listed on the strip.
129
- *
130
- * ONLY AN EXPLICIT PASS LISTS THE ROUTE. An unknown verdict withholds it, the
131
- * same as a rejection, because a three-signature route nobody has checked is
132
- * the case this gate exists for. The two remain distinct in
133
- * {@link ComposedRouteVerdict} so a surface can tell the reader "still
134
- * pricing" rather than "no route", which a boolean alone could not.
135
- *
136
- * @param verdict - the verdict from {@link assessComposedRoute}
137
- * @returns true only when the route passed every ceiling
138
- */
139
44
  export const composedRouteMayBeOffered = (verdict) => verdict.state === 'acceptable';