@gibs/bridge-sdk 1.13.0 → 1.17.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 +244 -18
- package/abis/package.json +6 -0
- package/bridge-limits/package.json +6 -0
- package/chain-info/package.json +6 -0
- package/chain-names/package.json +6 -0
- package/chains/package.json +6 -0
- package/claims/package.json +6 -0
- package/config/package.json +6 -0
- package/consolidated-quote/package.json +6 -0
- package/destination-calldata/package.json +6 -0
- package/dist/chain-info.d.ts +1 -1
- package/dist/chain-info.js +11 -12
- package/dist/chains.d.ts +25 -0
- package/dist/chains.js +55 -20
- package/dist/cjs/abis.d.ts +1 -0
- package/dist/cjs/abis.js +19 -0
- package/dist/cjs/bridge-limits.d.ts +103 -0
- package/dist/cjs/bridge-limits.js +89 -0
- package/dist/cjs/chain-info.d.ts +169 -0
- package/dist/cjs/chain-info.js +452 -0
- package/dist/cjs/chain-names.d.ts +160 -0
- package/dist/cjs/chain-names.js +207 -0
- package/dist/cjs/chains.d.ts +90 -0
- package/dist/cjs/chains.js +357 -0
- package/dist/cjs/claims.d.ts +198 -0
- package/dist/cjs/claims.js +182 -0
- package/dist/cjs/config.d.ts +240 -0
- package/dist/cjs/config.js +537 -0
- package/dist/cjs/consolidated-quote.d.ts +648 -0
- package/dist/cjs/consolidated-quote.js +1048 -0
- package/dist/cjs/destination-calldata.d.ts +118 -0
- package/dist/cjs/destination-calldata.js +156 -0
- package/dist/cjs/ecosystems.d.ts +217 -0
- package/dist/cjs/ecosystems.js +298 -0
- package/dist/cjs/execution.d.ts +346 -0
- package/dist/cjs/execution.js +468 -0
- package/dist/cjs/fee-type.d.ts +10 -0
- package/dist/cjs/fee-type.js +10 -0
- package/dist/cjs/hyperliquid-execution.d.ts +152 -0
- package/dist/cjs/hyperliquid-execution.js +175 -0
- package/dist/cjs/image-links.d.ts +117 -0
- package/dist/cjs/image-links.js +153 -0
- package/dist/cjs/index.d.ts +23 -0
- package/dist/cjs/index.js +39 -0
- package/dist/cjs/lifi-execution-guard.d.ts +210 -0
- package/dist/cjs/lifi-execution-guard.js +409 -0
- package/dist/cjs/near-intents-conformance.d.ts +85 -0
- package/dist/cjs/near-intents-conformance.js +337 -0
- package/dist/cjs/near-intents-route.d.ts +47 -0
- package/dist/cjs/near-intents-route.js +303 -0
- package/dist/cjs/near-intents.d.ts +524 -0
- package/dist/cjs/near-intents.js +1071 -0
- package/dist/cjs/origin-deposit.d.ts +326 -0
- package/dist/cjs/origin-deposit.js +82 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/pre-quote.d.ts +198 -0
- package/dist/cjs/pre-quote.js +180 -0
- package/dist/cjs/prices.d.ts +107 -0
- package/dist/cjs/prices.js +172 -0
- package/dist/cjs/providers.d.ts +238 -0
- package/dist/cjs/providers.js +171 -0
- package/dist/cjs/quote-fees.d.ts +102 -0
- package/dist/cjs/quote-fees.js +201 -0
- package/dist/cjs/referral.d.ts +85 -0
- package/dist/cjs/referral.js +85 -0
- package/dist/cjs/relay-currencies.d.ts +47 -0
- package/dist/cjs/relay-currencies.js +95 -0
- package/dist/cjs/relay-depository.d.ts +2 -0
- package/dist/cjs/relay-depository.js +85 -0
- package/dist/cjs/relay-quote.d.ts +301 -0
- package/dist/cjs/relay-quote.js +996 -0
- package/dist/cjs/route-steps.d.ts +138 -0
- package/dist/cjs/route-steps.js +310 -0
- package/dist/cjs/routing/amounts.d.ts +80 -0
- package/dist/cjs/routing/amounts.js +126 -0
- package/dist/cjs/routing/candidate-route.d.ts +43 -0
- package/dist/cjs/routing/candidate-route.js +50 -0
- package/dist/cjs/routing/candidate.d.ts +237 -0
- package/dist/cjs/routing/candidate.js +100 -0
- package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
- package/dist/cjs/routing/carriers/ask-answer.js +2 -0
- package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
- package/dist/cjs/routing/carriers/omnibridge.js +459 -0
- package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
- package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
- package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
- package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
- package/dist/cjs/routing/carriers/pulsex.d.ts +159 -0
- package/dist/cjs/routing/carriers/pulsex.js +565 -0
- package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
- package/dist/cjs/routing/carriers/wrap.js +119 -0
- package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
- package/dist/cjs/routing/composed-route-quality.js +144 -0
- package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
- package/dist/cjs/routing/entry-chain-anchor.js +59 -0
- package/dist/cjs/routing/format.d.ts +31 -0
- package/dist/cjs/routing/format.js +64 -0
- package/dist/cjs/routing/index.d.ts +38 -0
- package/dist/cjs/routing/index.js +134 -0
- package/dist/cjs/routing/producers.d.ts +770 -0
- package/dist/cjs/routing/producers.js +1617 -0
- package/dist/cjs/routing/providers.d.ts +31 -0
- package/dist/cjs/routing/providers.js +48 -0
- package/dist/cjs/routing/route-data.d.ts +355 -0
- package/dist/cjs/routing/route-data.js +2 -0
- package/dist/cjs/routing/search/compose.d.ts +168 -0
- package/dist/cjs/routing/search/compose.js +104 -0
- package/dist/cjs/routing/search/graph.d.ts +668 -0
- package/dist/cjs/routing/search/graph.js +461 -0
- package/dist/cjs/routing/search/plan-route-data.d.ts +269 -0
- package/dist/cjs/routing/search/plan-route-data.js +848 -0
- package/dist/cjs/routing/search/score.d.ts +356 -0
- package/dist/cjs/routing/search/score.js +445 -0
- package/dist/cjs/routing/swap-transactions.d.ts +91 -0
- package/dist/cjs/routing/swap-transactions.js +131 -0
- package/dist/cjs/routing/usd-value.d.ts +17 -0
- package/dist/cjs/routing/usd-value.js +29 -0
- package/dist/cjs/settings.d.ts +785 -0
- package/dist/cjs/settings.js +1074 -0
- package/dist/cjs/ton-connect.d.ts +98 -0
- package/dist/cjs/ton-connect.js +206 -0
- package/dist/cjs/ton-execution.d.ts +7 -0
- package/dist/cjs/ton-execution.js +23 -0
- package/dist/cjs/ton.d.ts +37 -0
- package/dist/cjs/ton.js +83 -0
- package/dist/cjs/tron-execution.d.ts +146 -0
- package/dist/cjs/tron-execution.js +248 -0
- package/dist/cjs/types.d.ts +51 -0
- package/dist/cjs/types.js +2 -0
- package/dist/claims.d.ts +198 -0
- package/dist/claims.js +140 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +39 -5
- package/dist/consolidated-quote.d.ts +109 -15
- package/dist/consolidated-quote.js +310 -52
- package/dist/execution.d.ts +346 -0
- package/dist/execution.js +428 -0
- package/dist/hyperliquid-execution.js +2 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lifi-execution-guard.d.ts +13 -2
- package/dist/lifi-execution-guard.js +4 -0
- package/dist/near-intents.js +2 -3
- package/dist/pre-quote.d.ts +6 -6
- package/dist/pre-quote.js +10 -14
- package/dist/providers.d.ts +17 -0
- package/dist/providers.js +6 -0
- package/dist/referral.d.ts +12 -4
- package/dist/referral.js +12 -4
- package/dist/relay-depository.d.ts +2 -0
- package/dist/relay-depository.js +82 -0
- package/dist/relay-quote.js +2 -3
- package/dist/routing/candidate.d.ts +3 -3
- package/dist/routing/candidate.js +3 -3
- package/dist/routing/carriers/ask-answer.d.ts +19 -0
- package/dist/routing/carriers/omnibridge.js +23 -21
- package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
- package/dist/routing/carriers/pulsex-paths.js +92 -0
- package/dist/routing/carriers/pulsex-split.d.ts +175 -0
- package/dist/routing/carriers/pulsex-split.js +185 -0
- package/dist/routing/carriers/pulsex.d.ts +69 -21
- package/dist/routing/carriers/pulsex.js +184 -65
- package/dist/routing/composed-route-quality.d.ts +28 -6
- package/dist/routing/composed-route-quality.js +36 -5
- package/dist/routing/entry-chain-anchor.d.ts +1 -1
- package/dist/routing/entry-chain-anchor.js +1 -1
- package/dist/routing/index.d.ts +7 -3
- package/dist/routing/index.js +7 -3
- package/dist/routing/producers.js +1 -1
- package/dist/routing/providers.d.ts +5 -0
- package/dist/routing/providers.js +5 -0
- package/dist/routing/search/graph.d.ts +29 -0
- package/dist/routing/search/plan-route-data.d.ts +25 -0
- package/dist/routing/search/plan-route-data.js +43 -7
- package/dist/routing/search/score.d.ts +33 -1
- package/dist/routing/search/score.js +41 -6
- package/dist/routing/swap-transactions.d.ts +91 -0
- package/dist/routing/swap-transactions.js +125 -0
- package/dist/settings.d.ts +94 -1
- package/dist/settings.js +95 -3
- package/dist/ton-connect.d.ts +98 -0
- package/dist/ton-connect.js +200 -0
- package/dist/ton-execution.d.ts +4 -114
- package/dist/ton-execution.js +4 -244
- package/dist/ton.d.ts +37 -0
- package/dist/ton.js +65 -0
- package/docs/examples/example-a-ether-to-pulse.ts +300 -0
- package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
- package/docs/examples/shared.ts +261 -0
- package/docs/examples/tsconfig.json +18 -0
- package/docs/integration-guide.md +617 -0
- package/ecosystems/package.json +6 -0
- package/execution/package.json +6 -0
- package/fee-type/package.json +6 -0
- package/hyperliquid-execution/package.json +6 -0
- package/image-links/package.json +6 -0
- package/lifi-execution-guard/package.json +6 -0
- package/llms.txt +234 -0
- package/near-intents/package.json +6 -0
- package/near-intents-route/package.json +6 -0
- package/origin-deposit/package.json +6 -0
- package/package.json +818 -10
- package/pre-quote/package.json +6 -0
- package/prices/package.json +6 -0
- package/providers/package.json +6 -0
- package/quote-fees/package.json +6 -0
- package/referral/package.json +6 -0
- package/relay-currencies/package.json +6 -0
- package/relay-quote/package.json +6 -0
- package/route-steps/package.json +6 -0
- package/routing/package.json +6 -0
- package/settings/package.json +6 -0
- package/ton/package.json +6 -0
- package/ton-connect/package.json +6 -0
- package/ton-execution/package.json +6 -0
- package/tron-execution/package.json +6 -0
- package/types/package.json +6 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createPulseXCarrier = exports.priceImpactAgainstMarginal = exports.amountOutAlongPath = exports.pulseXSwapPathForHop = exports.pulseXWrappedNativePath = exports.pulseXRouterForEdge = exports.pulseXVenues = void 0;
|
|
37
|
+
const multicall_1 = require("@gibs/common/multicall");
|
|
38
|
+
const viem_1 = require("viem");
|
|
39
|
+
const abis = __importStar(require("../../abis.js"));
|
|
40
|
+
const chains_js_1 = require("../../chains.js");
|
|
41
|
+
const config_js_1 = require("../../config.js");
|
|
42
|
+
const ecosystems_js_1 = require("../../ecosystems.js");
|
|
43
|
+
const providers_js_1 = require("../providers.js");
|
|
44
|
+
const producers_js_1 = require("../producers.js");
|
|
45
|
+
const graph_js_1 = require("../search/graph.js");
|
|
46
|
+
const pulsex_paths_js_1 = require("./pulsex-paths.js");
|
|
47
|
+
/**
|
|
48
|
+
* PulseX (the PulseChain-native decentralized exchange, used here for a
|
|
49
|
+
* conversion swap leg) as a plug-in `Carrier` for `@gibs/quotes`'s
|
|
50
|
+
* route-search engine. GIBS-SPECIFIC — see `docs/quote-service.md`'s
|
|
51
|
+
* "Carriers" section: "never built in ... PulseX." Registered from
|
|
52
|
+
* `packages/ui/src/lib/state/useRouteSearch.ts` / `route-search-quotes.ts`.
|
|
53
|
+
*
|
|
54
|
+
* See `omnibridge.ts`'s head doc for why this returns a plain object shaped
|
|
55
|
+
* like `Carrier` rather than importing that type.
|
|
56
|
+
*/
|
|
57
|
+
/** WPLS, the wrapped-native intermediary {@link pulseXConversionSwapPath} routes conversions through. */
|
|
58
|
+
const wplsAddress = config_js_1.nativeAssetOut[config_js_1.Chains.PLS];
|
|
59
|
+
/** The hub addresses every candidate path is built from — see {@link pulseXHubTokens}. */
|
|
60
|
+
const hubAddresses = pulsex_paths_js_1.pulseXHubTokens.map((hub) => hub.address);
|
|
61
|
+
/**
|
|
62
|
+
* Every PulseX deployment on PulseChain, V1 first: `uniV2Routers` paired with
|
|
63
|
+
* `uniV2Factories` by index. Each factory is the router's own
|
|
64
|
+
* `router.factory()` read on chain — see `uniV2Factories`'s own doc in
|
|
65
|
+
* `config.ts`. A router with no recorded factory is left out, because its
|
|
66
|
+
* pools cannot be read.
|
|
67
|
+
*/
|
|
68
|
+
exports.pulseXVenues = (config_js_1.uniV2Routers[config_js_1.Chains.PLS] ?? []).flatMap((router, index) => {
|
|
69
|
+
const factory = config_js_1.uniV2Factories[config_js_1.Chains.PLS]?.[index];
|
|
70
|
+
return factory === undefined ? [] : [{ router: (0, viem_1.getAddress)(router), factory: (0, viem_1.getAddress)(factory) }];
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* The venue an edge with no {@link HopEdge.venue} runs on: V1, index 0. Every
|
|
74
|
+
* PulseX edge built before venues existed was quoted and signed on V1 — the
|
|
75
|
+
* same-chain swap sender's own hand-built edge still is — so this keeps that
|
|
76
|
+
* meaning rather than guessing a new one.
|
|
77
|
+
*/
|
|
78
|
+
const defaultPulseXVenue = exports.pulseXVenues[0];
|
|
79
|
+
/**
|
|
80
|
+
* The router a PulseX edge must be signed on — the one its figure was quoted
|
|
81
|
+
* against. Reads the edge's own {@link HopEdge.venue}; an edge with none is a
|
|
82
|
+
* V1 edge (see {@link defaultPulseXVenue}).
|
|
83
|
+
*
|
|
84
|
+
* @param edge - the edge to sign
|
|
85
|
+
* @returns the router, or null when the edge is not a PulseX edge or names a venue that is not a known PulseX router
|
|
86
|
+
*/
|
|
87
|
+
const pulseXRouterForEdge = (edge) => pulseXVenueForEdge(edge)?.router ?? null;
|
|
88
|
+
exports.pulseXRouterForEdge = pulseXRouterForEdge;
|
|
89
|
+
/**
|
|
90
|
+
* The {@link PulseXVenue} a PulseX edge runs on — see {@link pulseXRouterForEdge}.
|
|
91
|
+
*
|
|
92
|
+
* @param edge - the edge
|
|
93
|
+
* @returns the venue, or null when there is none
|
|
94
|
+
*/
|
|
95
|
+
const pulseXVenueForEdge = (edge) => {
|
|
96
|
+
if (edge.carrier !== 'PulseX')
|
|
97
|
+
return null;
|
|
98
|
+
if (edge.venue === undefined)
|
|
99
|
+
return defaultPulseXVenue ?? null;
|
|
100
|
+
const venue = edge.venue;
|
|
101
|
+
return exports.pulseXVenues.find((candidate) => (0, ecosystems_js_1.sameAddress)(candidate.router, venue, 'evm')) ?? null;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* A node's own address for a PulseX pool lookup: native PLS (the zero
|
|
105
|
+
* address) is not a token the router or its factory ever holds a pool for —
|
|
106
|
+
* only Wrapped PLS is — so a native node resolves to {@link wplsAddress}
|
|
107
|
+
* before it ever reaches {@link pulseXConversionSwapPath}. Every other node
|
|
108
|
+
* is unchanged.
|
|
109
|
+
*
|
|
110
|
+
* MIRRORS `useRouteSearch.ts`'s `quotePathAddress` (the UI's async `ask`
|
|
111
|
+
* already resolves native this way through that function) — this is the
|
|
112
|
+
* SAME rule, restated here because `prepare`/`exact` never go through the
|
|
113
|
+
* UI's `readAmountOut` at all; see {@link pulseXCandidatePaths}'s own doc.
|
|
114
|
+
*
|
|
115
|
+
* @param node - the node to resolve
|
|
116
|
+
* @returns the node's own address, or {@link wplsAddress} for native PLS
|
|
117
|
+
*/
|
|
118
|
+
const poolTokenAddress = (node) => ((0, ecosystems_js_1.sameAddress)(node.address, viem_1.zeroAddress, 'evm') ? wplsAddress : node.address);
|
|
119
|
+
/**
|
|
120
|
+
* The fixed Wrapped-PLS path from `from` to `to` — `[from, WPLS, to]`, or
|
|
121
|
+
* `[from, to]` when one side is WPLS — or null when both ends resolve to the
|
|
122
|
+
* same pool token (PLS<->WPLS is the wrap carrier's job).
|
|
123
|
+
*
|
|
124
|
+
* NOT THE PATH A HOP IS SIGNED ALONG. The carrier's own `exact` picks the
|
|
125
|
+
* best of many paths ({@link candidatePulseXPaths}) and records the winner on
|
|
126
|
+
* the hop's outcome; the signer reads it back with {@link pulseXSwapPathForHop}.
|
|
127
|
+
* This fixed path serves only the carrier's asynchronous `ask` — the
|
|
128
|
+
* interface's `getAmountsOut` read, which the route search never calls for a
|
|
129
|
+
* PulseX hop, because the search only asks aggregators live.
|
|
130
|
+
*
|
|
131
|
+
* Native PLS resolves to WPLS first ({@link poolTokenAddress}), so a path
|
|
132
|
+
* never starts or ends on the zero address, which has no pool.
|
|
133
|
+
*
|
|
134
|
+
* @param from - the hop's origin node
|
|
135
|
+
* @param to - the hop's destination node
|
|
136
|
+
* @returns the fixed path, or null when there is nothing for PulseX to swap
|
|
137
|
+
*/
|
|
138
|
+
const pulseXWrappedNativePath = (from, to) => {
|
|
139
|
+
const fromAddress = poolTokenAddress(from);
|
|
140
|
+
const toAddress = poolTokenAddress(to);
|
|
141
|
+
if ((0, ecosystems_js_1.sameAddress)(fromAddress, toAddress, 'evm'))
|
|
142
|
+
return null;
|
|
143
|
+
return (0, providers_js_1.pulseXConversionSwapPath)(fromAddress, toAddress, wplsAddress);
|
|
144
|
+
};
|
|
145
|
+
exports.pulseXWrappedNativePath = pulseXWrappedNativePath;
|
|
146
|
+
/**
|
|
147
|
+
* Every path a PulseX hop from `from` to `to` may be priced along, or none
|
|
148
|
+
* when both ends resolve to the same pool token. EVERY PULSEX PRICE AND
|
|
149
|
+
* POOL READ IN THIS FILE GOES THROUGH THIS ONE FUNCTION, so `prepare` reads
|
|
150
|
+
* exactly the pools `exact` will look up, and native PLS resolves to WPLS
|
|
151
|
+
* the same way in both ({@link poolTokenAddress} — a path that started on
|
|
152
|
+
* the zero address once left every native-PLS hop unpriced).
|
|
153
|
+
*
|
|
154
|
+
* @param from - the hop's origin node
|
|
155
|
+
* @param to - the hop's destination node
|
|
156
|
+
* @returns the candidate paths, direct first; empty when there is nothing to swap
|
|
157
|
+
*/
|
|
158
|
+
const pulseXCandidatePaths = (from, to) => {
|
|
159
|
+
const fromAddress = poolTokenAddress(from);
|
|
160
|
+
const toAddress = poolTokenAddress(to);
|
|
161
|
+
if ((0, ecosystems_js_1.sameAddress)(fromAddress, toAddress, 'evm'))
|
|
162
|
+
return [];
|
|
163
|
+
return (0, pulsex_paths_js_1.candidatePulseXPaths)({ from: fromAddress, to: toAddress, wrappedNative: wplsAddress, hubs: hubAddresses });
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Whether PulseX can carry funds directly from `from` to `to`: both must be
|
|
167
|
+
* Ethereum-Virtual-Machine tokens on PulseChain itself — PulseX runs only on
|
|
168
|
+
* PulseChain, never on an entry chain — that resolve to two different pool
|
|
169
|
+
* tokens, so there is at least one path to price.
|
|
170
|
+
*
|
|
171
|
+
* NO TOKEN ALLOWLIST IS CONSULTED, on purpose: whether a pool exists is the
|
|
172
|
+
* reserve read's question, answered in `prepare`, not this one's.
|
|
173
|
+
*/
|
|
174
|
+
const pulseXCarries = (from, to) => {
|
|
175
|
+
if (from.ecosystem !== 'evm' || to.ecosystem !== 'evm')
|
|
176
|
+
return false;
|
|
177
|
+
if (from.chainId !== producers_js_1.pulsechainChainId || to.chainId !== producers_js_1.pulsechainChainId)
|
|
178
|
+
return false;
|
|
179
|
+
return pulseXCandidatePaths(from, to).length > 0;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* The swap path a planned PulseX hop must be signed along: the path its
|
|
183
|
+
* outcome records ({@link HopOutcome.swapPath}), which is the path the
|
|
184
|
+
* carrier's `exact` priced the figure on. NEVER REBUILT HERE — rebuilding a
|
|
185
|
+
* path at signing time is how a quote on one path becomes a signature on
|
|
186
|
+
* another.
|
|
187
|
+
*
|
|
188
|
+
* The recorded path is checked against the hop's own edge before it is
|
|
189
|
+
* returned: it must start at the edge's input pool token, end at its output
|
|
190
|
+
* pool token, hold only addresses, and name no token twice. A path that fails
|
|
191
|
+
* any check is refused (null) rather than signed.
|
|
192
|
+
*
|
|
193
|
+
* @param hop - the planned hop to sign
|
|
194
|
+
* @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
|
|
195
|
+
*/
|
|
196
|
+
const pulseXSwapPathForHop = (hop) => {
|
|
197
|
+
if (hop.edge.carrier !== 'PulseX')
|
|
198
|
+
return null;
|
|
199
|
+
const swapPath = hop.outcome.swapPath;
|
|
200
|
+
if (swapPath === undefined || swapPath.length < 2)
|
|
201
|
+
return null;
|
|
202
|
+
if (!swapPath.every((address) => (0, viem_1.isAddress)(address, { strict: false })))
|
|
203
|
+
return null;
|
|
204
|
+
const path = swapPath.map((address) => (0, viem_1.getAddress)(address));
|
|
205
|
+
if (new Set(path).size !== path.length)
|
|
206
|
+
return null;
|
|
207
|
+
if (!(0, ecosystems_js_1.sameAddress)(path[0], poolTokenAddress(hop.edge.from), 'evm'))
|
|
208
|
+
return null;
|
|
209
|
+
if (!(0, ecosystems_js_1.sameAddress)(path[path.length - 1], poolTokenAddress(hop.edge.to), 'evm'))
|
|
210
|
+
return null;
|
|
211
|
+
return path;
|
|
212
|
+
};
|
|
213
|
+
exports.pulseXSwapPathForHop = pulseXSwapPathForHop;
|
|
214
|
+
/**
|
|
215
|
+
* PulseX's own constant-product swap fee, as a numerator over
|
|
216
|
+
* {@link PULSEX_FEE_DENOMINATOR} — the SAME `getAmountOut` formula every
|
|
217
|
+
* Uniswap-V2-style router uses: `amountOut = floor(amountIn * feeNumerator *
|
|
218
|
+
* reserveOut / (reserveIn * feeDenominator + amountIn * feeNumerator))`.
|
|
219
|
+
*
|
|
220
|
+
* MEASURED, NOT ASSUMED — see `uniV2Factories`'s own doc in `config.ts` for
|
|
221
|
+
* the full account. Reading live reserves from both PulseX router
|
|
222
|
+
* deployments (V1 `0x98bf93…`, V2 `0x165C34…`) and back-solving this ratio
|
|
223
|
+
* against each router's own `getAmountsOut`, on a one-hop and a two-hop
|
|
224
|
+
* path, gave the IDENTICAL ratio — `9971/10000` — for both, to the base
|
|
225
|
+
* unit. One constant serves both, and `pulsex-carrier.fork.test.ts` pins
|
|
226
|
+
* this claim against live chain data rather than trusting the arithmetic
|
|
227
|
+
* alone.
|
|
228
|
+
*/
|
|
229
|
+
const PULSEX_FEE_NUMERATOR = 9971n;
|
|
230
|
+
/** The denominator {@link PULSEX_FEE_NUMERATOR} is a fraction of. */
|
|
231
|
+
const PULSEX_FEE_DENOMINATOR = 10000n;
|
|
232
|
+
/** The minimal read-only factory ABI `prepare` needs — just enough to resolve a pool's address from its two tokens. */
|
|
233
|
+
const univ2FactoryAbi = (0, viem_1.parseAbi)([
|
|
234
|
+
'function getPair(address tokenA, address tokenB) external view returns (address pair)',
|
|
235
|
+
]);
|
|
236
|
+
/**
|
|
237
|
+
* The Uniswap-V2-style constant-product `getAmountOut`, applied with
|
|
238
|
+
* {@link PULSEX_FEE_NUMERATOR}. Reproduces the router's own on-chain formula
|
|
239
|
+
* exactly — see `pulsex-carrier.fork.test.ts` — so PRICE IMPACT IS ALREADY
|
|
240
|
+
* IN HERE: this is the same nonlinear function the router evaluates, not a
|
|
241
|
+
* flat-fee approximation with an impact correction bolted on afterward.
|
|
242
|
+
*
|
|
243
|
+
* @param amountIn - the input amount, in `reserveIn`'s token's base units
|
|
244
|
+
* @param reserveIn - the pool's reserve of the input token
|
|
245
|
+
* @param reserveOut - the pool's reserve of the output token
|
|
246
|
+
* @returns the output amount, in `reserveOut`'s token's base units; 0 when either reserve is 0
|
|
247
|
+
*/
|
|
248
|
+
const constantProductAmountOut = (amountIn, reserveIn, reserveOut) => {
|
|
249
|
+
if (reserveIn <= 0n || reserveOut <= 0n || amountIn <= 0n)
|
|
250
|
+
return 0n;
|
|
251
|
+
const amountInWithFee = amountIn * PULSEX_FEE_NUMERATOR;
|
|
252
|
+
const numerator = amountInWithFee * reserveOut;
|
|
253
|
+
const denominator = reserveIn * PULSEX_FEE_DENOMINATOR + amountInWithFee;
|
|
254
|
+
return numerator / denominator;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* What `amountIn` delivers after crossing every pool of a path in order,
|
|
258
|
+
* chaining {@link constantProductAmountOut}.
|
|
259
|
+
*
|
|
260
|
+
* @param amountIn - the amount entering the first pool
|
|
261
|
+
* @param steps - the pools in order of travel
|
|
262
|
+
* @returns the amount leaving the last pool; 0 when any pool cannot pay out
|
|
263
|
+
*/
|
|
264
|
+
const amountOutAlongPath = (amountIn, steps) => steps.reduce((runningAmount, step) => constantProductAmountOut(runningAmount, step.reserveIn, step.reserveOut), amountIn);
|
|
265
|
+
exports.amountOutAlongPath = amountOutAlongPath;
|
|
266
|
+
/**
|
|
267
|
+
* How far a trade's executed rate falls below the marginal (near-spot) rate,
|
|
268
|
+
* in percent. The marginal rate is a tiny reference trade through the same
|
|
269
|
+
* pools (`amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR`). The swap fee rides
|
|
270
|
+
* in both rates, so it cancels and the gap is price impact alone. A path
|
|
271
|
+
* through several pools composes by itself: the reference trade crosses the
|
|
272
|
+
* same pools in the same order.
|
|
273
|
+
*
|
|
274
|
+
* impact = (1 - (amountOut / amountIn) / (referenceAmountOut / referenceAmountIn)) * 100
|
|
275
|
+
*
|
|
276
|
+
* @param options.amountIn - the full trade's input
|
|
277
|
+
* @param options.amountOut - what the full trade delivers
|
|
278
|
+
* @param options.referenceAmountIn - the reference trade's input
|
|
279
|
+
* @param options.referenceAmountOut - what the reference trade delivers
|
|
280
|
+
* @returns the impact in percent, never below 0; null when a reference figure is zero (impact cannot be measured)
|
|
281
|
+
*/
|
|
282
|
+
const priceImpactAgainstMarginal = (options) => {
|
|
283
|
+
const { amountIn, amountOut, referenceAmountIn, referenceAmountOut } = options;
|
|
284
|
+
if (amountIn <= 0n || referenceAmountIn <= 0n || referenceAmountOut <= 0n)
|
|
285
|
+
return null;
|
|
286
|
+
// The output the trade would deliver at the marginal rate with no impact.
|
|
287
|
+
const idealAmountOut = (referenceAmountOut * amountIn) / referenceAmountIn;
|
|
288
|
+
if (idealAmountOut <= 0n)
|
|
289
|
+
return null;
|
|
290
|
+
if (amountOut >= idealAmountOut)
|
|
291
|
+
return 0;
|
|
292
|
+
const MILLIONTHS = 1000000n;
|
|
293
|
+
return Number(((idealAmountOut - amountOut) * MILLIONTHS * 100n) / idealAmountOut) / Number(MILLIONTHS);
|
|
294
|
+
};
|
|
295
|
+
exports.priceImpactAgainstMarginal = priceImpactAgainstMarginal;
|
|
296
|
+
/** 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. */
|
|
297
|
+
const poolKey = (tokenA, tokenB) => {
|
|
298
|
+
const [lower, higher] = [tokenA.toLowerCase(), tokenB.toLowerCase()].sort();
|
|
299
|
+
return `${lower}|${higher}`;
|
|
300
|
+
};
|
|
301
|
+
/** 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. */
|
|
302
|
+
const venuePoolKey = (venue, tokenA, tokenB) => `${venue.factory.toLowerCase()}|${poolKey(tokenA, tokenB)}`;
|
|
303
|
+
/** Every consecutive (tokenIn, tokenOut) hop a PulseX swap path implies. */
|
|
304
|
+
const hopsAlongPath = (path) => path.slice(0, -1).map((tokenIn, index) => [tokenIn, path[index + 1]]);
|
|
305
|
+
/**
|
|
306
|
+
* The pools a path crosses on one venue, each read in the direction of
|
|
307
|
+
* travel, or null when any of them was never read — `getPair` found no pool,
|
|
308
|
+
* or the read failed. A path with a missing pool cannot be priced, exactly as
|
|
309
|
+
* the router's own `getAmountsOut` would revert on it.
|
|
310
|
+
*
|
|
311
|
+
* @param options.venue - the deployment whose pools the path trades against
|
|
312
|
+
* @param options.path - the tokens, in order
|
|
313
|
+
* @param options.reservesByPoolKey - every pool `prepare` read, keyed by {@link venuePoolKey}
|
|
314
|
+
* @returns the steps in order, or null when a pool is missing
|
|
315
|
+
*/
|
|
316
|
+
const stepsAlongPath = (options) => {
|
|
317
|
+
const { venue, path, reservesByPoolKey } = options;
|
|
318
|
+
const steps = [];
|
|
319
|
+
for (const [tokenIn, tokenOut] of hopsAlongPath(path)) {
|
|
320
|
+
const reserves = reservesByPoolKey.get(venuePoolKey(venue, tokenIn, tokenOut));
|
|
321
|
+
if (reserves === undefined)
|
|
322
|
+
return null;
|
|
323
|
+
const tokenInIsLower = tokenIn.toLowerCase() < tokenOut.toLowerCase();
|
|
324
|
+
steps.push({
|
|
325
|
+
reserveIn: tokenInIsLower ? reserves.reserveOfLowerAddress : reserves.reserveOfHigherAddress,
|
|
326
|
+
reserveOut: tokenInIsLower ? reserves.reserveOfHigherAddress : reserves.reserveOfLowerAddress,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return steps;
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* Batch-reads reserves for every pool `nodes`' candidate PulseX paths touch,
|
|
333
|
+
* on EVERY venue in `venues`, once, so {@link createPulseXCarrier}'s `exact`
|
|
334
|
+
* can answer every hop SYNCHRONOUSLY afterward.
|
|
335
|
+
*
|
|
336
|
+
* EACH VENUE IS READ THROUGH ITS OWN FACTORY. The same two tokens usually
|
|
337
|
+
* have a pool on V1 and another on V2, with different reserves; the two are
|
|
338
|
+
* stored apart ({@link venuePoolKey}) and never blended. `PULSEX_FEE_NUMERATOR`
|
|
339
|
+
* is proven identical for both deployments (see its own doc), so the formula
|
|
340
|
+
* is shared and only the reserves differ.
|
|
341
|
+
*
|
|
342
|
+
* TWO MULTICALLS ON PULSECHAIN, WHATEVER THE VENUE COUNT: `getPair` must
|
|
343
|
+
* resolve each pool's address before `getReserves` can be read from it, so
|
|
344
|
+
* the second read cannot join the first — but every venue's `getPair` rides
|
|
345
|
+
* the first multicall and every pool's `getReserves` rides the second.
|
|
346
|
+
*
|
|
347
|
+
* THE HUB SEARCH WIDENS EACH READ, NOT THEIR NUMBER. Every pool any candidate
|
|
348
|
+
* path crosses is read here ({@link pulseXCandidatePaths}), so `exact` can
|
|
349
|
+
* price every path with no further call. Measured 2026-10-06: an 8-token
|
|
350
|
+
* PulseChain universe asked 84 entries across the two reads (the fixed
|
|
351
|
+
* Wrapped-PLS path asked about 16). The shared multicall helper splits a read
|
|
352
|
+
* past its request-size budget into sequential posts; `getPair` crosses that
|
|
353
|
+
* line at about 11 pool tokens (198 entries over two venues).
|
|
354
|
+
*
|
|
355
|
+
* @param nodes - every node the search might branch through
|
|
356
|
+
* @param venues - the venues this carrier offers
|
|
357
|
+
* @param clientForChain - see {@link CreatePulseXCarrierOptions}'s own field
|
|
358
|
+
* @returns every pool this universe touches, keyed by {@link venuePoolKey} — pools with no `getPair` result are simply absent, not an error
|
|
359
|
+
*/
|
|
360
|
+
const preparePulseXReserves = async ({ nodes, venues, clientForChain, }) => {
|
|
361
|
+
const reservesByPoolKey = new Map();
|
|
362
|
+
const client = clientForChain(producers_js_1.pulsechainChainId);
|
|
363
|
+
if (client === null || venues.length === 0)
|
|
364
|
+
return reservesByPoolKey;
|
|
365
|
+
const pulsechainNodes = nodes.filter((node) => node.ecosystem === 'evm' && node.chainId === producers_js_1.pulsechainChainId);
|
|
366
|
+
const poolTokenPairs = new Map();
|
|
367
|
+
for (const from of pulsechainNodes) {
|
|
368
|
+
for (const to of pulsechainNodes) {
|
|
369
|
+
if ((0, graph_js_1.assetNodeKey)(from) === (0, graph_js_1.assetNodeKey)(to))
|
|
370
|
+
continue;
|
|
371
|
+
if (!pulseXCarries(from, to))
|
|
372
|
+
continue;
|
|
373
|
+
for (const path of pulseXCandidatePaths(from, to)) {
|
|
374
|
+
for (const [tokenIn, tokenOut] of hopsAlongPath(path)) {
|
|
375
|
+
poolTokenPairs.set(poolKey(tokenIn, tokenOut), [tokenIn, tokenOut]);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (poolTokenPairs.size === 0)
|
|
381
|
+
return reservesByPoolKey;
|
|
382
|
+
const poolQueries = venues.flatMap((venue) => Array.from(poolTokenPairs.values()).map(([tokenIn, tokenOut]) => ({
|
|
383
|
+
key: venuePoolKey(venue, tokenIn, tokenOut),
|
|
384
|
+
factory: venue.factory,
|
|
385
|
+
tokenIn,
|
|
386
|
+
tokenOut,
|
|
387
|
+
})));
|
|
388
|
+
const factoryResults = await (0, multicall_1.multicallRead)({
|
|
389
|
+
chain: chains_js_1.chainsMetadata[(0, config_js_1.toChain)(producers_js_1.pulsechainChainId)],
|
|
390
|
+
client,
|
|
391
|
+
abi: univ2FactoryAbi,
|
|
392
|
+
calls: poolQueries.map((query) => ({
|
|
393
|
+
functionName: 'getPair',
|
|
394
|
+
target: query.factory,
|
|
395
|
+
args: [query.tokenIn, query.tokenOut],
|
|
396
|
+
allowFailure: true,
|
|
397
|
+
})),
|
|
398
|
+
});
|
|
399
|
+
const pairAddressByPoolKey = new Map();
|
|
400
|
+
poolQueries.forEach((query, index) => {
|
|
401
|
+
const pairAddress = factoryResults[index];
|
|
402
|
+
// A decoded address is exactly 42 characters; a failed `allowFailure`
|
|
403
|
+
// call's undecoded revert bytes are almost never that length — see
|
|
404
|
+
// `omnibridge.ts`'s identical guard for why `typeof` alone cannot tell
|
|
405
|
+
// success from failure for an address-returning call.
|
|
406
|
+
if (typeof pairAddress === 'string' && pairAddress.length === 42 && pairAddress !== viem_1.zeroAddress) {
|
|
407
|
+
pairAddressByPoolKey.set(query.key, (0, viem_1.getAddress)(pairAddress));
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
if (pairAddressByPoolKey.size === 0)
|
|
411
|
+
return reservesByPoolKey;
|
|
412
|
+
const reservePoolKeys = Array.from(pairAddressByPoolKey.keys());
|
|
413
|
+
const reserveResults = await (0, multicall_1.multicallRead)({
|
|
414
|
+
chain: chains_js_1.chainsMetadata[(0, config_js_1.toChain)(producers_js_1.pulsechainChainId)],
|
|
415
|
+
client,
|
|
416
|
+
abi: abis.pair,
|
|
417
|
+
calls: reservePoolKeys.map((key) => ({
|
|
418
|
+
functionName: 'getReserves',
|
|
419
|
+
target: pairAddressByPoolKey.get(key),
|
|
420
|
+
allowFailure: true,
|
|
421
|
+
})),
|
|
422
|
+
});
|
|
423
|
+
reservePoolKeys.forEach((key, index) => {
|
|
424
|
+
const result = reserveResults[index];
|
|
425
|
+
if (!Array.isArray(result))
|
|
426
|
+
return;
|
|
427
|
+
const [reserve0, reserve1] = result;
|
|
428
|
+
if (typeof reserve0 !== 'bigint' || typeof reserve1 !== 'bigint')
|
|
429
|
+
return;
|
|
430
|
+
// Standard Uniswap-V2-style factories sort a pair's tokens before
|
|
431
|
+
// creating it, so `token0` is always the LEXICOGRAPHICALLY LOWER
|
|
432
|
+
// address — confirmed against live PulseX pools 2026-09-24 (both V1 and
|
|
433
|
+
// V2), never re-derived with an extra `token0()` read.
|
|
434
|
+
reservesByPoolKey.set(key, { reserveOfLowerAddress: reserve0, reserveOfHigherAddress: reserve1 });
|
|
435
|
+
});
|
|
436
|
+
return reservesByPoolKey;
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* The venues a carrier offers: every known venue, or only the routers the
|
|
440
|
+
* host names. A named router that is not a known PulseX router is a
|
|
441
|
+
* configuration error, thrown at construction rather than offered silently.
|
|
442
|
+
*
|
|
443
|
+
* @param routers - the routers the host can sign on, or undefined for all
|
|
444
|
+
* @returns the venues, in {@link pulseXVenues} order
|
|
445
|
+
*/
|
|
446
|
+
const selectPulseXVenues = (routers) => {
|
|
447
|
+
if (routers === undefined)
|
|
448
|
+
return exports.pulseXVenues;
|
|
449
|
+
for (const router of routers) {
|
|
450
|
+
if (!exports.pulseXVenues.some((venue) => (0, ecosystems_js_1.sameAddress)(venue.router, router, 'evm'))) {
|
|
451
|
+
throw new Error(`createPulseXCarrier: ${router} is not a known PulseX router on PulseChain`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return exports.pulseXVenues.filter((venue) => routers.some((router) => (0, ecosystems_js_1.sameAddress)(venue.router, router, 'evm')));
|
|
455
|
+
};
|
|
456
|
+
/**
|
|
457
|
+
* Builds the PulseX conversion swap as a plug-in carrier.
|
|
458
|
+
*
|
|
459
|
+
* @param options - see {@link CreatePulseXCarrierOptions}
|
|
460
|
+
* @returns the carrier — `id: 'PulseX'`
|
|
461
|
+
*/
|
|
462
|
+
const createPulseXCarrier = (options) => {
|
|
463
|
+
const { readAmountOut, clientForChain } = options;
|
|
464
|
+
const venues = selectPulseXVenues(options.routers);
|
|
465
|
+
// ONE EDGE PER VENUE. Each names its router as its `venue`, so the search
|
|
466
|
+
// quotes and ranks V1 and V2 as two separate hops and the better one wins
|
|
467
|
+
// on its own figure — and the winner is signed on the router it names.
|
|
468
|
+
const edgesFrom = (from, to) => {
|
|
469
|
+
if ((0, graph_js_1.assetNodeKey)(from) === (0, graph_js_1.assetNodeKey)(to))
|
|
470
|
+
return [];
|
|
471
|
+
if (!pulseXCarries(from, to))
|
|
472
|
+
return [];
|
|
473
|
+
return venues.map((venue) => ({
|
|
474
|
+
carrier: 'PulseX',
|
|
475
|
+
from,
|
|
476
|
+
to,
|
|
477
|
+
fusesNext: false,
|
|
478
|
+
origin: 'wallet',
|
|
479
|
+
floors: [],
|
|
480
|
+
venue: venue.router,
|
|
481
|
+
}));
|
|
482
|
+
};
|
|
483
|
+
const ask = async ({ edge, inputAmount }) => {
|
|
484
|
+
const amountOut = await readAmountOut(edge, inputAmount);
|
|
485
|
+
if (amountOut === null || amountOut <= 0n) {
|
|
486
|
+
return { ok: false, refusal: { kind: 'no-quote', reason: 'no liquidity on this PulseX path', minimumAmount: null } };
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
ok: true,
|
|
490
|
+
deliveredAmount: amountOut,
|
|
491
|
+
// getAmountsOut is exact for the CURRENT block; no slippage tolerance is
|
|
492
|
+
// modelled here, so the guaranteed figure is left equal to the expected
|
|
493
|
+
// one, matching the omnibridge carrier's own convention.
|
|
494
|
+
minimumDeliveredAmount: amountOut,
|
|
495
|
+
claimedDurationSeconds: 0,
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
// Populated by `prepare`, read by `exact` — see `omnibridge.ts`'s identical
|
|
499
|
+
// field for why a mutable closure here is safe (client-side,
|
|
500
|
+
// one-search-at-a-time; PulseX is likewise "never built into" the shared
|
|
501
|
+
// display-quote service).
|
|
502
|
+
let reservesByPoolKey = new Map();
|
|
503
|
+
const prepare = async (nodes) => {
|
|
504
|
+
if (clientForChain === undefined)
|
|
505
|
+
return;
|
|
506
|
+
reservesByPoolKey = await preparePulseXReserves({ nodes, venues, clientForChain });
|
|
507
|
+
};
|
|
508
|
+
const exact = (edge, amountIn) => {
|
|
509
|
+
if (!pulseXCarries(edge.from, edge.to))
|
|
510
|
+
return null;
|
|
511
|
+
// Quoted on the edge's OWN venue — the router it will be signed on — and
|
|
512
|
+
// only on a venue this carrier offers.
|
|
513
|
+
const venue = pulseXVenueForEdge(edge);
|
|
514
|
+
if (venue === null || !venues.includes(venue))
|
|
515
|
+
return null;
|
|
516
|
+
// EVERY CANDIDATE PATH IS PRICED, AND THE BEST ONE WINS. A path with a
|
|
517
|
+
// pool `prepare` never found is left out — the same as a router call
|
|
518
|
+
// that would revert — and a path that pays out nothing is never picked.
|
|
519
|
+
const quotes = pulseXCandidatePaths(edge.from, edge.to).flatMap((path) => {
|
|
520
|
+
const steps = stepsAlongPath({ venue, path, reservesByPoolKey });
|
|
521
|
+
return steps === null ? [] : [{ path, steps, deliveredAmount: (0, exports.amountOutAlongPath)(amountIn, steps) }];
|
|
522
|
+
});
|
|
523
|
+
// No path had every pool read — a structural non-answer, not a refusal
|
|
524
|
+
// (`exact`'s own doc: `null` means "nothing to say"). NOTE THIS DOES NOT
|
|
525
|
+
// FALL BACK TO AN ESTIMATE: a carrier with `exact` defined never yields
|
|
526
|
+
// an `'estimate'` outcome (see `enumerate.ts`'s `resolveHopOutcome`,
|
|
527
|
+
// branch 1) — `null` here PRUNES the hop entirely, the same as a refusal.
|
|
528
|
+
if (quotes.length === 0)
|
|
529
|
+
return null;
|
|
530
|
+
const best = (0, pulsex_paths_js_1.bestPulseXPathQuote)(quotes);
|
|
531
|
+
if (best === null) {
|
|
532
|
+
return { outcome: 'refused', refusal: { kind: 'no-quote', reason: 'no liquidity on this PulseX path', minimumAmount: null } };
|
|
533
|
+
}
|
|
534
|
+
// Price impact is measured on the CHOSEN path's own pools, with the same
|
|
535
|
+
// reference trade as before.
|
|
536
|
+
const referenceAmountIn = amountIn / producers_js_1.PULSEX_MARGINAL_REFERENCE_DIVISOR;
|
|
537
|
+
const priceImpactPercent = (0, exports.priceImpactAgainstMarginal)({
|
|
538
|
+
amountIn,
|
|
539
|
+
amountOut: best.deliveredAmount,
|
|
540
|
+
referenceAmountIn,
|
|
541
|
+
referenceAmountOut: (0, exports.amountOutAlongPath)(referenceAmountIn, best.steps),
|
|
542
|
+
});
|
|
543
|
+
// The path rides on the answer, so the signer signs exactly what was
|
|
544
|
+
// priced (`pulseXSwapPathForHop`). Impact is left off (not zero) when the
|
|
545
|
+
// reference trade rounds away: an unknown impact must stay unknown so the
|
|
546
|
+
// quality gate withholds the plan.
|
|
547
|
+
const delivered = { outcome: 'delivered', deliveredAmount: best.deliveredAmount, swapPath: best.path };
|
|
548
|
+
return priceImpactPercent === null ? delivered : { ...delivered, priceImpactPercent };
|
|
549
|
+
};
|
|
550
|
+
return {
|
|
551
|
+
id: 'PulseX',
|
|
552
|
+
edgesFrom,
|
|
553
|
+
ask,
|
|
554
|
+
prepare,
|
|
555
|
+
exact,
|
|
556
|
+
isRateLimited: false,
|
|
557
|
+
// A same-chain swap requires the reader's own wallet signature — never
|
|
558
|
+
// a by-hand deposit.
|
|
559
|
+
depositMode: 'wallet-signed',
|
|
560
|
+
sender: 'swap',
|
|
561
|
+
// A same-chain swap settles with the transaction itself.
|
|
562
|
+
settlementSeconds: 0,
|
|
563
|
+
};
|
|
564
|
+
};
|
|
565
|
+
exports.createPulseXCarrier = createPulseXCarrier;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type AssetNode, type HopEdge } from '../search/graph.js';
|
|
2
|
+
import type { CarrierAskAnswer, CarrierExactResult } from './ask-answer.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the native wrap/unwrap carrier as a plug-in.
|
|
5
|
+
*
|
|
6
|
+
* NO OPTIONS, NO `prepare`. There is no local read to batch — a wrap has no
|
|
7
|
+
* minimum, no maximum, no fee rate and no reserve to be wrong about; it is a
|
|
8
|
+
* pure 1:1 identity over every chain `config.ts` names a wrapped-native
|
|
9
|
+
* token for. `exact` alone is total over every edge this carrier offers.
|
|
10
|
+
*
|
|
11
|
+
* @returns the carrier — `id: 'wrap'`
|
|
12
|
+
*/
|
|
13
|
+
export declare const createWrapCarrier: () => {
|
|
14
|
+
id: string;
|
|
15
|
+
edgesFrom: (from: AssetNode, to: AssetNode) => readonly HopEdge[];
|
|
16
|
+
ask: ({ edge, inputAmount }: {
|
|
17
|
+
edge: HopEdge;
|
|
18
|
+
inputAmount: bigint;
|
|
19
|
+
}) => Promise<CarrierAskAnswer>;
|
|
20
|
+
exact: (edge: HopEdge, amountIn: bigint) => CarrierExactResult | null;
|
|
21
|
+
isRateLimited: boolean;
|
|
22
|
+
depositMode: "wallet-signed";
|
|
23
|
+
sender: "swap";
|
|
24
|
+
settlementSeconds: number;
|
|
25
|
+
};
|