@hyperbridge/simplex 0.16.11 → 0.16.12
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 +168 -34
- package/dist/bin/simplex.js.map +1 -1
- package/dist/index.cjs +120 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +120 -16
- package/dist/index.js.map +1 -1
- package/dist/ui/assets/{index-CD2NZXpG.js → index-QU1Ik7IX.js} +20 -20
- package/dist/ui/assets/{index-B_p2oQkc.css → index-XKHMOpY2.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -1165,6 +1165,7 @@ var import_viem2 = require("viem");
|
|
|
1165
1165
|
var import_sdk2 = require("@hyperbridge/sdk");
|
|
1166
1166
|
var DEFAULT_BLOCK_SCAN_INTERVAL_SECONDS = 3;
|
|
1167
1167
|
var MIN_BLOCK_SCAN_INTERVAL_SECONDS = 0.1;
|
|
1168
|
+
var DEFAULT_MIN_ORDER_SIZE_USD = 20;
|
|
1168
1169
|
function validateRpcUrls(rpcUrls) {
|
|
1169
1170
|
if (rpcUrls.length === 0) {
|
|
1170
1171
|
throw new Error("rpcUrls must contain at least one URL");
|
|
@@ -1587,6 +1588,13 @@ var FillerConfigService = class {
|
|
|
1587
1588
|
getBidValiditySeconds() {
|
|
1588
1589
|
return this.fillerConfig?.bidValiditySeconds ?? 300;
|
|
1589
1590
|
}
|
|
1591
|
+
/**
|
|
1592
|
+
* The order size, in USD, below which `order.fees` must cover the execution cost.
|
|
1593
|
+
* Defaults to 20. An order at or above it is filled whatever fees it carries.
|
|
1594
|
+
*/
|
|
1595
|
+
getMinOrderSizeUsd() {
|
|
1596
|
+
return this.fillerConfig?.minOrderSizeUsd ?? DEFAULT_MIN_ORDER_SIZE_USD;
|
|
1597
|
+
}
|
|
1590
1598
|
/** Ceiling bps above user-requested output. Default 500 (5%). */
|
|
1591
1599
|
getMaxOverfillBps() {
|
|
1592
1600
|
return BigInt(this.fillerConfig?.overfillProtection?.maxOverfillBps ?? 500);
|
|
@@ -1690,6 +1698,12 @@ function validateConfig(config, cliWatchOnly = false) {
|
|
|
1690
1698
|
);
|
|
1691
1699
|
}
|
|
1692
1700
|
}
|
|
1701
|
+
const minOrderSize = config.simplex.minOrderSizeUsd;
|
|
1702
|
+
if (minOrderSize !== void 0) {
|
|
1703
|
+
if (!Number.isFinite(minOrderSize) || minOrderSize < 0) {
|
|
1704
|
+
throw new Error(`simplex.minOrderSizeUsd must be a number >= 0 (USD); got ${minOrderSize}`);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1693
1707
|
if (config.allowlist) {
|
|
1694
1708
|
for (const user of config.allowlist.users ?? []) {
|
|
1695
1709
|
if (!(0, import_viem2.isAddress)(user)) {
|
|
@@ -12861,6 +12875,7 @@ var CacheService = class {
|
|
|
12861
12875
|
matchedLimitOrders: {},
|
|
12862
12876
|
bidPlans: {},
|
|
12863
12877
|
partialFills: {},
|
|
12878
|
+
feeChecksWaived: {},
|
|
12864
12879
|
feeTokens: {},
|
|
12865
12880
|
tokenDecimals: {},
|
|
12866
12881
|
solverSelection: {}
|
|
@@ -12900,6 +12915,10 @@ var CacheService = class {
|
|
|
12900
12915
|
stalePartialIds.forEach((orderId) => {
|
|
12901
12916
|
delete this.cacheData.partialFills[orderId];
|
|
12902
12917
|
});
|
|
12918
|
+
const staleWaivedIds = Object.entries(this.cacheData.feeChecksWaived).filter(([_, data]) => !this.isCacheValid(data.timestamp)).map(([orderId]) => orderId);
|
|
12919
|
+
staleWaivedIds.forEach((orderId) => {
|
|
12920
|
+
delete this.cacheData.feeChecksWaived[orderId];
|
|
12921
|
+
});
|
|
12903
12922
|
}
|
|
12904
12923
|
getGasEstimate(orderId) {
|
|
12905
12924
|
try {
|
|
@@ -13212,6 +13231,24 @@ var CacheService = class {
|
|
|
13212
13231
|
clearPartialFill(orderId) {
|
|
13213
13232
|
delete this.cacheData.partialFills[orderId];
|
|
13214
13233
|
}
|
|
13234
|
+
/**
|
|
13235
|
+
* Whether the strategy's completed evaluation let a bid through without holding
|
|
13236
|
+
* `order.fees` to the execution cost, because the order is at or above the
|
|
13237
|
+
* operator's minimum size.
|
|
13238
|
+
*
|
|
13239
|
+
* Written and cleared the way {@link isPartialFill} is, for the same reason: the
|
|
13240
|
+
* caller exempts such an order from its profit floor.
|
|
13241
|
+
*/
|
|
13242
|
+
isFeeCheckWaived(orderId) {
|
|
13243
|
+
const cache = this.cacheData.feeChecksWaived[orderId];
|
|
13244
|
+
return cache !== void 0 && this.isCacheValid(cache.timestamp) && cache.waived;
|
|
13245
|
+
}
|
|
13246
|
+
setFeeCheckWaived(orderId, waived) {
|
|
13247
|
+
this.cacheData.feeChecksWaived[orderId] = { waived, timestamp: Date.now() };
|
|
13248
|
+
}
|
|
13249
|
+
clearFeeCheckWaived(orderId) {
|
|
13250
|
+
delete this.cacheData.feeChecksWaived[orderId];
|
|
13251
|
+
}
|
|
13215
13252
|
getFeeTokenWithDecimals(chain) {
|
|
13216
13253
|
try {
|
|
13217
13254
|
const cache = this.cacheData.feeTokens[chain];
|
|
@@ -18501,7 +18538,10 @@ var ContractInteractionService = class {
|
|
|
18501
18538
|
order,
|
|
18502
18539
|
prependCalls: void 0,
|
|
18503
18540
|
maxPriorityFeePerGasBumpPercent: gasFeeBumpConfig?.maxPriorityFeePerGasBumpPercent,
|
|
18504
|
-
maxFeePerGasBumpPercent: gasFeeBumpConfig?.maxFeePerGasBumpPercent
|
|
18541
|
+
maxFeePerGasBumpPercent: gasFeeBumpConfig?.maxFeePerGasBumpPercent,
|
|
18542
|
+
// No bid without the bundler's own figures: an op signed with the SDK's
|
|
18543
|
+
// fixed fallback limits does not execute, so a failed estimate skips the order.
|
|
18544
|
+
requireBundlerEstimate: true
|
|
18505
18545
|
});
|
|
18506
18546
|
this.logger.info({ orderId: order.id }, "Caching gas estimate");
|
|
18507
18547
|
this.logger.info({ estimate }, "Estimate");
|
|
@@ -18524,7 +18564,7 @@ var ContractInteractionService = class {
|
|
|
18524
18564
|
callGasLimit: estimate.callGasLimit
|
|
18525
18565
|
};
|
|
18526
18566
|
} catch (error) {
|
|
18527
|
-
this.logger.error({ err: error }, "Error estimating gas,
|
|
18567
|
+
this.logger.error({ err: error, orderId: order.id }, "Error estimating gas, not bidding on this order");
|
|
18528
18568
|
throw new Error(`Failed to estimate gas: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
18529
18569
|
}
|
|
18530
18570
|
}
|
|
@@ -21679,6 +21719,23 @@ var IntentFiller = class {
|
|
|
21679
21719
|
this.monitor.emit("orderSkipped", { orderId: order.id, reason: "watch-only" });
|
|
21680
21720
|
return null;
|
|
21681
21721
|
}
|
|
21722
|
+
const destinationHead = await this.destinationHead(order);
|
|
21723
|
+
if (destinationHead !== null && order.deadline <= destinationHead) {
|
|
21724
|
+
this.logger.info(
|
|
21725
|
+
{
|
|
21726
|
+
orderId: order.id,
|
|
21727
|
+
destChain: order.destination,
|
|
21728
|
+
deadline: order.deadline.toString(),
|
|
21729
|
+
head: destinationHead.toString()
|
|
21730
|
+
},
|
|
21731
|
+
"Skipping order: its deadline has passed on the destination chain"
|
|
21732
|
+
);
|
|
21733
|
+
this.monitor.emit("orderSkipped", {
|
|
21734
|
+
orderId: order.id,
|
|
21735
|
+
reason: "Order deadline has passed on the destination chain"
|
|
21736
|
+
});
|
|
21737
|
+
return null;
|
|
21738
|
+
}
|
|
21682
21739
|
const evalStartMs = Date.now();
|
|
21683
21740
|
const eligibleStrategies = await Promise.all(
|
|
21684
21741
|
this.strategies.map(async (strategy) => {
|
|
@@ -21688,7 +21745,10 @@ var IntentFiller = class {
|
|
|
21688
21745
|
})
|
|
21689
21746
|
);
|
|
21690
21747
|
const fillsPartially = this.fillsPartially(order);
|
|
21691
|
-
const
|
|
21748
|
+
const feeCheckWaived = this.feeCheckWaived(order);
|
|
21749
|
+
const validStrategies = eligibleStrategies.filter(
|
|
21750
|
+
(s) => s !== null && (s.profitability > 0 || fillsPartially || feeCheckWaived)
|
|
21751
|
+
).sort((a, b) => b.profitability - a.profitability);
|
|
21692
21752
|
const evalDurationSec = (Date.now() - evalStartMs) / 1e3;
|
|
21693
21753
|
this.monitor.emit("orderTiming", { orderId: order.id, phase: "evaluation", durationSec: evalDurationSec });
|
|
21694
21754
|
if (validStrategies.length === 0) {
|
|
@@ -21713,6 +21773,23 @@ var IntentFiller = class {
|
|
|
21713
21773
|
);
|
|
21714
21774
|
return validStrategies[0];
|
|
21715
21775
|
}
|
|
21776
|
+
/**
|
|
21777
|
+
* The destination chain's latest block number, the clock `order.deadline` is read on.
|
|
21778
|
+
*
|
|
21779
|
+
* Null when the read fails. That is not an answer about the order, so evaluation goes
|
|
21780
|
+
* on: the gas estimate simulates the same deadline check and refuses an expired order.
|
|
21781
|
+
*/
|
|
21782
|
+
async destinationHead(order) {
|
|
21783
|
+
try {
|
|
21784
|
+
return await this.chainClientManager.getPublicClient(order.destination).getBlockNumber();
|
|
21785
|
+
} catch (err) {
|
|
21786
|
+
this.logger.warn(
|
|
21787
|
+
{ orderId: order.id, destChain: order.destination, err },
|
|
21788
|
+
"Could not read the destination head to check the order's deadline"
|
|
21789
|
+
);
|
|
21790
|
+
return null;
|
|
21791
|
+
}
|
|
21792
|
+
}
|
|
21716
21793
|
/**
|
|
21717
21794
|
* Whether the strategy's evaluation concluded in a deliberate partial fill.
|
|
21718
21795
|
*
|
|
@@ -21726,6 +21803,15 @@ var IntentFiller = class {
|
|
|
21726
21803
|
if (!order.id) return false;
|
|
21727
21804
|
return this.contractService.cacheService.isPartialFill(order.id);
|
|
21728
21805
|
}
|
|
21806
|
+
/**
|
|
21807
|
+
* Whether the strategy's evaluation let a bid through without the fee check, because
|
|
21808
|
+
* the order is at or above the operator's minimum size. Read from a flag the strategy
|
|
21809
|
+
* sets only once it has an answer, as {@link fillsPartially} is.
|
|
21810
|
+
*/
|
|
21811
|
+
feeCheckWaived(order) {
|
|
21812
|
+
if (!order.id) return false;
|
|
21813
|
+
return this.contractService.cacheService.isFeeCheckWaived(order.id);
|
|
21814
|
+
}
|
|
21729
21815
|
executeOrder(order, bestStrategy, solverSelectionActive, inputUsdValue, profitUsd) {
|
|
21730
21816
|
const chainQueue = this.chainQueues.get((0, import_sdk14.getChainId)(order.destination));
|
|
21731
21817
|
if (!chainQueue) {
|
|
@@ -22297,11 +22383,13 @@ var FXFiller = class _FXFiller {
|
|
|
22297
22383
|
* - Treat a payout below the ask as a partial fill, which only orders
|
|
22298
22384
|
* without output calldata and without an earlier partial fill allow.
|
|
22299
22385
|
* - Cap each payout by the filler's balance plus funding-venue withdrawals,
|
|
22300
|
-
* then gate it on fees (full fills) and the
|
|
22386
|
+
* then gate it on fees (full fills of orders below `minOrderSizeUsd`) and the
|
|
22387
|
+
* same-asset spread.
|
|
22301
22388
|
* - Cache the resulting bids for later use in `executeOrder`.
|
|
22302
22389
|
*/
|
|
22303
22390
|
async calculateProfitability(order) {
|
|
22304
22391
|
if (order.id) this.contractService.cacheService.clearPartialFill(order.id);
|
|
22392
|
+
if (order.id) this.contractService.cacheService.clearFeeCheckWaived(order.id);
|
|
22305
22393
|
if (this.halted) {
|
|
22306
22394
|
this.logger.warn({ orderId: order.id }, "FXFiller halted \u2014 rejecting order");
|
|
22307
22395
|
return 0;
|
|
@@ -22317,6 +22405,15 @@ var FXFiller = class _FXFiller {
|
|
|
22317
22405
|
const plans = [];
|
|
22318
22406
|
const plannedOn = /* @__PURE__ */ new Map();
|
|
22319
22407
|
const spentOn = /* @__PURE__ */ new Map();
|
|
22408
|
+
let atOrAboveMinSize;
|
|
22409
|
+
const feeCheckWaived = async () => {
|
|
22410
|
+
if (atOrAboveMinSize === void 0) {
|
|
22411
|
+
const usd = await this.getOrderUsdValue(order);
|
|
22412
|
+
atOrAboveMinSize = usd !== null && usd.inputUsd.gte(this.configService.getMinOrderSizeUsd());
|
|
22413
|
+
}
|
|
22414
|
+
return atOrAboveMinSize;
|
|
22415
|
+
};
|
|
22416
|
+
let feesWaived = false;
|
|
22320
22417
|
for (let leg = 0; leg < order.inputs.length; leg++) {
|
|
22321
22418
|
const input = order.inputs[leg];
|
|
22322
22419
|
const output = order.output.assets[leg];
|
|
@@ -22560,17 +22657,21 @@ var FXFiller = class _FXFiller {
|
|
|
22560
22657
|
const executionCost = totalCostInSourceFeeToken + relayerFeeInSourceFeeToken;
|
|
22561
22658
|
const partialEdgeUsd = sameAssetEdgeUsd.plus(payoutSurplusUsd);
|
|
22562
22659
|
if (!partialFill && order.fees < executionCost) {
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
|
|
22660
|
+
if (!await feeCheckWaived()) {
|
|
22661
|
+
this.logger.info(
|
|
22662
|
+
{
|
|
22663
|
+
orderId: order.id,
|
|
22664
|
+
orderFees: (0, import_viem29.formatUnits)(order.fees, feeTokenDecimals),
|
|
22665
|
+
fillGas: (0, import_viem29.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
|
|
22666
|
+
relayerFee: (0, import_viem29.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
|
|
22667
|
+
executionCost: (0, import_viem29.formatUnits)(executionCost, feeTokenDecimals),
|
|
22668
|
+
minOrderSizeUsd: this.configService.getMinOrderSizeUsd()
|
|
22669
|
+
},
|
|
22670
|
+
"Skipping order: attached fees do not cover execution cost (fill gas + relayer fee)"
|
|
22671
|
+
);
|
|
22672
|
+
continue;
|
|
22673
|
+
}
|
|
22674
|
+
feesWaived = true;
|
|
22574
22675
|
}
|
|
22575
22676
|
if (sameAsset && !sameAssetProfitable) {
|
|
22576
22677
|
this.logger.info(
|
|
@@ -22604,7 +22705,8 @@ var FXFiller = class _FXFiller {
|
|
|
22604
22705
|
realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals),
|
|
22605
22706
|
payoutSurplusUsd: payoutSurplusUsd.toString(),
|
|
22606
22707
|
totalProfit,
|
|
22607
|
-
profitable: totalProfit > 0
|
|
22708
|
+
profitable: totalProfit > 0,
|
|
22709
|
+
feeCheckWaived: !partialFill && order.fees < executionCost
|
|
22608
22710
|
},
|
|
22609
22711
|
"FX swap profitability evaluation"
|
|
22610
22712
|
);
|
|
@@ -22636,6 +22738,7 @@ var FXFiller = class _FXFiller {
|
|
|
22636
22738
|
plans[0].budget
|
|
22637
22739
|
);
|
|
22638
22740
|
this.contractService.cacheService.setPartialFill(order.id, plans[0].partialFill);
|
|
22741
|
+
this.contractService.cacheService.setFeeCheckWaived(order.id, feesWaived);
|
|
22639
22742
|
this.contractService.cacheService.setMatchedLimitOrder(order.id, [
|
|
22640
22743
|
{ limitOrderId: plans[0].limitOrderId, payout: plans[0].payout }
|
|
22641
22744
|
]);
|
|
@@ -23877,6 +23980,7 @@ async function bootFiller(config, options) {
|
|
|
23877
23980
|
rebalancing: config.rebalancing,
|
|
23878
23981
|
targetGasUnits: config.simplex.targetGasUnits,
|
|
23879
23982
|
bidValiditySeconds: config.simplex.bidValiditySeconds,
|
|
23983
|
+
minOrderSizeUsd: config.simplex.minOrderSizeUsd,
|
|
23880
23984
|
blockScanIntervalSeconds: config.simplex.blockScanIntervalSeconds,
|
|
23881
23985
|
gasFeeBump: config.simplex.gasFeeBump,
|
|
23882
23986
|
overfillProtection: config.simplex.overfillProtection,
|