@anywalletkit/protocol-swap2-router 0.0.0-stage → 0.2.1
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/LICENSE +202 -0
- package/README.md +70 -2
- package/dist/chunk-6WXEWTIF.js +296 -0
- package/dist/chunk-6WXEWTIF.js.map +1 -0
- package/dist/chunk-ATQMEXTG.js +98 -0
- package/dist/chunk-ATQMEXTG.js.map +1 -0
- package/dist/chunk-VRUHVDAX.js +78 -0
- package/dist/chunk-VRUHVDAX.js.map +1 -0
- package/dist/index.cjs +487 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/router.cjs +316 -0
- package/dist/router.cjs.map +1 -0
- package/dist/router.d.cts +118 -0
- package/dist/router.d.ts +118 -0
- package/dist/router.js +7 -0
- package/dist/router.js.map +1 -0
- package/dist/swap2/router.cjs +386 -0
- package/dist/swap2/router.cjs.map +1 -0
- package/dist/swap2/router.d.cts +100 -0
- package/dist/swap2/router.d.ts +100 -0
- package/dist/swap2/router.js +8 -0
- package/dist/swap2/router.js.map +1 -0
- package/dist/value.cjs +118 -0
- package/dist/value.cjs.map +1 -0
- package/dist/value.d.cts +51 -0
- package/dist/value.d.ts +51 -0
- package/dist/value.js +9 -0
- package/dist/value.js.map +1 -0
- package/module.json +20 -0
- package/package.json +69 -3
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/swap2/router.ts
|
|
21
|
+
var router_exports = {};
|
|
22
|
+
__export(router_exports, {
|
|
23
|
+
RouterSwap2Protocol: () => RouterSwap2Protocol
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(router_exports);
|
|
26
|
+
var import_base = require("@anywalletkit/protocol-swap2-core/swap2/base");
|
|
27
|
+
|
|
28
|
+
// src/router.ts
|
|
29
|
+
var import_validate = require("@anywalletkit/protocol-swap2-core/validate");
|
|
30
|
+
var DEFAULT_WEIGHTS = { price: 0.6, speed: 0.3, reliability: 0.1 };
|
|
31
|
+
var DEFAULT_TIE_BAND = 2e-3;
|
|
32
|
+
var QUOTE_TIMEOUT_MS = 8e3;
|
|
33
|
+
var SOLO_QUOTE_TIMEOUT_MS = 25e3;
|
|
34
|
+
function parseWeights(spec) {
|
|
35
|
+
if (!spec) return DEFAULT_WEIGHTS;
|
|
36
|
+
const w = { ...DEFAULT_WEIGHTS };
|
|
37
|
+
for (const part of spec.split(",")) {
|
|
38
|
+
const [k, v] = part.split(":");
|
|
39
|
+
const n = Number(v);
|
|
40
|
+
if ((k === "price" || k === "speed" || k === "reliability") && Number.isFinite(n)) {
|
|
41
|
+
w[k] = Math.max(0, n);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return w;
|
|
45
|
+
}
|
|
46
|
+
async function withTimeout(p, ms) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const timer = setTimeout(() => reject(new Error(`quote timed out after ${ms}ms`)), ms);
|
|
49
|
+
p.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
var CrossChainRouter = class _CrossChainRouter {
|
|
53
|
+
providers;
|
|
54
|
+
/** Why the venues of the LAST getBestQuote call sat out — for error surfaces. */
|
|
55
|
+
_lastRejections = [];
|
|
56
|
+
/** Why providers sat out the last auction, one line each. */
|
|
57
|
+
get lastRejections() {
|
|
58
|
+
return this._lastRejections;
|
|
59
|
+
}
|
|
60
|
+
weights;
|
|
61
|
+
valueUsd;
|
|
62
|
+
tieBand;
|
|
63
|
+
options;
|
|
64
|
+
constructor(providers, options) {
|
|
65
|
+
const opts = typeof options === "string" ? { weights: options } : { ...options ?? {} };
|
|
66
|
+
if (opts.tieBand !== void 0 && !(Number.isFinite(opts.tieBand) && opts.tieBand >= 0 && opts.tieBand < 1)) {
|
|
67
|
+
throw new Error("cross-chain router: tieBand must be a fraction in [0, 1)");
|
|
68
|
+
}
|
|
69
|
+
const names = /* @__PURE__ */ new Set();
|
|
70
|
+
for (const provider of providers) {
|
|
71
|
+
if (typeof provider.name !== "string" || !/^[A-Za-z0-9_-]+$/.test(provider.name) || names.has(provider.name)) {
|
|
72
|
+
throw new Error(`cross-chain router: invalid or duplicate provider name '${String(provider.name)}'`);
|
|
73
|
+
}
|
|
74
|
+
names.add(provider.name);
|
|
75
|
+
}
|
|
76
|
+
this.providers = providers.map((provider) => ({
|
|
77
|
+
name: provider.name,
|
|
78
|
+
enabled: provider.enabled.bind(provider),
|
|
79
|
+
supportsRoute: provider.supportsRoute.bind(provider),
|
|
80
|
+
getQuote: provider.getQuote.bind(provider),
|
|
81
|
+
buildDeposit: provider.buildDeposit.bind(provider),
|
|
82
|
+
trackIntent: provider.trackIntent.bind(provider),
|
|
83
|
+
...provider.preflightDeposit ? { preflightDeposit: provider.preflightDeposit.bind(provider) } : {},
|
|
84
|
+
...provider.notifyDeposit ? { notifyDeposit: provider.notifyDeposit.bind(provider) } : {},
|
|
85
|
+
...provider.registerSignedOrder ? { registerSignedOrder: provider.registerSignedOrder.bind(provider) } : {},
|
|
86
|
+
// Two-phase routes (CCTP): the destination claim. Missing here, the engine
|
|
87
|
+
// and the server both reported "provider 'cctp' has no claim step" (2026-10-13).
|
|
88
|
+
...provider.buildClaim ? { buildClaim: provider.buildClaim.bind(provider) } : {},
|
|
89
|
+
...provider.close ? { close: provider.close.bind(provider) } : {},
|
|
90
|
+
...provider.supportsChain ? { supportsChain: provider.supportsChain.bind(provider) } : {},
|
|
91
|
+
...provider.supportsChainPair ? { supportsChainPair: provider.supportsChainPair.bind(provider) } : {},
|
|
92
|
+
...provider.describeActivity ? { describeActivity: provider.describeActivity.bind(provider) } : {},
|
|
93
|
+
...provider.supportsSameChain !== void 0 ? { supportsSameChain: provider.supportsSameChain } : {}
|
|
94
|
+
}));
|
|
95
|
+
this.weights = parseWeights(opts.weights);
|
|
96
|
+
this.valueUsd = opts.valueUsd;
|
|
97
|
+
this.tieBand = opts.tieBand ?? DEFAULT_TIE_BAND;
|
|
98
|
+
this.options = opts;
|
|
99
|
+
}
|
|
100
|
+
/** The same auction restricted to one provider — to execute the route a user picked. */
|
|
101
|
+
only(name) {
|
|
102
|
+
const provider = this.getProvider(name);
|
|
103
|
+
if (!provider) throw new Error(`cross-chain router: provider '${name}' is not registered`);
|
|
104
|
+
return new _CrossChainRouter([provider], this.options);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Activity recognition across the installed providers: the first one that
|
|
108
|
+
* claims the transaction describes it. Null when none does (or none is held).
|
|
109
|
+
*/
|
|
110
|
+
describeActivity(tx, ctx) {
|
|
111
|
+
for (const provider of this.providers) {
|
|
112
|
+
const item = provider.describeActivity?.(tx, ctx);
|
|
113
|
+
if (item) return item;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/** A provider by name. */
|
|
118
|
+
getProvider(name) {
|
|
119
|
+
return this.providers.find((p) => p.name === name);
|
|
120
|
+
}
|
|
121
|
+
/** True when a provider of that name is installed. */
|
|
122
|
+
hasProvider(name) {
|
|
123
|
+
return this.providers.some((p) => p.name === name);
|
|
124
|
+
}
|
|
125
|
+
/** True when that provider is installed and enabled. */
|
|
126
|
+
hasEnabledProvider(name) {
|
|
127
|
+
return this.providers.some((p) => {
|
|
128
|
+
try {
|
|
129
|
+
return p.name === name && p.enabled();
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** True when any enabled provider declares the chain. */
|
|
136
|
+
supportsChain(caip2) {
|
|
137
|
+
return this.providers.some((p) => {
|
|
138
|
+
try {
|
|
139
|
+
return p.enabled() && !!p.supportsChain?.(caip2);
|
|
140
|
+
} catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** True when any enabled provider declares the chain pair. */
|
|
146
|
+
supportsChainPair(fromChain, toChain) {
|
|
147
|
+
return this.providers.some((p) => {
|
|
148
|
+
try {
|
|
149
|
+
return p.enabled() && (p.supportsChainPair?.(fromChain, toChain) ?? false);
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The auction's pick: the best price (net of native fees and gas when
|
|
157
|
+
* `valueUsd` is set; faster, then more reliable, among equal prices), or
|
|
158
|
+
* the fastest route with `prefer: "fastest"`.
|
|
159
|
+
*/
|
|
160
|
+
async getBestQuote(params, options) {
|
|
161
|
+
const set = await this.getQuotes(params, options);
|
|
162
|
+
if (!set) return null;
|
|
163
|
+
const pick = options?.prefer === "fastest" ? set.fastest : set.best;
|
|
164
|
+
const { netValueUsd: _n, relativeValue: _r, tags: _t, ...quote } = pick;
|
|
165
|
+
return {
|
|
166
|
+
...quote,
|
|
167
|
+
routeScore: {
|
|
168
|
+
score: pick.relativeValue,
|
|
169
|
+
weights: this.weights,
|
|
170
|
+
candidates: set.quotes.map((q) => ({
|
|
171
|
+
provider: q.provider,
|
|
172
|
+
sourceAmount: q.sourceAmount,
|
|
173
|
+
etaSeconds: q.etaSeconds,
|
|
174
|
+
score: q.relativeValue,
|
|
175
|
+
minDestAmount: q.minDestAmount,
|
|
176
|
+
netValueUsd: q.netValueUsd
|
|
177
|
+
}))
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Every validated route, ranked — for a route picker that shows the best and
|
|
183
|
+
* the fastest and lets the user choose. Providers run in parallel; with
|
|
184
|
+
* `onUpdate` each one that answers re-publishes the ranked set at once (the
|
|
185
|
+
* first price shows in the time of the fastest venue, not the slowest).
|
|
186
|
+
*/
|
|
187
|
+
async getQuotes(params, options) {
|
|
188
|
+
const rejections = [];
|
|
189
|
+
this._lastRejections = rejections;
|
|
190
|
+
const snapshot = Object.freeze({ ...params });
|
|
191
|
+
const from = Object.freeze({ chain: snapshot.sourceChain, token: snapshot.sourceToken });
|
|
192
|
+
const to = Object.freeze({ chain: snapshot.destChain, token: snapshot.destToken });
|
|
193
|
+
let eligible = this.providers.filter((p) => {
|
|
194
|
+
try {
|
|
195
|
+
return p.enabled() && p.supportsRoute(from, to);
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
if (snapshot.sourceChain === snapshot.destChain) {
|
|
201
|
+
eligible = eligible.filter((p) => p.supportsSameChain === true);
|
|
202
|
+
}
|
|
203
|
+
if (eligible.length === 0) return null;
|
|
204
|
+
const timeoutMs = eligible.length === 1 ? SOLO_QUOTE_TIMEOUT_MS : QUOTE_TIMEOUT_MS;
|
|
205
|
+
const refusals = [];
|
|
206
|
+
const accepted = [];
|
|
207
|
+
const pending = new Set(eligible.map((p) => p.name));
|
|
208
|
+
const publish = () => {
|
|
209
|
+
if (!options?.onUpdate || accepted.length === 0) return;
|
|
210
|
+
const set = this.rank(accepted, snapshot, [...pending], rejections);
|
|
211
|
+
try {
|
|
212
|
+
options.onUpdate(set);
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
await Promise.all(eligible.map(async (p, i) => {
|
|
217
|
+
const controller = new AbortController();
|
|
218
|
+
const own = Object.freeze({ ...snapshot, onRefusal: (reason) => {
|
|
219
|
+
refusals[i] ??= reason.startsWith(`${p.name}:`) ? reason : `${p.name}: ${reason}`;
|
|
220
|
+
try {
|
|
221
|
+
snapshot.onRefusal?.(reason);
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
} });
|
|
225
|
+
let q = null;
|
|
226
|
+
try {
|
|
227
|
+
q = await withTimeout(p.getQuote(own, controller.signal), timeoutMs);
|
|
228
|
+
if (!q) {
|
|
229
|
+
rejections.push(refusals[i] ?? `${p.name}: returned no quote`);
|
|
230
|
+
} else if (!(0, import_validate.validQuote)(q, snapshot, p.name)) {
|
|
231
|
+
const reason = `${p.name}: rejected by validation (minDest=${q.minDestAmount} vs floor=${snapshot.minAmountOut ?? "-"}, validUntil=${q.validUntilMs}, now=${Date.now()})`;
|
|
232
|
+
rejections.push(reason);
|
|
233
|
+
console.warn(`swap2-router: ${reason}`);
|
|
234
|
+
q = null;
|
|
235
|
+
} else if (options?.requireImmediateExecution && !(q.depositAddress.length > 0 || q.txRequest !== void 0)) {
|
|
236
|
+
q = null;
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
const reason = `${p.name}: ${err instanceof Error ? err.message.slice(0, 160) : String(err).slice(0, 160)}`;
|
|
240
|
+
rejections.push(reason);
|
|
241
|
+
console.warn(`swap2-router: quote failed \u2014 ${reason}`);
|
|
242
|
+
q = null;
|
|
243
|
+
} finally {
|
|
244
|
+
controller.abort();
|
|
245
|
+
}
|
|
246
|
+
pending.delete(p.name);
|
|
247
|
+
if (q) accepted.push({ ...q, provider: p.name });
|
|
248
|
+
publish();
|
|
249
|
+
}));
|
|
250
|
+
if (accepted.length === 0) return null;
|
|
251
|
+
return this.rank(accepted, snapshot, [], rejections);
|
|
252
|
+
}
|
|
253
|
+
/** Status from the named provider; `failed` when it is not installed. */
|
|
254
|
+
async trackIntent(providerName, ref) {
|
|
255
|
+
const provider = this.getProvider(providerName);
|
|
256
|
+
if (!provider) return { state: "failed" };
|
|
257
|
+
return provider.trackIntent(ref);
|
|
258
|
+
}
|
|
259
|
+
/** Forward a source-broadcast notification to the intent's provider. */
|
|
260
|
+
async notifyDeposit(providerName, intentId, txHash) {
|
|
261
|
+
await this.getProvider(providerName)?.notifyDeposit?.(intentId, txHash);
|
|
262
|
+
}
|
|
263
|
+
/** Release every provider's resources. */
|
|
264
|
+
close() {
|
|
265
|
+
for (const provider of this.providers) provider.close?.();
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Price first: each quote's value is its net USD (`valueUsd`) — or, when any
|
|
269
|
+
* quote cannot be priced, its guaranteed output — against the best one.
|
|
270
|
+
* Quotes within `tieBand` of the best are the same price; among those the
|
|
271
|
+
* weights order speed and reliability. A $0.01 edge no longer beats a route
|
|
272
|
+
* that lands in seconds instead of twenty minutes, and a route that charges
|
|
273
|
+
* a native bridge fee on top no longer wins on its gross output.
|
|
274
|
+
*/
|
|
275
|
+
rank(quotes, params, pending, rejections) {
|
|
276
|
+
const usd = this.valueUsd ? quotes.map((q) => {
|
|
277
|
+
try {
|
|
278
|
+
const v = this.valueUsd(q, params);
|
|
279
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}) : quotes.map(() => null);
|
|
284
|
+
const priced = usd.every((v) => v !== null);
|
|
285
|
+
let relative;
|
|
286
|
+
if (priced) {
|
|
287
|
+
const values = usd;
|
|
288
|
+
const max = Math.max(...values);
|
|
289
|
+
relative = values.map((v) => max > 0 ? Math.max(0, v) / max : v >= max ? 1 : 0);
|
|
290
|
+
} else {
|
|
291
|
+
const outs = quotes.map((q) => BigInt(q.minDestAmount));
|
|
292
|
+
const max = outs.reduce((a, b) => b > a ? b : a);
|
|
293
|
+
relative = outs.map((o) => fraction(o, max));
|
|
294
|
+
}
|
|
295
|
+
const ranked = quotes.map((q, i) => ({
|
|
296
|
+
...q,
|
|
297
|
+
netValueUsd: priced ? usd[i] : null,
|
|
298
|
+
relativeValue: relative[i],
|
|
299
|
+
tags: []
|
|
300
|
+
}));
|
|
301
|
+
const tier = ranked.filter((q) => q.relativeValue >= 1 - this.tieBand - 1e-12);
|
|
302
|
+
const etas = tier.map((q) => q.etaSeconds);
|
|
303
|
+
const minEta = Math.min(...etas);
|
|
304
|
+
const maxEta = Math.max(...etas);
|
|
305
|
+
const w = this.weights;
|
|
306
|
+
const tieScore = (q) => w.price * q.relativeValue + w.speed * (maxEta === minEta ? 1 : (maxEta - q.etaSeconds) / (maxEta - minEta)) + w.reliability * q.reliability;
|
|
307
|
+
const best = tier.reduce((a, b) => tieScore(b) > tieScore(a) ? b : a);
|
|
308
|
+
const fastest = ranked.reduce((a, b) => b.etaSeconds < a.etaSeconds || b.etaSeconds === a.etaSeconds && b.relativeValue > a.relativeValue ? b : a);
|
|
309
|
+
best.tags.push("best");
|
|
310
|
+
fastest.tags.push("fastest");
|
|
311
|
+
ranked.sort((a, b) => a === best ? -1 : b === best ? 1 : b.relativeValue - a.relativeValue || a.etaSeconds - b.etaSeconds);
|
|
312
|
+
return { quotes: ranked, best, fastest, pending, rejections: [...rejections] };
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
function fraction(num, den) {
|
|
316
|
+
if (den === 0n) return 1;
|
|
317
|
+
return Number(num * 10000n / den) / 1e4;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/swap2/router.ts
|
|
321
|
+
var RouterSwap2Protocol = class _RouterSwap2Protocol extends import_base.BaseSwap2Protocol {
|
|
322
|
+
router;
|
|
323
|
+
account;
|
|
324
|
+
routerConfig;
|
|
325
|
+
constructor(account, config) {
|
|
326
|
+
super(account, config);
|
|
327
|
+
if (!!config.router === !!config.providers) {
|
|
328
|
+
throw new Error("swap2: RouterSwap2Protocol needs exactly one of `providers` or `router`");
|
|
329
|
+
}
|
|
330
|
+
if (config.prefer !== void 0 && config.prefer !== "best" && config.prefer !== "fastest") {
|
|
331
|
+
throw new Error('swap2: prefer must be "best" or "fastest"');
|
|
332
|
+
}
|
|
333
|
+
this.router = config.router ?? new CrossChainRouter(config.providers, {
|
|
334
|
+
...config.weights !== void 0 ? { weights: config.weights } : {},
|
|
335
|
+
...config.valueUsd ? { valueUsd: config.valueUsd } : {},
|
|
336
|
+
...config.tieBand !== void 0 ? { tieBand: config.tieBand } : {}
|
|
337
|
+
});
|
|
338
|
+
this.account = account;
|
|
339
|
+
this.routerConfig = config;
|
|
340
|
+
}
|
|
341
|
+
resolveQuote(params) {
|
|
342
|
+
return this.router.getBestQuote(params, this.routerConfig.prefer ? { prefer: this.routerConfig.prefer } : void 0);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Every route for a picker, ranked, with the best and the fastest tagged.
|
|
346
|
+
* `onUpdate` gets the ranked list each time another provider answers, so a
|
|
347
|
+
* UI shows the first price as soon as the fastest venue replies. Routes the
|
|
348
|
+
* caller's floor or fee caps refuse are left out. Execute the one the user
|
|
349
|
+
* picks with `using(route.provider).swap2(...)`.
|
|
350
|
+
*/
|
|
351
|
+
async quoteSwap2Routes(options, opts) {
|
|
352
|
+
const snapshot = this.snapshotOf(options);
|
|
353
|
+
const view = (set2) => set2.quotes.flatMap((q) => {
|
|
354
|
+
const quote = this.routeQuote(q, snapshot);
|
|
355
|
+
return quote ? [{ provider: q.provider, quote, netValueUsd: q.netValueUsd, relativeValue: q.relativeValue, tags: [...q.tags] }] : [];
|
|
356
|
+
});
|
|
357
|
+
const set = await this.router.getQuotes(this.toParams(snapshot, true), opts?.onUpdate ? { onUpdate: (partial) => opts.onUpdate(view(partial), partial.pending) } : void 0);
|
|
358
|
+
return set ? view(set) : [];
|
|
359
|
+
}
|
|
360
|
+
/** This protocol pinned to one provider — the route a user picked from `quoteSwap2Routes`. */
|
|
361
|
+
using(provider) {
|
|
362
|
+
const { providers: _p, router: _r, ...rest } = this.routerConfig;
|
|
363
|
+
return new _RouterSwap2Protocol(this.account, { ...rest, router: this.router.only(provider) });
|
|
364
|
+
}
|
|
365
|
+
noRouteDetail() {
|
|
366
|
+
return this.router.lastRejections.join("; ").slice(0, 400);
|
|
367
|
+
}
|
|
368
|
+
providerFor(name) {
|
|
369
|
+
return this.router.getProvider(name);
|
|
370
|
+
}
|
|
371
|
+
/** Delegates to every provider the router holds (first match wins); null with no providers. */
|
|
372
|
+
describeActivity(tx, ctx) {
|
|
373
|
+
return this.router.describeActivity(tx, ctx);
|
|
374
|
+
}
|
|
375
|
+
chainSupported(caip2) {
|
|
376
|
+
return this.router.supportsChain(caip2);
|
|
377
|
+
}
|
|
378
|
+
pairSupported(fromChain, toChain) {
|
|
379
|
+
return this.router.supportsChainPair(fromChain, toChain);
|
|
380
|
+
}
|
|
381
|
+
/** Release the providers' resources (timers, sockets). */
|
|
382
|
+
close() {
|
|
383
|
+
this.router.close();
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
//# sourceMappingURL=router.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/swap2/router.ts","../../src/router.ts"],"sourcesContent":["/**\n * The optional aggregator: one swap2 protocol that quotes every provider the\n * caller installed and passed in, in parallel, and executes the best route.\n *\n * Every provider package (`@anywalletkit/protocol-swap2-lifi`, `-near-intents`,\n * `-thorchain`, `-omniston`) also works on its own through its single-provider\n * protocol class; this package is only for callers who want an auction across\n * several of them. It has no dependency on any provider: capabilities\n * (supported chains / pairs) come from the providers themselves, or — for a\n * `remoteProvider` stub — from the `supportsChain`/`supportsChainPair` options\n * the caller fills in.\n *\n * ```ts\n * const swap2 = new RouterSwap2Protocol(account, {\n * fromChain: \"eip155:1\",\n * providers: [lifiProvider({...}), nearIntentsProvider({...})],\n * })\n * ```\n */\nimport {\n BaseSwap2Protocol,\n type Swap2Account,\n type Swap2EngineConfig,\n} from \"@anywalletkit/protocol-swap2-core/swap2/base\";\nimport type {CrossChainProvider, Quote, QuoteParams} from \"@anywalletkit/protocol-swap2-core/types\";\nimport type {ActivityItem, ChainTx, DescribeActivityContext, Swap2Options, Swap2Quote} from \"@anywalletkit/core\";\nimport {CrossChainRouter, type RouteSet, type RouterOptions} from \"../router.js\";\n\n/** One route in a picker: the public quote plus its place in the auction. */\nexport interface Swap2Route {\n /** Provider name. */\n provider: string;\n /** The provider's quote. */\n quote: Swap2Quote;\n /** Net USD value when the router prices routes (`valueUsd`), else null. */\n netValueUsd: number | null;\n /** Value against the best route, 0..1. */\n relativeValue: number;\n /** `best` (most value within the tie band) and/or `fastest` (lowest ETA). */\n tags: (\"best\" | \"fastest\")[];\n}\n\n/**\n * Options for {@link RouterSwap2Protocol}: the providers (or a ready router), route preference and\n * the engine options.\n */\nexport interface RouterSwap2Config extends Swap2EngineConfig {\n /** The providers to aggregate over. Only what the caller installs is bundled. */\n providers?: CrossChainProvider[];\n /**\n * Or a ready auction engine (e.g. to share one between protocols or tune its\n * score weights). Exactly one of `providers` / `router` is required.\n */\n router?: CrossChainRouter;\n /** Order inside a price tie for a router built from `providers`, e.g. \"price:0.7,speed:0.2,reliability:0.1\". */\n weights?: string;\n /** Net USD value of a quote for a router built from `providers` — see `usdValue(prices)`. */\n valueUsd?: RouterOptions[\"valueUsd\"];\n /** Fraction of the best value inside which routes tie on price (default 0.002). */\n tieBand?: number;\n /** What `swap2()` / `quoteSwap2()` execute when several routes answer: the best (default) or the fastest. */\n prefer?: \"best\" | \"fastest\";\n}\n\n/**\n * A swap2 protocol that asks every installed provider in parallel, checks each quote the same way,\n * and executes the chosen route. Pass exactly one of `providers` or `router`.\n *\n * ```ts\n * const swap2 = new RouterSwap2Protocol(account, {\n * fromChain: 'eip155:1',\n * providers: [lifiProvider(), nearIntentsProvider(), cctpProvider()],\n * })\n * const quote = await swap2.quoteSwap2({ fromToken: 'USDC', toToken: 'USDC', toChain: 'eip155:8453', fromTokenAmount: 100_000_000n })\n * ```\n */\nexport class RouterSwap2Protocol extends BaseSwap2Protocol {\n private readonly router: CrossChainRouter;\n private readonly account: Swap2Account | undefined;\n private readonly routerConfig: RouterSwap2Config;\n\n constructor(account: Swap2Account | undefined, config: RouterSwap2Config) {\n super(account, config);\n if (!!config.router === !!config.providers) {\n throw new Error(\"swap2: RouterSwap2Protocol needs exactly one of `providers` or `router`\");\n }\n if (config.prefer !== undefined && config.prefer !== \"best\" && config.prefer !== \"fastest\") {\n throw new Error(\"swap2: prefer must be \\\"best\\\" or \\\"fastest\\\"\");\n }\n this.router = config.router ?? new CrossChainRouter(config.providers!, {\n ...(config.weights !== undefined ? {weights: config.weights} : {}),\n ...(config.valueUsd ? {valueUsd: config.valueUsd} : {}),\n ...(config.tieBand !== undefined ? {tieBand: config.tieBand} : {}),\n });\n this.account = account;\n this.routerConfig = config;\n }\n\n protected resolveQuote(params: QuoteParams): Promise<Quote | null> {\n return this.router.getBestQuote(params, this.routerConfig.prefer ? {prefer: this.routerConfig.prefer} : undefined);\n }\n\n /**\n * Every route for a picker, ranked, with the best and the fastest tagged.\n * `onUpdate` gets the ranked list each time another provider answers, so a\n * UI shows the first price as soon as the fastest venue replies. Routes the\n * caller's floor or fee caps refuse are left out. Execute the one the user\n * picks with `using(route.provider).swap2(...)`.\n */\n async quoteSwap2Routes(options: Swap2Options, opts?: {onUpdate?: (routes: Swap2Route[], pending: string[]) => void}): Promise<Swap2Route[]> {\n const snapshot = this.snapshotOf(options);\n const view = (set: RouteSet): Swap2Route[] =>\n set.quotes.flatMap((q) => {\n const quote = this.routeQuote(q, snapshot);\n return quote ? [{provider: q.provider, quote, netValueUsd: q.netValueUsd, relativeValue: q.relativeValue, tags: [...q.tags]}] : [];\n });\n const set = await this.router.getQuotes(this.toParams(snapshot, true), opts?.onUpdate\n ? {onUpdate: (partial) => opts.onUpdate!(view(partial), partial.pending)}\n : undefined);\n return set ? view(set) : [];\n }\n\n /** This protocol pinned to one provider — the route a user picked from `quoteSwap2Routes`. */\n using(provider: string): RouterSwap2Protocol {\n const {providers: _p, router: _r, ...rest} = this.routerConfig;\n return new RouterSwap2Protocol(this.account, {...rest, router: this.router.only(provider)});\n }\n\n protected override noRouteDetail(): string {\n return this.router.lastRejections.join(\"; \").slice(0, 400);\n }\n\n protected providerFor(name: string): CrossChainProvider | undefined {\n return this.router.getProvider(name);\n }\n\n /** Delegates to every provider the router holds (first match wins); null with no providers. */\n override describeActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n return this.router.describeActivity(tx, ctx);\n }\n\n protected chainSupported(caip2: string): boolean {\n return this.router.supportsChain(caip2);\n }\n\n protected override pairSupported(fromChain: string, toChain: string): boolean {\n return this.router.supportsChainPair(fromChain, toChain);\n }\n\n /** Release the providers' resources (timers, sockets). */\n close(): void {\n this.router.close();\n }\n}\n","/**\n * The auction engine of the optional aggregator: routes a cross-chain delivery\n * over whichever providers the caller installed and passed in. Protocol\n * modules never need it — each one runs standalone on @anywalletkit/protocol-swap2-core.\n */\nimport type {\n CrossChainProvider,\n IntentRef,\n IntentStatus,\n Quote,\n QuoteParams,\n RoutedQuote,\n ScoreWeights,\n} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {validQuote} from \"@anywalletkit/protocol-swap2-core/validate\";\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\n\nconst DEFAULT_WEIGHTS: ScoreWeights = {price: 0.6, speed: 0.3, reliability: 0.1};\n/** Quotes within 0.2% of the best net value count as the same price. */\nconst DEFAULT_TIE_BAND = 0.002;\n\nexport interface RouterOptions {\n /**\n * \"price:0.6,speed:0.3,reliability:0.1\" — how quotes inside the tie band\n * (same price, within `tieBand`) are ordered. Price itself always leads.\n */\n weights?: string;\n /**\n * What a quote is worth to the user in USD: its guaranteed output minus what\n * the route charges on top (a native bridge fee, the source gas). Return null\n * when it cannot be priced — the auction then compares guaranteed outputs.\n * `usdValue(prices)` builds one from a symbol → USD price map.\n */\n valueUsd?: (quote: Quote, params: QuoteParams) => number | null;\n /** Fraction of the best net value inside which quotes tie on price (default 0.002). */\n tieBand?: number;\n}\n\n/** A validated quote with its place in the auction. */\nexport interface RankedQuote extends Quote {\n /** Net USD value (`valueUsd`), or null when the auction compared raw outputs. */\n netValueUsd: number | null;\n /** This quote's value against the best one, 0..1 (1 = the best price). */\n relativeValue: number;\n /** \"best\": the pick (best price; faster/more reliable among equal prices). \"fastest\": the lowest ETA. */\n tags: (\"best\" | \"fastest\")[];\n}\n\n/** Every validated route for one request, ranked by value. */\nexport interface RouteSet {\n /** Every validated route, best first. */\n quotes: RankedQuote[];\n /** Most delivered value (net of fees when priced) within the tie band. */\n best: RankedQuote;\n /** Lowest ETA. */\n fastest: RankedQuote;\n /** Providers still quoting (only non-empty in progressive `onUpdate` snapshots). */\n pending: string[];\n /** Why the venues that sat out did (same as `lastRejections`). */\n rejections: string[];\n}\n// LI.FI's /quote routinely takes ~3.5s (it builds executable calldata) — 4s\n// raced it out of every round. All providers still run in parallel.\nconst QUOTE_TIMEOUT_MS = 8000;\n/** Pinned single-venue (execution) budget — see the comment at the call site. */\nconst SOLO_QUOTE_TIMEOUT_MS = 25_000;\n\nfunction parseWeights(spec?: string): ScoreWeights {\n if (!spec) return DEFAULT_WEIGHTS;\n const w = {...DEFAULT_WEIGHTS};\n for (const part of spec.split(\",\")) {\n const [k, v] = part.split(\":\");\n const n = Number(v);\n if ((k === \"price\" || k === \"speed\" || k === \"reliability\") && Number.isFinite(n)) {\n w[k] = Math.max(0, n);\n }\n }\n return w;\n}\n\nasync function withTimeout<T>(p: Promise<T>, ms: number): Promise<T | null> {\n return new Promise((resolve, reject) => {\n // Timeouts and failures must REJECT: the old `() => resolve(null)` arm\n // swallowed every provider error — including forwarded venue refusals —\n // into a null indistinguishable from \"no route\", which made execution\n // failures undiagnosable from the surface (the '(c2)' hunt of 2026-08-31).\n const timer = setTimeout(() => reject(new Error(`quote timed out after ${ms}ms`)), ms);\n p.then(resolve, reject).finally(() => clearTimeout(timer));\n });\n}\n\n/**\n * Routes a cross-chain delivery over pluggable providers: queries all that\n * support the route in parallel (with a timeout so a slow provider can't block),\n * normalizes their quotes, scores by configurable weights, and returns the best\n * tagged with `routeScore`. A failed/slow provider simply doesn't participate.\n */\nexport class CrossChainRouter {\n private readonly providers: CrossChainProvider[];\n /** Why the venues of the LAST getBestQuote call sat out — for error surfaces. */\n private _lastRejections: string[] = [];\n /** Why providers sat out the last auction, one line each. */\n get lastRejections(): readonly string[] {\n return this._lastRejections;\n }\n private readonly weights: ScoreWeights;\n private readonly valueUsd: RouterOptions[\"valueUsd\"];\n private readonly tieBand: number;\n private readonly options: RouterOptions;\n\n constructor(providers: CrossChainProvider[], options?: string | RouterOptions) {\n const opts: RouterOptions = typeof options === \"string\" ? {weights: options} : {...(options ?? {})};\n if (opts.tieBand !== undefined && !(Number.isFinite(opts.tieBand) && opts.tieBand >= 0 && opts.tieBand < 1)) {\n throw new Error(\"cross-chain router: tieBand must be a fraction in [0, 1)\");\n }\n const names = new Set<string>();\n for (const provider of providers) {\n if (typeof provider.name !== \"string\" || !/^[A-Za-z0-9_-]+$/.test(provider.name) || names.has(provider.name)) {\n throw new Error(`cross-chain router: invalid or duplicate provider name '${String(provider.name)}'`);\n }\n names.add(provider.name);\n }\n // Snapshot the dispatch surface. A caller mutating provider.name or method\n // properties after construction must not redirect quote→build/status.\n this.providers = providers.map((provider) => ({\n name: provider.name,\n enabled: provider.enabled.bind(provider),\n supportsRoute: provider.supportsRoute.bind(provider),\n getQuote: provider.getQuote.bind(provider),\n buildDeposit: provider.buildDeposit.bind(provider),\n trackIntent: provider.trackIntent.bind(provider),\n ...(provider.preflightDeposit ? {preflightDeposit: provider.preflightDeposit.bind(provider)} : {}),\n ...(provider.notifyDeposit ? {notifyDeposit: provider.notifyDeposit.bind(provider)} : {}),\n ...(provider.registerSignedOrder ? {registerSignedOrder: provider.registerSignedOrder.bind(provider)} : {}),\n // Two-phase routes (CCTP): the destination claim. Missing here, the engine\n // and the server both reported \"provider 'cctp' has no claim step\" (2026-10-13).\n ...(provider.buildClaim ? {buildClaim: provider.buildClaim.bind(provider)} : {}),\n ...(provider.close ? {close: provider.close.bind(provider)} : {}),\n ...(provider.supportsChain ? {supportsChain: provider.supportsChain.bind(provider)} : {}),\n ...(provider.supportsChainPair ? {supportsChainPair: provider.supportsChainPair.bind(provider)} : {}),\n ...(provider.describeActivity ? {describeActivity: provider.describeActivity.bind(provider)} : {}),\n ...(provider.supportsSameChain !== undefined ? {supportsSameChain: provider.supportsSameChain} : {}),\n }));\n this.weights = parseWeights(opts.weights);\n this.valueUsd = opts.valueUsd;\n this.tieBand = opts.tieBand ?? DEFAULT_TIE_BAND;\n this.options = opts;\n }\n\n /** The same auction restricted to one provider — to execute the route a user picked. */\n only(name: string): CrossChainRouter {\n const provider = this.getProvider(name);\n if (!provider) throw new Error(`cross-chain router: provider '${name}' is not registered`);\n return new CrossChainRouter([provider], this.options);\n }\n\n /**\n * Activity recognition across the installed providers: the first one that\n * claims the transaction describes it. Null when none does (or none is held).\n */\n describeActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n for (const provider of this.providers) {\n const item = provider.describeActivity?.(tx, ctx);\n if (item) return item;\n }\n return null;\n }\n\n /** A provider by name. */\n getProvider(name: string): CrossChainProvider | undefined {\n return this.providers.find((p) => p.name === name);\n }\n\n /** True when a provider of that name is installed. */\n hasProvider(name: string): boolean {\n return this.providers.some((p) => p.name === name);\n }\n\n /** True when that provider is installed and enabled. */\n hasEnabledProvider(name: string): boolean {\n return this.providers.some((p) => {\n try { return p.name === name && p.enabled(); } catch { return false; }\n });\n }\n\n /** True when any enabled provider declares the chain. */\n supportsChain(caip2: string): boolean {\n return this.providers.some((p) => {\n try { return p.enabled() && !!p.supportsChain?.(caip2); } catch { return false; }\n });\n }\n\n /** True when any enabled provider declares the chain pair. */\n supportsChainPair(fromChain: string, toChain: string): boolean {\n return this.providers.some((p) => {\n try {\n return p.enabled() && (p.supportsChainPair?.(fromChain, toChain) ?? false);\n } catch { return false; }\n });\n }\n\n /**\n * The auction's pick: the best price (net of native fees and gas when\n * `valueUsd` is set; faster, then more reliable, among equal prices), or\n * the fastest route with `prefer: \"fastest\"`.\n */\n async getBestQuote(\n params: QuoteParams,\n options?: {requireImmediateExecution?: boolean; prefer?: \"best\" | \"fastest\"},\n ): Promise<RoutedQuote | null> {\n const set = await this.getQuotes(params, options);\n if (!set) return null;\n const pick = options?.prefer === \"fastest\" ? set.fastest : set.best;\n const {netValueUsd: _n, relativeValue: _r, tags: _t, ...quote} = pick;\n return {\n ...quote,\n routeScore: {\n score: pick.relativeValue,\n weights: this.weights,\n candidates: set.quotes.map((q) => ({\n provider: q.provider,\n sourceAmount: q.sourceAmount,\n etaSeconds: q.etaSeconds,\n score: q.relativeValue,\n minDestAmount: q.minDestAmount,\n netValueUsd: q.netValueUsd,\n })),\n },\n };\n }\n\n /**\n * Every validated route, ranked — for a route picker that shows the best and\n * the fastest and lets the user choose. Providers run in parallel; with\n * `onUpdate` each one that answers re-publishes the ranked set at once (the\n * first price shows in the time of the fastest venue, not the slowest).\n */\n async getQuotes(\n params: QuoteParams,\n options?: {requireImmediateExecution?: boolean; onUpdate?: (set: RouteSet) => void},\n ): Promise<RouteSet | null> {\n // A public engine caller — or one provider running in the same auction —\n // must not be able to rewrite another provider's request while async quote\n // work is in flight. QuoteParams is intentionally flat, so a frozen copy is\n // a complete execution snapshot.\n const rejections: string[] = [];\n this._lastRejections = rejections;\n const snapshot = Object.freeze({...params});\n const from = Object.freeze({chain: snapshot.sourceChain, token: snapshot.sourceToken});\n const to = Object.freeze({chain: snapshot.destChain, token: snapshot.destToken});\n // Only enabled (validated) providers that can serve the route participate.\n let eligible = this.providers.filter((p) => {\n try { return p.enabled() && p.supportsRoute(from, to); } catch { return false; }\n });\n if (snapshot.sourceChain === snapshot.destChain) {\n eligible = eligible.filter((p) => p.supportsSameChain === true);\n }\n if (eligible.length === 0) return null;\n\n // The short timeout protects the AUCTION — one slow venue must not hold\n // the others hostage. With a single eligible provider (a pinned execution\n // re-quote) there is nobody to protect, and a live deposit-creating quote\n // legitimately takes longer than a dry one (serverless hop + upstream\n // retries) — starving it surfaced as a terminal \"no route\" on execution.\n const timeoutMs = eligible.length === 1 ? SOLO_QUOTE_TIMEOUT_MS : QUOTE_TIMEOUT_MS;\n // Each venue names its own refusal (per call, so concurrent quotes cannot race).\n const refusals: (string | undefined)[] = [];\n const accepted: Quote[] = [];\n const pending = new Set(eligible.map((p) => p.name));\n const publish = () => {\n if (!options?.onUpdate || accepted.length === 0) return;\n const set = this.rank(accepted, snapshot, [...pending], rejections);\n try { options.onUpdate(set); } catch { /* an observer must not break the auction */ }\n };\n await Promise.all(eligible.map(async (p, i) => {\n const controller = new AbortController();\n const own = Object.freeze({...snapshot, onRefusal: (reason: string) => {\n refusals[i] ??= reason.startsWith(`${p.name}:`) ? reason : `${p.name}: ${reason}`;\n try { snapshot.onRefusal?.(reason); } catch { /* diagnostics only */ }\n }});\n let q: Quote | null = null;\n try {\n q = await withTimeout(p.getQuote(own, controller.signal), timeoutMs);\n if (!q) {\n // A provider RETURNING null (vs throwing) was the one silent path\n // left — record it so the surfaced no-route error names the venue.\n rejections.push(refusals[i] ?? `${p.name}: returned no quote`);\n } else if (!validQuote(q, snapshot, p.name)) {\n const reason = `${p.name}: rejected by validation (minDest=${q.minDestAmount} vs floor=${snapshot.minAmountOut ?? \"-\"}, validUntil=${q.validUntilMs}, now=${Date.now()})`;\n rejections.push(reason);\n console.warn(`swap2-router: ${reason}`);\n q = null;\n } else if (options?.requireImmediateExecution && !(q.depositAddress.length > 0 || q.txRequest !== undefined)) {\n // Legacy deposit-address screens cannot execute order-based quotes that\n // require a separate build/sign/register lifecycle.\n q = null;\n }\n } catch (err) {\n // A failed venue sitting out silently is indistinguishable from \"no\n // route\" — record why (surfaced through lastRejections) and say it in\n // the console (same posture as onModuleSkipped).\n const reason = `${p.name}: ${err instanceof Error ? err.message.slice(0, 160) : String(err).slice(0, 160)}`;\n rejections.push(reason);\n console.warn(`swap2-router: quote failed — ${reason}`);\n q = null;\n } finally {\n controller.abort();\n }\n pending.delete(p.name);\n // Provider identity is an execution security boundary. Never trust the\n // provider field returned by an untrusted adapter; bind it to its slot.\n if (q) accepted.push({...q, provider: p.name});\n publish();\n }));\n if (accepted.length === 0) return null;\n return this.rank(accepted, snapshot, [], rejections);\n }\n\n /** Status from the named provider; `failed` when it is not installed. */\n async trackIntent(providerName: string, ref: IntentRef): Promise<IntentStatus> {\n const provider = this.getProvider(providerName);\n if (!provider) return {state: \"failed\"};\n return provider.trackIntent(ref);\n }\n\n /** Forward a source-broadcast notification to the intent's provider. */\n async notifyDeposit(providerName: string, intentId: string, txHash: string): Promise<void> {\n await this.getProvider(providerName)?.notifyDeposit?.(intentId, txHash);\n }\n\n /** Release every provider's resources. */\n close(): void {\n for (const provider of this.providers) provider.close?.();\n }\n\n /**\n * Price first: each quote's value is its net USD (`valueUsd`) — or, when any\n * quote cannot be priced, its guaranteed output — against the best one.\n * Quotes within `tieBand` of the best are the same price; among those the\n * weights order speed and reliability. A $0.01 edge no longer beats a route\n * that lands in seconds instead of twenty minutes, and a route that charges\n * a native bridge fee on top no longer wins on its gross output.\n */\n private rank(quotes: Quote[], params: QuoteParams, pending: string[], rejections: string[]): RouteSet {\n const usd = this.valueUsd ? quotes.map((q) => {\n try {\n const v = this.valueUsd!(q, params);\n return typeof v === \"number\" && Number.isFinite(v) ? v : null;\n } catch { return null; }\n }) : quotes.map(() => null);\n const priced = usd.every((v) => v !== null);\n let relative: number[];\n if (priced) {\n const values = usd as number[];\n const max = Math.max(...values);\n relative = values.map((v) => (max > 0 ? Math.max(0, v) / max : v >= max ? 1 : 0));\n } else {\n const outs = quotes.map((q) => BigInt(q.minDestAmount));\n const max = outs.reduce((a, b) => (b > a ? b : a));\n relative = outs.map((o) => fraction(o, max));\n }\n const ranked: RankedQuote[] = quotes.map((q, i) => ({\n ...q,\n netValueUsd: priced ? usd[i]! : null,\n relativeValue: relative[i]!,\n tags: [],\n }));\n\n const tier = ranked.filter((q) => q.relativeValue >= 1 - this.tieBand - 1e-12);\n const etas = tier.map((q) => q.etaSeconds);\n const minEta = Math.min(...etas);\n const maxEta = Math.max(...etas);\n const w = this.weights;\n const tieScore = (q: RankedQuote) =>\n w.price * q.relativeValue +\n w.speed * (maxEta === minEta ? 1 : (maxEta - q.etaSeconds) / (maxEta - minEta)) +\n w.reliability * q.reliability;\n const best = tier.reduce((a, b) => (tieScore(b) > tieScore(a) ? b : a));\n const fastest = ranked.reduce((a, b) =>\n b.etaSeconds < a.etaSeconds || (b.etaSeconds === a.etaSeconds && b.relativeValue > a.relativeValue) ? b : a);\n best.tags.push(\"best\");\n fastest.tags.push(\"fastest\");\n\n // The list reads top-down by value; the pick leads its price tier.\n ranked.sort((a, b) => (a === best ? -1 : b === best ? 1 : b.relativeValue - a.relativeValue || a.etaSeconds - b.etaSeconds));\n return {quotes: ranked, best, fastest, pending, rejections: [...rejections]};\n }\n}\n\n/** Ratio of two non-negative bigints as a 0..1 number. */\nfunction fraction(num: bigint, den: bigint): number {\n if (den === 0n) return 1;\n // scale to keep precision, then to float\n return Number((num * 10_000n) / den) / 10_000;\n}\n"],"mappings":"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","names":["set"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Swap2EngineConfig, BaseSwap2Protocol, Swap2Account } from '@anywalletkit/protocol-swap2-core/swap2/base';
|
|
2
|
+
import { CrossChainProvider, QuoteParams, Quote } from '@anywalletkit/protocol-swap2-core/types';
|
|
3
|
+
import { Swap2Options, Swap2Quote, ChainTx, DescribeActivityContext, ActivityItem } from '@anywalletkit/core';
|
|
4
|
+
import { CrossChainRouter, RouterOptions } from '../router.cjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The optional aggregator: one swap2 protocol that quotes every provider the
|
|
8
|
+
* caller installed and passed in, in parallel, and executes the best route.
|
|
9
|
+
*
|
|
10
|
+
* Every provider package (`@anywalletkit/protocol-swap2-lifi`, `-near-intents`,
|
|
11
|
+
* `-thorchain`, `-omniston`) also works on its own through its single-provider
|
|
12
|
+
* protocol class; this package is only for callers who want an auction across
|
|
13
|
+
* several of them. It has no dependency on any provider: capabilities
|
|
14
|
+
* (supported chains / pairs) come from the providers themselves, or — for a
|
|
15
|
+
* `remoteProvider` stub — from the `supportsChain`/`supportsChainPair` options
|
|
16
|
+
* the caller fills in.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const swap2 = new RouterSwap2Protocol(account, {
|
|
20
|
+
* fromChain: "eip155:1",
|
|
21
|
+
* providers: [lifiProvider({...}), nearIntentsProvider({...})],
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** One route in a picker: the public quote plus its place in the auction. */
|
|
27
|
+
interface Swap2Route {
|
|
28
|
+
/** Provider name. */
|
|
29
|
+
provider: string;
|
|
30
|
+
/** The provider's quote. */
|
|
31
|
+
quote: Swap2Quote;
|
|
32
|
+
/** Net USD value when the router prices routes (`valueUsd`), else null. */
|
|
33
|
+
netValueUsd: number | null;
|
|
34
|
+
/** Value against the best route, 0..1. */
|
|
35
|
+
relativeValue: number;
|
|
36
|
+
/** `best` (most value within the tie band) and/or `fastest` (lowest ETA). */
|
|
37
|
+
tags: ("best" | "fastest")[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for {@link RouterSwap2Protocol}: the providers (or a ready router), route preference and
|
|
41
|
+
* the engine options.
|
|
42
|
+
*/
|
|
43
|
+
interface RouterSwap2Config extends Swap2EngineConfig {
|
|
44
|
+
/** The providers to aggregate over. Only what the caller installs is bundled. */
|
|
45
|
+
providers?: CrossChainProvider[];
|
|
46
|
+
/**
|
|
47
|
+
* Or a ready auction engine (e.g. to share one between protocols or tune its
|
|
48
|
+
* score weights). Exactly one of `providers` / `router` is required.
|
|
49
|
+
*/
|
|
50
|
+
router?: CrossChainRouter;
|
|
51
|
+
/** Order inside a price tie for a router built from `providers`, e.g. "price:0.7,speed:0.2,reliability:0.1". */
|
|
52
|
+
weights?: string;
|
|
53
|
+
/** Net USD value of a quote for a router built from `providers` — see `usdValue(prices)`. */
|
|
54
|
+
valueUsd?: RouterOptions["valueUsd"];
|
|
55
|
+
/** Fraction of the best value inside which routes tie on price (default 0.002). */
|
|
56
|
+
tieBand?: number;
|
|
57
|
+
/** What `swap2()` / `quoteSwap2()` execute when several routes answer: the best (default) or the fastest. */
|
|
58
|
+
prefer?: "best" | "fastest";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A swap2 protocol that asks every installed provider in parallel, checks each quote the same way,
|
|
62
|
+
* and executes the chosen route. Pass exactly one of `providers` or `router`.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* const swap2 = new RouterSwap2Protocol(account, {
|
|
66
|
+
* fromChain: 'eip155:1',
|
|
67
|
+
* providers: [lifiProvider(), nearIntentsProvider(), cctpProvider()],
|
|
68
|
+
* })
|
|
69
|
+
* const quote = await swap2.quoteSwap2({ fromToken: 'USDC', toToken: 'USDC', toChain: 'eip155:8453', fromTokenAmount: 100_000_000n })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare class RouterSwap2Protocol extends BaseSwap2Protocol {
|
|
73
|
+
private readonly router;
|
|
74
|
+
private readonly account;
|
|
75
|
+
private readonly routerConfig;
|
|
76
|
+
constructor(account: Swap2Account | undefined, config: RouterSwap2Config);
|
|
77
|
+
protected resolveQuote(params: QuoteParams): Promise<Quote | null>;
|
|
78
|
+
/**
|
|
79
|
+
* Every route for a picker, ranked, with the best and the fastest tagged.
|
|
80
|
+
* `onUpdate` gets the ranked list each time another provider answers, so a
|
|
81
|
+
* UI shows the first price as soon as the fastest venue replies. Routes the
|
|
82
|
+
* caller's floor or fee caps refuse are left out. Execute the one the user
|
|
83
|
+
* picks with `using(route.provider).swap2(...)`.
|
|
84
|
+
*/
|
|
85
|
+
quoteSwap2Routes(options: Swap2Options, opts?: {
|
|
86
|
+
onUpdate?: (routes: Swap2Route[], pending: string[]) => void;
|
|
87
|
+
}): Promise<Swap2Route[]>;
|
|
88
|
+
/** This protocol pinned to one provider — the route a user picked from `quoteSwap2Routes`. */
|
|
89
|
+
using(provider: string): RouterSwap2Protocol;
|
|
90
|
+
protected noRouteDetail(): string;
|
|
91
|
+
protected providerFor(name: string): CrossChainProvider | undefined;
|
|
92
|
+
/** Delegates to every provider the router holds (first match wins); null with no providers. */
|
|
93
|
+
describeActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null;
|
|
94
|
+
protected chainSupported(caip2: string): boolean;
|
|
95
|
+
protected pairSupported(fromChain: string, toChain: string): boolean;
|
|
96
|
+
/** Release the providers' resources (timers, sockets). */
|
|
97
|
+
close(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { type RouterSwap2Config, RouterSwap2Protocol, type Swap2Route };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Swap2EngineConfig, BaseSwap2Protocol, Swap2Account } from '@anywalletkit/protocol-swap2-core/swap2/base';
|
|
2
|
+
import { CrossChainProvider, QuoteParams, Quote } from '@anywalletkit/protocol-swap2-core/types';
|
|
3
|
+
import { Swap2Options, Swap2Quote, ChainTx, DescribeActivityContext, ActivityItem } from '@anywalletkit/core';
|
|
4
|
+
import { CrossChainRouter, RouterOptions } from '../router.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The optional aggregator: one swap2 protocol that quotes every provider the
|
|
8
|
+
* caller installed and passed in, in parallel, and executes the best route.
|
|
9
|
+
*
|
|
10
|
+
* Every provider package (`@anywalletkit/protocol-swap2-lifi`, `-near-intents`,
|
|
11
|
+
* `-thorchain`, `-omniston`) also works on its own through its single-provider
|
|
12
|
+
* protocol class; this package is only for callers who want an auction across
|
|
13
|
+
* several of them. It has no dependency on any provider: capabilities
|
|
14
|
+
* (supported chains / pairs) come from the providers themselves, or — for a
|
|
15
|
+
* `remoteProvider` stub — from the `supportsChain`/`supportsChainPair` options
|
|
16
|
+
* the caller fills in.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const swap2 = new RouterSwap2Protocol(account, {
|
|
20
|
+
* fromChain: "eip155:1",
|
|
21
|
+
* providers: [lifiProvider({...}), nearIntentsProvider({...})],
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** One route in a picker: the public quote plus its place in the auction. */
|
|
27
|
+
interface Swap2Route {
|
|
28
|
+
/** Provider name. */
|
|
29
|
+
provider: string;
|
|
30
|
+
/** The provider's quote. */
|
|
31
|
+
quote: Swap2Quote;
|
|
32
|
+
/** Net USD value when the router prices routes (`valueUsd`), else null. */
|
|
33
|
+
netValueUsd: number | null;
|
|
34
|
+
/** Value against the best route, 0..1. */
|
|
35
|
+
relativeValue: number;
|
|
36
|
+
/** `best` (most value within the tie band) and/or `fastest` (lowest ETA). */
|
|
37
|
+
tags: ("best" | "fastest")[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for {@link RouterSwap2Protocol}: the providers (or a ready router), route preference and
|
|
41
|
+
* the engine options.
|
|
42
|
+
*/
|
|
43
|
+
interface RouterSwap2Config extends Swap2EngineConfig {
|
|
44
|
+
/** The providers to aggregate over. Only what the caller installs is bundled. */
|
|
45
|
+
providers?: CrossChainProvider[];
|
|
46
|
+
/**
|
|
47
|
+
* Or a ready auction engine (e.g. to share one between protocols or tune its
|
|
48
|
+
* score weights). Exactly one of `providers` / `router` is required.
|
|
49
|
+
*/
|
|
50
|
+
router?: CrossChainRouter;
|
|
51
|
+
/** Order inside a price tie for a router built from `providers`, e.g. "price:0.7,speed:0.2,reliability:0.1". */
|
|
52
|
+
weights?: string;
|
|
53
|
+
/** Net USD value of a quote for a router built from `providers` — see `usdValue(prices)`. */
|
|
54
|
+
valueUsd?: RouterOptions["valueUsd"];
|
|
55
|
+
/** Fraction of the best value inside which routes tie on price (default 0.002). */
|
|
56
|
+
tieBand?: number;
|
|
57
|
+
/** What `swap2()` / `quoteSwap2()` execute when several routes answer: the best (default) or the fastest. */
|
|
58
|
+
prefer?: "best" | "fastest";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A swap2 protocol that asks every installed provider in parallel, checks each quote the same way,
|
|
62
|
+
* and executes the chosen route. Pass exactly one of `providers` or `router`.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* const swap2 = new RouterSwap2Protocol(account, {
|
|
66
|
+
* fromChain: 'eip155:1',
|
|
67
|
+
* providers: [lifiProvider(), nearIntentsProvider(), cctpProvider()],
|
|
68
|
+
* })
|
|
69
|
+
* const quote = await swap2.quoteSwap2({ fromToken: 'USDC', toToken: 'USDC', toChain: 'eip155:8453', fromTokenAmount: 100_000_000n })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare class RouterSwap2Protocol extends BaseSwap2Protocol {
|
|
73
|
+
private readonly router;
|
|
74
|
+
private readonly account;
|
|
75
|
+
private readonly routerConfig;
|
|
76
|
+
constructor(account: Swap2Account | undefined, config: RouterSwap2Config);
|
|
77
|
+
protected resolveQuote(params: QuoteParams): Promise<Quote | null>;
|
|
78
|
+
/**
|
|
79
|
+
* Every route for a picker, ranked, with the best and the fastest tagged.
|
|
80
|
+
* `onUpdate` gets the ranked list each time another provider answers, so a
|
|
81
|
+
* UI shows the first price as soon as the fastest venue replies. Routes the
|
|
82
|
+
* caller's floor or fee caps refuse are left out. Execute the one the user
|
|
83
|
+
* picks with `using(route.provider).swap2(...)`.
|
|
84
|
+
*/
|
|
85
|
+
quoteSwap2Routes(options: Swap2Options, opts?: {
|
|
86
|
+
onUpdate?: (routes: Swap2Route[], pending: string[]) => void;
|
|
87
|
+
}): Promise<Swap2Route[]>;
|
|
88
|
+
/** This protocol pinned to one provider — the route a user picked from `quoteSwap2Routes`. */
|
|
89
|
+
using(provider: string): RouterSwap2Protocol;
|
|
90
|
+
protected noRouteDetail(): string;
|
|
91
|
+
protected providerFor(name: string): CrossChainProvider | undefined;
|
|
92
|
+
/** Delegates to every provider the router holds (first match wins); null with no providers. */
|
|
93
|
+
describeActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null;
|
|
94
|
+
protected chainSupported(caip2: string): boolean;
|
|
95
|
+
protected pairSupported(fromChain: string, toChain: string): boolean;
|
|
96
|
+
/** Release the providers' resources (timers, sockets). */
|
|
97
|
+
close(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { type RouterSwap2Config, RouterSwap2Protocol, type Swap2Route };
|