@gibs/bridge-sdk 1.13.1 → 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 +17 -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 +31 -21
- package/dist/routing/carriers/pulsex.js +141 -71
- 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 +6 -2
- package/dist/routing/index.js +6 -2
- 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 +15 -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 +3 -0
- package/dist/routing/search/score.js +11 -3
- 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 +75 -10
- 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 +817 -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,459 @@
|
|
|
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.withOmnibridgeEntryFusing = exports.createOmnibridgeCarrier = 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 bridge_limits_js_1 = require("../../bridge-limits.js");
|
|
41
|
+
const chains_js_1 = require("../../chains.js");
|
|
42
|
+
const config_js_1 = require("../../config.js");
|
|
43
|
+
const ecosystems_js_1 = require("../../ecosystems.js");
|
|
44
|
+
const providers_js_1 = require("../../providers.js");
|
|
45
|
+
const amounts_js_1 = require("../amounts.js");
|
|
46
|
+
const entry_chain_anchor_js_1 = require("../entry-chain-anchor.js");
|
|
47
|
+
const producers_js_1 = require("../producers.js");
|
|
48
|
+
const graph_js_1 = require("../search/graph.js");
|
|
49
|
+
/** Builds an {@link AssetNode} for an Ethereum-Virtual-Machine token. */
|
|
50
|
+
const evmNode = (token) => ({
|
|
51
|
+
chainId: token.chainId,
|
|
52
|
+
address: token.address,
|
|
53
|
+
decimals: token.decimals,
|
|
54
|
+
ecosystem: 'evm',
|
|
55
|
+
});
|
|
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
|
+
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
|
+
const omnibridgeCarrierBetween = (fromChainId, toChainId, isProductionNetwork) => {
|
|
69
|
+
const fromChain = (0, config_js_1.toChain)(fromChainId);
|
|
70
|
+
const toChainKey = (0, config_js_1.toChain)(toChainId);
|
|
71
|
+
const omnibridgeProviders = [config_js_1.Providers.PULSECHAIN, config_js_1.Providers.TOKENSEX];
|
|
72
|
+
for (const provider of omnibridgeProviders) {
|
|
73
|
+
const group = (0, config_js_1.pathwayGroup)(provider, fromChain, isProductionNetwork);
|
|
74
|
+
if (group?.[toChainKey]) {
|
|
75
|
+
return provider;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
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
|
+
const buildOmnibridgeEdgeMap = (bridgeablePairs, isProductionNetwork) => {
|
|
87
|
+
const edges = new Map();
|
|
88
|
+
for (const pair of bridgeablePairs) {
|
|
89
|
+
const inboundCarrier = omnibridgeCarrierBetween(pair.entryChainId, pair.pulsechainToken.chainId, isProductionNetwork);
|
|
90
|
+
if (inboundCarrier !== null) {
|
|
91
|
+
const anchors = (0, entry_chain_anchor_js_1.entryChainAnchorsForPair)({
|
|
92
|
+
entryChainToken: pair.entryChainToken,
|
|
93
|
+
pulsechainToken: pair.pulsechainToken,
|
|
94
|
+
entryChainId: pair.entryChainId,
|
|
95
|
+
});
|
|
96
|
+
for (const anchor of anchors) {
|
|
97
|
+
edges.set(directedPairKey(evmNode(anchor.token), evmNode(anchor.landsAs)), inboundCarrier);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const outboundCarrier = omnibridgeCarrierBetween(pair.pulsechainToken.chainId, pair.entryChainId, isProductionNetwork);
|
|
101
|
+
if (outboundCarrier !== null) {
|
|
102
|
+
edges.set(directedPairKey(evmNode(pair.pulsechainToken), evmNode(pair.entryChainToken)), outboundCarrier);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return edges;
|
|
106
|
+
};
|
|
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
|
+
const resolveCrossing = (edge, isProductionNetwork) => {
|
|
115
|
+
const bridgeKey = [edge.carrier, (0, config_js_1.toChain)(edge.from.chainId), (0, config_js_1.toChain)(edge.to.chainId)];
|
|
116
|
+
const wrappedNativeAddress = config_js_1.nativeAssetOut[(0, config_js_1.toChain)(edge.from.chainId)];
|
|
117
|
+
const isNativeEntry = edge.from.address.toLowerCase() === viem_1.zeroAddress;
|
|
118
|
+
const entryTokenAddress = isNativeEntry && wrappedNativeAddress !== undefined ? (0, viem_1.getAddress)(wrappedNativeAddress) : (0, viem_1.getAddress)(edge.from.address);
|
|
119
|
+
const path = (0, config_js_1.pathway)(bridgeKey, isProductionNetwork, entryTokenAddress);
|
|
120
|
+
if (path === undefined)
|
|
121
|
+
return null;
|
|
122
|
+
const feeManagerHostChainId = path.feeManager === 'from' ? edge.from.chainId : edge.to.chainId;
|
|
123
|
+
const feeManagerHostAddress = path.feeManager === 'from' ? path.from : path.to;
|
|
124
|
+
return {
|
|
125
|
+
edge,
|
|
126
|
+
entryTokenAddress,
|
|
127
|
+
entryMediator: path.from,
|
|
128
|
+
limitsChainId: edge.from.chainId,
|
|
129
|
+
feeManagerHostAddress,
|
|
130
|
+
feeManagerHostChainId,
|
|
131
|
+
feeTypeKey: path.toHome ? config_js_1.FOREIGN_TO_HOME_FEE : config_js_1.HOME_TO_FOREIGN_FEE,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
/** Groups values by a computed chain id, preserving the deduped-by-`keyOf` invariant callers rely on. */
|
|
135
|
+
const groupByChainId = (values, chainIdOf) => {
|
|
136
|
+
const groups = new Map();
|
|
137
|
+
for (const value of values) {
|
|
138
|
+
const chainId = chainIdOf(value);
|
|
139
|
+
const group = groups.get(chainId);
|
|
140
|
+
if (group === undefined) {
|
|
141
|
+
groups.set(chainId, [value]);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
group.push(value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return groups;
|
|
148
|
+
};
|
|
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
|
+
const readStageOneBatch = async ({ resolvedCrossings, clientForChain, }) => {
|
|
151
|
+
const limitsByKey = new Map();
|
|
152
|
+
const feeManagerAddressByKey = new Map();
|
|
153
|
+
const limitsRequests = new Map();
|
|
154
|
+
for (const crossing of resolvedCrossings) {
|
|
155
|
+
limitsRequests.set(limitsKey(crossing), {
|
|
156
|
+
chainId: crossing.limitsChainId,
|
|
157
|
+
mediator: crossing.entryMediator,
|
|
158
|
+
token: crossing.entryTokenAddress,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const feeManagerAddressRequests = new Map();
|
|
162
|
+
for (const crossing of resolvedCrossings) {
|
|
163
|
+
feeManagerAddressRequests.set(feeManagerHostKey(crossing), {
|
|
164
|
+
chainId: crossing.feeManagerHostChainId,
|
|
165
|
+
mediatorHost: crossing.feeManagerHostAddress,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const requestsByChain = groupByChainId([
|
|
169
|
+
...Array.from(limitsRequests.entries()).map(([key, request]) => ({ kind: 'limits', key, ...request })),
|
|
170
|
+
...Array.from(feeManagerAddressRequests.entries()).map(([key, request]) => ({
|
|
171
|
+
kind: 'fee-manager-address',
|
|
172
|
+
key,
|
|
173
|
+
chainId: request.chainId,
|
|
174
|
+
mediatorHost: request.mediatorHost,
|
|
175
|
+
})),
|
|
176
|
+
], (request) => request.chainId);
|
|
177
|
+
await Promise.all(Array.from(requestsByChain.entries()).map(async ([chainId, requests]) => {
|
|
178
|
+
const client = clientForChain(chainId);
|
|
179
|
+
if (client === null)
|
|
180
|
+
return;
|
|
181
|
+
const results = await (0, multicall_1.multicallRead)({
|
|
182
|
+
chain: chains_js_1.chainsMetadata[(0, config_js_1.toChain)(chainId)],
|
|
183
|
+
client,
|
|
184
|
+
abi: bridge_limits_js_1.entryLimitsAbi,
|
|
185
|
+
calls: requests.flatMap((request) => request.kind === 'limits'
|
|
186
|
+
? [
|
|
187
|
+
{ functionName: 'minPerTx', args: [request.token], target: request.mediator, allowFailure: true },
|
|
188
|
+
{ functionName: 'maxAvailablePerTx', args: [request.token], target: request.mediator, allowFailure: true },
|
|
189
|
+
]
|
|
190
|
+
: [{ functionName: 'feeManager', abi: abis.inputBridge, args: [], target: request.mediatorHost, allowFailure: true }]),
|
|
191
|
+
});
|
|
192
|
+
let cursor = 0;
|
|
193
|
+
for (const request of requests) {
|
|
194
|
+
if (request.kind === 'limits') {
|
|
195
|
+
const minimumPerTransaction = results[cursor];
|
|
196
|
+
const availablePerTransaction = results[cursor + 1];
|
|
197
|
+
cursor += 2;
|
|
198
|
+
if (typeof minimumPerTransaction === 'bigint' && typeof availablePerTransaction === 'bigint') {
|
|
199
|
+
limitsByKey.set(request.key, { minimumPerTransaction, availablePerTransaction });
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const feeManagerAddress = results[cursor];
|
|
204
|
+
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
|
+
if (typeof feeManagerAddress === 'string' && feeManagerAddress.length === 42) {
|
|
211
|
+
feeManagerAddressByKey.set(request.key, (0, viem_1.getAddress)(feeManagerAddress));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}));
|
|
215
|
+
return { limitsByKey, feeManagerAddressByKey };
|
|
216
|
+
};
|
|
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
|
+
const readStageTwoBatch = async ({ resolvedCrossings, feeManagerAddressByKey, clientForChain, }) => {
|
|
219
|
+
const feeRateByKey = new Map();
|
|
220
|
+
const feeRateRequests = new Map();
|
|
221
|
+
for (const crossing of resolvedCrossings) {
|
|
222
|
+
const feeManagerAddress = feeManagerAddressByKey.get(feeManagerHostKey(crossing));
|
|
223
|
+
if (feeManagerAddress === undefined)
|
|
224
|
+
continue;
|
|
225
|
+
feeRateRequests.set(feeRateKey(crossing, feeManagerAddress), {
|
|
226
|
+
chainId: crossing.feeManagerHostChainId,
|
|
227
|
+
feeManagerAddress,
|
|
228
|
+
feeTypeKey: crossing.feeTypeKey,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const requestsByChain = groupByChainId(Array.from(feeRateRequests.entries()), ([, request]) => request.chainId);
|
|
232
|
+
await Promise.all(Array.from(requestsByChain.entries()).map(async ([chainId, entries]) => {
|
|
233
|
+
const client = clientForChain(chainId);
|
|
234
|
+
if (client === null)
|
|
235
|
+
return;
|
|
236
|
+
const results = await (0, multicall_1.multicallRead)({
|
|
237
|
+
chain: chains_js_1.chainsMetadata[(0, config_js_1.toChain)(chainId)],
|
|
238
|
+
client,
|
|
239
|
+
abi: abis.feeManager,
|
|
240
|
+
calls: entries.map(([, request]) => ({
|
|
241
|
+
functionName: 'getFee',
|
|
242
|
+
args: [request.feeTypeKey, viem_1.zeroAddress],
|
|
243
|
+
target: request.feeManagerAddress,
|
|
244
|
+
allowFailure: true,
|
|
245
|
+
})),
|
|
246
|
+
});
|
|
247
|
+
entries.forEach(([key], index) => {
|
|
248
|
+
const rate = results[index];
|
|
249
|
+
if (typeof rate === 'bigint') {
|
|
250
|
+
feeRateByKey.set(key, rate);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}));
|
|
254
|
+
return feeRateByKey;
|
|
255
|
+
};
|
|
256
|
+
const limitsKey = (crossing) => `${crossing.limitsChainId}|${crossing.entryMediator}|${crossing.entryTokenAddress}`;
|
|
257
|
+
const feeManagerHostKey = (crossing) => `${crossing.feeManagerHostChainId}|${crossing.feeManagerHostAddress}`;
|
|
258
|
+
const feeRateKey = (crossing, feeManagerAddress) => `${crossing.feeManagerHostChainId}|${feeManagerAddress}|${crossing.feeTypeKey}`;
|
|
259
|
+
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
|
+
const prepareOmnibridgeReads = async ({ nodes, edgesFrom, isProductionNetwork, clientForChain, }) => {
|
|
290
|
+
const edges = [];
|
|
291
|
+
for (const from of nodes) {
|
|
292
|
+
for (const to of nodes) {
|
|
293
|
+
if ((0, graph_js_1.assetNodeKey)(from) === (0, graph_js_1.assetNodeKey)(to))
|
|
294
|
+
continue;
|
|
295
|
+
edges.push(...edgesFrom(from, to));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const resolvedCrossings = edges
|
|
299
|
+
.map((edge) => resolveCrossing(edge, isProductionNetwork))
|
|
300
|
+
.filter((resolved) => resolved !== null);
|
|
301
|
+
const preparedByEdgeKey = new Map();
|
|
302
|
+
if (resolvedCrossings.length === 0)
|
|
303
|
+
return preparedByEdgeKey;
|
|
304
|
+
const { limitsByKey, feeManagerAddressByKey } = await readStageOneBatch({ resolvedCrossings, clientForChain });
|
|
305
|
+
const feeRateByKey = await readStageTwoBatch({ resolvedCrossings, feeManagerAddressByKey, clientForChain });
|
|
306
|
+
for (const crossing of resolvedCrossings) {
|
|
307
|
+
const feeManagerAddress = feeManagerAddressByKey.get(feeManagerHostKey(crossing));
|
|
308
|
+
const feePerEther = feeManagerAddress === undefined ? null : (feeRateByKey.get(feeRateKey(crossing, feeManagerAddress)) ?? null);
|
|
309
|
+
preparedByEdgeKey.set(preparedEdgeKey(crossing.edge), {
|
|
310
|
+
limits: limitsByKey.get(limitsKey(crossing)) ?? null,
|
|
311
|
+
feePerEther,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return preparedByEdgeKey;
|
|
315
|
+
};
|
|
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
|
+
const entryAmountRefusal = (verdict) => verdict.reason === 'below-minimum'
|
|
326
|
+
? {
|
|
327
|
+
kind: 'below-minimum',
|
|
328
|
+
reason: `below the entry mediator's minimum of ${verdict.minimumPerTransaction} base units`,
|
|
329
|
+
minimumAmount: verdict.minimumPerTransaction,
|
|
330
|
+
}
|
|
331
|
+
: {
|
|
332
|
+
kind: 'above-available',
|
|
333
|
+
reason: `above what the entry mediator has left today (${verdict.availablePerTransaction} base units)`,
|
|
334
|
+
minimumAmount: null,
|
|
335
|
+
availableAmount: verdict.availablePerTransaction,
|
|
336
|
+
};
|
|
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
|
+
const createOmnibridgeCarrier = (options) => {
|
|
346
|
+
const { bridgeablePairs, isProductionNetwork, readEntryLimits, readFeePerEther, clientForChain } = options;
|
|
347
|
+
const edgeMap = buildOmnibridgeEdgeMap(bridgeablePairs, isProductionNetwork);
|
|
348
|
+
const edgesFrom = (from, to) => {
|
|
349
|
+
if ((0, graph_js_1.assetNodeKey)(from) === (0, graph_js_1.assetNodeKey)(to))
|
|
350
|
+
return [];
|
|
351
|
+
const carrier = edgeMap.get(directedPairKey(from, to));
|
|
352
|
+
if (carrier === undefined)
|
|
353
|
+
return [];
|
|
354
|
+
return [{ carrier, from, to, fusesNext: false, origin: 'wallet', floors: [] }];
|
|
355
|
+
};
|
|
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
|
+
let preparedByEdgeKey = new Map();
|
|
362
|
+
const prepare = async (nodes) => {
|
|
363
|
+
if (clientForChain === undefined)
|
|
364
|
+
return;
|
|
365
|
+
preparedByEdgeKey = await prepareOmnibridgeReads({ nodes, edgesFrom, isProductionNetwork, clientForChain });
|
|
366
|
+
};
|
|
367
|
+
const exact = (edge, amountIn) => {
|
|
368
|
+
const prepared = preparedByEdgeKey.get(preparedEdgeKey(edge));
|
|
369
|
+
if (prepared === undefined)
|
|
370
|
+
return null;
|
|
371
|
+
const verdict = (0, bridge_limits_js_1.checkEntryAmount)({ amount: amountIn, limits: prepared.limits });
|
|
372
|
+
if (!verdict.fits) {
|
|
373
|
+
return { outcome: 'refused', refusal: entryAmountRefusal(verdict) };
|
|
374
|
+
}
|
|
375
|
+
return { outcome: 'delivered', deliveredAmount: (0, amounts_js_1.netDeliveredAmount)(amountIn, prepared.feePerEther) };
|
|
376
|
+
};
|
|
377
|
+
const ask = async ({ edge, inputAmount }) => {
|
|
378
|
+
const limits = await readEntryLimits(edge);
|
|
379
|
+
const verdict = (0, bridge_limits_js_1.checkEntryAmount)({ amount: inputAmount, limits });
|
|
380
|
+
if (!verdict.fits) {
|
|
381
|
+
return { ok: false, refusal: entryAmountRefusal(verdict) };
|
|
382
|
+
}
|
|
383
|
+
const feePerEther = await readFeePerEther(edge);
|
|
384
|
+
const deliveredAmount = (0, amounts_js_1.netDeliveredAmount)(inputAmount, feePerEther);
|
|
385
|
+
return {
|
|
386
|
+
ok: true,
|
|
387
|
+
deliveredAmount,
|
|
388
|
+
// An exact local computation: the guaranteed figure IS the expected one.
|
|
389
|
+
minimumDeliveredAmount: deliveredAmount,
|
|
390
|
+
claimedDurationSeconds: producers_js_1.BRIDGE_SETTLEMENT_ETA_MINUTES * 60,
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
return {
|
|
394
|
+
id: 'omnibridge',
|
|
395
|
+
edgesFrom,
|
|
396
|
+
ask,
|
|
397
|
+
prepare,
|
|
398
|
+
exact,
|
|
399
|
+
isRateLimited: false,
|
|
400
|
+
// Crossing requires the reader's own wallet signature on the entry
|
|
401
|
+
// chain — never a by-hand deposit.
|
|
402
|
+
depositMode: 'wallet-signed',
|
|
403
|
+
sender: 'omnibridge',
|
|
404
|
+
settlementSeconds: producers_js_1.BRIDGE_SETTLEMENT_ETA_MINUTES * 60,
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
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
|
+
const withOmnibridgeEntryFusing = (carrier) => {
|
|
442
|
+
const descriptor = (0, providers_js_1.findProviderById)(carrier.id);
|
|
443
|
+
return {
|
|
444
|
+
...carrier,
|
|
445
|
+
fusesWithNext: (nextHop) => {
|
|
446
|
+
if (descriptor === null || !descriptor.executesDestinationCalls)
|
|
447
|
+
return false;
|
|
448
|
+
const nextHopIsOmnibridgeCrossing = nextHop.nextHopCarrier === config_js_1.Providers.PULSECHAIN || nextHop.nextHopCarrier === config_js_1.Providers.TOKENSEX;
|
|
449
|
+
if (!nextHopIsOmnibridgeCrossing || nextHop.nextHopDestinationChainId !== producers_js_1.pulsechainChainId)
|
|
450
|
+
return false;
|
|
451
|
+
const origins = descriptor.destinationCallOriginChainIds;
|
|
452
|
+
if (origins !== 'any' && !origins.includes(nextHop.hopFrom.chainId))
|
|
453
|
+
return false;
|
|
454
|
+
const entersNative = (0, ecosystems_js_1.isNativeAsset)(nextHop.nextHopFrom.address, nextHop.nextHopFrom.ecosystem);
|
|
455
|
+
return !entersNative || descriptor.destinationCallCarriesNativeValue;
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
exports.withOmnibridgeEntryFusing = withOmnibridgeEntryFusing;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type Hex } from 'viem';
|
|
2
|
+
/**
|
|
3
|
+
* Which swap path a PulseX hop takes. The hop no longer always goes through
|
|
4
|
+
* Wrapped PLS: the carrier prices every candidate path from the pool reserves
|
|
5
|
+
* it already read, and keeps the one that delivers the most. These are the
|
|
6
|
+
* pure parts of that choice — the hub list, the candidate paths, and the
|
|
7
|
+
* pick — kept apart from the carrier so each can be tested on its own.
|
|
8
|
+
*/
|
|
9
|
+
/** One token a PulseX swap may pass through on its way from the input token to the output token. */
|
|
10
|
+
export type PulseXHubToken = {
|
|
11
|
+
/** The token's on-chain `symbol()`, for logs and tests only. Never used to match a token. */
|
|
12
|
+
readonly symbol: string;
|
|
13
|
+
/** The token's checksummed PulseChain address. Tokens are always matched by this. */
|
|
14
|
+
readonly address: Hex;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The tokens a PulseX swap may route through, Wrapped PLS first. Each address
|
|
18
|
+
* comes from a constant this package already holds; none is written here:
|
|
19
|
+
*
|
|
20
|
+
* - WPLS — `nativeAssetOut[Chains.PLS]` (`config.ts`).
|
|
21
|
+
* - PLSX, HEX, USDC, USDT — `pulsechainTokenAddresses` (`config.ts`).
|
|
22
|
+
* - DAI (from Ethereum) — `PULSECHAIN_USD_ANCHOR_ADDRESS` (`prices.ts`).
|
|
23
|
+
* - WETH (from Ethereum) — the Ethereum pathway's `bridgedNativeAssetOut` (`config.ts`).
|
|
24
|
+
*
|
|
25
|
+
* KEPT SMALL ON PURPOSE. Each hub adds pools to the one batched reserve read
|
|
26
|
+
* the carrier makes per search, and adds paths to every price it computes.
|
|
27
|
+
* Seven hubs give at most 20 candidate paths per hop (see
|
|
28
|
+
* {@link candidatePulseXPaths}).
|
|
29
|
+
*/
|
|
30
|
+
export declare const pulseXHubTokens: readonly PulseXHubToken[];
|
|
31
|
+
/**
|
|
32
|
+
* Every swap path a PulseX hop from `from` to `to` may take:
|
|
33
|
+
*
|
|
34
|
+
* - direct: `[from, to]`;
|
|
35
|
+
* - one hub: `[from, hub, to]` for each hub, Wrapped PLS among them;
|
|
36
|
+
* - two hubs: `[from, wrappedNative, hub, to]` and `[from, hub, wrappedNative, to]`
|
|
37
|
+
* for each other hub — Wrapped PLS holds the deepest pools on PulseX, so it
|
|
38
|
+
* is the one middle token worth pairing with every other hub.
|
|
39
|
+
*
|
|
40
|
+
* A path that names a token twice is dropped. That removes every path whose
|
|
41
|
+
* hub IS `from` or `to`, and the same-token hop when `from` equals `to`.
|
|
42
|
+
*
|
|
43
|
+
* The fixed path this carrier used before — `[from, WPLS, to]`, or
|
|
44
|
+
* `[from, to]` when one side is WPLS — is always in the result, so the pick
|
|
45
|
+
* can only match or beat it.
|
|
46
|
+
*
|
|
47
|
+
* @param options.from - the pool token the swap spends
|
|
48
|
+
* @param options.to - the pool token the swap buys
|
|
49
|
+
* @param options.wrappedNative - Wrapped PLS
|
|
50
|
+
* @param options.hubs - the hub token addresses, Wrapped PLS included
|
|
51
|
+
* @returns the candidate paths, direct first, each at least two tokens long
|
|
52
|
+
*/
|
|
53
|
+
export declare const candidatePulseXPaths: (options: {
|
|
54
|
+
readonly from: Hex;
|
|
55
|
+
readonly to: Hex;
|
|
56
|
+
readonly wrappedNative: Hex;
|
|
57
|
+
readonly hubs: readonly Hex[];
|
|
58
|
+
}) => readonly (readonly Hex[])[];
|
|
59
|
+
/** One candidate path, priced. */
|
|
60
|
+
export type PulseXPathQuote<Step> = {
|
|
61
|
+
/** The tokens the swap passes through, in order. */
|
|
62
|
+
readonly path: readonly Hex[];
|
|
63
|
+
/** What the path delivers for the trade, in the last token's base units. */
|
|
64
|
+
readonly deliveredAmount: bigint;
|
|
65
|
+
/** The pools the path crosses, in order — kept so the caller can measure price impact on the same pools. */
|
|
66
|
+
readonly steps: readonly Step[];
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The priced path that delivers the most. A path that delivers nothing is
|
|
70
|
+
* never chosen. Among equal outputs the path with fewer pools wins, and among
|
|
71
|
+
* equal lengths the earlier candidate wins, so the pick is deterministic.
|
|
72
|
+
*
|
|
73
|
+
* Gas is not weighed beyond that tie-break: the search charges one flat gas
|
|
74
|
+
* figure per swap signature, whatever its path length (`route-search-gas.ts`
|
|
75
|
+
* in the interface), so there is no per-pool gas figure to subtract here.
|
|
76
|
+
*
|
|
77
|
+
* @param quotes - the priced candidates; paths with a missing pool are already left out
|
|
78
|
+
* @returns the best candidate, or null when none delivers anything
|
|
79
|
+
*/
|
|
80
|
+
export declare const bestPulseXPathQuote: <Step>(quotes: readonly PulseXPathQuote<Step>[]) => PulseXPathQuote<Step> | null;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bestPulseXPathQuote = exports.candidatePulseXPaths = exports.pulseXHubTokens = void 0;
|
|
4
|
+
const viem_1 = require("viem");
|
|
5
|
+
const config_js_1 = require("../../config.js");
|
|
6
|
+
const prices_js_1 = require("../../prices.js");
|
|
7
|
+
/** Wrapped PLS: the first hub, and the middle token of every two-hub path. */
|
|
8
|
+
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
|
+
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
|
+
exports.pulseXHubTokens = Object.freeze([
|
|
26
|
+
{ symbol: 'WPLS', address: wrappedPulseAddress },
|
|
27
|
+
{ symbol: 'PLSX', address: config_js_1.pulsechainTokenAddresses.PLSX },
|
|
28
|
+
{ symbol: 'HEX', address: config_js_1.pulsechainTokenAddresses.HEX },
|
|
29
|
+
{ symbol: 'DAI', address: (0, viem_1.getAddress)(prices_js_1.PULSECHAIN_USD_ANCHOR_ADDRESS) },
|
|
30
|
+
{ symbol: 'USDC', address: config_js_1.pulsechainTokenAddresses.USDC },
|
|
31
|
+
{ symbol: 'USDT', address: config_js_1.pulsechainTokenAddresses.USDT },
|
|
32
|
+
...(bridgedWrappedEtherAddress === undefined ? [] : [{ symbol: 'WETH', address: (0, viem_1.getAddress)(bridgedWrappedEtherAddress) }]),
|
|
33
|
+
].map((hub) => Object.freeze(hub)));
|
|
34
|
+
/** Whether a path names any token twice, compared without regard to letter case. */
|
|
35
|
+
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
|
+
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
|
+
const candidatePulseXPaths = (options) => {
|
|
61
|
+
const { from, to, wrappedNative, hubs } = options;
|
|
62
|
+
const oneHubPaths = hubs.map((hub) => [from, hub, to]);
|
|
63
|
+
const twoHubPaths = hubs
|
|
64
|
+
.filter((hub) => !sameToken(hub, wrappedNative))
|
|
65
|
+
.flatMap((hub) => [
|
|
66
|
+
[from, wrappedNative, hub, to],
|
|
67
|
+
[from, hub, wrappedNative, to],
|
|
68
|
+
]);
|
|
69
|
+
return [[from, to], ...oneHubPaths, ...twoHubPaths].filter((path) => !repeatsAToken(path));
|
|
70
|
+
};
|
|
71
|
+
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
|
+
const beats = (candidate, best) => {
|
|
78
|
+
if (candidate.deliveredAmount !== best.deliveredAmount)
|
|
79
|
+
return candidate.deliveredAmount > best.deliveredAmount;
|
|
80
|
+
return candidate.path.length < best.path.length;
|
|
81
|
+
};
|
|
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
|
+
const bestPulseXPathQuote = (quotes) => quotes
|
|
95
|
+
.filter((quote) => quote.deliveredAmount > 0n)
|
|
96
|
+
.reduce((best, quote) => (best === null || beats(quote, best) ? quote : best), null);
|
|
97
|
+
exports.bestPulseXPathQuote = bestPulseXPathQuote;
|