@gibs/bridge-sdk 1.19.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 (111) hide show
  1. package/dist/abis.js +0 -2
  2. package/dist/bridge-limits.js +0 -38
  3. package/dist/chain-info.js +0 -116
  4. package/dist/chain-names.js +0 -151
  5. package/dist/chains.js +0 -198
  6. package/dist/cjs/abis.js +0 -2
  7. package/dist/cjs/bridge-limits.js +0 -38
  8. package/dist/cjs/chain-info.js +0 -116
  9. package/dist/cjs/chain-names.js +0 -151
  10. package/dist/cjs/chains.js +0 -198
  11. package/dist/cjs/claims.js +0 -60
  12. package/dist/cjs/config.js +1 -208
  13. package/dist/cjs/consolidated-quote.js +0 -465
  14. package/dist/cjs/destination-calldata.js +0 -71
  15. package/dist/cjs/ecosystems.js +0 -183
  16. package/dist/cjs/execution.js +0 -92
  17. package/dist/cjs/fee-type.js +0 -1
  18. package/dist/cjs/hyperliquid-execution.js +0 -98
  19. package/dist/cjs/image-links.js +0 -108
  20. package/dist/cjs/lifi-execution-guard.js +65 -172
  21. package/dist/cjs/near-intents-conformance.js +0 -110
  22. package/dist/cjs/near-intents-route.js +0 -149
  23. package/dist/cjs/near-intents.js +0 -562
  24. package/dist/cjs/origin-deposit.js +0 -56
  25. package/dist/cjs/pre-quote.js +0 -80
  26. package/dist/cjs/prices.js +0 -113
  27. package/dist/cjs/providers.js +0 -83
  28. package/dist/cjs/quote-fees.js +0 -80
  29. package/dist/cjs/referral.js +0 -75
  30. package/dist/cjs/relay-currencies.js +0 -31
  31. package/dist/cjs/relay-depository.js +52 -77
  32. package/dist/cjs/relay-quote.js +0 -394
  33. package/dist/cjs/route-steps.js +0 -100
  34. package/dist/cjs/routing/amounts.js +0 -83
  35. package/dist/cjs/routing/candidate-route.js +0 -73
  36. package/dist/cjs/routing/candidate.js +0 -46
  37. package/dist/cjs/routing/carrier-provider-key.js +0 -27
  38. package/dist/cjs/routing/carriers/omnibridge.js +0 -120
  39. package/dist/cjs/routing/carriers/pulsex-paths.js +0 -57
  40. package/dist/cjs/routing/carriers/pulsex-split.js +0 -92
  41. package/dist/cjs/routing/carriers/pulsex.js +0 -279
  42. package/dist/cjs/routing/carriers/wrap.js +0 -57
  43. package/dist/cjs/routing/composed-route-quality.js +0 -95
  44. package/dist/cjs/routing/entry-chain-anchor.js +0 -27
  45. package/dist/cjs/routing/format.js +0 -50
  46. package/dist/cjs/routing/index.js +0 -28
  47. package/dist/cjs/routing/producers.js +0 -854
  48. package/dist/cjs/routing/providers.js +0 -27
  49. package/dist/cjs/routing/search/compose.js +0 -56
  50. package/dist/cjs/routing/search/graph.js +0 -364
  51. package/dist/cjs/routing/search/plan-route-data.js +0 -508
  52. package/dist/cjs/routing/search/score.js +0 -344
  53. package/dist/cjs/routing/swap-transactions.js +0 -74
  54. package/dist/cjs/routing/usd-value.js +0 -12
  55. package/dist/cjs/settings.js +1 -456
  56. package/dist/cjs/swap-venues.js +0 -17
  57. package/dist/cjs/ton-connect.js +0 -82
  58. package/dist/cjs/ton-execution.js +0 -6
  59. package/dist/cjs/ton.js +0 -42
  60. package/dist/cjs/tron-execution.js +0 -145
  61. package/dist/claims.js +0 -60
  62. package/dist/config.js +1 -211
  63. package/dist/consolidated-quote.js +0 -465
  64. package/dist/destination-calldata.js +0 -71
  65. package/dist/ecosystems.js +0 -183
  66. package/dist/execution.js +0 -92
  67. package/dist/fee-type.js +0 -2
  68. package/dist/hyperliquid-execution.js +0 -98
  69. package/dist/image-links.js +0 -108
  70. package/dist/lifi-execution-guard.js +65 -172
  71. package/dist/near-intents-conformance.js +0 -110
  72. package/dist/near-intents-route.js +0 -149
  73. package/dist/near-intents.js +0 -562
  74. package/dist/origin-deposit.js +0 -56
  75. package/dist/pre-quote.js +0 -80
  76. package/dist/prices.js +0 -113
  77. package/dist/providers.js +0 -83
  78. package/dist/quote-fees.js +0 -80
  79. package/dist/referral.js +0 -75
  80. package/dist/relay-currencies.js +0 -31
  81. package/dist/relay-depository.js +52 -77
  82. package/dist/relay-quote.js +0 -394
  83. package/dist/route-steps.js +0 -100
  84. package/dist/routing/amounts.js +0 -83
  85. package/dist/routing/candidate-route.js +0 -73
  86. package/dist/routing/candidate.js +0 -46
  87. package/dist/routing/carrier-provider-key.js +0 -27
  88. package/dist/routing/carriers/omnibridge.js +0 -120
  89. package/dist/routing/carriers/pulsex-paths.js +0 -57
  90. package/dist/routing/carriers/pulsex-split.js +0 -92
  91. package/dist/routing/carriers/pulsex.js +0 -279
  92. package/dist/routing/carriers/wrap.js +0 -57
  93. package/dist/routing/composed-route-quality.js +0 -95
  94. package/dist/routing/entry-chain-anchor.js +0 -27
  95. package/dist/routing/format.js +0 -50
  96. package/dist/routing/index.js +0 -28
  97. package/dist/routing/producers.js +0 -854
  98. package/dist/routing/providers.js +0 -27
  99. package/dist/routing/search/compose.js +0 -56
  100. package/dist/routing/search/graph.js +0 -364
  101. package/dist/routing/search/plan-route-data.js +0 -508
  102. package/dist/routing/search/score.js +0 -344
  103. package/dist/routing/swap-transactions.js +0 -74
  104. package/dist/routing/usd-value.js +0 -12
  105. package/dist/settings.js +1 -456
  106. package/dist/swap-venues.js +0 -17
  107. package/dist/ton-connect.js +0 -82
  108. package/dist/ton-execution.js +0 -6
  109. package/dist/ton.js +0 -42
  110. package/dist/tron-execution.js +0 -145
  111. package/package.json +3 -3
@@ -10,25 +10,13 @@ import { netDeliveredAmount } from '../amounts.js';
10
10
  import { entryChainAnchorsForPair } from '../entry-chain-anchor.js';
11
11
  import { BRIDGE_SETTLEMENT_ETA_MINUTES, pulsechainChainId } from '../producers.js';
12
12
  import { assetNodeKey } from '../search/graph.js';
13
- /** Builds an {@link AssetNode} for an Ethereum-Virtual-Machine token. */
14
13
  const evmNode = (token) => ({
15
14
  chainId: token.chainId,
16
15
  address: token.address,
17
16
  decimals: token.decimals,
18
17
  ecosystem: 'evm',
19
18
  });
20
- /**
21
- * A directed pair of node keys, joined for use as a `Map` key. Never leaves
22
- * this module — see `capabilities.ts`'s former `directedPairKey` doc for why
23
- * a plain `|` join is enough here.
24
- */
25
19
  const directedPairKey = (from, to) => `${assetNodeKey(from)}|${assetNodeKey(to)}`;
26
- /**
27
- * Finds which omnibridge provider, if any, has a live pathway carrying funds
28
- * from `fromChainId` to `toChainId`. Checks BOTH omnibridge providers rather
29
- * than assuming one: `Providers.PULSECHAIN` carries the Ethereum pathway,
30
- * `Providers.TOKENSEX` the Binance Smart Chain one.
31
- */
32
20
  const omnibridgeCarrierBetween = (fromChainId, toChainId, isProductionNetwork) => {
33
21
  const fromChain = toChain(fromChainId);
34
22
  const toChainKey = toChain(toChainId);
@@ -41,12 +29,6 @@ const omnibridgeCarrierBetween = (fromChainId, toChainId, isProductionNetwork) =
41
29
  }
42
30
  return null;
43
31
  };
44
- /**
45
- * Builds the omnibridge's directed edge map from the bridgeable pairs and the
46
- * live pathway config, in both directions. INBOUND goes through
47
- * {@link entryChainAnchorsForPair} so a native coin and its wrapped form both
48
- * anchor the same PulseChain mint; OUTBOUND needs no aliasing.
49
- */
50
32
  const buildOmnibridgeEdgeMap = (bridgeablePairs, isProductionNetwork) => {
51
33
  const edges = new Map();
52
34
  for (const pair of bridgeablePairs) {
@@ -68,13 +50,6 @@ const buildOmnibridgeEdgeMap = (bridgeablePairs, isProductionNetwork) => {
68
50
  }
69
51
  return edges;
70
52
  };
71
- /**
72
- * Resolves one edge's crossing details from the live pathway config, or null
73
- * when no pathway matches — the same "no live pathway" case `edgesFrom`
74
- * already prunes, kept here as a guard rather than assumed away, because
75
- * `prepare` walks every node pair itself rather than trusting the caller to
76
- * hand it only edges this carrier already offered.
77
- */
78
53
  const resolveCrossing = (edge, isProductionNetwork) => {
79
54
  const bridgeKey = [edge.carrier, toChain(edge.from.chainId), toChain(edge.to.chainId)];
80
55
  const wrappedNativeAddress = nativeAssetOut[toChain(edge.from.chainId)];
@@ -95,7 +70,6 @@ const resolveCrossing = (edge, isProductionNetwork) => {
95
70
  feeTypeKey: path.toHome ? FOREIGN_TO_HOME_FEE : HOME_TO_FOREIGN_FEE,
96
71
  };
97
72
  };
98
- /** Groups values by a computed chain id, preserving the deduped-by-`keyOf` invariant callers rely on. */
99
73
  const groupByChainId = (values, chainIdOf) => {
100
74
  const groups = new Map();
101
75
  for (const value of values) {
@@ -110,7 +84,6 @@ const groupByChainId = (values, chainIdOf) => {
110
84
  }
111
85
  return groups;
112
86
  };
113
- /** Stage-one read: `minPerTx`, `maxAvailablePerTx` (an entry mediator's own bounds) and `feeManager()` (the fee contract's address) — every one a fixed-arity view call, so one multicall per chain covers all three kinds at once. */
114
87
  const readStageOneBatch = async ({ resolvedCrossings, clientForChain, }) => {
115
88
  const limitsByKey = new Map();
116
89
  const feeManagerAddressByKey = new Map();
@@ -166,11 +139,6 @@ const readStageOneBatch = async ({ resolvedCrossings, clientForChain, }) => {
166
139
  }
167
140
  const feeManagerAddress = results[cursor];
168
141
  cursor += 1;
169
- // A DECODED address is exactly 42 characters ("0x" + 40 hex digits).
170
- // `multicallRead` returns the UNDECODED revert bytes on a failed
171
- // `allowFailure` call — also a `string`, usually a different length
172
- // (`'0x'` for a bare revert) — so `typeof` alone cannot tell success
173
- // from failure here the way it does for the `bigint` reads above.
174
142
  if (typeof feeManagerAddress === 'string' && feeManagerAddress.length === 42) {
175
143
  feeManagerAddressByKey.set(request.key, getAddress(feeManagerAddress));
176
144
  }
@@ -178,7 +146,6 @@ const readStageOneBatch = async ({ resolvedCrossings, clientForChain, }) => {
178
146
  }));
179
147
  return { limitsByKey, feeManagerAddressByKey };
180
148
  };
181
- /** Stage-two read: `getFee(feeTypeKey, address(0))` on every fee manager contract stage one resolved — needs the address stage one just learned, so it cannot join that multicall. */
182
149
  const readStageTwoBatch = async ({ resolvedCrossings, feeManagerAddressByKey, clientForChain, }) => {
183
150
  const feeRateByKey = new Map();
184
151
  const feeRateRequests = new Map();
@@ -221,35 +188,6 @@ const limitsKey = (crossing) => `${crossing.limitsChainId}|${crossing.entryMedia
221
188
  const feeManagerHostKey = (crossing) => `${crossing.feeManagerHostChainId}|${crossing.feeManagerHostAddress}`;
222
189
  const feeRateKey = (crossing, feeManagerAddress) => `${crossing.feeManagerHostChainId}|${feeManagerAddress}|${crossing.feeTypeKey}`;
223
190
  const preparedEdgeKey = (edge) => `${edge.carrier}|${assetNodeKey(edge.from)}|${assetNodeKey(edge.to)}`;
224
- /**
225
- * Batch-reads every entry-mediator bound and fee rate `nodes` implies, once,
226
- * so {@link createOmnibridgeCarrier}'s `exact` can answer every crossing
227
- * SYNCHRONOUSLY afterward — the owner's own model, "each individual step and
228
- * its associated fees can be known ahead of time."
229
- *
230
- * TWO MULTICALLS PER CHAIN, NOT ONE: `getFee` needs the fee manager's
231
- * address, which stage one is what LEARNS — the same two-stage shape
232
- * `chain-info.ts`'s `loadBridgeFees` already uses for one pathway at a time,
233
- * generalized here to batch across every crossing the search's node universe
234
- * touches. `HOME_TO_FOREIGN_FEE`/`FOREIGN_TO_HOME_FEE` are not re-read from
235
- * the chain — they are fixed `keccak256` constants (`config.ts`), so reading
236
- * them per pathway (as `loadBridgeFees` still does, defensively) would only
237
- * spend a third multicall confirming a value that cannot change.
238
- *
239
- * WATCH PAYLOAD SIZE (request-payload-size-traps.md). `nodes` is the search
240
- * graph's own hub-token-restricted universe, not a full token list — this
241
- * function reads one `minPerTx`/`maxAvailablePerTx` pair per DISTINCT
242
- * (mediator, token) crossing in that universe, never per token a chain
243
- * lists. `multicallRead` (`@gibs/common`) chunks by measured encoded size on
244
- * top of that, so a universe large enough to matter still cannot repeat the
245
- * 8.5 MB trap that memory records.
246
- *
247
- * @param nodes - every node the search might branch through
248
- * @param edgesFrom - this carrier's own edge enumerator, reused to find every candidate crossing among `nodes`
249
- * @param isProductionNetwork - see `CreateOmnibridgeCarrierOptions`'s own field
250
- * @param clientForChain - see `CreateOmnibridgeCarrierOptions`'s own field
251
- * @returns every resolved crossing's edge key, mapped to what `exact` needs — never thrown; a chain this host has no client for is simply left unanswered
252
- */
253
191
  const prepareOmnibridgeReads = async ({ nodes, edgesFrom, isProductionNetwork, clientForChain, }) => {
254
192
  const edges = [];
255
193
  for (const from of nodes) {
@@ -277,15 +215,6 @@ const prepareOmnibridgeReads = async ({ nodes, edgesFrom, isProductionNetwork, c
277
215
  }
278
216
  return preparedByEdgeKey;
279
217
  };
280
- /**
281
- * The refusal an entry mediator's own bound produces, carrying the bound
282
- * itself as a figure: `minimumAmount` below the floor, `availableAmount`
283
- * above what is left today. A reader is told the number to type, so the
284
- * number must survive here rather than live only inside the reason string.
285
- *
286
- * @param verdict - the crossed bound, in the entry token's base units
287
- * @returns the refusal, in `edge.from`'s base units
288
- */
289
218
  const entryAmountRefusal = (verdict) => verdict.reason === 'below-minimum'
290
219
  ? {
291
220
  kind: 'below-minimum',
@@ -298,14 +227,6 @@ const entryAmountRefusal = (verdict) => verdict.reason === 'below-minimum'
298
227
  minimumAmount: null,
299
228
  availableAmount: verdict.availablePerTransaction,
300
229
  };
301
- /**
302
- * Builds the omnibridge crossing as a plug-in carrier.
303
- *
304
- * @param options - see {@link CreateOmnibridgeCarrierOptions}
305
- * @returns the carrier — `id: 'omnibridge'`; every EDGE it offers carries the
306
- * real `'pulsechain'`/`'tokensex'` provider id, since one registration can
307
- * speak for either entry chain depending on the pair
308
- */
309
230
  export const createOmnibridgeCarrier = (options) => {
310
231
  const { bridgeablePairs, isProductionNetwork, readEntryLimits, readFeePerEther, clientForChain } = options;
311
232
  const edgeMap = buildOmnibridgeEdgeMap(bridgeablePairs, isProductionNetwork);
@@ -317,11 +238,6 @@ export const createOmnibridgeCarrier = (options) => {
317
238
  return [];
318
239
  return [{ carrier, from, to, fusesNext: false, origin: 'wallet', floors: [] }];
319
240
  };
320
- // Populated by `prepare`, read by `exact` — see that method's own doc for
321
- // why a mutable closure here is safe: this carrier is a client-side,
322
- // one-search-at-a-time object (quote-service.md: the omnibridge crossing
323
- // is "never built into" the shared, multi-tenant display-quote service),
324
- // so nothing else can be mutating this map while a search reads it.
325
241
  let preparedByEdgeKey = new Map();
326
242
  const prepare = async (nodes) => {
327
243
  if (clientForChain === undefined)
@@ -349,7 +265,6 @@ export const createOmnibridgeCarrier = (options) => {
349
265
  return {
350
266
  ok: true,
351
267
  deliveredAmount,
352
- // An exact local computation: the guaranteed figure IS the expected one.
353
268
  minimumDeliveredAmount: deliveredAmount,
354
269
  claimedDurationSeconds: BRIDGE_SETTLEMENT_ETA_MINUTES * 60,
355
270
  };
@@ -361,46 +276,11 @@ export const createOmnibridgeCarrier = (options) => {
361
276
  prepare,
362
277
  exact,
363
278
  isRateLimited: false,
364
- // Crossing requires the reader's own wallet signature on the entry
365
- // chain — never a by-hand deposit.
366
279
  depositMode: 'wallet-signed',
367
280
  sender: 'omnibridge',
368
281
  settlementSeconds: BRIDGE_SETTLEMENT_ETA_MINUTES * 60,
369
282
  };
370
283
  };
371
- /**
372
- * Wraps ANY carrier-shaped object (built-in or a host's own) with the
373
- * omnibridge's OWN fuse rule: whether THIS carrier's execution, landing on
374
- * arrival, also performs the omnibridge's entry call — so the two hops
375
- * together cost the reader one signature instead of two.
376
- *
377
- * EVERY CONDITION IS A FACT THE PROVIDER'S OWN DESCRIPTOR STATES
378
- * (`../../providers.ts`), never a name compared here:
379
- *
380
- * - it executes destination calls at all (`executesDestinationCalls` — true
381
- * for LI.FI and Relay, false for NEAR Intents, which delivers to the
382
- * reader's own wallet and stops);
383
- * - it can do so FROM this hop's origin chain (`destinationCallOriginChainIds`
384
- * — LI.FI's contract-calls endpoint is Ethereum only, so from Base or
385
- * Arbitrum a LI.FI hop is an unfused delivery followed by a crossing the
386
- * reader signs);
387
- * - when the entry is the omnibridge's NATIVE one (the provider lands a
388
- * native coin, and `wrapAndRelayTokens` is payable), it can attach value to
389
- * the call (`destinationCallCarriesNativeValue`);
390
- * - and the next hop really IS the omnibridge entry, landing on PulseChain
391
- * (`relayTokens`/`wrapAndRelayTokens` both MINT there; any other
392
- * destination is the omnibridge's OUTBOUND leg, which no arrival can make).
393
- *
394
- * THIS IS THE ONE PLACE `'pulsechain'`/`'tokensex'`/PulseChain'S CHAIN ID
395
- * APPEAR IN A `fusesWithNext` CALLBACK — gibs's own registration
396
- * (`packages/ui/src/lib/state/useRouteSearch.ts`) wraps `createLifiCarrier`/
397
- * `createRelayCarrier` with this before registering them, so the generic
398
- * search engine (`@gibs/quotes`'s `capabilities.ts`) never has to know what
399
- * "the bridge" means.
400
- *
401
- * @param carrier - the carrier to wrap; its own shape is preserved, only `fusesWithNext` is added/replaced
402
- * @returns the same carrier, with a `fusesWithNext` that recognizes the omnibridge entry call
403
- */
404
284
  export const withOmnibridgeEntryFusing = (carrier) => {
405
285
  const descriptor = findProviderById(carrier.id);
406
286
  return {
@@ -1,24 +1,8 @@
1
1
  import { getAddress } from 'viem';
2
2
  import { Chains, Providers, nativeAssetOut, pathways, pulsechainTokenAddresses } from '../../config.js';
3
3
  import { PULSECHAIN_USD_ANCHOR_ADDRESS } from '../../prices.js';
4
- /** Wrapped PLS: the first hub, and the middle token of every two-hub path. */
5
4
  const wrappedPulseAddress = getAddress(nativeAssetOut[Chains.PLS]);
6
- /** The Ethereum-bridged WETH on PulseChain, read off the Ethereum pathway's own config. Undefined only if that pathway is removed. */
7
5
  const bridgedWrappedEtherAddress = pathways[Providers.PULSECHAIN][Chains.PLS]?.[Chains.ETH]?.bridgedNativeAssetOut;
8
- /**
9
- * The tokens a PulseX swap may route through, Wrapped PLS first. Each address
10
- * comes from a constant this package already holds; none is written here:
11
- *
12
- * - WPLS — `nativeAssetOut[Chains.PLS]` (`config.ts`).
13
- * - PLSX, HEX, USDC, USDT — `pulsechainTokenAddresses` (`config.ts`).
14
- * - DAI (from Ethereum) — `PULSECHAIN_USD_ANCHOR_ADDRESS` (`prices.ts`).
15
- * - WETH (from Ethereum) — the Ethereum pathway's `bridgedNativeAssetOut` (`config.ts`).
16
- *
17
- * KEPT SMALL ON PURPOSE. Each hub adds pools to the one batched reserve read
18
- * the carrier makes per search, and adds paths to every price it computes.
19
- * Seven hubs give at most 20 candidate paths per hop (see
20
- * {@link candidatePulseXPaths}).
21
- */
22
6
  export const pulseXHubTokens = Object.freeze([
23
7
  { symbol: 'WPLS', address: wrappedPulseAddress },
24
8
  { symbol: 'PLSX', address: pulsechainTokenAddresses.PLSX },
@@ -28,32 +12,8 @@ export const pulseXHubTokens = Object.freeze([
28
12
  { symbol: 'USDT', address: pulsechainTokenAddresses.USDT },
29
13
  ...(bridgedWrappedEtherAddress === undefined ? [] : [{ symbol: 'WETH', address: getAddress(bridgedWrappedEtherAddress) }]),
30
14
  ].map((hub) => Object.freeze(hub)));
31
- /** Whether a path names any token twice, compared without regard to letter case. */
32
15
  const repeatsAToken = (path) => new Set(path.map((address) => address.toLowerCase())).size !== path.length;
33
- /** Whether two addresses are the same token, compared without regard to letter case. */
34
16
  const sameToken = (left, right) => left.toLowerCase() === right.toLowerCase();
35
- /**
36
- * Every swap path a PulseX hop from `from` to `to` may take:
37
- *
38
- * - direct: `[from, to]`;
39
- * - one hub: `[from, hub, to]` for each hub, Wrapped PLS among them;
40
- * - two hubs: `[from, wrappedNative, hub, to]` and `[from, hub, wrappedNative, to]`
41
- * for each other hub — Wrapped PLS holds the deepest pools on PulseX, so it
42
- * is the one middle token worth pairing with every other hub.
43
- *
44
- * A path that names a token twice is dropped. That removes every path whose
45
- * hub IS `from` or `to`, and the same-token hop when `from` equals `to`.
46
- *
47
- * The fixed path this carrier used before — `[from, WPLS, to]`, or
48
- * `[from, to]` when one side is WPLS — is always in the result, so the pick
49
- * can only match or beat it.
50
- *
51
- * @param options.from - the pool token the swap spends
52
- * @param options.to - the pool token the swap buys
53
- * @param options.wrappedNative - Wrapped PLS
54
- * @param options.hubs - the hub token addresses, Wrapped PLS included
55
- * @returns the candidate paths, direct first, each at least two tokens long
56
- */
57
17
  export const candidatePulseXPaths = (options) => {
58
18
  const { from, to, wrappedNative, hubs } = options;
59
19
  const oneHubPaths = hubs.map((hub) => [from, hub, to]);
@@ -65,28 +25,11 @@ export const candidatePulseXPaths = (options) => {
65
25
  ]);
66
26
  return [[from, to], ...oneHubPaths, ...twoHubPaths].filter((path) => !repeatsAToken(path));
67
27
  };
68
- /**
69
- * Whether `candidate` should replace `best`: it delivers more, or it delivers
70
- * the same and crosses fewer pools. A tie goes to the shorter path because
71
- * each extra pool costs gas, and nothing else separates the two.
72
- */
73
28
  const beats = (candidate, best) => {
74
29
  if (candidate.deliveredAmount !== best.deliveredAmount)
75
30
  return candidate.deliveredAmount > best.deliveredAmount;
76
31
  return candidate.path.length < best.path.length;
77
32
  };
78
- /**
79
- * The priced path that delivers the most. A path that delivers nothing is
80
- * never chosen. Among equal outputs the path with fewer pools wins, and among
81
- * equal lengths the earlier candidate wins, so the pick is deterministic.
82
- *
83
- * Gas is not weighed beyond that tie-break: the search charges one flat gas
84
- * figure per swap signature, whatever its path length (`route-search-gas.ts`
85
- * in the interface), so there is no per-pool gas figure to subtract here.
86
- *
87
- * @param quotes - the priced candidates; paths with a missing pool are already left out
88
- * @returns the best candidate, or null when none delivers anything
89
- */
90
33
  export const bestPulseXPathQuote = (quotes) => quotes
91
34
  .filter((quote) => quote.deliveredAmount > 0n)
92
35
  .reduce((best, quote) => (best === null || beats(quote, best) ? quote : best), null);
@@ -1,41 +1,11 @@
1
1
  import { PULSEX_MARGINAL_REFERENCE_DIVISOR } from '../producers.js';
2
2
  import { amountOutAlongPath, priceImpactAgainstMarginal } from './pulsex.js';
3
- /**
4
- * How many equal chunks the search divides the trade into: 20, so legs move
5
- * in 5% steps. Finer steps buy a little output for proportionally more work.
6
- */
7
3
  export const PULSEX_SPLIT_CHUNKS = 20;
8
- /** The most legs one split may have. Each leg is one more router call to sign or batch, and one more swap's gas. */
9
4
  export const PULSEX_SPLIT_MAX_LEGS = 3;
10
- /** How many candidate paths, best single-path output first, the search may spread the trade over. */
11
5
  export const PULSEX_SPLIT_CANDIDATE_LIMIT = 4;
12
- /**
13
- * The gain a split must show over the best single path before it is chosen,
14
- * in basis points of the single path's output: 25, or 0.25%.
15
- *
16
- * GAS IS REAL. Each extra leg costs roughly one more swap's gas, and a
17
- * batching contract adds its own overhead. A split that wins by a rounding
18
- * margin buys the reader nothing but a larger fee. The figure is a judgement,
19
- * not a measurement; revisit it once an execution path has a real gas cost.
20
- */
21
6
  export const PULSEX_SPLIT_MINIMUM_GAIN_BASIS_POINTS = 25n;
22
7
  const BASIS_POINTS = 10000n;
23
- /** The key one token's reserve in one pool is held under while a split executes. */
24
8
  const reserveKey = (pool, token) => `${pool}|${token.toLowerCase()}`;
25
- /**
26
- * Executes legs one after another against shared pool state, exactly as
27
- * sequential router calls would. A leg that crosses a pool an earlier leg
28
- * already traded sees that pool's moved reserves, in whichever direction the
29
- * earlier leg traded it.
30
- *
31
- * Each trade adds its full input to the pool and removes its output, the
32
- * Uniswap-V2 accounting. See the open risk in the report: if PulseX moves
33
- * part of its fee out of the pool during a swap, a shared pool is a few
34
- * hundredths of a percent shallower here than on chain.
35
- *
36
- * @param legs - each candidate with the amount it spends, in execution order
37
- * @returns what each leg delivers, in the same order
38
- */
39
9
  export const executePulseXSplitInOrder = (legs) => {
40
10
  const reserves = new Map();
41
11
  const reserveOf = (pool, token, initial) => reserves.get(reserveKey(pool, token)) ?? initial;
@@ -48,18 +18,7 @@ export const executePulseXSplitInOrder = (legs) => {
48
18
  return amountOut;
49
19
  }, amountIn));
50
20
  };
51
- /** What one candidate alone delivers for the whole trade. */
52
21
  const singlePathOutput = (candidate, amountIn) => executePulseXSplitInOrder([{ candidate, amountIn }])[0] ?? 0n;
53
- /**
54
- * The candidates a split may use: the {@link PULSEX_SPLIT_CANDIDATE_LIMIT}
55
- * best by single-path output for the whole trade. A candidate that delivers
56
- * nothing is dropped. Ties go to the shorter path, then the earlier
57
- * candidate, so the ranking is deterministic.
58
- *
59
- * @param candidates - every priced path, on every venue
60
- * @param amountIn - the whole trade
61
- * @returns the ranked candidates, best first, each with its single-path output
62
- */
63
22
  export const rankPulseXSplitCandidates = (candidates, amountIn) => candidates
64
23
  .map((candidate, index) => ({ candidate, index, deliveredAmount: singlePathOutput(candidate, amountIn) }))
65
24
  .filter((ranked) => ranked.deliveredAmount > 0n)
@@ -72,42 +31,16 @@ export const rankPulseXSplitCandidates = (candidates, amountIn) => candidates
72
31
  })
73
32
  .slice(0, PULSEX_SPLIT_CANDIDATE_LIMIT)
74
33
  .map(({ candidate, deliveredAmount }) => ({ candidate, deliveredAmount }));
75
- /** The trade divided into {@link PULSEX_SPLIT_CHUNKS} chunks; the last one takes the remainder, so the chunks sum to the trade exactly. */
76
34
  const chunksOf = (amountIn) => {
77
35
  const count = BigInt(PULSEX_SPLIT_CHUNKS);
78
36
  const chunk = amountIn / count;
79
37
  return Array.from({ length: PULSEX_SPLIT_CHUNKS }, (_, index) => index === PULSEX_SPLIT_CHUNKS - 1 ? amountIn - chunk * (count - 1n) : chunk);
80
38
  };
81
- /** Each candidate with a positive allocation, in candidate order — the order the legs execute in. */
82
39
  const legsOf = (candidates, allocation) => candidates.flatMap((candidate, index) => {
83
40
  const amountIn = allocation[index] ?? 0n;
84
41
  return amountIn > 0n ? [{ candidate, amountIn }] : [];
85
42
  });
86
- /** What an allocation delivers in total, executed in candidate order. */
87
43
  const allocationOutput = (candidates, allocation) => executePulseXSplitInOrder(legsOf(candidates, allocation)).reduce((total, amount) => total + amount, 0n);
88
- /**
89
- * Spreads a trade over ranked candidates, one chunk at a time. Each chunk goes
90
- * to the candidate whose share, once raised by that chunk, makes the WHOLE
91
- * split deliver the most — the split is re-executed in full each time
92
- * ({@link executePulseXSplitInOrder}), so a shared pool is always priced with
93
- * the reserves every leg leaves behind. A candidate not yet used is skipped
94
- * once {@link PULSEX_SPLIT_MAX_LEGS} legs exist. Ties go to the earlier
95
- * candidate.
96
- *
97
- * BOUNDED AND DETERMINISTIC. Work per quote is at most
98
- * `PULSEX_SPLIT_CHUNKS × PULSEX_SPLIT_CANDIDATE_LIMIT` full executions (80),
99
- * each of at most `PULSEX_SPLIT_MAX_LEGS` legs of at most three pools: under
100
- * 750 constant-product evaluations, no network call.
101
- *
102
- * On pools that share nothing, each candidate's output is concave in its
103
- * share, so this greedy fill reaches the best allocation on the chunk grid.
104
- * Shared pools break that guarantee; {@link bestPulseXSplitQuote} still never
105
- * returns a split worse than the best single path.
106
- *
107
- * @param candidates - ranked candidates, best first
108
- * @param amountIn - the whole trade
109
- * @returns the share of the trade each candidate spends, in candidate order
110
- */
111
44
  export const allocatePulseXSplit = (candidates, amountIn) => chunksOf(amountIn).reduce((allocation, chunk) => {
112
45
  const usedLegs = allocation.filter((amount) => amount > 0n).length;
113
46
  const trials = candidates.flatMap((_, index) => {
@@ -120,20 +53,6 @@ export const allocatePulseXSplit = (candidates, amountIn) => chunksOf(amountIn).
120
53
  const best = trials.reduce((leader, entry) => (leader === null || entry.output > leader.output ? entry : leader), null);
121
54
  return best === null ? allocation : best.trial;
122
55
  }, candidates.map(() => 0n));
123
- /**
124
- * Price impact of a combined trade, in percent, measured with the same
125
- * reference size the single-path figure uses
126
- * (`amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR`).
127
- *
128
- * THE REFERENCE IS THE BEST MARGINAL RATE OF ANY CANDIDATE, not a blend of
129
- * the legs' own rates. A split that leans on a path with a worse spot price
130
- * pays for that, and the figure shows it. A blend would hide it.
131
- *
132
- * @param options.candidates - every ranked candidate
133
- * @param options.amountIn - the whole trade
134
- * @param options.deliveredAmount - what the combined trade delivers
135
- * @returns the impact in percent, or null when the reference trade rounds away
136
- */
137
56
  export const splitPriceImpact = (options) => {
138
57
  const { candidates, amountIn, deliveredAmount } = options;
139
58
  const referenceAmountIn = amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR;
@@ -143,17 +62,6 @@ export const splitPriceImpact = (options) => {
143
62
  }, 0n);
144
63
  return priceImpactAgainstMarginal({ amountIn, amountOut: deliveredAmount, referenceAmountIn, referenceAmountOut });
145
64
  };
146
- /**
147
- * The best split of a PulseX trade, or null when no split beats the best
148
- * single path by {@link PULSEX_SPLIT_MINIMUM_GAIN_BASIS_POINTS}.
149
- *
150
- * NEVER WORSE THAN ONE PATH. A null answer means "take the best single path";
151
- * this never returns a split that delivers less than that path.
152
- *
153
- * @param candidates - every priced path, on every venue
154
- * @param amountIn - the whole trade
155
- * @returns the split with its legs in execution order, or null
156
- */
157
65
  export const bestPulseXSplitQuote = (candidates, amountIn) => {
158
66
  if (amountIn <= 0n)
159
67
  return null;