@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.js
CHANGED
|
@@ -2615,6 +2615,35 @@ var LIMITS_QUERY = `
|
|
|
2615
2615
|
chains { id name tokens { symbol decimals } }
|
|
2616
2616
|
}
|
|
2617
2617
|
`;
|
|
2618
|
+
var LEVEL_FIELDS = "fillChain priceBucket price worstPrice baseSize quoteSize orderCount solverCount";
|
|
2619
|
+
var SNAPSHOT_QUERY = `
|
|
2620
|
+
query OrderbookSnapshot($id: ID!, $sourceChain: String, $fillChain: String) {
|
|
2621
|
+
book(id: $id) {
|
|
2622
|
+
id base quote
|
|
2623
|
+
bids: levels(side: BID, sourceChain: $sourceChain, fillChain: $fillChain) { ${LEVEL_FIELDS} }
|
|
2624
|
+
asks: levels(side: ASK, sourceChain: $sourceChain, fillChain: $fillChain) { ${LEVEL_FIELDS} }
|
|
2625
|
+
bidLiquidity: availableLiquidity(side: BID, sourceChain: $sourceChain, fillChain: $fillChain)
|
|
2626
|
+
askLiquidity: availableLiquidity(side: ASK, sourceChain: $sourceChain, fillChain: $fillChain)
|
|
2627
|
+
}
|
|
2628
|
+
serverInfo { priceGranularities { book granularity } }
|
|
2629
|
+
}
|
|
2630
|
+
`;
|
|
2631
|
+
var LEVEL_ORDERS_QUERY = `
|
|
2632
|
+
query OrderbookLevelOrders($id: ID!, $side: Side!, $fillChain: String, $sourceChain: String, $after: String) {
|
|
2633
|
+
book(id: $id) {
|
|
2634
|
+
orders(side: $side, fillChain: $fillChain, sourceChain: $sourceChain, first: 50, after: $after) {
|
|
2635
|
+
edges {
|
|
2636
|
+
node {
|
|
2637
|
+
solver { address }
|
|
2638
|
+
commitment fillChain price priceBucket advertisedSize quotedAmount resized expiresAt acceptedSources
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
pageInfo { hasNextPage endCursor }
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
`;
|
|
2646
|
+
var LEVEL_ORDER_PAGES = 20;
|
|
2618
2647
|
var POSTED_ORDER_FIELDS = "commitment side status price quotedAmount advertisedSize expiresAt acceptedSources";
|
|
2619
2648
|
var SUBMIT_ORDER_MUTATION = `
|
|
2620
2649
|
mutation SubmitOrder($userOp: Bytes!) {
|
|
@@ -2679,6 +2708,44 @@ var OrderbookClient = class {
|
|
|
2679
2708
|
async limits() {
|
|
2680
2709
|
return this.request(LIMITS_QUERY, {});
|
|
2681
2710
|
}
|
|
2711
|
+
/** The public book across all solvers, restricted to the selected route. */
|
|
2712
|
+
async snapshot(id, filters = {}) {
|
|
2713
|
+
const { book, serverInfo } = await this.request(SNAPSHOT_QUERY, {
|
|
2714
|
+
id,
|
|
2715
|
+
sourceChain: filters.sourceChain || null,
|
|
2716
|
+
fillChain: filters.fillChain || null
|
|
2717
|
+
});
|
|
2718
|
+
if (!book) return null;
|
|
2719
|
+
const granularity = serverInfo.priceGranularities.find((entry) => entry.book === book.id)?.granularity ?? null;
|
|
2720
|
+
return { ...book, granularity };
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* The orders behind one price level. The orderbook filters orders by side and route but not by
|
|
2724
|
+
* price bucket, so this walks the side's orders on the level's fill chain and keeps the bucket.
|
|
2725
|
+
* Null when the book does not exist.
|
|
2726
|
+
*/
|
|
2727
|
+
async levelOrders(id, level) {
|
|
2728
|
+
const found = [];
|
|
2729
|
+
let after = null;
|
|
2730
|
+
for (let page = 0; page < LEVEL_ORDER_PAGES; page++) {
|
|
2731
|
+
const { book } = await this.request(LEVEL_ORDERS_QUERY, {
|
|
2732
|
+
id,
|
|
2733
|
+
side: level.side,
|
|
2734
|
+
fillChain: level.fillChain,
|
|
2735
|
+
sourceChain: level.sourceChain || null,
|
|
2736
|
+
after
|
|
2737
|
+
});
|
|
2738
|
+
if (!book) return null;
|
|
2739
|
+
for (const { node } of book.orders.edges) {
|
|
2740
|
+
if (node.priceBucket !== level.priceBucket) continue;
|
|
2741
|
+
const { solver, priceBucket: _bucket, ...order } = node;
|
|
2742
|
+
found.push({ ...order, solver: solver.address });
|
|
2743
|
+
}
|
|
2744
|
+
if (!book.orders.pageInfo.hasNextPage || !book.orders.pageInfo.endCursor) break;
|
|
2745
|
+
after = book.orders.pageInfo.endCursor;
|
|
2746
|
+
}
|
|
2747
|
+
return found;
|
|
2748
|
+
}
|
|
2682
2749
|
async submitOrder(userOp) {
|
|
2683
2750
|
const { submitOrder } = await this.request(SUBMIT_ORDER_MUTATION, { userOp });
|
|
2684
2751
|
switch (submitOrder.__typename) {
|
|
@@ -2863,16 +2930,16 @@ function signedAmounts(params) {
|
|
|
2863
2930
|
function offerFor(params) {
|
|
2864
2931
|
const { side, inputAmount, price, outputDecimals } = params;
|
|
2865
2932
|
if (price <= 0n) throw new Error("A limit order's price must be greater than zero");
|
|
2866
|
-
const
|
|
2933
|
+
const scaled2 = side === "BID" ? inputAmount * price / ORDERBOOK_SCALE : inputAmount * ORDERBOOK_SCALE / price;
|
|
2867
2934
|
const unit = outputDecimals >= 18 ? 1n : 10n ** BigInt(18 - outputDecimals);
|
|
2868
|
-
return
|
|
2935
|
+
return scaled2 / unit * unit;
|
|
2869
2936
|
}
|
|
2870
2937
|
function inputFor(params) {
|
|
2871
2938
|
const { side, outputAmount, price, inputDecimals } = params;
|
|
2872
2939
|
if (price <= 0n) throw new Error("A limit order's price must be greater than zero");
|
|
2873
|
-
const
|
|
2940
|
+
const scaled2 = side === "BID" ? divCeil(outputAmount * ORDERBOOK_SCALE, price) : divCeil(outputAmount * price, ORDERBOOK_SCALE);
|
|
2874
2941
|
const unit = inputDecimals >= 18 ? 1n : 10n ** BigInt(18 - inputDecimals);
|
|
2875
|
-
return divCeil(
|
|
2942
|
+
return divCeil(scaled2, unit) * unit;
|
|
2876
2943
|
}
|
|
2877
2944
|
|
|
2878
2945
|
// src/orderbook/matching.ts
|
|
@@ -3128,6 +3195,14 @@ var LimitOrderService = class {
|
|
|
3128
3195
|
throw err;
|
|
3129
3196
|
}
|
|
3130
3197
|
}
|
|
3198
|
+
/** Public market levels read through the configured orderbook client. */
|
|
3199
|
+
orderbookSnapshot(book, filters = {}) {
|
|
3200
|
+
return this.client.snapshot(book, filters);
|
|
3201
|
+
}
|
|
3202
|
+
/** Every solver's order behind one public price level. */
|
|
3203
|
+
orderbookLevelOrders(book, level) {
|
|
3204
|
+
return this.client.levelOrders(book, level);
|
|
3205
|
+
}
|
|
3131
3206
|
/**
|
|
3132
3207
|
* The pairs an order can be written against, the smallest payout each token may carry, and
|
|
3133
3208
|
* the tokens the orderbook registers on each chain.
|
|
@@ -3182,16 +3257,16 @@ var LimitOrderService = class {
|
|
|
3182
3257
|
if (!this.configService.getConfiguredChainIds().includes(getChainId(request.fillChain) ?? -1)) {
|
|
3183
3258
|
throw new LimitOrderValidationError(`'${request.fillChain}' is not a chain this filler is configured for`);
|
|
3184
3259
|
}
|
|
3185
|
-
const
|
|
3260
|
+
const scaled2 = { amountIn: 0n, amountOut: 0n };
|
|
3186
3261
|
for (const name of ["amountIn", "amountOut"]) {
|
|
3187
3262
|
try {
|
|
3188
|
-
|
|
3263
|
+
scaled2[name] = fromHuman(request[name] ?? "");
|
|
3189
3264
|
} catch (err) {
|
|
3190
3265
|
throw new LimitOrderValidationError(
|
|
3191
3266
|
`${name} must be an amount in whole tokens, like "1000" or "1500.25"; ${err.message}`
|
|
3192
3267
|
);
|
|
3193
3268
|
}
|
|
3194
|
-
if (
|
|
3269
|
+
if (scaled2[name] <= 0n) throw new LimitOrderValidationError(`${name} must be greater than zero`);
|
|
3195
3270
|
}
|
|
3196
3271
|
const sources = request.acceptedSources ?? [];
|
|
3197
3272
|
if (sources.length === 0) {
|
|
@@ -3238,7 +3313,7 @@ var LimitOrderService = class {
|
|
|
3238
3313
|
);
|
|
3239
3314
|
}
|
|
3240
3315
|
const floor = limits.serverInfo.minOrderSizes.find((entry) => entry.symbol === request.tokenOut);
|
|
3241
|
-
if (floor &&
|
|
3316
|
+
if (floor && scaled2.amountOut < BigInt(floor.size)) {
|
|
3242
3317
|
throw new LimitOrderValidationError(
|
|
3243
3318
|
`amountOut is below the orderbook's dust floor for ${request.tokenOut} (${toHuman(BigInt(floor.size))})`
|
|
3244
3319
|
);
|
|
@@ -3248,7 +3323,7 @@ var LimitOrderService = class {
|
|
|
3248
3323
|
throw new LimitOrderValidationError(`'${symbol}' does not resolve to a token address on ${request.fillChain}`);
|
|
3249
3324
|
}
|
|
3250
3325
|
}
|
|
3251
|
-
return { amountIn:
|
|
3326
|
+
return { amountIn: scaled2.amountIn, amountOut: scaled2.amountOut };
|
|
3252
3327
|
}
|
|
3253
3328
|
/**
|
|
3254
3329
|
* Refuses an order the wallet cannot pay out.
|
|
@@ -3774,6 +3849,451 @@ function underFunded(entry) {
|
|
|
3774
3849
|
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";
|
|
3775
3850
|
}
|
|
3776
3851
|
|
|
3852
|
+
// src/orderbook/profitability.ts
|
|
3853
|
+
import { Decimal as BaseDecimal } from "decimal.js";
|
|
3854
|
+
|
|
3855
|
+
// src/data/inventory.ts
|
|
3856
|
+
var INVENTORY_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3857
|
+
function inventoryOf(snapshot) {
|
|
3858
|
+
if (snapshot.status !== "fresh") return null;
|
|
3859
|
+
const held = {};
|
|
3860
|
+
for (const chain of snapshot.chains) {
|
|
3861
|
+
for (const asset of chain.assets) {
|
|
3862
|
+
if (asset.total === null) return null;
|
|
3863
|
+
held[asset.symbol] = (held[asset.symbol] ?? 0) + asset.total;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
return Object.keys(held).length > 0 ? held : null;
|
|
3867
|
+
}
|
|
3868
|
+
function parseUtc(value) {
|
|
3869
|
+
return Date.parse(`${value.replace(" ", "T")}Z`);
|
|
3870
|
+
}
|
|
3871
|
+
var InventoryRecorder = class {
|
|
3872
|
+
constructor(store, balances, logger, now = Date.now) {
|
|
3873
|
+
this.store = store;
|
|
3874
|
+
this.balances = balances;
|
|
3875
|
+
this.logger = logger;
|
|
3876
|
+
this.now = now;
|
|
3877
|
+
this.recording = false;
|
|
3878
|
+
this.onSnapshot = (snapshot) => void this.consider(snapshot);
|
|
3879
|
+
}
|
|
3880
|
+
start() {
|
|
3881
|
+
this.balances.on("snapshot", this.onSnapshot);
|
|
3882
|
+
void this.consider(this.balances.getSnapshot());
|
|
3883
|
+
}
|
|
3884
|
+
stop() {
|
|
3885
|
+
this.balances.off("snapshot", this.onSnapshot);
|
|
3886
|
+
}
|
|
3887
|
+
/** Takes a snapshot from `snapshot` if one is due and the read is complete. Never rejects. */
|
|
3888
|
+
async consider(snapshot) {
|
|
3889
|
+
if (this.recording) return;
|
|
3890
|
+
const held = inventoryOf(snapshot);
|
|
3891
|
+
if (!held) return;
|
|
3892
|
+
this.recording = true;
|
|
3893
|
+
try {
|
|
3894
|
+
if (this.lastTakenAt === void 0) {
|
|
3895
|
+
const latest = await this.store.latest();
|
|
3896
|
+
this.lastTakenAt = latest ? parseUtc(latest.takenAt) : 0;
|
|
3897
|
+
}
|
|
3898
|
+
if (this.now() - this.lastTakenAt < INVENTORY_INTERVAL_MS) return;
|
|
3899
|
+
await this.store.record(held);
|
|
3900
|
+
this.lastTakenAt = this.now();
|
|
3901
|
+
this.logger.info({ tokens: Object.keys(held).length }, "Recorded an inventory snapshot");
|
|
3902
|
+
} catch (err) {
|
|
3903
|
+
this.logger.warn({ err }, "Could not record an inventory snapshot");
|
|
3904
|
+
} finally {
|
|
3905
|
+
this.recording = false;
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3910
|
+
// src/orderbook/usd.ts
|
|
3911
|
+
import { Decimal as Decimal2 } from "decimal.js";
|
|
3912
|
+
function limitOrderUsdEdges(orders) {
|
|
3913
|
+
return orders.filter((order) => normalizeSymbol(order.base) !== normalizeSymbol(order.quote)).map((order) => ({
|
|
3914
|
+
base: normalizeSymbol(order.base),
|
|
3915
|
+
quote: normalizeSymbol(order.quote),
|
|
3916
|
+
rate: new Decimal2(order.price).div(ORDERBOOK_SCALE.toString())
|
|
3917
|
+
})).filter((edge) => edge.rate.isFinite() && edge.rate.gt(0));
|
|
3918
|
+
}
|
|
3919
|
+
function usdFactorsFrom(edges) {
|
|
3920
|
+
const factors = /* @__PURE__ */ new Map();
|
|
3921
|
+
for (const symbol of USD_STABLE_SYMBOLS) factors.set(symbol, new Decimal2(1));
|
|
3922
|
+
const ordered = [...edges].sort((a, b) => a.base.localeCompare(b.base) || a.quote.localeCompare(b.quote));
|
|
3923
|
+
let grew = true;
|
|
3924
|
+
while (grew) {
|
|
3925
|
+
grew = false;
|
|
3926
|
+
for (const edge of ordered) {
|
|
3927
|
+
const usdBase = factors.get(edge.base);
|
|
3928
|
+
const usdQuote = factors.get(edge.quote);
|
|
3929
|
+
if (usdBase && !usdQuote) {
|
|
3930
|
+
factors.set(edge.quote, usdBase.div(edge.rate));
|
|
3931
|
+
grew = true;
|
|
3932
|
+
} else if (usdQuote && !usdBase) {
|
|
3933
|
+
factors.set(edge.base, usdQuote.mul(edge.rate));
|
|
3934
|
+
grew = true;
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
return factors;
|
|
3939
|
+
}
|
|
3940
|
+
function usdValueOf(factors, symbol, amount) {
|
|
3941
|
+
const factor = factors.get(normalizeSymbol(symbol));
|
|
3942
|
+
return factor ? amount.mul(factor) : null;
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
// src/orderbook/profitability.ts
|
|
3946
|
+
var Decimal3 = BaseDecimal.clone({ precision: 40 });
|
|
3947
|
+
var PROFIT_PERIODS = {
|
|
3948
|
+
"7d": { bucket: "day", count: 7 },
|
|
3949
|
+
"30d": { bucket: "day", count: 30 },
|
|
3950
|
+
"12w": { bucket: "week", count: 12 },
|
|
3951
|
+
"12m": { bucket: "month", count: 12 },
|
|
3952
|
+
all: { bucket: "year", count: null }
|
|
3953
|
+
};
|
|
3954
|
+
var MINUTE = 6e4;
|
|
3955
|
+
var DAY = 864e5;
|
|
3956
|
+
var BALANCES_REACH_MS = 7 * DAY;
|
|
3957
|
+
function bucketStart(local, bucket) {
|
|
3958
|
+
const date = new Date(local);
|
|
3959
|
+
const year = date.getUTCFullYear();
|
|
3960
|
+
const month = date.getUTCMonth();
|
|
3961
|
+
if (bucket === "year") return Date.UTC(year, 0, 1);
|
|
3962
|
+
if (bucket === "month") return Date.UTC(year, month, 1);
|
|
3963
|
+
const day = Date.UTC(year, month, date.getUTCDate());
|
|
3964
|
+
return bucket === "day" ? day : day - (date.getUTCDay() + 6) % 7 * DAY;
|
|
3965
|
+
}
|
|
3966
|
+
function shiftBucket(start, bucket, steps) {
|
|
3967
|
+
if (bucket === "day") return start + steps * DAY;
|
|
3968
|
+
if (bucket === "week") return start + steps * 7 * DAY;
|
|
3969
|
+
const date = new Date(start);
|
|
3970
|
+
return bucket === "month" ? Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + steps, 1) : Date.UTC(date.getUTCFullYear() + steps, 0, 1);
|
|
3971
|
+
}
|
|
3972
|
+
function scaled(value) {
|
|
3973
|
+
return new Decimal3(value).div(ORDERBOOK_SCALE.toString());
|
|
3974
|
+
}
|
|
3975
|
+
function tradeOf(fill, at) {
|
|
3976
|
+
try {
|
|
3977
|
+
const paid = scaled(fill.amount);
|
|
3978
|
+
const taken = fill.amountIn === null ? null : scaled(fill.amountIn);
|
|
3979
|
+
const price = scaled(fill.price);
|
|
3980
|
+
if (!paid.gt(0) || !price.gt(0) || taken !== null && !taken.gt(0)) return null;
|
|
3981
|
+
const buying = fill.side === "BID";
|
|
3982
|
+
const [baseAmount, quoteAmount] = buying ? [taken ?? paid.div(price), paid] : [paid, taken ?? paid.mul(price)];
|
|
3983
|
+
return {
|
|
3984
|
+
at,
|
|
3985
|
+
book: fill.book,
|
|
3986
|
+
base: normalizeSymbol(fill.base),
|
|
3987
|
+
quote: normalizeSymbol(fill.quote),
|
|
3988
|
+
buying,
|
|
3989
|
+
baseAmount,
|
|
3990
|
+
quoteAmount,
|
|
3991
|
+
estimated: taken === null
|
|
3992
|
+
};
|
|
3993
|
+
} catch {
|
|
3994
|
+
return null;
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
function add(holdings, symbol, amount) {
|
|
3998
|
+
holdings.set(symbol, (holdings.get(symbol) ?? new Decimal3(0)).add(amount));
|
|
3999
|
+
}
|
|
4000
|
+
function apply(holdings, trade, sign) {
|
|
4001
|
+
const base = trade.buying ? trade.baseAmount : trade.baseAmount.neg();
|
|
4002
|
+
const quote = trade.buying ? trade.quoteAmount.neg() : trade.quoteAmount;
|
|
4003
|
+
add(holdings, trade.base, base.mul(sign));
|
|
4004
|
+
add(holdings, trade.quote, quote.mul(sign));
|
|
4005
|
+
}
|
|
4006
|
+
var Sums = class {
|
|
4007
|
+
constructor() {
|
|
4008
|
+
this.change = /* @__PURE__ */ new Map();
|
|
4009
|
+
this.boughtUsd = new Decimal3(0);
|
|
4010
|
+
this.soldUsd = new Decimal3(0);
|
|
4011
|
+
this.buys = 0;
|
|
4012
|
+
this.sells = 0;
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
var BookLedger = class {
|
|
4016
|
+
constructor(id, base, quote) {
|
|
4017
|
+
this.id = id;
|
|
4018
|
+
this.base = base;
|
|
4019
|
+
this.quote = quote;
|
|
4020
|
+
this.bought = new Decimal3(0);
|
|
4021
|
+
this.boughtQuote = new Decimal3(0);
|
|
4022
|
+
this.sold = new Decimal3(0);
|
|
4023
|
+
this.soldQuote = new Decimal3(0);
|
|
4024
|
+
/** The rates of the book's latest buy and latest sell, over all of its history. */
|
|
4025
|
+
this.latestBuy = null;
|
|
4026
|
+
this.latestSell = null;
|
|
4027
|
+
this.inPeriod = false;
|
|
4028
|
+
}
|
|
4029
|
+
/**
|
|
4030
|
+
* The rate the book is valued at. Midway between the latest buy and the latest sell, because
|
|
4031
|
+
* either alone sits half a spread to one side and would price open volume against whichever
|
|
4032
|
+
* way the book happened to trade last.
|
|
4033
|
+
*/
|
|
4034
|
+
rate() {
|
|
4035
|
+
if (this.latestBuy && this.latestSell) return this.latestBuy.add(this.latestSell).div(2);
|
|
4036
|
+
return this.latestBuy ?? this.latestSell;
|
|
4037
|
+
}
|
|
4038
|
+
};
|
|
4039
|
+
function readingOf(reading) {
|
|
4040
|
+
const held = /* @__PURE__ */ new Map();
|
|
4041
|
+
const cover = /* @__PURE__ */ new Map();
|
|
4042
|
+
for (const [symbol, amount] of Object.entries(reading.balances)) {
|
|
4043
|
+
const chains = reading.chains[symbol];
|
|
4044
|
+
if (!Number.isFinite(amount) || !chains?.length) continue;
|
|
4045
|
+
add(held, normalizeSymbol(symbol), new Decimal3(amount));
|
|
4046
|
+
cover.set(normalizeSymbol(symbol), [...chains].sort().join(","));
|
|
4047
|
+
}
|
|
4048
|
+
return { held, cover };
|
|
4049
|
+
}
|
|
4050
|
+
function movedBetween(earlier, later) {
|
|
4051
|
+
if (!earlier || !later) return null;
|
|
4052
|
+
const moved = /* @__PURE__ */ new Map();
|
|
4053
|
+
for (const [symbol, cover] of later.cover) {
|
|
4054
|
+
if (earlier.cover.get(symbol) !== cover) continue;
|
|
4055
|
+
const before = earlier.held.get(symbol) ?? new Decimal3(0);
|
|
4056
|
+
moved.set(symbol, (later.held.get(symbol) ?? new Decimal3(0)).sub(before));
|
|
4057
|
+
}
|
|
4058
|
+
return moved;
|
|
4059
|
+
}
|
|
4060
|
+
function nearestRecord(instant, records) {
|
|
4061
|
+
let record;
|
|
4062
|
+
let gap = Number.POSITIVE_INFINITY;
|
|
4063
|
+
for (const candidate of records) {
|
|
4064
|
+
const distance = Math.abs(candidate.at - instant);
|
|
4065
|
+
if (candidate.live && distance > BALANCES_REACH_MS) continue;
|
|
4066
|
+
if (distance < gap || distance === gap && record?.live && !candidate.live) {
|
|
4067
|
+
record = candidate;
|
|
4068
|
+
gap = distance;
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
return record;
|
|
4072
|
+
}
|
|
4073
|
+
function inventoryAt(instant, records, trades, outflows, readings) {
|
|
4074
|
+
const record = nearestRecord(instant, records);
|
|
4075
|
+
if (!record) return null;
|
|
4076
|
+
const recorded = /* @__PURE__ */ new Map();
|
|
4077
|
+
for (const [symbol, amount] of Object.entries(record.balances)) {
|
|
4078
|
+
if (Number.isFinite(amount)) add(recorded, normalizeSymbol(symbol), new Decimal3(amount));
|
|
4079
|
+
}
|
|
4080
|
+
const held = new Map(recorded);
|
|
4081
|
+
const after = record.at >= instant;
|
|
4082
|
+
const [from, to] = after ? [instant, record.at] : [record.at, instant];
|
|
4083
|
+
const sign = after ? -1 : 1;
|
|
4084
|
+
for (const trade of trades) {
|
|
4085
|
+
if (trade.at >= from && trade.at < to) apply(held, trade, sign);
|
|
4086
|
+
}
|
|
4087
|
+
for (const outflow of outflows) {
|
|
4088
|
+
if (outflow.at >= from && outflow.at < to) {
|
|
4089
|
+
add(held, normalizeSymbol(outflow.symbol), new Decimal3(outflow.amount).mul(-sign));
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
const moved = movedBetween(readings.get(from), readings.get(to));
|
|
4093
|
+
if (moved) {
|
|
4094
|
+
for (const [symbol, amount] of moved) {
|
|
4095
|
+
held.set(symbol, (recorded.get(symbol) ?? new Decimal3(0)).add(amount.mul(sign)));
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
for (const [symbol, amount] of held) if (amount.isNegative()) held.set(symbol, new Decimal3(0));
|
|
4099
|
+
return { held, record };
|
|
4100
|
+
}
|
|
4101
|
+
function tradesOf(fills, now) {
|
|
4102
|
+
const trades = [];
|
|
4103
|
+
for (const fill of fills) {
|
|
4104
|
+
const at = parseUtc(fill.filledAt);
|
|
4105
|
+
if (!Number.isFinite(at) || at > now) continue;
|
|
4106
|
+
const trade = tradeOf(fill, at);
|
|
4107
|
+
if (trade) trades.push(trade);
|
|
4108
|
+
}
|
|
4109
|
+
return trades.sort((a, b) => a.at - b.at);
|
|
4110
|
+
}
|
|
4111
|
+
function bucketStarts(period, now, offset, firstAt) {
|
|
4112
|
+
const { bucket, count } = PROFIT_PERIODS[period];
|
|
4113
|
+
const last = bucketStart(now - offset, bucket);
|
|
4114
|
+
const first = count === null ? bucketStart((firstAt ?? now) - offset, bucket) : shiftBucket(last, bucket, 1 - count);
|
|
4115
|
+
const starts = [];
|
|
4116
|
+
for (let start = first; start <= last; start = shiftBucket(start, bucket, 1)) starts.push(start);
|
|
4117
|
+
return starts;
|
|
4118
|
+
}
|
|
4119
|
+
function inventoryInstants(fills, options) {
|
|
4120
|
+
const { period, now } = options;
|
|
4121
|
+
const offset = (options.tzOffsetMinutes ?? 0) * MINUTE;
|
|
4122
|
+
const records = (options.inventory ?? []).filter((record) => record.at <= now);
|
|
4123
|
+
const instants = /* @__PURE__ */ new Set([now]);
|
|
4124
|
+
for (const start of bucketStarts(period, now, offset, tradesOf(fills, now)[0]?.at)) {
|
|
4125
|
+
instants.add(start + offset);
|
|
4126
|
+
const record = nearestRecord(start + offset, records);
|
|
4127
|
+
if (record) instants.add(record.at);
|
|
4128
|
+
}
|
|
4129
|
+
return [...instants].sort((a, b) => a - b);
|
|
4130
|
+
}
|
|
4131
|
+
function summarizeProfit(fills, options) {
|
|
4132
|
+
const { period, now } = options;
|
|
4133
|
+
const offset = (options.tzOffsetMinutes ?? 0) * MINUTE;
|
|
4134
|
+
const { bucket } = PROFIT_PERIODS[period];
|
|
4135
|
+
const records = (options.inventory ?? []).filter((record) => record.at <= now);
|
|
4136
|
+
const outflows = options.outflows ?? [];
|
|
4137
|
+
const readings = new Map((options.readings ?? []).map((reading) => [reading.at, readingOf(reading)]));
|
|
4138
|
+
const trades = tradesOf(fills, now);
|
|
4139
|
+
const starts = bucketStarts(period, now, offset, trades[0]?.at);
|
|
4140
|
+
const first = starts[0];
|
|
4141
|
+
const books = /* @__PURE__ */ new Map();
|
|
4142
|
+
const spelling = /* @__PURE__ */ new Map();
|
|
4143
|
+
for (const record of records) {
|
|
4144
|
+
for (const symbol of Object.keys(record.balances)) {
|
|
4145
|
+
if (!spelling.has(normalizeSymbol(symbol))) spelling.set(normalizeSymbol(symbol), symbol);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
for (const fill of fills) {
|
|
4149
|
+
for (const symbol of [fill.base, fill.quote]) {
|
|
4150
|
+
if (!spelling.has(normalizeSymbol(symbol))) spelling.set(normalizeSymbol(symbol), symbol);
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
for (const trade of trades) {
|
|
4154
|
+
let book = books.get(trade.book);
|
|
4155
|
+
if (!book) {
|
|
4156
|
+
book = new BookLedger(trade.book, trade.base, trade.quote);
|
|
4157
|
+
books.set(trade.book, book);
|
|
4158
|
+
}
|
|
4159
|
+
const rate = trade.quoteAmount.div(trade.baseAmount);
|
|
4160
|
+
if (trade.buying) book.latestBuy = rate;
|
|
4161
|
+
else book.latestSell = rate;
|
|
4162
|
+
}
|
|
4163
|
+
const edges = [];
|
|
4164
|
+
for (const book of books.values()) {
|
|
4165
|
+
const rate = book.rate();
|
|
4166
|
+
if (rate && book.base !== book.quote) {
|
|
4167
|
+
edges.push({ base: book.base, quote: book.quote, rate: new BaseDecimal(rate.toString()) });
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
const factors = usdFactorsFrom(edges);
|
|
4171
|
+
const priceOf = (symbol) => {
|
|
4172
|
+
const factor = factors.get(symbol);
|
|
4173
|
+
return factor ? new Decimal3(factor.toString()) : null;
|
|
4174
|
+
};
|
|
4175
|
+
const unpriced = /* @__PURE__ */ new Set();
|
|
4176
|
+
const valueOf = (holdings) => {
|
|
4177
|
+
let total = new Decimal3(0);
|
|
4178
|
+
for (const [symbol, amount] of holdings) {
|
|
4179
|
+
if (amount.isZero()) continue;
|
|
4180
|
+
const price = priceOf(symbol);
|
|
4181
|
+
if (price) total = total.add(amount.mul(price));
|
|
4182
|
+
else unpriced.add(symbol);
|
|
4183
|
+
}
|
|
4184
|
+
return total;
|
|
4185
|
+
};
|
|
4186
|
+
const totals = new Sums();
|
|
4187
|
+
const buckets = new Map(starts.map((start) => [start, new Sums()]));
|
|
4188
|
+
let estimatedFills = 0;
|
|
4189
|
+
for (const trade of trades) {
|
|
4190
|
+
const sums = buckets.get(bucketStart(trade.at - offset, bucket));
|
|
4191
|
+
if (!sums) continue;
|
|
4192
|
+
const book = books.get(trade.book);
|
|
4193
|
+
if (!book) continue;
|
|
4194
|
+
book.inPeriod = true;
|
|
4195
|
+
if (trade.estimated) estimatedFills += 1;
|
|
4196
|
+
if (trade.buying) {
|
|
4197
|
+
book.bought = book.bought.add(trade.baseAmount);
|
|
4198
|
+
book.boughtQuote = book.boughtQuote.add(trade.quoteAmount);
|
|
4199
|
+
} else {
|
|
4200
|
+
book.sold = book.sold.add(trade.baseAmount);
|
|
4201
|
+
book.soldQuote = book.soldQuote.add(trade.quoteAmount);
|
|
4202
|
+
}
|
|
4203
|
+
const basePrice = priceOf(trade.base);
|
|
4204
|
+
const quotePrice = priceOf(trade.quote);
|
|
4205
|
+
const volume = basePrice ? trade.baseAmount.mul(basePrice) : quotePrice ? trade.quoteAmount.mul(quotePrice) : new Decimal3(0);
|
|
4206
|
+
for (const sum of [totals, sums]) {
|
|
4207
|
+
apply(sum.change, trade, 1);
|
|
4208
|
+
if (trade.buying) {
|
|
4209
|
+
sum.buys += 1;
|
|
4210
|
+
sum.boughtUsd = sum.boughtUsd.add(volume);
|
|
4211
|
+
} else {
|
|
4212
|
+
sum.sells += 1;
|
|
4213
|
+
sum.soldUsd = sum.soldUsd.add(volume);
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
const figures = (sums, startsAt, endsAt) => {
|
|
4218
|
+
const profit = valueOf(sums.change);
|
|
4219
|
+
const began2 = inventoryAt(startsAt, records, trades, outflows, readings);
|
|
4220
|
+
const inventory = began2 ? valueOf(began2.held) : null;
|
|
4221
|
+
const moved = movedBetween(readings.get(startsAt), readings.get(endsAt));
|
|
4222
|
+
if (moved) {
|
|
4223
|
+
for (const [symbol, amount] of sums.change) {
|
|
4224
|
+
if (moved.has(symbol)) moved.set(symbol, (moved.get(symbol) ?? new Decimal3(0)).sub(amount));
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
return {
|
|
4228
|
+
profitUsd: profit.toNumber(),
|
|
4229
|
+
startInventoryUsd: inventory ? inventory.toNumber() : null,
|
|
4230
|
+
returnPct: inventory?.gt(0) ? profit.div(inventory).mul(100).toNumber() : null,
|
|
4231
|
+
transfersUsd: moved && moved.size > 0 ? valueOf(moved).toNumber() : null,
|
|
4232
|
+
boughtUsd: sums.boughtUsd.toNumber(),
|
|
4233
|
+
soldUsd: sums.soldUsd.toNumber(),
|
|
4234
|
+
buys: sums.buys,
|
|
4235
|
+
sells: sums.sells
|
|
4236
|
+
};
|
|
4237
|
+
};
|
|
4238
|
+
const from = first + offset;
|
|
4239
|
+
const began = inventoryAt(from, records, trades, outflows, readings);
|
|
4240
|
+
const startInventory = began && {
|
|
4241
|
+
at: from,
|
|
4242
|
+
source: began.record.live ? "balances" : "snapshot",
|
|
4243
|
+
recordedAt: began.record.at,
|
|
4244
|
+
usd: valueOf(began.held).toNumber(),
|
|
4245
|
+
tokens: [...began.held].filter(([, amount]) => amount.gt(0)).map(([symbol, amount]) => {
|
|
4246
|
+
const price = priceOf(symbol);
|
|
4247
|
+
return {
|
|
4248
|
+
symbol: spelling.get(symbol) ?? symbol,
|
|
4249
|
+
amount: amount.toNumber(),
|
|
4250
|
+
usd: price ? amount.mul(price).toNumber() : null
|
|
4251
|
+
};
|
|
4252
|
+
}).sort((a, b) => (b.usd ?? 0) - (a.usd ?? 0) || a.symbol.localeCompare(b.symbol))
|
|
4253
|
+
};
|
|
4254
|
+
const rows = [];
|
|
4255
|
+
for (const book of books.values()) {
|
|
4256
|
+
if (!book.inPeriod) continue;
|
|
4257
|
+
const averageBuy = book.bought.gt(0) ? book.boughtQuote.div(book.bought) : null;
|
|
4258
|
+
const averageSell = book.sold.gt(0) ? book.soldQuote.div(book.sold) : null;
|
|
4259
|
+
const baseChange = book.bought.sub(book.sold);
|
|
4260
|
+
const quoteChange = book.soldQuote.sub(book.boughtQuote);
|
|
4261
|
+
const basePrice = priceOf(book.base);
|
|
4262
|
+
const quotePrice = priceOf(book.quote);
|
|
4263
|
+
rows.push({
|
|
4264
|
+
book: book.id,
|
|
4265
|
+
base: spelling.get(book.base) ?? book.base,
|
|
4266
|
+
quote: spelling.get(book.quote) ?? book.quote,
|
|
4267
|
+
bought: book.bought.toNumber(),
|
|
4268
|
+
sold: book.sold.toNumber(),
|
|
4269
|
+
averageBuy: averageBuy ? averageBuy.toNumber() : null,
|
|
4270
|
+
averageSell: averageSell ? averageSell.toNumber() : null,
|
|
4271
|
+
spreadPct: averageBuy && averageSell ? averageSell.div(averageBuy).sub(1).mul(100).toNumber() : null,
|
|
4272
|
+
baseChange: baseChange.toNumber(),
|
|
4273
|
+
quoteChange: quoteChange.toNumber(),
|
|
4274
|
+
rate: book.rate()?.toNumber() ?? null,
|
|
4275
|
+
profitUsd: basePrice && quotePrice ? baseChange.mul(basePrice).add(quoteChange.mul(quotePrice)).toNumber() : null
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
rows.sort((a, b) => (b.profitUsd ?? 0) - (a.profitUsd ?? 0) || a.book.localeCompare(b.book));
|
|
4279
|
+
return {
|
|
4280
|
+
period,
|
|
4281
|
+
bucket,
|
|
4282
|
+
from,
|
|
4283
|
+
to: now,
|
|
4284
|
+
totals: figures(totals, from, now),
|
|
4285
|
+
series: starts.map((start, index) => {
|
|
4286
|
+
const sums = buckets.get(start) ?? new Sums();
|
|
4287
|
+
const endsAt = index + 1 < starts.length ? starts[index + 1] + offset : now;
|
|
4288
|
+
return { start: start + offset, ...figures(sums, start + offset, endsAt) };
|
|
4289
|
+
}),
|
|
4290
|
+
books: rows,
|
|
4291
|
+
startInventory,
|
|
4292
|
+
estimatedFills,
|
|
4293
|
+
unpricedTokens: [...unpriced].map((symbol) => spelling.get(symbol) ?? symbol).sort()
|
|
4294
|
+
};
|
|
4295
|
+
}
|
|
4296
|
+
|
|
3777
4297
|
// src/data/state.ts
|
|
3778
4298
|
async function patchRuntimeState(store, patch) {
|
|
3779
4299
|
if (store.patch) return store.patch(patch);
|
|
@@ -13045,6 +13565,7 @@ var CacheService = class {
|
|
|
13045
13565
|
})),
|
|
13046
13566
|
partialFill: plan.partialFill,
|
|
13047
13567
|
profit: plan.profit,
|
|
13568
|
+
...plan.inputDecimals !== void 0 ? { inputDecimals: plan.inputDecimals } : {},
|
|
13048
13569
|
...plan.budget ? { budget: budgetFromCache(plan.budget) } : {}
|
|
13049
13570
|
}));
|
|
13050
13571
|
} catch (error) {
|
|
@@ -13075,6 +13596,7 @@ var CacheService = class {
|
|
|
13075
13596
|
})),
|
|
13076
13597
|
partialFill: plan.partialFill,
|
|
13077
13598
|
profit: plan.profit,
|
|
13599
|
+
...plan.inputDecimals !== void 0 ? { inputDecimals: plan.inputDecimals } : {},
|
|
13078
13600
|
...plan.budget ? { budget: budgetToCache(plan.budget) } : {}
|
|
13079
13601
|
})),
|
|
13080
13602
|
timestamp: Date.now()
|
|
@@ -15713,7 +16235,7 @@ var EVM_HOST = [
|
|
|
15713
16235
|
];
|
|
15714
16236
|
|
|
15715
16237
|
// src/services/ContractInteractionService.ts
|
|
15716
|
-
import { Decimal as
|
|
16238
|
+
import { Decimal as Decimal4 } from "decimal.js";
|
|
15717
16239
|
|
|
15718
16240
|
// src/config/abis/IntentGatewayV2.ts
|
|
15719
16241
|
var INTENT_GATEWAY_V2_ABI = [
|
|
@@ -18355,7 +18877,7 @@ function describeShortfall(balances) {
|
|
|
18355
18877
|
}
|
|
18356
18878
|
|
|
18357
18879
|
// src/services/ContractInteractionService.ts
|
|
18358
|
-
|
|
18880
|
+
Decimal4.config({ precision: 28, rounding: 4 });
|
|
18359
18881
|
var FUNDING_GAS_PER_CALL = 400000n;
|
|
18360
18882
|
var DEBIT_ORDER_GAS = 60000n;
|
|
18361
18883
|
function settles(budget) {
|
|
@@ -18696,7 +19218,7 @@ var ContractInteractionService = class {
|
|
|
18696
19218
|
* @returns The total USD value of inputs (sum of normalized stable amounts, or 0 if none)
|
|
18697
19219
|
*/
|
|
18698
19220
|
async getInputUsdValue(order) {
|
|
18699
|
-
let inputUsdValue = new
|
|
19221
|
+
let inputUsdValue = new Decimal4(0);
|
|
18700
19222
|
const inputs = order.inputs;
|
|
18701
19223
|
const sourceUsdc = this.configService.getUsdcAsset(order.source).toLowerCase();
|
|
18702
19224
|
const sourceUsdt = this.configService.getUsdtAsset(order.source).toLowerCase();
|
|
@@ -18705,7 +19227,7 @@ var ContractInteractionService = class {
|
|
|
18705
19227
|
const addr = tokenAddress.toLowerCase();
|
|
18706
19228
|
if (addr !== sourceUsdc && addr !== sourceUsdt) continue;
|
|
18707
19229
|
const decimals = await this.getTokenDecimals(tokenAddress, order.source);
|
|
18708
|
-
const tokenAmount = new
|
|
19230
|
+
const tokenAmount = new Decimal4(formatUnits2(input.amount, decimals));
|
|
18709
19231
|
inputUsdValue = inputUsdValue.plus(tokenAmount);
|
|
18710
19232
|
}
|
|
18711
19233
|
return inputUsdValue;
|
|
@@ -19834,7 +20356,7 @@ var DelegationService = class {
|
|
|
19834
20356
|
// src/services/RebalancingService.ts
|
|
19835
20357
|
import { formatUnits as formatUnits3 } from "viem";
|
|
19836
20358
|
import { parseStateMachineId as parseStateMachineId4 } from "@hyperbridge/sdk";
|
|
19837
|
-
import { Decimal as
|
|
20359
|
+
import { Decimal as Decimal5 } from "decimal.js";
|
|
19838
20360
|
|
|
19839
20361
|
// src/services/rebalancers/binance.ts
|
|
19840
20362
|
import { Wallet as Wallet2 } from "@binance/wallet";
|
|
@@ -20998,7 +21520,7 @@ var RebalancingService = class {
|
|
|
20998
21520
|
functionName: "balanceOf",
|
|
20999
21521
|
args: [this.walletAddress]
|
|
21000
21522
|
});
|
|
21001
|
-
const balanceDecimal = new
|
|
21523
|
+
const balanceDecimal = new Decimal5(formatUnits3(balanceWei, Number(decimals)));
|
|
21002
21524
|
return balanceDecimal.toNumber();
|
|
21003
21525
|
} catch (error) {
|
|
21004
21526
|
this.logger.error({ error, chain, asset }, "Failed to get current balance");
|
|
@@ -21031,7 +21553,7 @@ var RebalancingService = class {
|
|
|
21031
21553
|
};
|
|
21032
21554
|
|
|
21033
21555
|
// src/core/filler.ts
|
|
21034
|
-
import { Decimal as
|
|
21556
|
+
import { Decimal as Decimal6 } from "decimal.js";
|
|
21035
21557
|
var HYPERBRIDGE_CONNECT_TIMEOUT_MS = 3e4;
|
|
21036
21558
|
function byAmount(a, b) {
|
|
21037
21559
|
return BigInt(a.amount) < BigInt(b.amount) ? -1 : BigInt(a.amount) > BigInt(b.amount) ? 1 : 0;
|
|
@@ -21587,7 +22109,7 @@ var IntentFiller = class {
|
|
|
21587
22109
|
try {
|
|
21588
22110
|
const stratValue = await strategy.getOrderUsdValue(order);
|
|
21589
22111
|
if (stratValue != null) {
|
|
21590
|
-
inputUsdValue =
|
|
22112
|
+
inputUsdValue = Decimal6.max(baseInputUsd, stratValue.inputUsd);
|
|
21591
22113
|
valued = true;
|
|
21592
22114
|
break;
|
|
21593
22115
|
}
|
|
@@ -21835,7 +22357,12 @@ var IntentFiller = class {
|
|
|
21835
22357
|
else cache.clearFundingPrepends(order.id);
|
|
21836
22358
|
}
|
|
21837
22359
|
reservation = await this.holdAll([
|
|
21838
|
-
{
|
|
22360
|
+
{
|
|
22361
|
+
limitOrderId: plan.limitOrderId,
|
|
22362
|
+
payout: plan.payout,
|
|
22363
|
+
take: plan.fillerInputs[plan.leg]?.amount,
|
|
22364
|
+
takeDecimals: plan.inputDecimals
|
|
22365
|
+
}
|
|
21839
22366
|
]);
|
|
21840
22367
|
if (reservation.length === 0) {
|
|
21841
22368
|
this.logger.info(
|
|
@@ -22005,7 +22532,8 @@ var IntentFiller = class {
|
|
|
22005
22532
|
taken.push({
|
|
22006
22533
|
limitOrderId: hold.limitOrderId,
|
|
22007
22534
|
amount,
|
|
22008
|
-
...hold.take !== void 0 ? { take: hold.take.toString() } : {}
|
|
22535
|
+
...hold.take !== void 0 ? { take: hold.take.toString() } : {},
|
|
22536
|
+
...hold.takeDecimals !== void 0 ? { takeDecimals: hold.takeDecimals } : {}
|
|
22009
22537
|
});
|
|
22010
22538
|
}
|
|
22011
22539
|
return taken;
|
|
@@ -22032,6 +22560,7 @@ var IntentFiller = class {
|
|
|
22032
22560
|
}
|
|
22033
22561
|
const settled = executedHold(claimed, delivered.scaled, released);
|
|
22034
22562
|
const charged = chargedFor(settled, delivered, released);
|
|
22563
|
+
const takenIn = released > 0n && settled.takeDecimals !== void 0 ? toScaled(released, settled.takeDecimals) : null;
|
|
22035
22564
|
const settledBid = rows.find(
|
|
22036
22565
|
(row) => row.reservations.some(
|
|
22037
22566
|
(hold) => hold.limitOrderId === settled.limitOrderId && hold.amount === settled.amount && hold.take === settled.take
|
|
@@ -22050,6 +22579,7 @@ var IntentFiller = class {
|
|
|
22050
22579
|
commitment,
|
|
22051
22580
|
bid: settledBid,
|
|
22052
22581
|
amount: share.toString(),
|
|
22582
|
+
amountIn: takenIn?.toString() ?? null,
|
|
22053
22583
|
transactionHash: transactionHash ?? null
|
|
22054
22584
|
});
|
|
22055
22585
|
}
|
|
@@ -22201,44 +22731,7 @@ import {
|
|
|
22201
22731
|
ADDRESS_ZERO as ADDRESS_ZERO2
|
|
22202
22732
|
} from "@hyperbridge/sdk";
|
|
22203
22733
|
import { formatUnits as formatUnits4 } from "viem";
|
|
22204
|
-
import { Decimal as
|
|
22205
|
-
|
|
22206
|
-
// src/orderbook/usd.ts
|
|
22207
|
-
import { Decimal as Decimal5 } from "decimal.js";
|
|
22208
|
-
function limitOrderUsdEdges(orders) {
|
|
22209
|
-
return orders.filter((order) => normalizeSymbol(order.base) !== normalizeSymbol(order.quote)).map((order) => ({
|
|
22210
|
-
base: normalizeSymbol(order.base),
|
|
22211
|
-
quote: normalizeSymbol(order.quote),
|
|
22212
|
-
rate: new Decimal5(order.price).div(ORDERBOOK_SCALE.toString())
|
|
22213
|
-
})).filter((edge) => edge.rate.isFinite() && edge.rate.gt(0));
|
|
22214
|
-
}
|
|
22215
|
-
function usdFactorsFrom(edges) {
|
|
22216
|
-
const factors = /* @__PURE__ */ new Map();
|
|
22217
|
-
for (const symbol of USD_STABLE_SYMBOLS) factors.set(symbol, new Decimal5(1));
|
|
22218
|
-
const ordered = [...edges].sort((a, b) => a.base.localeCompare(b.base) || a.quote.localeCompare(b.quote));
|
|
22219
|
-
let grew = true;
|
|
22220
|
-
while (grew) {
|
|
22221
|
-
grew = false;
|
|
22222
|
-
for (const edge of ordered) {
|
|
22223
|
-
const usdBase = factors.get(edge.base);
|
|
22224
|
-
const usdQuote = factors.get(edge.quote);
|
|
22225
|
-
if (usdBase && !usdQuote) {
|
|
22226
|
-
factors.set(edge.quote, usdBase.div(edge.rate));
|
|
22227
|
-
grew = true;
|
|
22228
|
-
} else if (usdQuote && !usdBase) {
|
|
22229
|
-
factors.set(edge.base, usdQuote.mul(edge.rate));
|
|
22230
|
-
grew = true;
|
|
22231
|
-
}
|
|
22232
|
-
}
|
|
22233
|
-
}
|
|
22234
|
-
return factors;
|
|
22235
|
-
}
|
|
22236
|
-
function usdValueOf(factors, symbol, amount) {
|
|
22237
|
-
const factor = factors.get(normalizeSymbol(symbol));
|
|
22238
|
-
return factor ? amount.mul(factor) : null;
|
|
22239
|
-
}
|
|
22240
|
-
|
|
22241
|
-
// src/strategies/fx.ts
|
|
22734
|
+
import { Decimal as Decimal7 } from "decimal.js";
|
|
22242
22735
|
function adjustDecimalsFloor(amount, fromDecimals, toDecimals) {
|
|
22243
22736
|
if (fromDecimals === toDecimals) return amount;
|
|
22244
22737
|
if (fromDecimals < toDecimals) return amount * BigInt(10 ** (toDecimals - fromDecimals));
|
|
@@ -22286,7 +22779,7 @@ var FXFiller = class _FXFiller {
|
|
|
22286
22779
|
this.maxConsecutiveClamps = configService.getMaxConsecutiveClamps();
|
|
22287
22780
|
if (confirmationPolicy) {
|
|
22288
22781
|
this.confirmationPolicy = {
|
|
22289
|
-
getConfirmationBlocks: (chainId, amountUsd) => confirmationPolicy.getConfirmationBlocks(chainId, new
|
|
22782
|
+
getConfirmationBlocks: (chainId, amountUsd) => confirmationPolicy.getConfirmationBlocks(chainId, new Decimal7(amountUsd))
|
|
22290
22783
|
};
|
|
22291
22784
|
}
|
|
22292
22785
|
}
|
|
@@ -22622,17 +23115,17 @@ var FXFiller = class _FXFiller {
|
|
|
22622
23115
|
const outputSymbol = this.registry.symbolFor(outputToken, destChain);
|
|
22623
23116
|
const sameAsset = outputSymbol !== null && normalizeSymbol(outputSymbol) === normalizeSymbol(candidate.order.base) && normalizeSymbol(outputSymbol) === normalizeSymbol(candidate.order.quote);
|
|
22624
23117
|
let realizedSpreadProfit = 0n;
|
|
22625
|
-
let sameAssetEdgeUsd = new
|
|
23118
|
+
let sameAssetEdgeUsd = new Decimal7(0);
|
|
22626
23119
|
let sameAssetProfitable = true;
|
|
22627
23120
|
if (sameAsset) {
|
|
22628
23121
|
const convertedInput = adjustDecimalsFloor(releasedInput, inputDecimals, outputDecimals);
|
|
22629
23122
|
const spread = convertedInput - finalOutputAmount;
|
|
22630
23123
|
if (spread <= 0n) sameAssetProfitable = false;
|
|
22631
23124
|
realizedSpreadProfit = adjustDecimalsFloor(spread, outputDecimals, feeTokenDecimals);
|
|
22632
|
-
const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new
|
|
23125
|
+
const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new Decimal7(formatUnits4(spread, outputDecimals)));
|
|
22633
23126
|
if (spreadUsd) sameAssetEdgeUsd = spreadUsd;
|
|
22634
23127
|
}
|
|
22635
|
-
const payoutSurplusUsd = new
|
|
23128
|
+
const payoutSurplusUsd = new Decimal7(0);
|
|
22636
23129
|
const { totalCostInSourceFeeToken, relayerFeeInSourceFeeToken, dispatchFee } = await this.contractService.estimateGasFillPost(order);
|
|
22637
23130
|
if (sourceChain !== destChain && dispatchFee > 0n) {
|
|
22638
23131
|
const feeToken = await this.contractService.getFeeTokenWithDecimals(destChain);
|
|
@@ -22719,6 +23212,7 @@ var FXFiller = class _FXFiller {
|
|
|
22719
23212
|
payout: toScaled(finalOutputAmount, outputDecimals),
|
|
22720
23213
|
fillerOutputs,
|
|
22721
23214
|
fillerInputs,
|
|
23215
|
+
inputDecimals,
|
|
22722
23216
|
fundingCalls: [...fundingCalls],
|
|
22723
23217
|
partialFill,
|
|
22724
23218
|
profit: totalProfit,
|
|
@@ -22997,13 +23491,13 @@ var FXFiller = class _FXFiller {
|
|
|
22997
23491
|
async getOrderUsdValue(order) {
|
|
22998
23492
|
if (!this.limitOrders || order.inputs.length === 0) return null;
|
|
22999
23493
|
const factors = usdFactorsFrom(limitOrderUsdEdges(await this.limitOrders.open()));
|
|
23000
|
-
let inputUsd = new
|
|
23494
|
+
let inputUsd = new Decimal7(0);
|
|
23001
23495
|
for (const input of order.inputs) {
|
|
23002
23496
|
const inputToken = bytes32ToBytes204(input.token);
|
|
23003
23497
|
const symbol = this.registry.symbolFor(inputToken, order.source);
|
|
23004
23498
|
if (!symbol) return null;
|
|
23005
23499
|
const decimals = await this.contractService.getTokenDecimals(inputToken, order.source);
|
|
23006
|
-
const legUsd = usdValueOf(factors, symbol, new
|
|
23500
|
+
const legUsd = usdValueOf(factors, symbol, new Decimal7(formatUnits4(input.amount, decimals)));
|
|
23007
23501
|
if (!legUsd) return null;
|
|
23008
23502
|
inputUsd = inputUsd.plus(legUsd);
|
|
23009
23503
|
}
|
|
@@ -24188,9 +24682,10 @@ async function bootFiller(config, options) {
|
|
|
24188
24682
|
const activity = new ActivityRecorder(options.data.activity, options.loggers, { describeToken });
|
|
24189
24683
|
activity.attach(intentFiller.monitor);
|
|
24190
24684
|
const network = resolvedChains.some((chain) => chainByChainId(chain.chainId)?.network === "testnet") ? "testnet" : "mainnet";
|
|
24685
|
+
const indexerUrl = config.simplex.indexerUrl ?? DEFAULT_INDEXER_URLS[network];
|
|
24191
24686
|
void backfillOrderSummaries({
|
|
24192
24687
|
store: options.data.activity,
|
|
24193
|
-
indexerUrl
|
|
24688
|
+
indexerUrl,
|
|
24194
24689
|
fillerAddress: runtimeSigner.address,
|
|
24195
24690
|
describeToken,
|
|
24196
24691
|
onUpdated: (rows) => {
|
|
@@ -24256,6 +24751,15 @@ async function bootFiller(config, options) {
|
|
|
24256
24751
|
vaultVenue?.startSweeping();
|
|
24257
24752
|
started.push(() => balanceProvider.stop());
|
|
24258
24753
|
await balanceProvider.start();
|
|
24754
|
+
if (options.signer) {
|
|
24755
|
+
const inventoryRecorder = new InventoryRecorder(
|
|
24756
|
+
options.data.inventory,
|
|
24757
|
+
balanceProvider,
|
|
24758
|
+
moduleLogger(options.loggers, "inventory")
|
|
24759
|
+
);
|
|
24760
|
+
started.push(() => inventoryRecorder.stop());
|
|
24761
|
+
inventoryRecorder.start();
|
|
24762
|
+
}
|
|
24259
24763
|
const watchOnlyChains = watchOnlyConfig ? Object.entries(watchOnlyConfig).filter(([, value]) => value === true).map(([chainId]) => Number.parseInt(chainId, 10)) : [];
|
|
24260
24764
|
logger.info(
|
|
24261
24765
|
{
|
|
@@ -24301,6 +24805,8 @@ async function bootFiller(config, options) {
|
|
|
24301
24805
|
rebalancingService,
|
|
24302
24806
|
resolvedChains,
|
|
24303
24807
|
fillerAddress: runtimeSigner.address,
|
|
24808
|
+
indexerUrl,
|
|
24809
|
+
describeToken,
|
|
24304
24810
|
watchOnly: watchOnlyConfig,
|
|
24305
24811
|
config,
|
|
24306
24812
|
configPath: options.configPath,
|
|
@@ -24575,10 +25081,20 @@ var MemoryLimitOrderStore = class {
|
|
|
24575
25081
|
commitment: fill.commitment,
|
|
24576
25082
|
bid: fill.bid ?? null,
|
|
24577
25083
|
amount: fill.amount,
|
|
25084
|
+
amountIn: fill.amountIn ?? null,
|
|
24578
25085
|
transactionHash: fill.transactionHash ?? null,
|
|
24579
25086
|
filledAt: sqliteDatetime(/* @__PURE__ */ new Date())
|
|
24580
25087
|
});
|
|
24581
25088
|
}
|
|
25089
|
+
async fillHistory() {
|
|
25090
|
+
return this.fillRows.flatMap((fill) => {
|
|
25091
|
+
const order = this.orders.get(fill.limitOrderId);
|
|
25092
|
+
if (!order) return [];
|
|
25093
|
+
const { id, limitOrderId, amount, amountIn, filledAt } = fill;
|
|
25094
|
+
const { book, base, quote, side, price } = order;
|
|
25095
|
+
return [{ id, limitOrderId, book, base, quote, side, price, amount, amountIn, filledAt }];
|
|
25096
|
+
});
|
|
25097
|
+
}
|
|
24582
25098
|
async fills(limitOrderId, limit = 100) {
|
|
24583
25099
|
return this.fillRows.filter((fill) => fill.limitOrderId === limitOrderId).slice(-capLimit(limit)).reverse().map((fill) => ({ ...fill }));
|
|
24584
25100
|
}
|
|
@@ -24685,12 +25201,29 @@ var MemoryStateStore = class {
|
|
|
24685
25201
|
this.state = { ...state };
|
|
24686
25202
|
}
|
|
24687
25203
|
};
|
|
25204
|
+
var MemoryInventoryStore = class {
|
|
25205
|
+
constructor() {
|
|
25206
|
+
this.rows = [];
|
|
25207
|
+
}
|
|
25208
|
+
async record(balances) {
|
|
25209
|
+
this.rows.push({ id: this.rows.length + 1, takenAt: sqliteDatetime(/* @__PURE__ */ new Date()), balances: { ...balances } });
|
|
25210
|
+
}
|
|
25211
|
+
async latest() {
|
|
25212
|
+
const last = this.rows[this.rows.length - 1];
|
|
25213
|
+
return last ? { ...last, balances: { ...last.balances } } : null;
|
|
25214
|
+
}
|
|
25215
|
+
async since(from) {
|
|
25216
|
+
const cutoff = sqliteDatetime(from);
|
|
25217
|
+
return this.rows.filter((row) => row.takenAt >= cutoff).map((row) => ({ ...row, balances: { ...row.balances } }));
|
|
25218
|
+
}
|
|
25219
|
+
};
|
|
24688
25220
|
var MemoryDataStore = class {
|
|
24689
25221
|
constructor() {
|
|
24690
25222
|
this.bids = new MemoryBidStore();
|
|
24691
25223
|
this.activity = new MemoryActivityStore();
|
|
24692
25224
|
this.state = new MemoryStateStore();
|
|
24693
25225
|
this.limitOrders = new MemoryLimitOrderStore();
|
|
25226
|
+
this.inventory = new MemoryInventoryStore();
|
|
24694
25227
|
}
|
|
24695
25228
|
};
|
|
24696
25229
|
|
|
@@ -25295,6 +25828,106 @@ var OrderScanner = class _OrderScanner {
|
|
|
25295
25828
|
}
|
|
25296
25829
|
};
|
|
25297
25830
|
|
|
25831
|
+
// src/data/indexer-inventory.ts
|
|
25832
|
+
import { formatUnits as formatUnits7 } from "viem";
|
|
25833
|
+
var READ_TIMEOUT_MS = 1e4;
|
|
25834
|
+
var SETTLED_AFTER_MS = 10 * 6e4;
|
|
25835
|
+
var BATCH = 40;
|
|
25836
|
+
var MAX_KEPT = 2e3;
|
|
25837
|
+
var IndexerInventory = class {
|
|
25838
|
+
constructor(options) {
|
|
25839
|
+
this.options = options;
|
|
25840
|
+
this.kept = /* @__PURE__ */ new Map();
|
|
25841
|
+
this.described = /* @__PURE__ */ new Map();
|
|
25842
|
+
this.fetch = options.fetch ?? fetch;
|
|
25843
|
+
}
|
|
25844
|
+
/**
|
|
25845
|
+
* A reading for each of `instants` the indexer could answer for. Never rejects: an instant it
|
|
25846
|
+
* could not be asked about is simply missing from the answer.
|
|
25847
|
+
*/
|
|
25848
|
+
async at(instants, now) {
|
|
25849
|
+
const readings = [];
|
|
25850
|
+
const wanted = [];
|
|
25851
|
+
for (const instant of new Set(instants)) {
|
|
25852
|
+
const kept = this.kept.get(instant);
|
|
25853
|
+
if (kept) readings.push(kept);
|
|
25854
|
+
else wanted.push(instant);
|
|
25855
|
+
}
|
|
25856
|
+
try {
|
|
25857
|
+
for (let index = 0; index < wanted.length; index += BATCH) {
|
|
25858
|
+
for (const reading of await this.read(wanted.slice(index, index + BATCH))) {
|
|
25859
|
+
readings.push(reading);
|
|
25860
|
+
if (reading.at <= now - SETTLED_AFTER_MS) this.keep(reading);
|
|
25861
|
+
}
|
|
25862
|
+
}
|
|
25863
|
+
} catch (err) {
|
|
25864
|
+
this.options.logger.warn(
|
|
25865
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
25866
|
+
"Could not read inventory from the indexer; carrying records by fills and sends alone"
|
|
25867
|
+
);
|
|
25868
|
+
}
|
|
25869
|
+
return readings;
|
|
25870
|
+
}
|
|
25871
|
+
keep(reading) {
|
|
25872
|
+
if (this.kept.size >= MAX_KEPT) this.kept.clear();
|
|
25873
|
+
this.kept.set(reading.at, reading);
|
|
25874
|
+
}
|
|
25875
|
+
/** One request for several instants, each its own aliased query at that moment in history. */
|
|
25876
|
+
async read(instants) {
|
|
25877
|
+
const queries = instants.map(
|
|
25878
|
+
(instant, index) => `r${index}: solverInventories(filter: { solver: { equalTo: $solver } }, blockHeight: "${Math.floor(instant)}", first: 1000) { nodes { chain tokenAddress balance } }`
|
|
25879
|
+
);
|
|
25880
|
+
const response = await this.fetch(this.options.indexerUrl, {
|
|
25881
|
+
method: "POST",
|
|
25882
|
+
headers: { "content-type": "application/json" },
|
|
25883
|
+
body: JSON.stringify({
|
|
25884
|
+
query: `query SolverInventory($solver: String!) {
|
|
25885
|
+
${queries.join("\n")}
|
|
25886
|
+
}`,
|
|
25887
|
+
variables: { solver: this.options.solver.toLowerCase() }
|
|
25888
|
+
}),
|
|
25889
|
+
signal: AbortSignal.timeout(READ_TIMEOUT_MS)
|
|
25890
|
+
});
|
|
25891
|
+
if (!response.ok) throw new Error(`Indexer responded ${response.status}`);
|
|
25892
|
+
const body = await response.json();
|
|
25893
|
+
if (body.errors?.length) throw new Error(body.errors.map((error) => error.message).join("; "));
|
|
25894
|
+
const readings = [];
|
|
25895
|
+
for (const [index, instant] of instants.entries()) {
|
|
25896
|
+
const rows = body.data?.[`r${index}`]?.nodes;
|
|
25897
|
+
if (rows) readings.push(await this.readingOf(instant, rows));
|
|
25898
|
+
}
|
|
25899
|
+
return readings;
|
|
25900
|
+
}
|
|
25901
|
+
async readingOf(at, rows) {
|
|
25902
|
+
const balances = {};
|
|
25903
|
+
const chains = {};
|
|
25904
|
+
for (const row of rows) {
|
|
25905
|
+
const token = await this.describe(row.chain, row.tokenAddress);
|
|
25906
|
+
if (!token) continue;
|
|
25907
|
+
let amount;
|
|
25908
|
+
try {
|
|
25909
|
+
amount = Number(formatUnits7(BigInt(row.balance), token.decimals));
|
|
25910
|
+
} catch {
|
|
25911
|
+
continue;
|
|
25912
|
+
}
|
|
25913
|
+
balances[token.symbol] = (balances[token.symbol] ?? 0) + amount;
|
|
25914
|
+
chains[token.symbol] = [...chains[token.symbol] ?? [], row.chain];
|
|
25915
|
+
}
|
|
25916
|
+
return { at, balances, chains };
|
|
25917
|
+
}
|
|
25918
|
+
/** A token's symbol and decimals, remembered once both are known. */
|
|
25919
|
+
async describe(chain, token) {
|
|
25920
|
+
const key = `${chain}:${token.toLowerCase()}`;
|
|
25921
|
+
const known = this.described.get(key);
|
|
25922
|
+
if (known) return known;
|
|
25923
|
+
const { symbol, decimals } = await this.options.describeToken(chain, token);
|
|
25924
|
+
if (!symbol || decimals === null) return null;
|
|
25925
|
+
const description = { symbol, decimals };
|
|
25926
|
+
this.described.set(key, description);
|
|
25927
|
+
return description;
|
|
25928
|
+
}
|
|
25929
|
+
};
|
|
25930
|
+
|
|
25298
25931
|
// src/simplex.ts
|
|
25299
25932
|
var EVENT_MAP = {
|
|
25300
25933
|
newOrder: "order:detected",
|
|
@@ -25340,10 +25973,66 @@ var LimitOrderController = class {
|
|
|
25340
25973
|
]);
|
|
25341
25974
|
return { order, fills, bids };
|
|
25342
25975
|
}
|
|
25976
|
+
/**
|
|
25977
|
+
* What the operator's buys and sells earned over `period`, bucketed on a clock
|
|
25978
|
+
* `tzOffsetMinutes` behind UTC.
|
|
25979
|
+
*
|
|
25980
|
+
* Profit is the change the period's fills made to what the solver holds, and
|
|
25981
|
+
* it is compared with the inventory the period began with. That inventory
|
|
25982
|
+
* comes from the stored snapshots and, when the balances have been read in
|
|
25983
|
+
* full, from what is held now, each carried to the period's start by what
|
|
25984
|
+
* moved between. The indexer says what moved, deposits and withdrawals
|
|
25985
|
+
* included, wherever it has followed the solver; elsewhere the fills and
|
|
25986
|
+
* sends on record stand in. It answers whether or not an orderbook is
|
|
25987
|
+
* configured, and whether or not the indexer does.
|
|
25988
|
+
*/
|
|
25989
|
+
async profitability(period, tzOffsetMinutes = 0) {
|
|
25990
|
+
const now = Date.now();
|
|
25991
|
+
const { data, balanceProvider } = this.runtime;
|
|
25992
|
+
const [fills, snapshots, ledger] = await Promise.all([
|
|
25993
|
+
data.limitOrders.fillHistory(),
|
|
25994
|
+
data.inventory.since(/* @__PURE__ */ new Date(0)),
|
|
25995
|
+
data.activity.walletTxs(500)
|
|
25996
|
+
]);
|
|
25997
|
+
const inventory = snapshots.map((snapshot) => ({
|
|
25998
|
+
at: parseUtc(snapshot.takenAt),
|
|
25999
|
+
balances: snapshot.balances
|
|
26000
|
+
}));
|
|
26001
|
+
const held = inventoryOf(balanceProvider.getSnapshot());
|
|
26002
|
+
if (held) inventory.push({ at: now, balances: held, live: true });
|
|
26003
|
+
const outflows = [];
|
|
26004
|
+
for (const tx of ledger) {
|
|
26005
|
+
const amount = Number(tx.amount);
|
|
26006
|
+
if (tx.kind === "send" && tx.token && Number.isFinite(amount) && amount > 0) {
|
|
26007
|
+
outflows.push({ at: tx.ts, symbol: tx.token, amount });
|
|
26008
|
+
}
|
|
26009
|
+
}
|
|
26010
|
+
const instants = inventoryInstants(fills, { period, now, tzOffsetMinutes, inventory });
|
|
26011
|
+
const readings = await this.indexerInventory().at(instants, now);
|
|
26012
|
+
return summarizeProfit(fills, { period, now, tzOffsetMinutes, inventory, outflows, readings });
|
|
26013
|
+
}
|
|
26014
|
+
/** One reader for the life of the filler, so the readings it has already fetched are kept. */
|
|
26015
|
+
indexerInventory() {
|
|
26016
|
+
this.indexed ?? (this.indexed = new IndexerInventory({
|
|
26017
|
+
indexerUrl: this.runtime.indexerUrl,
|
|
26018
|
+
solver: this.runtime.fillerAddress,
|
|
26019
|
+
describeToken: this.runtime.describeToken,
|
|
26020
|
+
logger: this.runtime.loggers.get("inventory")
|
|
26021
|
+
}));
|
|
26022
|
+
return this.indexed;
|
|
26023
|
+
}
|
|
25343
26024
|
/** The pairs the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
|
|
25344
26025
|
books() {
|
|
25345
26026
|
return this.service.books();
|
|
25346
26027
|
}
|
|
26028
|
+
/** Public bids and asks across solvers, optionally restricted to a route. */
|
|
26029
|
+
orderbookSnapshot(book, filters = {}) {
|
|
26030
|
+
return this.service.orderbookSnapshot(book, filters);
|
|
26031
|
+
}
|
|
26032
|
+
/** Every solver's order behind one public price level. */
|
|
26033
|
+
orderbookLevelOrders(book, level) {
|
|
26034
|
+
return this.service.orderbookLevelOrders(book, level);
|
|
26035
|
+
}
|
|
25347
26036
|
/** Creates the order, posts it, and reports what the orderbook made of it. */
|
|
25348
26037
|
async create(request) {
|
|
25349
26038
|
const posted = await this.service.create(request);
|