@hyperbridge/simplex 0.16.12 → 0.16.13
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/dist/bin/simplex.js +966 -148
- package/dist/bin/simplex.js.map +1 -1
- package/dist/index.cjs +753 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -12
- package/dist/index.d.ts +168 -12
- package/dist/index.js +753 -64
- package/dist/index.js.map +1 -1
- package/dist/sqlite.cjs +59 -3
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +21 -2
- package/dist/sqlite.d.ts +21 -2
- package/dist/sqlite.js +58 -3
- package/dist/sqlite.js.map +1 -1
- package/dist/{types-B5zkFhxR.d.cts → types-BISgqJhM.d.cts} +57 -1
- package/dist/{types-B5zkFhxR.d.ts → types-BISgqJhM.d.ts} +57 -1
- package/dist/ui/assets/Novatica-Medium-CjNgGPEL.ttf +0 -0
- package/dist/ui/assets/index-CK8E6aPx.css +1 -0
- package/dist/ui/assets/index-DShjZe_c.js +106 -0
- package/dist/ui/index.html +2 -2
- package/package.json +4 -4
- package/dist/ui/assets/index-QU1Ik7IX.js +0 -106
- package/dist/ui/assets/index-XKHMOpY2.css +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2677,6 +2677,35 @@ var LIMITS_QUERY = `
|
|
|
2677
2677
|
chains { id name tokens { symbol decimals } }
|
|
2678
2678
|
}
|
|
2679
2679
|
`;
|
|
2680
|
+
var LEVEL_FIELDS = "fillChain priceBucket price worstPrice baseSize quoteSize orderCount solverCount";
|
|
2681
|
+
var SNAPSHOT_QUERY = `
|
|
2682
|
+
query OrderbookSnapshot($id: ID!, $sourceChain: String, $fillChain: String) {
|
|
2683
|
+
book(id: $id) {
|
|
2684
|
+
id base quote
|
|
2685
|
+
bids: levels(side: BID, sourceChain: $sourceChain, fillChain: $fillChain) { ${LEVEL_FIELDS} }
|
|
2686
|
+
asks: levels(side: ASK, sourceChain: $sourceChain, fillChain: $fillChain) { ${LEVEL_FIELDS} }
|
|
2687
|
+
bidLiquidity: availableLiquidity(side: BID, sourceChain: $sourceChain, fillChain: $fillChain)
|
|
2688
|
+
askLiquidity: availableLiquidity(side: ASK, sourceChain: $sourceChain, fillChain: $fillChain)
|
|
2689
|
+
}
|
|
2690
|
+
serverInfo { priceGranularities { book granularity } }
|
|
2691
|
+
}
|
|
2692
|
+
`;
|
|
2693
|
+
var LEVEL_ORDERS_QUERY = `
|
|
2694
|
+
query OrderbookLevelOrders($id: ID!, $side: Side!, $fillChain: String, $sourceChain: String, $after: String) {
|
|
2695
|
+
book(id: $id) {
|
|
2696
|
+
orders(side: $side, fillChain: $fillChain, sourceChain: $sourceChain, first: 50, after: $after) {
|
|
2697
|
+
edges {
|
|
2698
|
+
node {
|
|
2699
|
+
solver { address }
|
|
2700
|
+
commitment fillChain price priceBucket advertisedSize quotedAmount resized expiresAt acceptedSources
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
pageInfo { hasNextPage endCursor }
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
`;
|
|
2708
|
+
var LEVEL_ORDER_PAGES = 20;
|
|
2680
2709
|
var POSTED_ORDER_FIELDS = "commitment side status price quotedAmount advertisedSize expiresAt acceptedSources";
|
|
2681
2710
|
var SUBMIT_ORDER_MUTATION = `
|
|
2682
2711
|
mutation SubmitOrder($userOp: Bytes!) {
|
|
@@ -2741,6 +2770,44 @@ var OrderbookClient = class {
|
|
|
2741
2770
|
async limits() {
|
|
2742
2771
|
return this.request(LIMITS_QUERY, {});
|
|
2743
2772
|
}
|
|
2773
|
+
/** The public book across all solvers, restricted to the selected route. */
|
|
2774
|
+
async snapshot(id, filters = {}) {
|
|
2775
|
+
const { book, serverInfo } = await this.request(SNAPSHOT_QUERY, {
|
|
2776
|
+
id,
|
|
2777
|
+
sourceChain: filters.sourceChain || null,
|
|
2778
|
+
fillChain: filters.fillChain || null
|
|
2779
|
+
});
|
|
2780
|
+
if (!book) return null;
|
|
2781
|
+
const granularity = serverInfo.priceGranularities.find((entry) => entry.book === book.id)?.granularity ?? null;
|
|
2782
|
+
return { ...book, granularity };
|
|
2783
|
+
}
|
|
2784
|
+
/**
|
|
2785
|
+
* The orders behind one price level. The orderbook filters orders by side and route but not by
|
|
2786
|
+
* price bucket, so this walks the side's orders on the level's fill chain and keeps the bucket.
|
|
2787
|
+
* Null when the book does not exist.
|
|
2788
|
+
*/
|
|
2789
|
+
async levelOrders(id, level) {
|
|
2790
|
+
const found = [];
|
|
2791
|
+
let after = null;
|
|
2792
|
+
for (let page = 0; page < LEVEL_ORDER_PAGES; page++) {
|
|
2793
|
+
const { book } = await this.request(LEVEL_ORDERS_QUERY, {
|
|
2794
|
+
id,
|
|
2795
|
+
side: level.side,
|
|
2796
|
+
fillChain: level.fillChain,
|
|
2797
|
+
sourceChain: level.sourceChain || null,
|
|
2798
|
+
after
|
|
2799
|
+
});
|
|
2800
|
+
if (!book) return null;
|
|
2801
|
+
for (const { node } of book.orders.edges) {
|
|
2802
|
+
if (node.priceBucket !== level.priceBucket) continue;
|
|
2803
|
+
const { solver, priceBucket: _bucket, ...order } = node;
|
|
2804
|
+
found.push({ ...order, solver: solver.address });
|
|
2805
|
+
}
|
|
2806
|
+
if (!book.orders.pageInfo.hasNextPage || !book.orders.pageInfo.endCursor) break;
|
|
2807
|
+
after = book.orders.pageInfo.endCursor;
|
|
2808
|
+
}
|
|
2809
|
+
return found;
|
|
2810
|
+
}
|
|
2744
2811
|
async submitOrder(userOp) {
|
|
2745
2812
|
const { submitOrder } = await this.request(SUBMIT_ORDER_MUTATION, { userOp });
|
|
2746
2813
|
switch (submitOrder.__typename) {
|
|
@@ -2925,16 +2992,16 @@ function signedAmounts(params) {
|
|
|
2925
2992
|
function offerFor(params) {
|
|
2926
2993
|
const { side, inputAmount, price, outputDecimals } = params;
|
|
2927
2994
|
if (price <= 0n) throw new Error("A limit order's price must be greater than zero");
|
|
2928
|
-
const
|
|
2995
|
+
const scaled2 = side === "BID" ? inputAmount * price / ORDERBOOK_SCALE : inputAmount * ORDERBOOK_SCALE / price;
|
|
2929
2996
|
const unit = outputDecimals >= 18 ? 1n : 10n ** BigInt(18 - outputDecimals);
|
|
2930
|
-
return
|
|
2997
|
+
return scaled2 / unit * unit;
|
|
2931
2998
|
}
|
|
2932
2999
|
function inputFor(params) {
|
|
2933
3000
|
const { side, outputAmount, price, inputDecimals } = params;
|
|
2934
3001
|
if (price <= 0n) throw new Error("A limit order's price must be greater than zero");
|
|
2935
|
-
const
|
|
3002
|
+
const scaled2 = side === "BID" ? divCeil(outputAmount * ORDERBOOK_SCALE, price) : divCeil(outputAmount * price, ORDERBOOK_SCALE);
|
|
2936
3003
|
const unit = inputDecimals >= 18 ? 1n : 10n ** BigInt(18 - inputDecimals);
|
|
2937
|
-
return divCeil(
|
|
3004
|
+
return divCeil(scaled2, unit) * unit;
|
|
2938
3005
|
}
|
|
2939
3006
|
|
|
2940
3007
|
// src/orderbook/matching.ts
|
|
@@ -3190,6 +3257,14 @@ var LimitOrderService = class {
|
|
|
3190
3257
|
throw err;
|
|
3191
3258
|
}
|
|
3192
3259
|
}
|
|
3260
|
+
/** Public market levels read through the configured orderbook client. */
|
|
3261
|
+
orderbookSnapshot(book, filters = {}) {
|
|
3262
|
+
return this.client.snapshot(book, filters);
|
|
3263
|
+
}
|
|
3264
|
+
/** Every solver's order behind one public price level. */
|
|
3265
|
+
orderbookLevelOrders(book, level) {
|
|
3266
|
+
return this.client.levelOrders(book, level);
|
|
3267
|
+
}
|
|
3193
3268
|
/**
|
|
3194
3269
|
* The pairs an order can be written against, the smallest payout each token may carry, and
|
|
3195
3270
|
* the tokens the orderbook registers on each chain.
|
|
@@ -3244,16 +3319,16 @@ var LimitOrderService = class {
|
|
|
3244
3319
|
if (!this.configService.getConfiguredChainIds().includes((0, import_sdk4.getChainId)(request.fillChain) ?? -1)) {
|
|
3245
3320
|
throw new LimitOrderValidationError(`'${request.fillChain}' is not a chain this filler is configured for`);
|
|
3246
3321
|
}
|
|
3247
|
-
const
|
|
3322
|
+
const scaled2 = { amountIn: 0n, amountOut: 0n };
|
|
3248
3323
|
for (const name of ["amountIn", "amountOut"]) {
|
|
3249
3324
|
try {
|
|
3250
|
-
|
|
3325
|
+
scaled2[name] = fromHuman(request[name] ?? "");
|
|
3251
3326
|
} catch (err) {
|
|
3252
3327
|
throw new LimitOrderValidationError(
|
|
3253
3328
|
`${name} must be an amount in whole tokens, like "1000" or "1500.25"; ${err.message}`
|
|
3254
3329
|
);
|
|
3255
3330
|
}
|
|
3256
|
-
if (
|
|
3331
|
+
if (scaled2[name] <= 0n) throw new LimitOrderValidationError(`${name} must be greater than zero`);
|
|
3257
3332
|
}
|
|
3258
3333
|
const sources = request.acceptedSources ?? [];
|
|
3259
3334
|
if (sources.length === 0) {
|
|
@@ -3300,7 +3375,7 @@ var LimitOrderService = class {
|
|
|
3300
3375
|
);
|
|
3301
3376
|
}
|
|
3302
3377
|
const floor = limits.serverInfo.minOrderSizes.find((entry) => entry.symbol === request.tokenOut);
|
|
3303
|
-
if (floor &&
|
|
3378
|
+
if (floor && scaled2.amountOut < BigInt(floor.size)) {
|
|
3304
3379
|
throw new LimitOrderValidationError(
|
|
3305
3380
|
`amountOut is below the orderbook's dust floor for ${request.tokenOut} (${toHuman(BigInt(floor.size))})`
|
|
3306
3381
|
);
|
|
@@ -3310,7 +3385,7 @@ var LimitOrderService = class {
|
|
|
3310
3385
|
throw new LimitOrderValidationError(`'${symbol}' does not resolve to a token address on ${request.fillChain}`);
|
|
3311
3386
|
}
|
|
3312
3387
|
}
|
|
3313
|
-
return { amountIn:
|
|
3388
|
+
return { amountIn: scaled2.amountIn, amountOut: scaled2.amountOut };
|
|
3314
3389
|
}
|
|
3315
3390
|
/**
|
|
3316
3391
|
* Refuses an order the wallet cannot pay out.
|
|
@@ -3836,6 +3911,451 @@ function underFunded(entry) {
|
|
|
3836
3911
|
return entry.resized ? `UNDER_FUNDED: the orderbook is advertising ${entry.advertisedSize} of the ${entry.quotedAmount} quoted` : "UNDER_FUNDED: the solver's balance does not cover this posting";
|
|
3837
3912
|
}
|
|
3838
3913
|
|
|
3914
|
+
// src/orderbook/profitability.ts
|
|
3915
|
+
var import_decimal3 = require("decimal.js");
|
|
3916
|
+
|
|
3917
|
+
// src/data/inventory.ts
|
|
3918
|
+
var INVENTORY_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3919
|
+
function inventoryOf(snapshot) {
|
|
3920
|
+
if (snapshot.status !== "fresh") return null;
|
|
3921
|
+
const held = {};
|
|
3922
|
+
for (const chain of snapshot.chains) {
|
|
3923
|
+
for (const asset of chain.assets) {
|
|
3924
|
+
if (asset.total === null) return null;
|
|
3925
|
+
held[asset.symbol] = (held[asset.symbol] ?? 0) + asset.total;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
return Object.keys(held).length > 0 ? held : null;
|
|
3929
|
+
}
|
|
3930
|
+
function parseUtc(value) {
|
|
3931
|
+
return Date.parse(`${value.replace(" ", "T")}Z`);
|
|
3932
|
+
}
|
|
3933
|
+
var InventoryRecorder = class {
|
|
3934
|
+
constructor(store, balances, logger, now = Date.now) {
|
|
3935
|
+
this.store = store;
|
|
3936
|
+
this.balances = balances;
|
|
3937
|
+
this.logger = logger;
|
|
3938
|
+
this.now = now;
|
|
3939
|
+
this.recording = false;
|
|
3940
|
+
this.onSnapshot = (snapshot) => void this.consider(snapshot);
|
|
3941
|
+
}
|
|
3942
|
+
start() {
|
|
3943
|
+
this.balances.on("snapshot", this.onSnapshot);
|
|
3944
|
+
void this.consider(this.balances.getSnapshot());
|
|
3945
|
+
}
|
|
3946
|
+
stop() {
|
|
3947
|
+
this.balances.off("snapshot", this.onSnapshot);
|
|
3948
|
+
}
|
|
3949
|
+
/** Takes a snapshot from `snapshot` if one is due and the read is complete. Never rejects. */
|
|
3950
|
+
async consider(snapshot) {
|
|
3951
|
+
if (this.recording) return;
|
|
3952
|
+
const held = inventoryOf(snapshot);
|
|
3953
|
+
if (!held) return;
|
|
3954
|
+
this.recording = true;
|
|
3955
|
+
try {
|
|
3956
|
+
if (this.lastTakenAt === void 0) {
|
|
3957
|
+
const latest = await this.store.latest();
|
|
3958
|
+
this.lastTakenAt = latest ? parseUtc(latest.takenAt) : 0;
|
|
3959
|
+
}
|
|
3960
|
+
if (this.now() - this.lastTakenAt < INVENTORY_INTERVAL_MS) return;
|
|
3961
|
+
await this.store.record(held);
|
|
3962
|
+
this.lastTakenAt = this.now();
|
|
3963
|
+
this.logger.info({ tokens: Object.keys(held).length }, "Recorded an inventory snapshot");
|
|
3964
|
+
} catch (err) {
|
|
3965
|
+
this.logger.warn({ err }, "Could not record an inventory snapshot");
|
|
3966
|
+
} finally {
|
|
3967
|
+
this.recording = false;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
};
|
|
3971
|
+
|
|
3972
|
+
// src/orderbook/usd.ts
|
|
3973
|
+
var import_decimal2 = require("decimal.js");
|
|
3974
|
+
function limitOrderUsdEdges(orders) {
|
|
3975
|
+
return orders.filter((order) => normalizeSymbol(order.base) !== normalizeSymbol(order.quote)).map((order) => ({
|
|
3976
|
+
base: normalizeSymbol(order.base),
|
|
3977
|
+
quote: normalizeSymbol(order.quote),
|
|
3978
|
+
rate: new import_decimal2.Decimal(order.price).div(ORDERBOOK_SCALE.toString())
|
|
3979
|
+
})).filter((edge) => edge.rate.isFinite() && edge.rate.gt(0));
|
|
3980
|
+
}
|
|
3981
|
+
function usdFactorsFrom(edges) {
|
|
3982
|
+
const factors = /* @__PURE__ */ new Map();
|
|
3983
|
+
for (const symbol of USD_STABLE_SYMBOLS) factors.set(symbol, new import_decimal2.Decimal(1));
|
|
3984
|
+
const ordered = [...edges].sort((a, b) => a.base.localeCompare(b.base) || a.quote.localeCompare(b.quote));
|
|
3985
|
+
let grew = true;
|
|
3986
|
+
while (grew) {
|
|
3987
|
+
grew = false;
|
|
3988
|
+
for (const edge of ordered) {
|
|
3989
|
+
const usdBase = factors.get(edge.base);
|
|
3990
|
+
const usdQuote = factors.get(edge.quote);
|
|
3991
|
+
if (usdBase && !usdQuote) {
|
|
3992
|
+
factors.set(edge.quote, usdBase.div(edge.rate));
|
|
3993
|
+
grew = true;
|
|
3994
|
+
} else if (usdQuote && !usdBase) {
|
|
3995
|
+
factors.set(edge.base, usdQuote.mul(edge.rate));
|
|
3996
|
+
grew = true;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
return factors;
|
|
4001
|
+
}
|
|
4002
|
+
function usdValueOf(factors, symbol, amount) {
|
|
4003
|
+
const factor = factors.get(normalizeSymbol(symbol));
|
|
4004
|
+
return factor ? amount.mul(factor) : null;
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
// src/orderbook/profitability.ts
|
|
4008
|
+
var Decimal3 = import_decimal3.Decimal.clone({ precision: 40 });
|
|
4009
|
+
var PROFIT_PERIODS = {
|
|
4010
|
+
"7d": { bucket: "day", count: 7 },
|
|
4011
|
+
"30d": { bucket: "day", count: 30 },
|
|
4012
|
+
"12w": { bucket: "week", count: 12 },
|
|
4013
|
+
"12m": { bucket: "month", count: 12 },
|
|
4014
|
+
all: { bucket: "year", count: null }
|
|
4015
|
+
};
|
|
4016
|
+
var MINUTE = 6e4;
|
|
4017
|
+
var DAY = 864e5;
|
|
4018
|
+
var BALANCES_REACH_MS = 7 * DAY;
|
|
4019
|
+
function bucketStart(local, bucket) {
|
|
4020
|
+
const date = new Date(local);
|
|
4021
|
+
const year = date.getUTCFullYear();
|
|
4022
|
+
const month = date.getUTCMonth();
|
|
4023
|
+
if (bucket === "year") return Date.UTC(year, 0, 1);
|
|
4024
|
+
if (bucket === "month") return Date.UTC(year, month, 1);
|
|
4025
|
+
const day = Date.UTC(year, month, date.getUTCDate());
|
|
4026
|
+
return bucket === "day" ? day : day - (date.getUTCDay() + 6) % 7 * DAY;
|
|
4027
|
+
}
|
|
4028
|
+
function shiftBucket(start, bucket, steps) {
|
|
4029
|
+
if (bucket === "day") return start + steps * DAY;
|
|
4030
|
+
if (bucket === "week") return start + steps * 7 * DAY;
|
|
4031
|
+
const date = new Date(start);
|
|
4032
|
+
return bucket === "month" ? Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + steps, 1) : Date.UTC(date.getUTCFullYear() + steps, 0, 1);
|
|
4033
|
+
}
|
|
4034
|
+
function scaled(value) {
|
|
4035
|
+
return new Decimal3(value).div(ORDERBOOK_SCALE.toString());
|
|
4036
|
+
}
|
|
4037
|
+
function tradeOf(fill, at) {
|
|
4038
|
+
try {
|
|
4039
|
+
const paid = scaled(fill.amount);
|
|
4040
|
+
const taken = fill.amountIn === null ? null : scaled(fill.amountIn);
|
|
4041
|
+
const price = scaled(fill.price);
|
|
4042
|
+
if (!paid.gt(0) || !price.gt(0) || taken !== null && !taken.gt(0)) return null;
|
|
4043
|
+
const buying = fill.side === "BID";
|
|
4044
|
+
const [baseAmount, quoteAmount] = buying ? [taken ?? paid.div(price), paid] : [paid, taken ?? paid.mul(price)];
|
|
4045
|
+
return {
|
|
4046
|
+
at,
|
|
4047
|
+
book: fill.book,
|
|
4048
|
+
base: normalizeSymbol(fill.base),
|
|
4049
|
+
quote: normalizeSymbol(fill.quote),
|
|
4050
|
+
buying,
|
|
4051
|
+
baseAmount,
|
|
4052
|
+
quoteAmount,
|
|
4053
|
+
estimated: taken === null
|
|
4054
|
+
};
|
|
4055
|
+
} catch {
|
|
4056
|
+
return null;
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
function add(holdings, symbol, amount) {
|
|
4060
|
+
holdings.set(symbol, (holdings.get(symbol) ?? new Decimal3(0)).add(amount));
|
|
4061
|
+
}
|
|
4062
|
+
function apply(holdings, trade, sign) {
|
|
4063
|
+
const base = trade.buying ? trade.baseAmount : trade.baseAmount.neg();
|
|
4064
|
+
const quote = trade.buying ? trade.quoteAmount.neg() : trade.quoteAmount;
|
|
4065
|
+
add(holdings, trade.base, base.mul(sign));
|
|
4066
|
+
add(holdings, trade.quote, quote.mul(sign));
|
|
4067
|
+
}
|
|
4068
|
+
var Sums = class {
|
|
4069
|
+
constructor() {
|
|
4070
|
+
this.change = /* @__PURE__ */ new Map();
|
|
4071
|
+
this.boughtUsd = new Decimal3(0);
|
|
4072
|
+
this.soldUsd = new Decimal3(0);
|
|
4073
|
+
this.buys = 0;
|
|
4074
|
+
this.sells = 0;
|
|
4075
|
+
}
|
|
4076
|
+
};
|
|
4077
|
+
var BookLedger = class {
|
|
4078
|
+
constructor(id, base, quote) {
|
|
4079
|
+
this.id = id;
|
|
4080
|
+
this.base = base;
|
|
4081
|
+
this.quote = quote;
|
|
4082
|
+
this.bought = new Decimal3(0);
|
|
4083
|
+
this.boughtQuote = new Decimal3(0);
|
|
4084
|
+
this.sold = new Decimal3(0);
|
|
4085
|
+
this.soldQuote = new Decimal3(0);
|
|
4086
|
+
/** The rates of the book's latest buy and latest sell, over all of its history. */
|
|
4087
|
+
this.latestBuy = null;
|
|
4088
|
+
this.latestSell = null;
|
|
4089
|
+
this.inPeriod = false;
|
|
4090
|
+
}
|
|
4091
|
+
/**
|
|
4092
|
+
* The rate the book is valued at. Midway between the latest buy and the latest sell, because
|
|
4093
|
+
* either alone sits half a spread to one side and would price open volume against whichever
|
|
4094
|
+
* way the book happened to trade last.
|
|
4095
|
+
*/
|
|
4096
|
+
rate() {
|
|
4097
|
+
if (this.latestBuy && this.latestSell) return this.latestBuy.add(this.latestSell).div(2);
|
|
4098
|
+
return this.latestBuy ?? this.latestSell;
|
|
4099
|
+
}
|
|
4100
|
+
};
|
|
4101
|
+
function readingOf(reading) {
|
|
4102
|
+
const held = /* @__PURE__ */ new Map();
|
|
4103
|
+
const cover = /* @__PURE__ */ new Map();
|
|
4104
|
+
for (const [symbol, amount] of Object.entries(reading.balances)) {
|
|
4105
|
+
const chains = reading.chains[symbol];
|
|
4106
|
+
if (!Number.isFinite(amount) || !chains?.length) continue;
|
|
4107
|
+
add(held, normalizeSymbol(symbol), new Decimal3(amount));
|
|
4108
|
+
cover.set(normalizeSymbol(symbol), [...chains].sort().join(","));
|
|
4109
|
+
}
|
|
4110
|
+
return { held, cover };
|
|
4111
|
+
}
|
|
4112
|
+
function movedBetween(earlier, later) {
|
|
4113
|
+
if (!earlier || !later) return null;
|
|
4114
|
+
const moved = /* @__PURE__ */ new Map();
|
|
4115
|
+
for (const [symbol, cover] of later.cover) {
|
|
4116
|
+
if (earlier.cover.get(symbol) !== cover) continue;
|
|
4117
|
+
const before = earlier.held.get(symbol) ?? new Decimal3(0);
|
|
4118
|
+
moved.set(symbol, (later.held.get(symbol) ?? new Decimal3(0)).sub(before));
|
|
4119
|
+
}
|
|
4120
|
+
return moved;
|
|
4121
|
+
}
|
|
4122
|
+
function nearestRecord(instant, records) {
|
|
4123
|
+
let record;
|
|
4124
|
+
let gap = Number.POSITIVE_INFINITY;
|
|
4125
|
+
for (const candidate of records) {
|
|
4126
|
+
const distance = Math.abs(candidate.at - instant);
|
|
4127
|
+
if (candidate.live && distance > BALANCES_REACH_MS) continue;
|
|
4128
|
+
if (distance < gap || distance === gap && record?.live && !candidate.live) {
|
|
4129
|
+
record = candidate;
|
|
4130
|
+
gap = distance;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
return record;
|
|
4134
|
+
}
|
|
4135
|
+
function inventoryAt(instant, records, trades, outflows, readings) {
|
|
4136
|
+
const record = nearestRecord(instant, records);
|
|
4137
|
+
if (!record) return null;
|
|
4138
|
+
const recorded = /* @__PURE__ */ new Map();
|
|
4139
|
+
for (const [symbol, amount] of Object.entries(record.balances)) {
|
|
4140
|
+
if (Number.isFinite(amount)) add(recorded, normalizeSymbol(symbol), new Decimal3(amount));
|
|
4141
|
+
}
|
|
4142
|
+
const held = new Map(recorded);
|
|
4143
|
+
const after = record.at >= instant;
|
|
4144
|
+
const [from, to] = after ? [instant, record.at] : [record.at, instant];
|
|
4145
|
+
const sign = after ? -1 : 1;
|
|
4146
|
+
for (const trade of trades) {
|
|
4147
|
+
if (trade.at >= from && trade.at < to) apply(held, trade, sign);
|
|
4148
|
+
}
|
|
4149
|
+
for (const outflow of outflows) {
|
|
4150
|
+
if (outflow.at >= from && outflow.at < to) {
|
|
4151
|
+
add(held, normalizeSymbol(outflow.symbol), new Decimal3(outflow.amount).mul(-sign));
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
const moved = movedBetween(readings.get(from), readings.get(to));
|
|
4155
|
+
if (moved) {
|
|
4156
|
+
for (const [symbol, amount] of moved) {
|
|
4157
|
+
held.set(symbol, (recorded.get(symbol) ?? new Decimal3(0)).add(amount.mul(sign)));
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
for (const [symbol, amount] of held) if (amount.isNegative()) held.set(symbol, new Decimal3(0));
|
|
4161
|
+
return { held, record };
|
|
4162
|
+
}
|
|
4163
|
+
function tradesOf(fills, now) {
|
|
4164
|
+
const trades = [];
|
|
4165
|
+
for (const fill of fills) {
|
|
4166
|
+
const at = parseUtc(fill.filledAt);
|
|
4167
|
+
if (!Number.isFinite(at) || at > now) continue;
|
|
4168
|
+
const trade = tradeOf(fill, at);
|
|
4169
|
+
if (trade) trades.push(trade);
|
|
4170
|
+
}
|
|
4171
|
+
return trades.sort((a, b) => a.at - b.at);
|
|
4172
|
+
}
|
|
4173
|
+
function bucketStarts(period, now, offset, firstAt) {
|
|
4174
|
+
const { bucket, count } = PROFIT_PERIODS[period];
|
|
4175
|
+
const last = bucketStart(now - offset, bucket);
|
|
4176
|
+
const first = count === null ? bucketStart((firstAt ?? now) - offset, bucket) : shiftBucket(last, bucket, 1 - count);
|
|
4177
|
+
const starts = [];
|
|
4178
|
+
for (let start = first; start <= last; start = shiftBucket(start, bucket, 1)) starts.push(start);
|
|
4179
|
+
return starts;
|
|
4180
|
+
}
|
|
4181
|
+
function inventoryInstants(fills, options) {
|
|
4182
|
+
const { period, now } = options;
|
|
4183
|
+
const offset = (options.tzOffsetMinutes ?? 0) * MINUTE;
|
|
4184
|
+
const records = (options.inventory ?? []).filter((record) => record.at <= now);
|
|
4185
|
+
const instants = /* @__PURE__ */ new Set([now]);
|
|
4186
|
+
for (const start of bucketStarts(period, now, offset, tradesOf(fills, now)[0]?.at)) {
|
|
4187
|
+
instants.add(start + offset);
|
|
4188
|
+
const record = nearestRecord(start + offset, records);
|
|
4189
|
+
if (record) instants.add(record.at);
|
|
4190
|
+
}
|
|
4191
|
+
return [...instants].sort((a, b) => a - b);
|
|
4192
|
+
}
|
|
4193
|
+
function summarizeProfit(fills, options) {
|
|
4194
|
+
const { period, now } = options;
|
|
4195
|
+
const offset = (options.tzOffsetMinutes ?? 0) * MINUTE;
|
|
4196
|
+
const { bucket } = PROFIT_PERIODS[period];
|
|
4197
|
+
const records = (options.inventory ?? []).filter((record) => record.at <= now);
|
|
4198
|
+
const outflows = options.outflows ?? [];
|
|
4199
|
+
const readings = new Map((options.readings ?? []).map((reading) => [reading.at, readingOf(reading)]));
|
|
4200
|
+
const trades = tradesOf(fills, now);
|
|
4201
|
+
const starts = bucketStarts(period, now, offset, trades[0]?.at);
|
|
4202
|
+
const first = starts[0];
|
|
4203
|
+
const books = /* @__PURE__ */ new Map();
|
|
4204
|
+
const spelling = /* @__PURE__ */ new Map();
|
|
4205
|
+
for (const record of records) {
|
|
4206
|
+
for (const symbol of Object.keys(record.balances)) {
|
|
4207
|
+
if (!spelling.has(normalizeSymbol(symbol))) spelling.set(normalizeSymbol(symbol), symbol);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
for (const fill of fills) {
|
|
4211
|
+
for (const symbol of [fill.base, fill.quote]) {
|
|
4212
|
+
if (!spelling.has(normalizeSymbol(symbol))) spelling.set(normalizeSymbol(symbol), symbol);
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
for (const trade of trades) {
|
|
4216
|
+
let book = books.get(trade.book);
|
|
4217
|
+
if (!book) {
|
|
4218
|
+
book = new BookLedger(trade.book, trade.base, trade.quote);
|
|
4219
|
+
books.set(trade.book, book);
|
|
4220
|
+
}
|
|
4221
|
+
const rate = trade.quoteAmount.div(trade.baseAmount);
|
|
4222
|
+
if (trade.buying) book.latestBuy = rate;
|
|
4223
|
+
else book.latestSell = rate;
|
|
4224
|
+
}
|
|
4225
|
+
const edges = [];
|
|
4226
|
+
for (const book of books.values()) {
|
|
4227
|
+
const rate = book.rate();
|
|
4228
|
+
if (rate && book.base !== book.quote) {
|
|
4229
|
+
edges.push({ base: book.base, quote: book.quote, rate: new import_decimal3.Decimal(rate.toString()) });
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
const factors = usdFactorsFrom(edges);
|
|
4233
|
+
const priceOf = (symbol) => {
|
|
4234
|
+
const factor = factors.get(symbol);
|
|
4235
|
+
return factor ? new Decimal3(factor.toString()) : null;
|
|
4236
|
+
};
|
|
4237
|
+
const unpriced = /* @__PURE__ */ new Set();
|
|
4238
|
+
const valueOf = (holdings) => {
|
|
4239
|
+
let total = new Decimal3(0);
|
|
4240
|
+
for (const [symbol, amount] of holdings) {
|
|
4241
|
+
if (amount.isZero()) continue;
|
|
4242
|
+
const price = priceOf(symbol);
|
|
4243
|
+
if (price) total = total.add(amount.mul(price));
|
|
4244
|
+
else unpriced.add(symbol);
|
|
4245
|
+
}
|
|
4246
|
+
return total;
|
|
4247
|
+
};
|
|
4248
|
+
const totals = new Sums();
|
|
4249
|
+
const buckets = new Map(starts.map((start) => [start, new Sums()]));
|
|
4250
|
+
let estimatedFills = 0;
|
|
4251
|
+
for (const trade of trades) {
|
|
4252
|
+
const sums = buckets.get(bucketStart(trade.at - offset, bucket));
|
|
4253
|
+
if (!sums) continue;
|
|
4254
|
+
const book = books.get(trade.book);
|
|
4255
|
+
if (!book) continue;
|
|
4256
|
+
book.inPeriod = true;
|
|
4257
|
+
if (trade.estimated) estimatedFills += 1;
|
|
4258
|
+
if (trade.buying) {
|
|
4259
|
+
book.bought = book.bought.add(trade.baseAmount);
|
|
4260
|
+
book.boughtQuote = book.boughtQuote.add(trade.quoteAmount);
|
|
4261
|
+
} else {
|
|
4262
|
+
book.sold = book.sold.add(trade.baseAmount);
|
|
4263
|
+
book.soldQuote = book.soldQuote.add(trade.quoteAmount);
|
|
4264
|
+
}
|
|
4265
|
+
const basePrice = priceOf(trade.base);
|
|
4266
|
+
const quotePrice = priceOf(trade.quote);
|
|
4267
|
+
const volume = basePrice ? trade.baseAmount.mul(basePrice) : quotePrice ? trade.quoteAmount.mul(quotePrice) : new Decimal3(0);
|
|
4268
|
+
for (const sum of [totals, sums]) {
|
|
4269
|
+
apply(sum.change, trade, 1);
|
|
4270
|
+
if (trade.buying) {
|
|
4271
|
+
sum.buys += 1;
|
|
4272
|
+
sum.boughtUsd = sum.boughtUsd.add(volume);
|
|
4273
|
+
} else {
|
|
4274
|
+
sum.sells += 1;
|
|
4275
|
+
sum.soldUsd = sum.soldUsd.add(volume);
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
const figures = (sums, startsAt, endsAt) => {
|
|
4280
|
+
const profit = valueOf(sums.change);
|
|
4281
|
+
const began2 = inventoryAt(startsAt, records, trades, outflows, readings);
|
|
4282
|
+
const inventory = began2 ? valueOf(began2.held) : null;
|
|
4283
|
+
const moved = movedBetween(readings.get(startsAt), readings.get(endsAt));
|
|
4284
|
+
if (moved) {
|
|
4285
|
+
for (const [symbol, amount] of sums.change) {
|
|
4286
|
+
if (moved.has(symbol)) moved.set(symbol, (moved.get(symbol) ?? new Decimal3(0)).sub(amount));
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
return {
|
|
4290
|
+
profitUsd: profit.toNumber(),
|
|
4291
|
+
startInventoryUsd: inventory ? inventory.toNumber() : null,
|
|
4292
|
+
returnPct: inventory?.gt(0) ? profit.div(inventory).mul(100).toNumber() : null,
|
|
4293
|
+
transfersUsd: moved && moved.size > 0 ? valueOf(moved).toNumber() : null,
|
|
4294
|
+
boughtUsd: sums.boughtUsd.toNumber(),
|
|
4295
|
+
soldUsd: sums.soldUsd.toNumber(),
|
|
4296
|
+
buys: sums.buys,
|
|
4297
|
+
sells: sums.sells
|
|
4298
|
+
};
|
|
4299
|
+
};
|
|
4300
|
+
const from = first + offset;
|
|
4301
|
+
const began = inventoryAt(from, records, trades, outflows, readings);
|
|
4302
|
+
const startInventory = began && {
|
|
4303
|
+
at: from,
|
|
4304
|
+
source: began.record.live ? "balances" : "snapshot",
|
|
4305
|
+
recordedAt: began.record.at,
|
|
4306
|
+
usd: valueOf(began.held).toNumber(),
|
|
4307
|
+
tokens: [...began.held].filter(([, amount]) => amount.gt(0)).map(([symbol, amount]) => {
|
|
4308
|
+
const price = priceOf(symbol);
|
|
4309
|
+
return {
|
|
4310
|
+
symbol: spelling.get(symbol) ?? symbol,
|
|
4311
|
+
amount: amount.toNumber(),
|
|
4312
|
+
usd: price ? amount.mul(price).toNumber() : null
|
|
4313
|
+
};
|
|
4314
|
+
}).sort((a, b) => (b.usd ?? 0) - (a.usd ?? 0) || a.symbol.localeCompare(b.symbol))
|
|
4315
|
+
};
|
|
4316
|
+
const rows = [];
|
|
4317
|
+
for (const book of books.values()) {
|
|
4318
|
+
if (!book.inPeriod) continue;
|
|
4319
|
+
const averageBuy = book.bought.gt(0) ? book.boughtQuote.div(book.bought) : null;
|
|
4320
|
+
const averageSell = book.sold.gt(0) ? book.soldQuote.div(book.sold) : null;
|
|
4321
|
+
const baseChange = book.bought.sub(book.sold);
|
|
4322
|
+
const quoteChange = book.soldQuote.sub(book.boughtQuote);
|
|
4323
|
+
const basePrice = priceOf(book.base);
|
|
4324
|
+
const quotePrice = priceOf(book.quote);
|
|
4325
|
+
rows.push({
|
|
4326
|
+
book: book.id,
|
|
4327
|
+
base: spelling.get(book.base) ?? book.base,
|
|
4328
|
+
quote: spelling.get(book.quote) ?? book.quote,
|
|
4329
|
+
bought: book.bought.toNumber(),
|
|
4330
|
+
sold: book.sold.toNumber(),
|
|
4331
|
+
averageBuy: averageBuy ? averageBuy.toNumber() : null,
|
|
4332
|
+
averageSell: averageSell ? averageSell.toNumber() : null,
|
|
4333
|
+
spreadPct: averageBuy && averageSell ? averageSell.div(averageBuy).sub(1).mul(100).toNumber() : null,
|
|
4334
|
+
baseChange: baseChange.toNumber(),
|
|
4335
|
+
quoteChange: quoteChange.toNumber(),
|
|
4336
|
+
rate: book.rate()?.toNumber() ?? null,
|
|
4337
|
+
profitUsd: basePrice && quotePrice ? baseChange.mul(basePrice).add(quoteChange.mul(quotePrice)).toNumber() : null
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
4340
|
+
rows.sort((a, b) => (b.profitUsd ?? 0) - (a.profitUsd ?? 0) || a.book.localeCompare(b.book));
|
|
4341
|
+
return {
|
|
4342
|
+
period,
|
|
4343
|
+
bucket,
|
|
4344
|
+
from,
|
|
4345
|
+
to: now,
|
|
4346
|
+
totals: figures(totals, from, now),
|
|
4347
|
+
series: starts.map((start, index) => {
|
|
4348
|
+
const sums = buckets.get(start) ?? new Sums();
|
|
4349
|
+
const endsAt = index + 1 < starts.length ? starts[index + 1] + offset : now;
|
|
4350
|
+
return { start: start + offset, ...figures(sums, start + offset, endsAt) };
|
|
4351
|
+
}),
|
|
4352
|
+
books: rows,
|
|
4353
|
+
startInventory,
|
|
4354
|
+
estimatedFills,
|
|
4355
|
+
unpricedTokens: [...unpriced].map((symbol) => spelling.get(symbol) ?? symbol).sort()
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4358
|
+
|
|
3839
4359
|
// src/data/state.ts
|
|
3840
4360
|
async function patchRuntimeState(store, patch) {
|
|
3841
4361
|
if (store.patch) return store.patch(patch);
|
|
@@ -13100,6 +13620,7 @@ var CacheService = class {
|
|
|
13100
13620
|
})),
|
|
13101
13621
|
partialFill: plan.partialFill,
|
|
13102
13622
|
profit: plan.profit,
|
|
13623
|
+
...plan.inputDecimals !== void 0 ? { inputDecimals: plan.inputDecimals } : {},
|
|
13103
13624
|
...plan.budget ? { budget: budgetFromCache(plan.budget) } : {}
|
|
13104
13625
|
}));
|
|
13105
13626
|
} catch (error) {
|
|
@@ -13130,6 +13651,7 @@ var CacheService = class {
|
|
|
13130
13651
|
})),
|
|
13131
13652
|
partialFill: plan.partialFill,
|
|
13132
13653
|
profit: plan.profit,
|
|
13654
|
+
...plan.inputDecimals !== void 0 ? { inputDecimals: plan.inputDecimals } : {},
|
|
13133
13655
|
...plan.budget ? { budget: budgetToCache(plan.budget) } : {}
|
|
13134
13656
|
})),
|
|
13135
13657
|
timestamp: Date.now()
|
|
@@ -15744,7 +16266,7 @@ var EVM_HOST = [
|
|
|
15744
16266
|
];
|
|
15745
16267
|
|
|
15746
16268
|
// src/services/ContractInteractionService.ts
|
|
15747
|
-
var
|
|
16269
|
+
var import_decimal4 = require("decimal.js");
|
|
15748
16270
|
|
|
15749
16271
|
// src/config/abis/IntentGatewayV2.ts
|
|
15750
16272
|
var INTENT_GATEWAY_V2_ABI = [
|
|
@@ -18372,7 +18894,7 @@ function describeShortfall(balances) {
|
|
|
18372
18894
|
}
|
|
18373
18895
|
|
|
18374
18896
|
// src/services/ContractInteractionService.ts
|
|
18375
|
-
|
|
18897
|
+
import_decimal4.Decimal.config({ precision: 28, rounding: 4 });
|
|
18376
18898
|
var FUNDING_GAS_PER_CALL = 400000n;
|
|
18377
18899
|
var DEBIT_ORDER_GAS = 60000n;
|
|
18378
18900
|
function settles(budget) {
|
|
@@ -18713,7 +19235,7 @@ var ContractInteractionService = class {
|
|
|
18713
19235
|
* @returns The total USD value of inputs (sum of normalized stable amounts, or 0 if none)
|
|
18714
19236
|
*/
|
|
18715
19237
|
async getInputUsdValue(order) {
|
|
18716
|
-
let inputUsdValue = new
|
|
19238
|
+
let inputUsdValue = new import_decimal4.Decimal(0);
|
|
18717
19239
|
const inputs = order.inputs;
|
|
18718
19240
|
const sourceUsdc = this.configService.getUsdcAsset(order.source).toLowerCase();
|
|
18719
19241
|
const sourceUsdt = this.configService.getUsdtAsset(order.source).toLowerCase();
|
|
@@ -18722,7 +19244,7 @@ var ContractInteractionService = class {
|
|
|
18722
19244
|
const addr = tokenAddress.toLowerCase();
|
|
18723
19245
|
if (addr !== sourceUsdc && addr !== sourceUsdt) continue;
|
|
18724
19246
|
const decimals = await this.getTokenDecimals(tokenAddress, order.source);
|
|
18725
|
-
const tokenAmount = new
|
|
19247
|
+
const tokenAmount = new import_decimal4.Decimal((0, import_viem23.formatUnits)(input.amount, decimals));
|
|
18726
19248
|
inputUsdValue = inputUsdValue.plus(tokenAmount);
|
|
18727
19249
|
}
|
|
18728
19250
|
return inputUsdValue;
|
|
@@ -19851,7 +20373,7 @@ var DelegationService = class {
|
|
|
19851
20373
|
// src/services/RebalancingService.ts
|
|
19852
20374
|
var import_viem28 = require("viem");
|
|
19853
20375
|
var import_sdk13 = require("@hyperbridge/sdk");
|
|
19854
|
-
var
|
|
20376
|
+
var import_decimal5 = require("decimal.js");
|
|
19855
20377
|
|
|
19856
20378
|
// src/services/rebalancers/binance.ts
|
|
19857
20379
|
var import_wallet2 = require("@binance/wallet");
|
|
@@ -21002,7 +21524,7 @@ var RebalancingService = class {
|
|
|
21002
21524
|
functionName: "balanceOf",
|
|
21003
21525
|
args: [this.walletAddress]
|
|
21004
21526
|
});
|
|
21005
|
-
const balanceDecimal = new
|
|
21527
|
+
const balanceDecimal = new import_decimal5.Decimal((0, import_viem28.formatUnits)(balanceWei, Number(decimals)));
|
|
21006
21528
|
return balanceDecimal.toNumber();
|
|
21007
21529
|
} catch (error) {
|
|
21008
21530
|
this.logger.error({ error, chain, asset }, "Failed to get current balance");
|
|
@@ -21035,7 +21557,7 @@ var RebalancingService = class {
|
|
|
21035
21557
|
};
|
|
21036
21558
|
|
|
21037
21559
|
// src/core/filler.ts
|
|
21038
|
-
var
|
|
21560
|
+
var import_decimal6 = require("decimal.js");
|
|
21039
21561
|
var HYPERBRIDGE_CONNECT_TIMEOUT_MS = 3e4;
|
|
21040
21562
|
function byAmount(a, b) {
|
|
21041
21563
|
return BigInt(a.amount) < BigInt(b.amount) ? -1 : BigInt(a.amount) > BigInt(b.amount) ? 1 : 0;
|
|
@@ -21591,7 +22113,7 @@ var IntentFiller = class {
|
|
|
21591
22113
|
try {
|
|
21592
22114
|
const stratValue = await strategy.getOrderUsdValue(order);
|
|
21593
22115
|
if (stratValue != null) {
|
|
21594
|
-
inputUsdValue =
|
|
22116
|
+
inputUsdValue = import_decimal6.Decimal.max(baseInputUsd, stratValue.inputUsd);
|
|
21595
22117
|
valued = true;
|
|
21596
22118
|
break;
|
|
21597
22119
|
}
|
|
@@ -21839,7 +22361,12 @@ var IntentFiller = class {
|
|
|
21839
22361
|
else cache.clearFundingPrepends(order.id);
|
|
21840
22362
|
}
|
|
21841
22363
|
reservation = await this.holdAll([
|
|
21842
|
-
{
|
|
22364
|
+
{
|
|
22365
|
+
limitOrderId: plan.limitOrderId,
|
|
22366
|
+
payout: plan.payout,
|
|
22367
|
+
take: plan.fillerInputs[plan.leg]?.amount,
|
|
22368
|
+
takeDecimals: plan.inputDecimals
|
|
22369
|
+
}
|
|
21843
22370
|
]);
|
|
21844
22371
|
if (reservation.length === 0) {
|
|
21845
22372
|
this.logger.info(
|
|
@@ -22009,7 +22536,8 @@ var IntentFiller = class {
|
|
|
22009
22536
|
taken.push({
|
|
22010
22537
|
limitOrderId: hold.limitOrderId,
|
|
22011
22538
|
amount,
|
|
22012
|
-
...hold.take !== void 0 ? { take: hold.take.toString() } : {}
|
|
22539
|
+
...hold.take !== void 0 ? { take: hold.take.toString() } : {},
|
|
22540
|
+
...hold.takeDecimals !== void 0 ? { takeDecimals: hold.takeDecimals } : {}
|
|
22013
22541
|
});
|
|
22014
22542
|
}
|
|
22015
22543
|
return taken;
|
|
@@ -22036,6 +22564,7 @@ var IntentFiller = class {
|
|
|
22036
22564
|
}
|
|
22037
22565
|
const settled = executedHold(claimed, delivered.scaled, released);
|
|
22038
22566
|
const charged = chargedFor(settled, delivered, released);
|
|
22567
|
+
const takenIn = released > 0n && settled.takeDecimals !== void 0 ? toScaled(released, settled.takeDecimals) : null;
|
|
22039
22568
|
const settledBid = rows.find(
|
|
22040
22569
|
(row) => row.reservations.some(
|
|
22041
22570
|
(hold) => hold.limitOrderId === settled.limitOrderId && hold.amount === settled.amount && hold.take === settled.take
|
|
@@ -22054,6 +22583,7 @@ var IntentFiller = class {
|
|
|
22054
22583
|
commitment,
|
|
22055
22584
|
bid: settledBid,
|
|
22056
22585
|
amount: share.toString(),
|
|
22586
|
+
amountIn: takenIn?.toString() ?? null,
|
|
22057
22587
|
transactionHash: transactionHash ?? null
|
|
22058
22588
|
});
|
|
22059
22589
|
}
|
|
@@ -22202,44 +22732,7 @@ var IntentFiller = class {
|
|
|
22202
22732
|
// src/strategies/fx.ts
|
|
22203
22733
|
var import_sdk15 = require("@hyperbridge/sdk");
|
|
22204
22734
|
var import_viem29 = require("viem");
|
|
22205
|
-
var
|
|
22206
|
-
|
|
22207
|
-
// src/orderbook/usd.ts
|
|
22208
|
-
var import_decimal5 = require("decimal.js");
|
|
22209
|
-
function limitOrderUsdEdges(orders) {
|
|
22210
|
-
return orders.filter((order) => normalizeSymbol(order.base) !== normalizeSymbol(order.quote)).map((order) => ({
|
|
22211
|
-
base: normalizeSymbol(order.base),
|
|
22212
|
-
quote: normalizeSymbol(order.quote),
|
|
22213
|
-
rate: new import_decimal5.Decimal(order.price).div(ORDERBOOK_SCALE.toString())
|
|
22214
|
-
})).filter((edge) => edge.rate.isFinite() && edge.rate.gt(0));
|
|
22215
|
-
}
|
|
22216
|
-
function usdFactorsFrom(edges) {
|
|
22217
|
-
const factors = /* @__PURE__ */ new Map();
|
|
22218
|
-
for (const symbol of USD_STABLE_SYMBOLS) factors.set(symbol, new import_decimal5.Decimal(1));
|
|
22219
|
-
const ordered = [...edges].sort((a, b) => a.base.localeCompare(b.base) || a.quote.localeCompare(b.quote));
|
|
22220
|
-
let grew = true;
|
|
22221
|
-
while (grew) {
|
|
22222
|
-
grew = false;
|
|
22223
|
-
for (const edge of ordered) {
|
|
22224
|
-
const usdBase = factors.get(edge.base);
|
|
22225
|
-
const usdQuote = factors.get(edge.quote);
|
|
22226
|
-
if (usdBase && !usdQuote) {
|
|
22227
|
-
factors.set(edge.quote, usdBase.div(edge.rate));
|
|
22228
|
-
grew = true;
|
|
22229
|
-
} else if (usdQuote && !usdBase) {
|
|
22230
|
-
factors.set(edge.base, usdQuote.mul(edge.rate));
|
|
22231
|
-
grew = true;
|
|
22232
|
-
}
|
|
22233
|
-
}
|
|
22234
|
-
}
|
|
22235
|
-
return factors;
|
|
22236
|
-
}
|
|
22237
|
-
function usdValueOf(factors, symbol, amount) {
|
|
22238
|
-
const factor = factors.get(normalizeSymbol(symbol));
|
|
22239
|
-
return factor ? amount.mul(factor) : null;
|
|
22240
|
-
}
|
|
22241
|
-
|
|
22242
|
-
// src/strategies/fx.ts
|
|
22735
|
+
var import_decimal7 = require("decimal.js");
|
|
22243
22736
|
function adjustDecimalsFloor(amount, fromDecimals, toDecimals) {
|
|
22244
22737
|
if (fromDecimals === toDecimals) return amount;
|
|
22245
22738
|
if (fromDecimals < toDecimals) return amount * BigInt(10 ** (toDecimals - fromDecimals));
|
|
@@ -22287,7 +22780,7 @@ var FXFiller = class _FXFiller {
|
|
|
22287
22780
|
this.maxConsecutiveClamps = configService.getMaxConsecutiveClamps();
|
|
22288
22781
|
if (confirmationPolicy) {
|
|
22289
22782
|
this.confirmationPolicy = {
|
|
22290
|
-
getConfirmationBlocks: (chainId, amountUsd) => confirmationPolicy.getConfirmationBlocks(chainId, new
|
|
22783
|
+
getConfirmationBlocks: (chainId, amountUsd) => confirmationPolicy.getConfirmationBlocks(chainId, new import_decimal7.Decimal(amountUsd))
|
|
22291
22784
|
};
|
|
22292
22785
|
}
|
|
22293
22786
|
}
|
|
@@ -22623,17 +23116,17 @@ var FXFiller = class _FXFiller {
|
|
|
22623
23116
|
const outputSymbol = this.registry.symbolFor(outputToken, destChain);
|
|
22624
23117
|
const sameAsset = outputSymbol !== null && normalizeSymbol(outputSymbol) === normalizeSymbol(candidate.order.base) && normalizeSymbol(outputSymbol) === normalizeSymbol(candidate.order.quote);
|
|
22625
23118
|
let realizedSpreadProfit = 0n;
|
|
22626
|
-
let sameAssetEdgeUsd = new
|
|
23119
|
+
let sameAssetEdgeUsd = new import_decimal7.Decimal(0);
|
|
22627
23120
|
let sameAssetProfitable = true;
|
|
22628
23121
|
if (sameAsset) {
|
|
22629
23122
|
const convertedInput = adjustDecimalsFloor(releasedInput, inputDecimals, outputDecimals);
|
|
22630
23123
|
const spread = convertedInput - finalOutputAmount;
|
|
22631
23124
|
if (spread <= 0n) sameAssetProfitable = false;
|
|
22632
23125
|
realizedSpreadProfit = adjustDecimalsFloor(spread, outputDecimals, feeTokenDecimals);
|
|
22633
|
-
const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new
|
|
23126
|
+
const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new import_decimal7.Decimal((0, import_viem29.formatUnits)(spread, outputDecimals)));
|
|
22634
23127
|
if (spreadUsd) sameAssetEdgeUsd = spreadUsd;
|
|
22635
23128
|
}
|
|
22636
|
-
const payoutSurplusUsd = new
|
|
23129
|
+
const payoutSurplusUsd = new import_decimal7.Decimal(0);
|
|
22637
23130
|
const { totalCostInSourceFeeToken, relayerFeeInSourceFeeToken, dispatchFee } = await this.contractService.estimateGasFillPost(order);
|
|
22638
23131
|
if (sourceChain !== destChain && dispatchFee > 0n) {
|
|
22639
23132
|
const feeToken = await this.contractService.getFeeTokenWithDecimals(destChain);
|
|
@@ -22720,6 +23213,7 @@ var FXFiller = class _FXFiller {
|
|
|
22720
23213
|
payout: toScaled(finalOutputAmount, outputDecimals),
|
|
22721
23214
|
fillerOutputs,
|
|
22722
23215
|
fillerInputs,
|
|
23216
|
+
inputDecimals,
|
|
22723
23217
|
fundingCalls: [...fundingCalls],
|
|
22724
23218
|
partialFill,
|
|
22725
23219
|
profit: totalProfit,
|
|
@@ -22998,13 +23492,13 @@ var FXFiller = class _FXFiller {
|
|
|
22998
23492
|
async getOrderUsdValue(order) {
|
|
22999
23493
|
if (!this.limitOrders || order.inputs.length === 0) return null;
|
|
23000
23494
|
const factors = usdFactorsFrom(limitOrderUsdEdges(await this.limitOrders.open()));
|
|
23001
|
-
let inputUsd = new
|
|
23495
|
+
let inputUsd = new import_decimal7.Decimal(0);
|
|
23002
23496
|
for (const input of order.inputs) {
|
|
23003
23497
|
const inputToken = (0, import_sdk15.bytes32ToBytes20)(input.token);
|
|
23004
23498
|
const symbol = this.registry.symbolFor(inputToken, order.source);
|
|
23005
23499
|
if (!symbol) return null;
|
|
23006
23500
|
const decimals = await this.contractService.getTokenDecimals(inputToken, order.source);
|
|
23007
|
-
const legUsd = usdValueOf(factors, symbol, new
|
|
23501
|
+
const legUsd = usdValueOf(factors, symbol, new import_decimal7.Decimal((0, import_viem29.formatUnits)(input.amount, decimals)));
|
|
23008
23502
|
if (!legUsd) return null;
|
|
23009
23503
|
inputUsd = inputUsd.plus(legUsd);
|
|
23010
23504
|
}
|
|
@@ -24189,9 +24683,10 @@ async function bootFiller(config, options) {
|
|
|
24189
24683
|
const activity = new ActivityRecorder(options.data.activity, options.loggers, { describeToken });
|
|
24190
24684
|
activity.attach(intentFiller.monitor);
|
|
24191
24685
|
const network = resolvedChains.some((chain) => chainByChainId(chain.chainId)?.network === "testnet") ? "testnet" : "mainnet";
|
|
24686
|
+
const indexerUrl = config.simplex.indexerUrl ?? DEFAULT_INDEXER_URLS[network];
|
|
24192
24687
|
void backfillOrderSummaries({
|
|
24193
24688
|
store: options.data.activity,
|
|
24194
|
-
indexerUrl
|
|
24689
|
+
indexerUrl,
|
|
24195
24690
|
fillerAddress: runtimeSigner.address,
|
|
24196
24691
|
describeToken,
|
|
24197
24692
|
onUpdated: (rows) => {
|
|
@@ -24257,6 +24752,15 @@ async function bootFiller(config, options) {
|
|
|
24257
24752
|
vaultVenue?.startSweeping();
|
|
24258
24753
|
started.push(() => balanceProvider.stop());
|
|
24259
24754
|
await balanceProvider.start();
|
|
24755
|
+
if (options.signer) {
|
|
24756
|
+
const inventoryRecorder = new InventoryRecorder(
|
|
24757
|
+
options.data.inventory,
|
|
24758
|
+
balanceProvider,
|
|
24759
|
+
moduleLogger(options.loggers, "inventory")
|
|
24760
|
+
);
|
|
24761
|
+
started.push(() => inventoryRecorder.stop());
|
|
24762
|
+
inventoryRecorder.start();
|
|
24763
|
+
}
|
|
24260
24764
|
const watchOnlyChains = watchOnlyConfig ? Object.entries(watchOnlyConfig).filter(([, value]) => value === true).map(([chainId]) => Number.parseInt(chainId, 10)) : [];
|
|
24261
24765
|
logger.info(
|
|
24262
24766
|
{
|
|
@@ -24302,6 +24806,8 @@ async function bootFiller(config, options) {
|
|
|
24302
24806
|
rebalancingService,
|
|
24303
24807
|
resolvedChains,
|
|
24304
24808
|
fillerAddress: runtimeSigner.address,
|
|
24809
|
+
indexerUrl,
|
|
24810
|
+
describeToken,
|
|
24305
24811
|
watchOnly: watchOnlyConfig,
|
|
24306
24812
|
config,
|
|
24307
24813
|
configPath: options.configPath,
|
|
@@ -24576,10 +25082,20 @@ var MemoryLimitOrderStore = class {
|
|
|
24576
25082
|
commitment: fill.commitment,
|
|
24577
25083
|
bid: fill.bid ?? null,
|
|
24578
25084
|
amount: fill.amount,
|
|
25085
|
+
amountIn: fill.amountIn ?? null,
|
|
24579
25086
|
transactionHash: fill.transactionHash ?? null,
|
|
24580
25087
|
filledAt: sqliteDatetime(/* @__PURE__ */ new Date())
|
|
24581
25088
|
});
|
|
24582
25089
|
}
|
|
25090
|
+
async fillHistory() {
|
|
25091
|
+
return this.fillRows.flatMap((fill) => {
|
|
25092
|
+
const order = this.orders.get(fill.limitOrderId);
|
|
25093
|
+
if (!order) return [];
|
|
25094
|
+
const { id, limitOrderId, amount, amountIn, filledAt } = fill;
|
|
25095
|
+
const { book, base, quote, side, price } = order;
|
|
25096
|
+
return [{ id, limitOrderId, book, base, quote, side, price, amount, amountIn, filledAt }];
|
|
25097
|
+
});
|
|
25098
|
+
}
|
|
24583
25099
|
async fills(limitOrderId, limit = 100) {
|
|
24584
25100
|
return this.fillRows.filter((fill) => fill.limitOrderId === limitOrderId).slice(-capLimit(limit)).reverse().map((fill) => ({ ...fill }));
|
|
24585
25101
|
}
|
|
@@ -24686,12 +25202,29 @@ var MemoryStateStore = class {
|
|
|
24686
25202
|
this.state = { ...state };
|
|
24687
25203
|
}
|
|
24688
25204
|
};
|
|
25205
|
+
var MemoryInventoryStore = class {
|
|
25206
|
+
constructor() {
|
|
25207
|
+
this.rows = [];
|
|
25208
|
+
}
|
|
25209
|
+
async record(balances) {
|
|
25210
|
+
this.rows.push({ id: this.rows.length + 1, takenAt: sqliteDatetime(/* @__PURE__ */ new Date()), balances: { ...balances } });
|
|
25211
|
+
}
|
|
25212
|
+
async latest() {
|
|
25213
|
+
const last = this.rows[this.rows.length - 1];
|
|
25214
|
+
return last ? { ...last, balances: { ...last.balances } } : null;
|
|
25215
|
+
}
|
|
25216
|
+
async since(from) {
|
|
25217
|
+
const cutoff = sqliteDatetime(from);
|
|
25218
|
+
return this.rows.filter((row) => row.takenAt >= cutoff).map((row) => ({ ...row, balances: { ...row.balances } }));
|
|
25219
|
+
}
|
|
25220
|
+
};
|
|
24689
25221
|
var MemoryDataStore = class {
|
|
24690
25222
|
constructor() {
|
|
24691
25223
|
this.bids = new MemoryBidStore();
|
|
24692
25224
|
this.activity = new MemoryActivityStore();
|
|
24693
25225
|
this.state = new MemoryStateStore();
|
|
24694
25226
|
this.limitOrders = new MemoryLimitOrderStore();
|
|
25227
|
+
this.inventory = new MemoryInventoryStore();
|
|
24695
25228
|
}
|
|
24696
25229
|
};
|
|
24697
25230
|
|
|
@@ -25296,6 +25829,106 @@ var OrderScanner = class _OrderScanner {
|
|
|
25296
25829
|
}
|
|
25297
25830
|
};
|
|
25298
25831
|
|
|
25832
|
+
// src/data/indexer-inventory.ts
|
|
25833
|
+
var import_viem33 = require("viem");
|
|
25834
|
+
var READ_TIMEOUT_MS = 1e4;
|
|
25835
|
+
var SETTLED_AFTER_MS = 10 * 6e4;
|
|
25836
|
+
var BATCH = 40;
|
|
25837
|
+
var MAX_KEPT = 2e3;
|
|
25838
|
+
var IndexerInventory = class {
|
|
25839
|
+
constructor(options) {
|
|
25840
|
+
this.options = options;
|
|
25841
|
+
this.kept = /* @__PURE__ */ new Map();
|
|
25842
|
+
this.described = /* @__PURE__ */ new Map();
|
|
25843
|
+
this.fetch = options.fetch ?? fetch;
|
|
25844
|
+
}
|
|
25845
|
+
/**
|
|
25846
|
+
* A reading for each of `instants` the indexer could answer for. Never rejects: an instant it
|
|
25847
|
+
* could not be asked about is simply missing from the answer.
|
|
25848
|
+
*/
|
|
25849
|
+
async at(instants, now) {
|
|
25850
|
+
const readings = [];
|
|
25851
|
+
const wanted = [];
|
|
25852
|
+
for (const instant of new Set(instants)) {
|
|
25853
|
+
const kept = this.kept.get(instant);
|
|
25854
|
+
if (kept) readings.push(kept);
|
|
25855
|
+
else wanted.push(instant);
|
|
25856
|
+
}
|
|
25857
|
+
try {
|
|
25858
|
+
for (let index = 0; index < wanted.length; index += BATCH) {
|
|
25859
|
+
for (const reading of await this.read(wanted.slice(index, index + BATCH))) {
|
|
25860
|
+
readings.push(reading);
|
|
25861
|
+
if (reading.at <= now - SETTLED_AFTER_MS) this.keep(reading);
|
|
25862
|
+
}
|
|
25863
|
+
}
|
|
25864
|
+
} catch (err) {
|
|
25865
|
+
this.options.logger.warn(
|
|
25866
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
25867
|
+
"Could not read inventory from the indexer; carrying records by fills and sends alone"
|
|
25868
|
+
);
|
|
25869
|
+
}
|
|
25870
|
+
return readings;
|
|
25871
|
+
}
|
|
25872
|
+
keep(reading) {
|
|
25873
|
+
if (this.kept.size >= MAX_KEPT) this.kept.clear();
|
|
25874
|
+
this.kept.set(reading.at, reading);
|
|
25875
|
+
}
|
|
25876
|
+
/** One request for several instants, each its own aliased query at that moment in history. */
|
|
25877
|
+
async read(instants) {
|
|
25878
|
+
const queries = instants.map(
|
|
25879
|
+
(instant, index) => `r${index}: solverInventories(filter: { solver: { equalTo: $solver } }, blockHeight: "${Math.floor(instant)}", first: 1000) { nodes { chain tokenAddress balance } }`
|
|
25880
|
+
);
|
|
25881
|
+
const response = await this.fetch(this.options.indexerUrl, {
|
|
25882
|
+
method: "POST",
|
|
25883
|
+
headers: { "content-type": "application/json" },
|
|
25884
|
+
body: JSON.stringify({
|
|
25885
|
+
query: `query SolverInventory($solver: String!) {
|
|
25886
|
+
${queries.join("\n")}
|
|
25887
|
+
}`,
|
|
25888
|
+
variables: { solver: this.options.solver.toLowerCase() }
|
|
25889
|
+
}),
|
|
25890
|
+
signal: AbortSignal.timeout(READ_TIMEOUT_MS)
|
|
25891
|
+
});
|
|
25892
|
+
if (!response.ok) throw new Error(`Indexer responded ${response.status}`);
|
|
25893
|
+
const body = await response.json();
|
|
25894
|
+
if (body.errors?.length) throw new Error(body.errors.map((error) => error.message).join("; "));
|
|
25895
|
+
const readings = [];
|
|
25896
|
+
for (const [index, instant] of instants.entries()) {
|
|
25897
|
+
const rows = body.data?.[`r${index}`]?.nodes;
|
|
25898
|
+
if (rows) readings.push(await this.readingOf(instant, rows));
|
|
25899
|
+
}
|
|
25900
|
+
return readings;
|
|
25901
|
+
}
|
|
25902
|
+
async readingOf(at, rows) {
|
|
25903
|
+
const balances = {};
|
|
25904
|
+
const chains = {};
|
|
25905
|
+
for (const row of rows) {
|
|
25906
|
+
const token = await this.describe(row.chain, row.tokenAddress);
|
|
25907
|
+
if (!token) continue;
|
|
25908
|
+
let amount;
|
|
25909
|
+
try {
|
|
25910
|
+
amount = Number((0, import_viem33.formatUnits)(BigInt(row.balance), token.decimals));
|
|
25911
|
+
} catch {
|
|
25912
|
+
continue;
|
|
25913
|
+
}
|
|
25914
|
+
balances[token.symbol] = (balances[token.symbol] ?? 0) + amount;
|
|
25915
|
+
chains[token.symbol] = [...chains[token.symbol] ?? [], row.chain];
|
|
25916
|
+
}
|
|
25917
|
+
return { at, balances, chains };
|
|
25918
|
+
}
|
|
25919
|
+
/** A token's symbol and decimals, remembered once both are known. */
|
|
25920
|
+
async describe(chain, token) {
|
|
25921
|
+
const key = `${chain}:${token.toLowerCase()}`;
|
|
25922
|
+
const known = this.described.get(key);
|
|
25923
|
+
if (known) return known;
|
|
25924
|
+
const { symbol, decimals } = await this.options.describeToken(chain, token);
|
|
25925
|
+
if (!symbol || decimals === null) return null;
|
|
25926
|
+
const description = { symbol, decimals };
|
|
25927
|
+
this.described.set(key, description);
|
|
25928
|
+
return description;
|
|
25929
|
+
}
|
|
25930
|
+
};
|
|
25931
|
+
|
|
25299
25932
|
// src/simplex.ts
|
|
25300
25933
|
var EVENT_MAP = {
|
|
25301
25934
|
newOrder: "order:detected",
|
|
@@ -25341,10 +25974,66 @@ var LimitOrderController = class {
|
|
|
25341
25974
|
]);
|
|
25342
25975
|
return { order, fills, bids };
|
|
25343
25976
|
}
|
|
25977
|
+
/**
|
|
25978
|
+
* What the operator's buys and sells earned over `period`, bucketed on a clock
|
|
25979
|
+
* `tzOffsetMinutes` behind UTC.
|
|
25980
|
+
*
|
|
25981
|
+
* Profit is the change the period's fills made to what the solver holds, and
|
|
25982
|
+
* it is compared with the inventory the period began with. That inventory
|
|
25983
|
+
* comes from the stored snapshots and, when the balances have been read in
|
|
25984
|
+
* full, from what is held now, each carried to the period's start by what
|
|
25985
|
+
* moved between. The indexer says what moved, deposits and withdrawals
|
|
25986
|
+
* included, wherever it has followed the solver; elsewhere the fills and
|
|
25987
|
+
* sends on record stand in. It answers whether or not an orderbook is
|
|
25988
|
+
* configured, and whether or not the indexer does.
|
|
25989
|
+
*/
|
|
25990
|
+
async profitability(period, tzOffsetMinutes = 0) {
|
|
25991
|
+
const now = Date.now();
|
|
25992
|
+
const { data, balanceProvider } = this.runtime;
|
|
25993
|
+
const [fills, snapshots, ledger] = await Promise.all([
|
|
25994
|
+
data.limitOrders.fillHistory(),
|
|
25995
|
+
data.inventory.since(/* @__PURE__ */ new Date(0)),
|
|
25996
|
+
data.activity.walletTxs(500)
|
|
25997
|
+
]);
|
|
25998
|
+
const inventory = snapshots.map((snapshot) => ({
|
|
25999
|
+
at: parseUtc(snapshot.takenAt),
|
|
26000
|
+
balances: snapshot.balances
|
|
26001
|
+
}));
|
|
26002
|
+
const held = inventoryOf(balanceProvider.getSnapshot());
|
|
26003
|
+
if (held) inventory.push({ at: now, balances: held, live: true });
|
|
26004
|
+
const outflows = [];
|
|
26005
|
+
for (const tx of ledger) {
|
|
26006
|
+
const amount = Number(tx.amount);
|
|
26007
|
+
if (tx.kind === "send" && tx.token && Number.isFinite(amount) && amount > 0) {
|
|
26008
|
+
outflows.push({ at: tx.ts, symbol: tx.token, amount });
|
|
26009
|
+
}
|
|
26010
|
+
}
|
|
26011
|
+
const instants = inventoryInstants(fills, { period, now, tzOffsetMinutes, inventory });
|
|
26012
|
+
const readings = await this.indexerInventory().at(instants, now);
|
|
26013
|
+
return summarizeProfit(fills, { period, now, tzOffsetMinutes, inventory, outflows, readings });
|
|
26014
|
+
}
|
|
26015
|
+
/** One reader for the life of the filler, so the readings it has already fetched are kept. */
|
|
26016
|
+
indexerInventory() {
|
|
26017
|
+
this.indexed ?? (this.indexed = new IndexerInventory({
|
|
26018
|
+
indexerUrl: this.runtime.indexerUrl,
|
|
26019
|
+
solver: this.runtime.fillerAddress,
|
|
26020
|
+
describeToken: this.runtime.describeToken,
|
|
26021
|
+
logger: this.runtime.loggers.get("inventory")
|
|
26022
|
+
}));
|
|
26023
|
+
return this.indexed;
|
|
26024
|
+
}
|
|
25344
26025
|
/** The pairs the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
|
|
25345
26026
|
books() {
|
|
25346
26027
|
return this.service.books();
|
|
25347
26028
|
}
|
|
26029
|
+
/** Public bids and asks across solvers, optionally restricted to a route. */
|
|
26030
|
+
orderbookSnapshot(book, filters = {}) {
|
|
26031
|
+
return this.service.orderbookSnapshot(book, filters);
|
|
26032
|
+
}
|
|
26033
|
+
/** Every solver's order behind one public price level. */
|
|
26034
|
+
orderbookLevelOrders(book, level) {
|
|
26035
|
+
return this.service.orderbookLevelOrders(book, level);
|
|
26036
|
+
}
|
|
25348
26037
|
/** Creates the order, posts it, and reports what the orderbook made of it. */
|
|
25349
26038
|
async create(request) {
|
|
25350
26039
|
const posted = await this.service.create(request);
|