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