@hyperbridge/simplex 0.16.13 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/simplex.js +829 -536
- package/dist/bin/simplex.js.map +1 -1
- package/dist/index.cjs +334 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -17
- package/dist/index.d.ts +61 -17
- package/dist/index.js +340 -123
- package/dist/index.js.map +1 -1
- package/dist/sqlite.cjs +8 -1
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/sqlite.js +8 -1
- package/dist/sqlite.js.map +1 -1
- package/dist/{types-BISgqJhM.d.cts → types-BPyUX8qu.d.cts} +7 -0
- package/dist/{types-BISgqJhM.d.ts → types-BPyUX8qu.d.ts} +7 -0
- package/dist/ui/assets/{index-DShjZe_c.js → index-BctI82KY.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -563,6 +563,20 @@ var ConfirmationPolicy = class {
|
|
|
563
563
|
// src/core/filler.ts
|
|
564
564
|
import pQueue from "p-queue";
|
|
565
565
|
|
|
566
|
+
// src/config/bundlers.ts
|
|
567
|
+
var HYPERBRIDGE_BUNDLER_URLS = {
|
|
568
|
+
1: "https://bundler.polytope.technology/ethereum",
|
|
569
|
+
56: "https://bundler.polytope.technology/bsc",
|
|
570
|
+
97: "https://bundler.polytope.technology/bsc-chapel",
|
|
571
|
+
137: "https://bundler.polytope.technology/polygon",
|
|
572
|
+
8453: "https://bundler.polytope.technology/base",
|
|
573
|
+
42161: "https://bundler.polytope.technology/arbitrum",
|
|
574
|
+
80002: "https://bundler.polytope.technology/polygon-amoy"
|
|
575
|
+
};
|
|
576
|
+
function hyperbridgeBundlerUrl(chainId) {
|
|
577
|
+
return HYPERBRIDGE_BUNDLER_URLS[chainId];
|
|
578
|
+
}
|
|
579
|
+
|
|
566
580
|
// src/config/asset-registry.ts
|
|
567
581
|
import { isAddress } from "viem";
|
|
568
582
|
var BUILTIN_ASSETS = {
|
|
@@ -574,7 +588,8 @@ var BUILTIN_ASSETS = {
|
|
|
574
588
|
ZARP: { resolve: (r, chain) => r.getAssetBySymbol(chain, "ZARP") },
|
|
575
589
|
EURC: { resolve: (r, chain) => r.getAssetBySymbol(chain, "EURC") },
|
|
576
590
|
XSGD: { resolve: (r, chain) => r.getAssetBySymbol(chain, "XSGD") },
|
|
577
|
-
TRYB: { resolve: (r, chain) => r.getAssetBySymbol(chain, "TRYB") }
|
|
591
|
+
TRYB: { resolve: (r, chain) => r.getAssetBySymbol(chain, "TRYB") },
|
|
592
|
+
BRIDGE: { resolve: (r, chain) => r.getAssetBySymbol(chain, "BRIDGE") }
|
|
578
593
|
};
|
|
579
594
|
var USD_STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT", "DAI"]);
|
|
580
595
|
function normalizeSymbol(symbol) {
|
|
@@ -712,7 +727,7 @@ function assertPairSymbolsResolve(pairs, registry, chainNames) {
|
|
|
712
727
|
const pairSymbols = new Set(pairs.flatMap((pair) => [pair.token0, pair.token1]));
|
|
713
728
|
for (const pair of pairs) {
|
|
714
729
|
for (const symbol of [pair.token0, pair.token1]) {
|
|
715
|
-
const resolvesSomewhere = chainNames.some((
|
|
730
|
+
const resolvesSomewhere = chainNames.some((chainName2) => registry.getAddress(symbol, chainName2) !== null);
|
|
716
731
|
if (!resolvesSomewhere) {
|
|
717
732
|
throw new Error(
|
|
718
733
|
`pairs.${pair.token0}/${pair.token1}: '${symbol}' does not resolve to a deployed contract on any configured chain`
|
|
@@ -720,15 +735,15 @@ function assertPairSymbolsResolve(pairs, registry, chainNames) {
|
|
|
720
735
|
}
|
|
721
736
|
}
|
|
722
737
|
}
|
|
723
|
-
for (const
|
|
738
|
+
for (const chainName2 of chainNames) {
|
|
724
739
|
const addressOwner = /* @__PURE__ */ new Map();
|
|
725
740
|
for (const symbol of pairSymbols) {
|
|
726
|
-
const address = registry.getAddress(symbol,
|
|
741
|
+
const address = registry.getAddress(symbol, chainName2)?.toLowerCase();
|
|
727
742
|
if (!address) continue;
|
|
728
743
|
const owner = addressOwner.get(address);
|
|
729
744
|
if (owner && normalizeSymbol(owner) !== normalizeSymbol(symbol)) {
|
|
730
745
|
throw new Error(
|
|
731
|
-
`assets: '${symbol}' and '${owner}' both resolve to ${address} on ${
|
|
746
|
+
`assets: '${symbol}' and '${owner}' both resolve to ${address} on ${chainName2} \u2014 symbols must map to distinct contracts`
|
|
732
747
|
);
|
|
733
748
|
}
|
|
734
749
|
addressOwner.set(address, symbol);
|
|
@@ -1182,7 +1197,7 @@ async function resolveChainConfigs(chains, options = {}) {
|
|
|
1182
1197
|
"RPC endpoint could not report its chainId at startup; keeping it for the quorum to judge per call"
|
|
1183
1198
|
);
|
|
1184
1199
|
}
|
|
1185
|
-
return { chainId: first.chainId, rpcUrls, bundlerUrl:
|
|
1200
|
+
return { chainId: first.chainId, rpcUrls, bundlerUrl: hyperbridgeBundlerUrl(first.chainId) };
|
|
1186
1201
|
})
|
|
1187
1202
|
);
|
|
1188
1203
|
}
|
|
@@ -1251,12 +1266,6 @@ var FillerConfigService = class {
|
|
|
1251
1266
|
}
|
|
1252
1267
|
this.rpcOverrides.set(chainId, validateRpcUrls(rpcUrls));
|
|
1253
1268
|
}
|
|
1254
|
-
setBundlerUrl(chainId, bundlerUrl) {
|
|
1255
|
-
if (!this.rpcOverrides.has(chainId)) {
|
|
1256
|
-
throw new Error(`Chain ${chainId} is not configured`);
|
|
1257
|
-
}
|
|
1258
|
-
this.bundlerUrls.set(chainId, bundlerUrl);
|
|
1259
|
-
}
|
|
1260
1269
|
/** Replaces the rebalancing config at runtime; trigger checks read it live. */
|
|
1261
1270
|
setRebalancing(rebalancing) {
|
|
1262
1271
|
if (this.fillerConfig) this.fillerConfig.rebalancing = rebalancing;
|
|
@@ -1433,7 +1442,7 @@ var FillerConfigService = class {
|
|
|
1433
1442
|
return this.fillerConfig?.substratePrivateKey;
|
|
1434
1443
|
}
|
|
1435
1444
|
getEntryPointAddress(chain) {
|
|
1436
|
-
return this.chainConfigService.
|
|
1445
|
+
return this.chainConfigService.getEntryPointAddress(chain);
|
|
1437
1446
|
}
|
|
1438
1447
|
getSolverAccountContractAddress(chain) {
|
|
1439
1448
|
return this.chainConfigService.getSolverAccountAddress(chain);
|
|
@@ -1618,9 +1627,6 @@ function validateConfig(config, cliWatchOnly = false) {
|
|
|
1618
1627
|
if (!Array.isArray(chain.rpcUrls) || chain.rpcUrls.length === 0 || chain.rpcUrls.some((u) => !u)) {
|
|
1619
1628
|
throw new Error("Each chain configuration must have rpcUrls (a non-empty array of strings)");
|
|
1620
1629
|
}
|
|
1621
|
-
if (!chain.bundlerUrl) {
|
|
1622
|
-
throw new Error("Each chain configuration must have bundlerUrl");
|
|
1623
|
-
}
|
|
1624
1630
|
}
|
|
1625
1631
|
const concurrent = config.simplex.maxConcurrentOrders;
|
|
1626
1632
|
if (concurrent !== void 0) {
|
|
@@ -2510,7 +2516,8 @@ var INIT_CHAINS = [
|
|
|
2510
2516
|
network: "mainnet",
|
|
2511
2517
|
alchemySubdomain: "eth-mainnet",
|
|
2512
2518
|
explorerUrl: external_links_default.chainExplorers.ethereum,
|
|
2513
|
-
defaultRpcUrls: FREE_RPC_URLS[1]
|
|
2519
|
+
defaultRpcUrls: FREE_RPC_URLS[1],
|
|
2520
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[1]
|
|
2514
2521
|
},
|
|
2515
2522
|
{
|
|
2516
2523
|
chainId: 42161,
|
|
@@ -2519,7 +2526,8 @@ var INIT_CHAINS = [
|
|
|
2519
2526
|
network: "mainnet",
|
|
2520
2527
|
alchemySubdomain: "arb-mainnet",
|
|
2521
2528
|
explorerUrl: external_links_default.chainExplorers.arbitrum,
|
|
2522
|
-
defaultRpcUrls: FREE_RPC_URLS[42161]
|
|
2529
|
+
defaultRpcUrls: FREE_RPC_URLS[42161],
|
|
2530
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[42161]
|
|
2523
2531
|
},
|
|
2524
2532
|
{
|
|
2525
2533
|
chainId: 8453,
|
|
@@ -2528,7 +2536,8 @@ var INIT_CHAINS = [
|
|
|
2528
2536
|
network: "mainnet",
|
|
2529
2537
|
alchemySubdomain: "base-mainnet",
|
|
2530
2538
|
explorerUrl: external_links_default.chainExplorers.base,
|
|
2531
|
-
defaultRpcUrls: FREE_RPC_URLS[8453]
|
|
2539
|
+
defaultRpcUrls: FREE_RPC_URLS[8453],
|
|
2540
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[8453]
|
|
2532
2541
|
},
|
|
2533
2542
|
{
|
|
2534
2543
|
chainId: 137,
|
|
@@ -2537,7 +2546,8 @@ var INIT_CHAINS = [
|
|
|
2537
2546
|
network: "mainnet",
|
|
2538
2547
|
alchemySubdomain: "polygon-mainnet",
|
|
2539
2548
|
explorerUrl: external_links_default.chainExplorers.polygon,
|
|
2540
|
-
defaultRpcUrls: FREE_RPC_URLS[137]
|
|
2549
|
+
defaultRpcUrls: FREE_RPC_URLS[137],
|
|
2550
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[137]
|
|
2541
2551
|
},
|
|
2542
2552
|
{
|
|
2543
2553
|
chainId: 56,
|
|
@@ -2546,7 +2556,8 @@ var INIT_CHAINS = [
|
|
|
2546
2556
|
network: "mainnet",
|
|
2547
2557
|
alchemySubdomain: "bnb-mainnet",
|
|
2548
2558
|
explorerUrl: external_links_default.chainExplorers.bnb,
|
|
2549
|
-
defaultRpcUrls: FREE_RPC_URLS[56]
|
|
2559
|
+
defaultRpcUrls: FREE_RPC_URLS[56],
|
|
2560
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[56]
|
|
2550
2561
|
},
|
|
2551
2562
|
{
|
|
2552
2563
|
chainId: 11155111,
|
|
@@ -2578,7 +2589,8 @@ var INIT_CHAINS = [
|
|
|
2578
2589
|
label: "Polygon Amoy",
|
|
2579
2590
|
network: "testnet",
|
|
2580
2591
|
alchemySubdomain: "polygon-amoy",
|
|
2581
|
-
explorerUrl: external_links_default.chainExplorers.polygonAmoy
|
|
2592
|
+
explorerUrl: external_links_default.chainExplorers.polygonAmoy,
|
|
2593
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[80002]
|
|
2582
2594
|
},
|
|
2583
2595
|
{
|
|
2584
2596
|
chainId: 97,
|
|
@@ -2586,7 +2598,8 @@ var INIT_CHAINS = [
|
|
|
2586
2598
|
label: "BSC Chapel",
|
|
2587
2599
|
network: "testnet",
|
|
2588
2600
|
alchemySubdomain: "bnb-testnet",
|
|
2589
|
-
explorerUrl: external_links_default.chainExplorers.bnbChapel
|
|
2601
|
+
explorerUrl: external_links_default.chainExplorers.bnbChapel,
|
|
2602
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[97]
|
|
2590
2603
|
}
|
|
2591
2604
|
];
|
|
2592
2605
|
function chainByChainId(chainId) {
|
|
@@ -2859,7 +2872,7 @@ function describe(err) {
|
|
|
2859
2872
|
|
|
2860
2873
|
// src/orderbook/limit-orders.ts
|
|
2861
2874
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
2862
|
-
import { getChainId, MAX_DECLARED_ENTRIES } from "@hyperbridge/sdk";
|
|
2875
|
+
import { ENTRY_POINT_V08, getChainId, MAX_DECLARED_ENTRIES } from "@hyperbridge/sdk";
|
|
2863
2876
|
|
|
2864
2877
|
// src/orderbook/amounts.ts
|
|
2865
2878
|
import { keccak256, stringToBytes } from "viem";
|
|
@@ -3060,6 +3073,8 @@ var LimitOrderService = class {
|
|
|
3060
3073
|
this.delegationService = delegationService;
|
|
3061
3074
|
this.startingNonce = startingNonce;
|
|
3062
3075
|
this.vaultBalances = vaultBalances;
|
|
3076
|
+
/** Re-signs in flight, by order id. A fill's resize waits one out rather than reposting alongside it. */
|
|
3077
|
+
this.resigning = /* @__PURE__ */ new Map();
|
|
3063
3078
|
this.logger = loggers.get("limit-orders");
|
|
3064
3079
|
}
|
|
3065
3080
|
/**
|
|
@@ -3153,6 +3168,7 @@ var LimitOrderService = class {
|
|
|
3153
3168
|
bookExpiresAt: null,
|
|
3154
3169
|
bookPrice: null,
|
|
3155
3170
|
orderNonce: order.orderNonce,
|
|
3171
|
+
entryPoint: null,
|
|
3156
3172
|
status: "cancelled",
|
|
3157
3173
|
lastError: null
|
|
3158
3174
|
}),
|
|
@@ -3419,24 +3435,32 @@ var LimitOrderService = class {
|
|
|
3419
3435
|
async resize(remaining, delivered) {
|
|
3420
3436
|
const id = remaining.id;
|
|
3421
3437
|
const floor = await this.dustFloor(remaining);
|
|
3422
|
-
|
|
3438
|
+
for (let resigning = this.resigning.get(id); resigning; resigning = this.resigning.get(id)) await resigning;
|
|
3439
|
+
const order = await this.store.setStatus(id, "resizing", null, POSTABLE);
|
|
3440
|
+
if (!order) return null;
|
|
3441
|
+
if (BigInt(order.remaining) < floor) {
|
|
3423
3442
|
this.logger.info(
|
|
3424
|
-
{ id, remaining:
|
|
3443
|
+
{ id, remaining: order.remaining, floor: floor.toString() },
|
|
3425
3444
|
"Limit order worked down past the dust floor; closing it"
|
|
3426
3445
|
);
|
|
3427
|
-
if (
|
|
3428
|
-
const closed = await this.store.setPosting(
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3446
|
+
if (order.commitment) await this.withdraw(order.commitment);
|
|
3447
|
+
const closed = await this.store.setPosting(
|
|
3448
|
+
id,
|
|
3449
|
+
{
|
|
3450
|
+
commitment: null,
|
|
3451
|
+
bookExpiresAt: null,
|
|
3452
|
+
bookPrice: null,
|
|
3453
|
+
orderNonce: order.orderNonce,
|
|
3454
|
+
entryPoint: null,
|
|
3455
|
+
status: "filled",
|
|
3456
|
+
lastError: null
|
|
3457
|
+
},
|
|
3458
|
+
POSTABLE
|
|
3459
|
+
);
|
|
3436
3460
|
if (closed) this.report({ kind: "filled", order: closed });
|
|
3437
3461
|
return closed;
|
|
3438
3462
|
}
|
|
3439
|
-
const resized = await this.repost(
|
|
3463
|
+
const resized = await this.repost(order);
|
|
3440
3464
|
if (resized) this.report({ kind: "resized", order: resized, delivered });
|
|
3441
3465
|
return resized;
|
|
3442
3466
|
}
|
|
@@ -3449,7 +3473,7 @@ var LimitOrderService = class {
|
|
|
3449
3473
|
*/
|
|
3450
3474
|
async repost(order) {
|
|
3451
3475
|
if (!order) return null;
|
|
3452
|
-
if (isLocal(order)) return this.store.setStatus(order.id, "open");
|
|
3476
|
+
if (isLocal(order)) return this.store.setStatus(order.id, "open", null, POSTABLE);
|
|
3453
3477
|
if (hasExpired(order.expiresAt, /* @__PURE__ */ new Date())) return this.retire(order);
|
|
3454
3478
|
if (order.commitment) {
|
|
3455
3479
|
const withdrawn = await this.withdraw(order.commitment);
|
|
@@ -3460,10 +3484,13 @@ var LimitOrderService = class {
|
|
|
3460
3484
|
{ id: order.id, commitment: order.commitment, err: message },
|
|
3461
3485
|
"Could not clear the old entry; leaving the posting alone rather than adding a second"
|
|
3462
3486
|
);
|
|
3463
|
-
return this.store.setStatus(order.id, "open", message);
|
|
3487
|
+
return this.store.setStatus(order.id, "open", message, POSTABLE);
|
|
3464
3488
|
}
|
|
3465
3489
|
}
|
|
3466
|
-
await this.store.setStatus(order.id, "resizing")
|
|
3490
|
+
if (!await this.store.setStatus(order.id, "resizing", null, POSTABLE)) {
|
|
3491
|
+
this.logger.info({ id: order.id }, "Limit order closed while it was being reposted; leaving it closed");
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3467
3494
|
const nonce = (BigInt(order.orderNonce) + 1n).toString();
|
|
3468
3495
|
return (await this.post({ ...order, commitment: null, orderNonce: nonce })).order;
|
|
3469
3496
|
}
|
|
@@ -3506,7 +3533,9 @@ var LimitOrderService = class {
|
|
|
3506
3533
|
* They drift apart when a request never got an answer or the process died
|
|
3507
3534
|
* between two of them: an entry the orderbook still lists that nothing here
|
|
3508
3535
|
* owns, an order here whose entry has gone, or an entry the orderbook has cut
|
|
3509
|
-
* down because the solver cannot cover what it quoted.
|
|
3536
|
+
* down because the solver cannot cover what it quoted. A posted op is also
|
|
3537
|
+
* signed over one EntryPoint, so when the fill chain moves to another, every
|
|
3538
|
+
* entry signed before the move is withdrawn and posted again.
|
|
3510
3539
|
*/
|
|
3511
3540
|
async reconcile(now = /* @__PURE__ */ new Date()) {
|
|
3512
3541
|
const [entries, live] = await Promise.all([this.postedOrders(), this.live()]);
|
|
@@ -3533,7 +3562,17 @@ var LimitOrderService = class {
|
|
|
3533
3562
|
}
|
|
3534
3563
|
for (const order of live) {
|
|
3535
3564
|
if (isLocal(order)) continue;
|
|
3536
|
-
if (order.commitment && found.has(order.commitment.toLowerCase()))
|
|
3565
|
+
if (order.commitment && found.has(order.commitment.toLowerCase())) {
|
|
3566
|
+
try {
|
|
3567
|
+
if (await this.resign(order)) report.reposted++;
|
|
3568
|
+
} catch (err) {
|
|
3569
|
+
this.logger.error(
|
|
3570
|
+
{ id: order.id, err },
|
|
3571
|
+
"Could not sign the limit order again for its chain's EntryPoint"
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
continue;
|
|
3575
|
+
}
|
|
3537
3576
|
if (sinceMs(order.updatedAt, now) < POST_GRACE_MS) continue;
|
|
3538
3577
|
this.logger.warn({ id: order.id }, "Limit order has no entry on the orderbook; posting it again");
|
|
3539
3578
|
try {
|
|
@@ -3545,6 +3584,45 @@ var LimitOrderService = class {
|
|
|
3545
3584
|
}
|
|
3546
3585
|
return report;
|
|
3547
3586
|
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Withdraws a posting signed for an EntryPoint its fill chain no longer uses,
|
|
3589
|
+
* and posts it again signed for the one it uses now. Answers whether a new
|
|
3590
|
+
* posting landed.
|
|
3591
|
+
*
|
|
3592
|
+
* The row is read again and claimed out of `open` first: a fill can have worked
|
|
3593
|
+
* it down or started its own repost since reconciliation listed it, and that
|
|
3594
|
+
* write is the one to follow. A fill that lands once the claim is taken waits
|
|
3595
|
+
* in `resize` for this to finish. The EntryPoint is only written with a posting
|
|
3596
|
+
* that landed, so a pass cut short anywhere leaves a row the next pass still
|
|
3597
|
+
* reads as stale, or as having no entry, and finishes the job.
|
|
3598
|
+
*/
|
|
3599
|
+
async resign(listed) {
|
|
3600
|
+
const entryPoint = this.configService.getEntryPointAddress(listed.fillChain);
|
|
3601
|
+
if (!entryPoint || sameAddress(signedFor(listed), entryPoint)) return false;
|
|
3602
|
+
const order = await this.store.get(listed.id);
|
|
3603
|
+
if (!order || order.status !== "open" || order.commitment !== listed.commitment) return false;
|
|
3604
|
+
if (sameAddress(signedFor(order), entryPoint) || this.resigning.has(order.id)) return false;
|
|
3605
|
+
const resigned = this.signAgain(order, entryPoint);
|
|
3606
|
+
this.resigning.set(
|
|
3607
|
+
order.id,
|
|
3608
|
+
resigned.catch(() => false)
|
|
3609
|
+
);
|
|
3610
|
+
try {
|
|
3611
|
+
return await resigned;
|
|
3612
|
+
} finally {
|
|
3613
|
+
this.resigning.delete(order.id);
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
async signAgain(order, entryPoint) {
|
|
3617
|
+
const claimed = await this.store.setStatus(order.id, "resizing", null, ["open"]);
|
|
3618
|
+
if (!claimed) return false;
|
|
3619
|
+
this.logger.warn(
|
|
3620
|
+
{ id: order.id, commitment: order.commitment, signedFor: signedFor(order), entryPoint },
|
|
3621
|
+
"Limit order was signed for an EntryPoint its chain no longer uses; signing it again"
|
|
3622
|
+
);
|
|
3623
|
+
const posted = await this.repost(claimed);
|
|
3624
|
+
return !!posted?.commitment && sameAddress(signedFor(posted), entryPoint);
|
|
3625
|
+
}
|
|
3548
3626
|
/**
|
|
3549
3627
|
* Lowers every open order's `remaining` to what the solver's account will still
|
|
3550
3628
|
* pay for it, and puts the order back on the book at that size.
|
|
@@ -3626,6 +3704,7 @@ var LimitOrderService = class {
|
|
|
3626
3704
|
bookExpiresAt: null,
|
|
3627
3705
|
bookPrice: null,
|
|
3628
3706
|
orderNonce: order.orderNonce,
|
|
3707
|
+
entryPoint: null,
|
|
3629
3708
|
status: "expired",
|
|
3630
3709
|
lastError: null
|
|
3631
3710
|
});
|
|
@@ -3682,11 +3761,11 @@ var LimitOrderService = class {
|
|
|
3682
3761
|
}
|
|
3683
3762
|
/** Builds and submits the posting, then writes the orderbook's answer onto the row. */
|
|
3684
3763
|
async post(order) {
|
|
3685
|
-
const { result, orderNonce } = await this.submit(order);
|
|
3764
|
+
const { result, orderNonce, entryPoint } = await this.submit(order);
|
|
3686
3765
|
if (result.kind === "accepted" || result.kind === "unchanged") {
|
|
3687
3766
|
const posted = result.order;
|
|
3688
3767
|
this.logger.info(
|
|
3689
|
-
{ id: order.id, commitment: posted.commitment, price: posted.price },
|
|
3768
|
+
{ id: order.id, commitment: posted.commitment, price: posted.price, entryPoint },
|
|
3690
3769
|
"Limit order posted to the orderbook"
|
|
3691
3770
|
);
|
|
3692
3771
|
const stored = await this.store.setPosting(
|
|
@@ -3696,6 +3775,10 @@ var LimitOrderService = class {
|
|
|
3696
3775
|
bookExpiresAt: posted.expiresAt,
|
|
3697
3776
|
bookPrice: posted.price,
|
|
3698
3777
|
orderNonce: orderNonce.toString(),
|
|
3778
|
+
// An entry the orderbook already held was signed by an earlier attempt,
|
|
3779
|
+
// for whichever EntryPoint was current then. Recorded as unknown, which
|
|
3780
|
+
// reconciliation reads as v0.8 and signs again once the chain is past it.
|
|
3781
|
+
entryPoint: result.kind === "accepted" ? entryPoint : null,
|
|
3699
3782
|
status: "open",
|
|
3700
3783
|
lastError: null
|
|
3701
3784
|
},
|
|
@@ -3725,6 +3808,7 @@ var LimitOrderService = class {
|
|
|
3725
3808
|
bookExpiresAt: null,
|
|
3726
3809
|
bookPrice: null,
|
|
3727
3810
|
orderNonce: orderNonce.toString(),
|
|
3811
|
+
entryPoint: null,
|
|
3728
3812
|
status: "open",
|
|
3729
3813
|
lastError: message
|
|
3730
3814
|
}),
|
|
@@ -3747,22 +3831,30 @@ var LimitOrderService = class {
|
|
|
3747
3831
|
* only the second one.
|
|
3748
3832
|
*/
|
|
3749
3833
|
async submit(order) {
|
|
3834
|
+
const entryPoint = this.entryPointFor(order.fillChain);
|
|
3750
3835
|
const orderNonce = BigInt(order.orderNonce);
|
|
3751
|
-
const attempt = await this.buildAndSubmit(order, orderNonce);
|
|
3836
|
+
const attempt = await this.buildAndSubmit(order, orderNonce, entryPoint);
|
|
3752
3837
|
const first = attempt.result;
|
|
3753
3838
|
if (first.kind !== "rejected" || first.code !== "REPLAYED" && first.code !== "ORDER_EXISTS") {
|
|
3754
|
-
return { result: first, orderNonce };
|
|
3839
|
+
return { result: first, orderNonce, entryPoint };
|
|
3755
3840
|
}
|
|
3756
3841
|
if (first.code === "ORDER_EXISTS" && attempt.commitment) {
|
|
3757
3842
|
const live = await this.entryAt(order, attempt.commitment);
|
|
3758
3843
|
if (live) {
|
|
3759
3844
|
this.logger.info({ id: order.id, commitment: live.commitment }, "This posting is already on the orderbook");
|
|
3760
|
-
return { result: { kind: "unchanged", order: live }, orderNonce };
|
|
3845
|
+
return { result: { kind: "unchanged", order: live }, orderNonce, entryPoint };
|
|
3761
3846
|
}
|
|
3762
3847
|
}
|
|
3763
3848
|
this.logger.warn({ id: order.id, code: first.code }, "Orderbook has seen this op before; reposting on a new nonce");
|
|
3764
3849
|
const retried = orderNonce + 1n;
|
|
3765
|
-
|
|
3850
|
+
const { result } = await this.buildAndSubmit(order, retried, entryPoint);
|
|
3851
|
+
return { result, orderNonce: retried, entryPoint };
|
|
3852
|
+
}
|
|
3853
|
+
/** The EntryPoint a posting on `chain` is signed for now. */
|
|
3854
|
+
entryPointFor(chain) {
|
|
3855
|
+
const entryPoint = this.configService.getEntryPointAddress(chain);
|
|
3856
|
+
if (!entryPoint) throw new LimitOrderValidationError(`No EntryPoint is configured for ${chain}`);
|
|
3857
|
+
return entryPoint;
|
|
3766
3858
|
}
|
|
3767
3859
|
/**
|
|
3768
3860
|
* The entry the orderbook holds at `commitment`, if it holds one. A lookup that
|
|
@@ -3778,8 +3870,8 @@ var LimitOrderService = class {
|
|
|
3778
3870
|
}
|
|
3779
3871
|
}
|
|
3780
3872
|
/** The orderbook's answer, and the commitment the op we sent hashes to. */
|
|
3781
|
-
async buildAndSubmit(order, orderNonce) {
|
|
3782
|
-
const { commitment, userOp } = await this.buildUserOp(order, orderNonce);
|
|
3873
|
+
async buildAndSubmit(order, orderNonce, entryPoint) {
|
|
3874
|
+
const { commitment, userOp } = await this.buildUserOp(order, orderNonce, entryPoint);
|
|
3783
3875
|
try {
|
|
3784
3876
|
return { result: await this.client.submitOrder(userOp), commitment };
|
|
3785
3877
|
} catch (err) {
|
|
@@ -3789,7 +3881,7 @@ var LimitOrderService = class {
|
|
|
3789
3881
|
throw err;
|
|
3790
3882
|
}
|
|
3791
3883
|
}
|
|
3792
|
-
async buildUserOp(order, orderNonce) {
|
|
3884
|
+
async buildUserOp(order, orderNonce, entryPointAddress) {
|
|
3793
3885
|
const baseToken = this.assetRegistry.getAddress(order.base, order.fillChain);
|
|
3794
3886
|
const quoteToken = this.assetRegistry.getAddress(order.quote, order.fillChain);
|
|
3795
3887
|
const [baseDecimals, quoteDecimals] = await Promise.all([
|
|
@@ -3804,10 +3896,6 @@ var LimitOrderService = class {
|
|
|
3804
3896
|
baseDecimals,
|
|
3805
3897
|
quoteDecimals
|
|
3806
3898
|
});
|
|
3807
|
-
const entryPointAddress = this.configService.getEntryPointAddress(order.fillChain);
|
|
3808
|
-
if (!entryPointAddress) {
|
|
3809
|
-
throw new LimitOrderValidationError(`No EntryPoint is configured for ${order.fillChain}`);
|
|
3810
|
-
}
|
|
3811
3899
|
return this.contractService.prepareLimitOrderUserOp({
|
|
3812
3900
|
fillChain: order.fillChain,
|
|
3813
3901
|
entryPointAddress,
|
|
@@ -3838,6 +3926,12 @@ function hasExpired(expiresAt, now) {
|
|
|
3838
3926
|
const at = Date.parse(expiresAt);
|
|
3839
3927
|
return !Number.isNaN(at) && at <= now.getTime();
|
|
3840
3928
|
}
|
|
3929
|
+
function signedFor(order) {
|
|
3930
|
+
return order.entryPoint ?? ENTRY_POINT_V08;
|
|
3931
|
+
}
|
|
3932
|
+
function sameAddress(a, b) {
|
|
3933
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
3934
|
+
}
|
|
3841
3935
|
function sinceMs(updatedAt, now) {
|
|
3842
3936
|
const written = Date.parse(`${updatedAt.replace(" ", "T")}Z`);
|
|
3843
3937
|
return Number.isNaN(written) ? Number.POSITIVE_INFINITY : now.getTime() - written;
|
|
@@ -17496,9 +17590,9 @@ var INTENT_GATEWAY_V2_ABI = [
|
|
|
17496
17590
|
internalType: "bytes32"
|
|
17497
17591
|
},
|
|
17498
17592
|
{
|
|
17499
|
-
name: "
|
|
17500
|
-
type: "
|
|
17501
|
-
internalType: "
|
|
17593
|
+
name: "userOpHash",
|
|
17594
|
+
type: "bytes32",
|
|
17595
|
+
internalType: "bytes32"
|
|
17502
17596
|
},
|
|
17503
17597
|
{
|
|
17504
17598
|
name: "signature",
|
|
@@ -18887,10 +18981,13 @@ var LIMIT_ORDER_CALL_GAS_LIMIT = 500000n;
|
|
|
18887
18981
|
var LIMIT_ORDER_VERIFICATION_GAS_LIMIT = 150000n;
|
|
18888
18982
|
var LIMIT_ORDER_PRE_VERIFICATION_GAS = 50000n;
|
|
18889
18983
|
var BID_DISCOVERY_PAD_SECONDS = 30;
|
|
18984
|
+
var BLOCK_TIME_SAMPLE_BLOCKS = 1000n;
|
|
18985
|
+
var FALLBACK_BLOCK_TIME_SECONDS = 2;
|
|
18890
18986
|
var ContractInteractionService = class {
|
|
18891
18987
|
constructor(clientManager, configService, signer, sharedCacheService) {
|
|
18892
18988
|
this.clientManager = clientManager;
|
|
18893
18989
|
this.sdkHelperCache = /* @__PURE__ */ new Map();
|
|
18990
|
+
this.blockTimes = /* @__PURE__ */ new Map();
|
|
18894
18991
|
this.logger = moduleLogger(configService.loggers, "contract-service");
|
|
18895
18992
|
this.configService = configService;
|
|
18896
18993
|
this.cacheService = sharedCacheService || new CacheService();
|
|
@@ -18950,11 +19047,20 @@ var ContractInteractionService = class {
|
|
|
18950
19047
|
try {
|
|
18951
19048
|
const chainIds = this.configService.getConfiguredChainIds();
|
|
18952
19049
|
const chainNames = chainIds.map((id) => `EVM-${id}`);
|
|
18953
|
-
|
|
19050
|
+
await Promise.all(
|
|
19051
|
+
chainNames.map(async (chainName2) => {
|
|
19052
|
+
try {
|
|
19053
|
+
await this.blockTimeSeconds(chainName2);
|
|
19054
|
+
} catch (err) {
|
|
19055
|
+
this.logger.warn({ err, chain: chainName2 }, "Could not measure the block time at startup");
|
|
19056
|
+
}
|
|
19057
|
+
})
|
|
19058
|
+
);
|
|
19059
|
+
for (const chainName2 of chainNames) {
|
|
18954
19060
|
try {
|
|
18955
|
-
await this.getFeeTokenWithDecimals(
|
|
19061
|
+
await this.getFeeTokenWithDecimals(chainName2);
|
|
18956
19062
|
} catch (err) {
|
|
18957
|
-
this.logger.warn({ err, chain:
|
|
19063
|
+
this.logger.warn({ err, chain: chainName2 }, "Could not prewarm fee token decimals");
|
|
18958
19064
|
}
|
|
18959
19065
|
}
|
|
18960
19066
|
for (const destChain of chainNames) {
|
|
@@ -19539,18 +19645,59 @@ var ContractInteractionService = class {
|
|
|
19539
19645
|
*
|
|
19540
19646
|
* Operators configure seconds because that is the unit the risk is actually in, but the
|
|
19541
19647
|
* contract compares against block numbers so `order.deadline` and this read the same
|
|
19542
|
-
* clock. The conversion uses the chain's
|
|
19543
|
-
* up, since erring long costs a slightly stale quote while erring
|
|
19544
|
-
* winnable bids.
|
|
19648
|
+
* clock. The conversion uses the chain's block time from {@link blockTimeSeconds}; it is
|
|
19649
|
+
* deliberately rounded up, since erring long costs a slightly stale quote while erring
|
|
19650
|
+
* short silently drops winnable bids.
|
|
19545
19651
|
*/
|
|
19546
19652
|
async bidValidUntilBlock(chain) {
|
|
19547
19653
|
const client = this.clientManager.getPublicClient(chain);
|
|
19548
|
-
const currentBlock = await client.getBlockNumber();
|
|
19549
|
-
const blockTimeMs = client.chain?.blockTime;
|
|
19550
|
-
const blockTimeSec = blockTimeMs ? blockTimeMs / 1e3 : 2;
|
|
19654
|
+
const [currentBlock, blockTimeSec] = await Promise.all([client.getBlockNumber(), this.blockTimeSeconds(chain)]);
|
|
19551
19655
|
const windowSec = this.configService.getBidValiditySeconds() + BID_DISCOVERY_PAD_SECONDS;
|
|
19552
19656
|
return currentBlock + BigInt(Math.ceil(windowSec / blockTimeSec));
|
|
19553
19657
|
}
|
|
19658
|
+
/**
|
|
19659
|
+
* Seconds per block on `chain`. Measured once, when simplex starts ({@link initCache}), and
|
|
19660
|
+
* kept until it stops. A chain with no measurement yet, because it was added later or its
|
|
19661
|
+
* blocks could not be read at startup, is measured at its first bid.
|
|
19662
|
+
*
|
|
19663
|
+
* The nominal `Chain.blockTime` cannot be relied on alone: it goes stale when a chain
|
|
19664
|
+
* speeds up (BSC makes a block every 0.45s against a declared 0.75s) and some chains
|
|
19665
|
+
* declare none. Converting at a slower block time than the chain's ends a bid early, so
|
|
19666
|
+
* the faster of the measured and the declared figure is used, and a stalled or idle
|
|
19667
|
+
* stretch in the sample never shortens a bid.
|
|
19668
|
+
*
|
|
19669
|
+
* Blocks that cannot be read fall back to the declared figure, then to
|
|
19670
|
+
* {@link FALLBACK_BLOCK_TIME_SECONDS}. That answer is not kept.
|
|
19671
|
+
*/
|
|
19672
|
+
blockTimeSeconds(chain) {
|
|
19673
|
+
const known = this.blockTimes.get(chain);
|
|
19674
|
+
if (known) return known;
|
|
19675
|
+
const client = this.clientManager.getPublicClient(chain);
|
|
19676
|
+
const declaredMs = client.chain?.blockTime;
|
|
19677
|
+
const declared = declaredMs ? declaredMs / 1e3 : void 0;
|
|
19678
|
+
const seconds = this.measureBlockTimeSeconds(client).then((measured) => declared ? Math.min(measured, declared) : measured).catch((err) => {
|
|
19679
|
+
this.blockTimes.delete(chain);
|
|
19680
|
+
const fallback2 = declared ?? FALLBACK_BLOCK_TIME_SECONDS;
|
|
19681
|
+
this.logger.warn(
|
|
19682
|
+
{ err, chain, seconds: fallback2 },
|
|
19683
|
+
"Could not measure the block time; using the declared one"
|
|
19684
|
+
);
|
|
19685
|
+
return fallback2;
|
|
19686
|
+
});
|
|
19687
|
+
this.blockTimes.set(chain, seconds);
|
|
19688
|
+
return seconds;
|
|
19689
|
+
}
|
|
19690
|
+
/** The average block time over the chain's last {@link BLOCK_TIME_SAMPLE_BLOCKS} blocks, in seconds. */
|
|
19691
|
+
async measureBlockTimeSeconds(client) {
|
|
19692
|
+
const head = await client.getBlock({ blockTag: "latest" });
|
|
19693
|
+
const span = head.number < BLOCK_TIME_SAMPLE_BLOCKS ? head.number : BLOCK_TIME_SAMPLE_BLOCKS;
|
|
19694
|
+
const tail = await client.getBlock({ blockNumber: head.number - span });
|
|
19695
|
+
const measured = Number(head.timestamp - tail.timestamp) / Number(span);
|
|
19696
|
+
if (!Number.isFinite(measured) || measured <= 0) {
|
|
19697
|
+
throw new Error(`${span} blocks span ${head.timestamp - tail.timestamp}s`);
|
|
19698
|
+
}
|
|
19699
|
+
return measured;
|
|
19700
|
+
}
|
|
19554
19701
|
/**
|
|
19555
19702
|
* Builds the signed, never-executed UserOperation that carries one limit order
|
|
19556
19703
|
* to the HyperFX orderbook.
|
|
@@ -19725,11 +19872,17 @@ import { encodeFunctionData as encodeFunctionData3, erc20Abi as erc20Abi3, forma
|
|
|
19725
19872
|
import { encodeERC7821ExecuteBatch as encodeERC7821ExecuteBatch3 } from "@hyperbridge/sdk";
|
|
19726
19873
|
|
|
19727
19874
|
// src/services/UserOpSender.ts
|
|
19728
|
-
import {
|
|
19875
|
+
import {
|
|
19876
|
+
CryptoUtils as CryptoUtils2,
|
|
19877
|
+
BundlerMethod,
|
|
19878
|
+
rundlerUserOperationFees
|
|
19879
|
+
} from "@hyperbridge/sdk";
|
|
19729
19880
|
import { toHex as toHex2 } from "viem";
|
|
19730
19881
|
var FALLBACK_VERIFICATION_GAS_LIMIT = 250000n;
|
|
19731
19882
|
var FALLBACK_CALL_GAS_LIMIT = 1500000n;
|
|
19732
19883
|
var FALLBACK_PRE_VERIFICATION_GAS = 150000n;
|
|
19884
|
+
var PRIORITY_FEE_BUMP_PERCENT = 8n;
|
|
19885
|
+
var MAX_FEE_BUMP_PERCENT = 10n;
|
|
19733
19886
|
var UserOpSender = class {
|
|
19734
19887
|
constructor(clientManager, configService, signer) {
|
|
19735
19888
|
this.clientManager = clientManager;
|
|
@@ -19756,7 +19909,7 @@ var UserOpSender = class {
|
|
|
19756
19909
|
let auth;
|
|
19757
19910
|
let fees;
|
|
19758
19911
|
try {
|
|
19759
|
-
fees = await this.getGasPrice(bundlerUrl, publicClient
|
|
19912
|
+
fees = await this.getGasPrice(bundlerUrl, publicClient);
|
|
19760
19913
|
const gasForPrefund = gas ?? {
|
|
19761
19914
|
verificationGasLimit: FALLBACK_VERIFICATION_GAS_LIMIT,
|
|
19762
19915
|
callGasLimit: FALLBACK_CALL_GAS_LIMIT,
|
|
@@ -19910,31 +20063,27 @@ var UserOpSender = class {
|
|
|
19910
20063
|
);
|
|
19911
20064
|
return userOp;
|
|
19912
20065
|
}
|
|
19913
|
-
/**
|
|
19914
|
-
|
|
19915
|
-
|
|
19916
|
-
|
|
19917
|
-
|
|
19918
|
-
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
19924
|
-
|
|
19925
|
-
|
|
19926
|
-
|
|
19927
|
-
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
return { maxFeePerGas: bufferedBaseFee + maxPriorityFeePerGas, maxPriorityFeePerGas };
|
|
19935
|
-
}
|
|
19936
|
-
const gasPrice = await publicClient.getGasPrice();
|
|
19937
|
-
return { maxFeePerGas: gasPrice + gasPrice * 10n / 100n, maxPriorityFeePerGas: gasPrice + gasPrice * 8n / 100n };
|
|
20066
|
+
/**
|
|
20067
|
+
* Prices the op for the rundler bundler, as the fill path does: its suggested fees, or the
|
|
20068
|
+
* chain's gas price raised to the priority fee it requires where it suggests none.
|
|
20069
|
+
*/
|
|
20070
|
+
async getGasPrice(bundlerUrl, publicClient) {
|
|
20071
|
+
const [gasPrice, latestBlock] = await Promise.all([
|
|
20072
|
+
publicClient.getGasPrice(),
|
|
20073
|
+
publicClient.getBlock({ blockTag: "latest" })
|
|
20074
|
+
]);
|
|
20075
|
+
return rundlerUserOperationFees(
|
|
20076
|
+
bundlerUrl,
|
|
20077
|
+
{
|
|
20078
|
+
maxFeePerGas: gasPrice + gasPrice * MAX_FEE_BUMP_PERCENT / 100n,
|
|
20079
|
+
maxPriorityFeePerGas: gasPrice + gasPrice * PRIORITY_FEE_BUMP_PERCENT / 100n
|
|
20080
|
+
},
|
|
20081
|
+
{
|
|
20082
|
+
baseFeePerGas: latestBlock.baseFeePerGas ?? gasPrice,
|
|
20083
|
+
priorityFeeBumpPercent: PRIORITY_FEE_BUMP_PERCENT,
|
|
20084
|
+
maxFeeBumpPercent: MAX_FEE_BUMP_PERCENT
|
|
20085
|
+
}
|
|
20086
|
+
);
|
|
19938
20087
|
}
|
|
19939
20088
|
async sendBundlerRpc(bundlerUrl, method, params) {
|
|
19940
20089
|
const response = await fetch(bundlerUrl, {
|
|
@@ -23671,6 +23820,82 @@ var TokenSender = class {
|
|
|
23671
23820
|
}
|
|
23672
23821
|
};
|
|
23673
23822
|
|
|
23823
|
+
// src/services/bundler-preflight.ts
|
|
23824
|
+
var BUNDLER_PREFLIGHT_TIMEOUT_MS = 1e4;
|
|
23825
|
+
async function assertBundlersServeEntryPoint(chains, configService, options = {}) {
|
|
23826
|
+
const timeoutMs = options.timeoutMs ?? BUNDLER_PREFLIGHT_TIMEOUT_MS;
|
|
23827
|
+
const logger = moduleLogger(configService.loggers, "bundler-preflight");
|
|
23828
|
+
const failures = await Promise.all(
|
|
23829
|
+
chains.map(async ({ chainId, bundlerUrl }) => {
|
|
23830
|
+
if (options.watchOnly?.[chainId] === true) return void 0;
|
|
23831
|
+
if (!bundlerUrl?.trim()) {
|
|
23832
|
+
return `Hyperbridge runs no bundler for ${chainName(chainId)}, so simplex cannot fill there; set it watch-only`;
|
|
23833
|
+
}
|
|
23834
|
+
const entryPoint = configService.getEntryPointAddress(formatChainKey(chainId));
|
|
23835
|
+
if (!entryPoint) return void 0;
|
|
23836
|
+
return checkBundler(chainId, bundlerUrl.trim(), entryPoint, timeoutMs, logger);
|
|
23837
|
+
})
|
|
23838
|
+
);
|
|
23839
|
+
const messages = failures.filter((failure) => failure !== void 0);
|
|
23840
|
+
if (messages.length > 0) throw new Error(messages.join("; "));
|
|
23841
|
+
}
|
|
23842
|
+
function servesEntryPoint(listed, entryPoint) {
|
|
23843
|
+
return Array.isArray(listed) && listed.some((address) => String(address).toLowerCase() === entryPoint.toLowerCase());
|
|
23844
|
+
}
|
|
23845
|
+
function describeEntryPoints(listed) {
|
|
23846
|
+
return Array.isArray(listed) && listed.length > 0 ? listed.map(String).join(", ") : "none";
|
|
23847
|
+
}
|
|
23848
|
+
function bundlerHost(url) {
|
|
23849
|
+
try {
|
|
23850
|
+
return new URL(url).host || "an unparseable URL";
|
|
23851
|
+
} catch {
|
|
23852
|
+
return "an unparseable URL";
|
|
23853
|
+
}
|
|
23854
|
+
}
|
|
23855
|
+
function chainName(chainId) {
|
|
23856
|
+
const label = chainByChainId(chainId)?.label;
|
|
23857
|
+
return label ? `${label} (${formatChainKey(chainId)})` : formatChainKey(chainId);
|
|
23858
|
+
}
|
|
23859
|
+
async function checkBundler(chainId, bundlerUrl, entryPoint, timeoutMs, logger) {
|
|
23860
|
+
const host = bundlerHost(bundlerUrl);
|
|
23861
|
+
const subject = `Bundler ${host} for ${chainName(chainId)}`;
|
|
23862
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
23863
|
+
let listed;
|
|
23864
|
+
try {
|
|
23865
|
+
listed = await supportedEntryPoints(bundlerUrl, signal);
|
|
23866
|
+
} catch (err) {
|
|
23867
|
+
const reason = signal.aborted ? `timed out after ${timeoutMs}ms` : errorReason(err);
|
|
23868
|
+
logger.warn(
|
|
23869
|
+
{ chain: formatChainKey(chainId), bundler: host },
|
|
23870
|
+
`${subject} did not answer eth_supportedEntryPoints: ${reason.split(bundlerUrl).join(host)}. Continuing without confirming it serves EntryPoint ${entryPoint}`
|
|
23871
|
+
);
|
|
23872
|
+
return void 0;
|
|
23873
|
+
}
|
|
23874
|
+
if (servesEntryPoint(listed, entryPoint)) return void 0;
|
|
23875
|
+
return `${subject} does not support EntryPoint ${entryPoint}; it lists ${describeEntryPoints(listed)}`;
|
|
23876
|
+
}
|
|
23877
|
+
async function supportedEntryPoints(bundlerUrl, signal) {
|
|
23878
|
+
const response = await fetch(bundlerUrl, {
|
|
23879
|
+
method: "POST",
|
|
23880
|
+
headers: { "Content-Type": "application/json" },
|
|
23881
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_supportedEntryPoints", params: [] }),
|
|
23882
|
+
signal
|
|
23883
|
+
});
|
|
23884
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
23885
|
+
const json = await response.json();
|
|
23886
|
+
if (json.error) throw new Error(json.error.message ?? JSON.stringify(json.error));
|
|
23887
|
+
const { result } = json;
|
|
23888
|
+
if (!Array.isArray(result) || !result.every((address) => typeof address === "string")) {
|
|
23889
|
+
throw new Error("the result is not a list of addresses");
|
|
23890
|
+
}
|
|
23891
|
+
return result;
|
|
23892
|
+
}
|
|
23893
|
+
function errorReason(err) {
|
|
23894
|
+
if (!(err instanceof Error)) return String(err);
|
|
23895
|
+
const { cause } = err;
|
|
23896
|
+
return cause instanceof Error ? `${err.message} (${cause.message})` : err.message;
|
|
23897
|
+
}
|
|
23898
|
+
|
|
23674
23899
|
// src/orderbook/lifecycle.ts
|
|
23675
23900
|
var EXPIRY_SWEEP_MS = 3e4;
|
|
23676
23901
|
var LimitOrderLifecycle = class {
|
|
@@ -24481,8 +24706,8 @@ async function bootFiller(config, options) {
|
|
|
24481
24706
|
};
|
|
24482
24707
|
const configService = new FillerConfigService(resolvedChains, fillerConfigForService, options.loggers);
|
|
24483
24708
|
const chainConfigs = resolvedChains.map((chain) => {
|
|
24484
|
-
const
|
|
24485
|
-
return configService.getChainConfig(
|
|
24709
|
+
const chainName2 = `EVM-${chain.chainId}`;
|
|
24710
|
+
return configService.getChainConfig(chainName2);
|
|
24486
24711
|
});
|
|
24487
24712
|
let watchOnlyConfig;
|
|
24488
24713
|
const globalWatchOnly = options.watchOnlyOverride === true || config.simplex.watchOnly === true;
|
|
@@ -24520,6 +24745,8 @@ async function bootFiller(config, options) {
|
|
|
24520
24745
|
"A signer is required unless every chain is watch-only: pass `signer` to Simplex.start (privateKeySigner, secretPhraseSigner, turnkeySigner, mpcVaultSigner, viemSigner, or your own Signer implementation). Running the binary? Configure it under [simplex.signer]."
|
|
24521
24746
|
);
|
|
24522
24747
|
}
|
|
24748
|
+
logger.info("Checking each bundler serves its chain's EntryPoint...");
|
|
24749
|
+
await assertBundlersServeEntryPoint(resolvedChains, configService, { watchOnly: watchOnlyConfig });
|
|
24523
24750
|
const chainClientManager = new ChainClientManager(configService, options.signer);
|
|
24524
24751
|
const runtimeSigner = chainClientManager.getSigner();
|
|
24525
24752
|
if (options.signer) {
|
|
@@ -24710,10 +24937,10 @@ async function bootFiller(config, options) {
|
|
|
24710
24937
|
const token1 = {};
|
|
24711
24938
|
for (const pair of config.pairs ?? []) {
|
|
24712
24939
|
if (normalizeSymbol(pair.token0) === normalizeSymbol(pair.token1)) continue;
|
|
24713
|
-
for (const
|
|
24714
|
-
const address = assetRegistry.getAddress(pair.token1,
|
|
24940
|
+
for (const chainName2 of configuredChainNames) {
|
|
24941
|
+
const address = assetRegistry.getAddress(pair.token1, chainName2);
|
|
24715
24942
|
if (!address) continue;
|
|
24716
|
-
const list = token1[
|
|
24943
|
+
const list = token1[chainName2] ?? (token1[chainName2] = []);
|
|
24717
24944
|
if (!list.includes(address)) list.push(address);
|
|
24718
24945
|
}
|
|
24719
24946
|
}
|
|
@@ -25109,6 +25336,7 @@ var MemoryLimitOrderStore = class {
|
|
|
25109
25336
|
status: "open",
|
|
25110
25337
|
commitment: null,
|
|
25111
25338
|
orderNonce: order.orderNonce ?? "0",
|
|
25339
|
+
entryPoint: null,
|
|
25112
25340
|
bookExpiresAt: null,
|
|
25113
25341
|
bookPrice: null,
|
|
25114
25342
|
lastError: null,
|
|
@@ -25744,7 +25972,7 @@ var OrderScanner = class _OrderScanner {
|
|
|
25744
25972
|
async addChainNow(chain) {
|
|
25745
25973
|
if (this.closed) throw new Error("This OrderScanner is closed");
|
|
25746
25974
|
const rpcUrls = validateRpcUrls(chain.rpcUrls);
|
|
25747
|
-
const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls
|
|
25975
|
+
const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls }], {
|
|
25748
25976
|
loggers: this.loggers,
|
|
25749
25977
|
tolerateUnreachable: true
|
|
25750
25978
|
}))[0].chainId;
|
|
@@ -26094,10 +26322,10 @@ var PairController = class {
|
|
|
26094
26322
|
if (assets && Object.keys(assets).length > 0) config.assets = nextAssets;
|
|
26095
26323
|
if (normalizeSymbol(pair.token0) !== normalizeSymbol(pair.token1)) {
|
|
26096
26324
|
for (const chain of this.runtime.resolvedChains) {
|
|
26097
|
-
const
|
|
26098
|
-
const address = assetRegistry.getAddress(pair.token1,
|
|
26325
|
+
const chainName2 = formatChainKey(chain.chainId);
|
|
26326
|
+
const address = assetRegistry.getAddress(pair.token1, chainName2);
|
|
26099
26327
|
if (!address) continue;
|
|
26100
|
-
const list = balanceTokens[
|
|
26328
|
+
const list = balanceTokens[chainName2] ?? (balanceTokens[chainName2] = []);
|
|
26101
26329
|
if (!list.includes(address)) list.push(address);
|
|
26102
26330
|
}
|
|
26103
26331
|
}
|
|
@@ -26176,10 +26404,7 @@ var ChainController = class {
|
|
|
26176
26404
|
async add(chain) {
|
|
26177
26405
|
return this.serialise(async () => {
|
|
26178
26406
|
validateRpcUrls(chain.rpcUrls);
|
|
26179
|
-
|
|
26180
|
-
throw new Error("A bundler URL is required to submit fill UserOperations");
|
|
26181
|
-
}
|
|
26182
|
-
const [resolved] = await resolveChainConfigs([{ rpcUrls: chain.rpcUrls, bundlerUrl: chain.bundlerUrl }]);
|
|
26407
|
+
const [resolved] = await resolveChainConfigs([{ rpcUrls: chain.rpcUrls }]);
|
|
26183
26408
|
const { chainId } = resolved;
|
|
26184
26409
|
const { configService, config, intentFiller } = this.runtime;
|
|
26185
26410
|
if (configService.getConfiguredChainIds().includes(chainId)) {
|
|
@@ -26202,12 +26427,13 @@ var ChainController = class {
|
|
|
26202
26427
|
`Chain ${chainId} cannot be added as filling: this solver was started without a signer. Restart with \`signer\` to fill.`
|
|
26203
26428
|
);
|
|
26204
26429
|
}
|
|
26205
|
-
configService.addChain(resolved);
|
|
26206
26430
|
const watchOnly = chain.watchOnly ?? (this.runtime.signerless || this.runtime.globalWatchOnly);
|
|
26431
|
+
await assertBundlersServeEntryPoint([resolved], configService, { watchOnly: { [chainId]: watchOnly } });
|
|
26432
|
+
configService.addChain(resolved);
|
|
26207
26433
|
if (watchOnly) intentFiller.setWatchOnly(chainId, true);
|
|
26208
26434
|
try {
|
|
26209
26435
|
if (!inScanner) {
|
|
26210
|
-
await this.scanner.addChain({ rpcUrls: chain.rpcUrls,
|
|
26436
|
+
await this.scanner.addChain({ rpcUrls: chain.rpcUrls, chainId });
|
|
26211
26437
|
}
|
|
26212
26438
|
this.runtime.chainClientManager.invalidate(formatChainKey(chainId));
|
|
26213
26439
|
await intentFiller.addChain(configService.getChainConfig(formatChainKey(chainId)));
|
|
@@ -26224,7 +26450,7 @@ var ChainController = class {
|
|
|
26224
26450
|
this.runtime.confirmationPolicy.adopt(chainId, resolvedPolicy);
|
|
26225
26451
|
}
|
|
26226
26452
|
this.runtime.resolvedChains.push(resolved);
|
|
26227
|
-
config.chains = [...config.chains, { rpcUrls: chain.rpcUrls
|
|
26453
|
+
config.chains = [...config.chains, { rpcUrls: chain.rpcUrls }];
|
|
26228
26454
|
if (Object.keys(confirmationPolicies).length > 0) config.confirmationPolicies = confirmationPolicies;
|
|
26229
26455
|
this.syncWatchOnlyToConfig();
|
|
26230
26456
|
await this.persist();
|
|
@@ -26269,9 +26495,7 @@ var ChainController = class {
|
|
|
26269
26495
|
validateRpcUrls(rpcUrls);
|
|
26270
26496
|
const index = this.runtime.resolvedChains.findIndex((chain) => chain.chainId === chainId);
|
|
26271
26497
|
if (index < 0) throw new Error(`Chain ${chainId} is not configured`);
|
|
26272
|
-
const [probed] = await resolveChainConfigs([
|
|
26273
|
-
{ rpcUrls, bundlerUrl: this.runtime.resolvedChains[index].bundlerUrl ?? "" }
|
|
26274
|
-
]);
|
|
26498
|
+
const [probed] = await resolveChainConfigs([{ rpcUrls }]);
|
|
26275
26499
|
if (probed.chainId !== chainId) {
|
|
26276
26500
|
throw new Error(`Those endpoints answer for chain ${probed.chainId}, not ${chainId}`);
|
|
26277
26501
|
}
|
|
@@ -26290,17 +26514,6 @@ var ChainController = class {
|
|
|
26290
26514
|
await this.persist();
|
|
26291
26515
|
});
|
|
26292
26516
|
}
|
|
26293
|
-
async setBundlerUrl(chainId, bundlerUrl) {
|
|
26294
|
-
return this.serialise(async () => {
|
|
26295
|
-
if (!bundlerUrl?.trim()) throw new Error("A bundler URL is required");
|
|
26296
|
-
const index = this.runtime.resolvedChains.findIndex((chain) => chain.chainId === chainId);
|
|
26297
|
-
if (index < 0) throw new Error(`Chain ${chainId} is not configured`);
|
|
26298
|
-
this.runtime.configService.setBundlerUrl(chainId, bundlerUrl);
|
|
26299
|
-
this.runtime.resolvedChains[index].bundlerUrl = bundlerUrl;
|
|
26300
|
-
this.runtime.config.chains[index].bundlerUrl = bundlerUrl;
|
|
26301
|
-
await this.persist();
|
|
26302
|
-
});
|
|
26303
|
-
}
|
|
26304
26517
|
/** Monitor without filling. Takes effect on the next order. */
|
|
26305
26518
|
async setWatchOnly(chainId, watchOnly) {
|
|
26306
26519
|
return this.serialise(async () => {
|
|
@@ -26309,6 +26522,10 @@ var ChainController = class {
|
|
|
26309
26522
|
"Watch-only cannot be disabled: this solver was started without a signer. Restart with `signer` to fill."
|
|
26310
26523
|
);
|
|
26311
26524
|
}
|
|
26525
|
+
if (!watchOnly) {
|
|
26526
|
+
const chain = this.runtime.resolvedChains.find((resolved) => resolved.chainId === chainId);
|
|
26527
|
+
if (chain) await assertBundlersServeEntryPoint([chain], this.runtime.configService);
|
|
26528
|
+
}
|
|
26312
26529
|
this.runtime.intentFiller.setWatchOnly(chainId, watchOnly);
|
|
26313
26530
|
this.syncWatchOnlyToConfig();
|
|
26314
26531
|
await this.persist();
|