@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.cjs
CHANGED
|
@@ -625,6 +625,20 @@ var ConfirmationPolicy = class {
|
|
|
625
625
|
// src/core/filler.ts
|
|
626
626
|
var import_p_queue = __toESM(require("p-queue"), 1);
|
|
627
627
|
|
|
628
|
+
// src/config/bundlers.ts
|
|
629
|
+
var HYPERBRIDGE_BUNDLER_URLS = {
|
|
630
|
+
1: "https://bundler.polytope.technology/ethereum",
|
|
631
|
+
56: "https://bundler.polytope.technology/bsc",
|
|
632
|
+
97: "https://bundler.polytope.technology/bsc-chapel",
|
|
633
|
+
137: "https://bundler.polytope.technology/polygon",
|
|
634
|
+
8453: "https://bundler.polytope.technology/base",
|
|
635
|
+
42161: "https://bundler.polytope.technology/arbitrum",
|
|
636
|
+
80002: "https://bundler.polytope.technology/polygon-amoy"
|
|
637
|
+
};
|
|
638
|
+
function hyperbridgeBundlerUrl(chainId) {
|
|
639
|
+
return HYPERBRIDGE_BUNDLER_URLS[chainId];
|
|
640
|
+
}
|
|
641
|
+
|
|
628
642
|
// src/config/asset-registry.ts
|
|
629
643
|
var import_viem = require("viem");
|
|
630
644
|
var BUILTIN_ASSETS = {
|
|
@@ -636,7 +650,8 @@ var BUILTIN_ASSETS = {
|
|
|
636
650
|
ZARP: { resolve: (r, chain) => r.getAssetBySymbol(chain, "ZARP") },
|
|
637
651
|
EURC: { resolve: (r, chain) => r.getAssetBySymbol(chain, "EURC") },
|
|
638
652
|
XSGD: { resolve: (r, chain) => r.getAssetBySymbol(chain, "XSGD") },
|
|
639
|
-
TRYB: { resolve: (r, chain) => r.getAssetBySymbol(chain, "TRYB") }
|
|
653
|
+
TRYB: { resolve: (r, chain) => r.getAssetBySymbol(chain, "TRYB") },
|
|
654
|
+
BRIDGE: { resolve: (r, chain) => r.getAssetBySymbol(chain, "BRIDGE") }
|
|
640
655
|
};
|
|
641
656
|
var USD_STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT", "DAI"]);
|
|
642
657
|
function normalizeSymbol(symbol) {
|
|
@@ -774,7 +789,7 @@ function assertPairSymbolsResolve(pairs, registry, chainNames) {
|
|
|
774
789
|
const pairSymbols = new Set(pairs.flatMap((pair) => [pair.token0, pair.token1]));
|
|
775
790
|
for (const pair of pairs) {
|
|
776
791
|
for (const symbol of [pair.token0, pair.token1]) {
|
|
777
|
-
const resolvesSomewhere = chainNames.some((
|
|
792
|
+
const resolvesSomewhere = chainNames.some((chainName2) => registry.getAddress(symbol, chainName2) !== null);
|
|
778
793
|
if (!resolvesSomewhere) {
|
|
779
794
|
throw new Error(
|
|
780
795
|
`pairs.${pair.token0}/${pair.token1}: '${symbol}' does not resolve to a deployed contract on any configured chain`
|
|
@@ -782,15 +797,15 @@ function assertPairSymbolsResolve(pairs, registry, chainNames) {
|
|
|
782
797
|
}
|
|
783
798
|
}
|
|
784
799
|
}
|
|
785
|
-
for (const
|
|
800
|
+
for (const chainName2 of chainNames) {
|
|
786
801
|
const addressOwner = /* @__PURE__ */ new Map();
|
|
787
802
|
for (const symbol of pairSymbols) {
|
|
788
|
-
const address = registry.getAddress(symbol,
|
|
803
|
+
const address = registry.getAddress(symbol, chainName2)?.toLowerCase();
|
|
789
804
|
if (!address) continue;
|
|
790
805
|
const owner = addressOwner.get(address);
|
|
791
806
|
if (owner && normalizeSymbol(owner) !== normalizeSymbol(symbol)) {
|
|
792
807
|
throw new Error(
|
|
793
|
-
`assets: '${symbol}' and '${owner}' both resolve to ${address} on ${
|
|
808
|
+
`assets: '${symbol}' and '${owner}' both resolve to ${address} on ${chainName2} \u2014 symbols must map to distinct contracts`
|
|
794
809
|
);
|
|
795
810
|
}
|
|
796
811
|
addressOwner.set(address, symbol);
|
|
@@ -1244,7 +1259,7 @@ async function resolveChainConfigs(chains, options = {}) {
|
|
|
1244
1259
|
"RPC endpoint could not report its chainId at startup; keeping it for the quorum to judge per call"
|
|
1245
1260
|
);
|
|
1246
1261
|
}
|
|
1247
|
-
return { chainId: first.chainId, rpcUrls, bundlerUrl:
|
|
1262
|
+
return { chainId: first.chainId, rpcUrls, bundlerUrl: hyperbridgeBundlerUrl(first.chainId) };
|
|
1248
1263
|
})
|
|
1249
1264
|
);
|
|
1250
1265
|
}
|
|
@@ -1313,12 +1328,6 @@ var FillerConfigService = class {
|
|
|
1313
1328
|
}
|
|
1314
1329
|
this.rpcOverrides.set(chainId, validateRpcUrls(rpcUrls));
|
|
1315
1330
|
}
|
|
1316
|
-
setBundlerUrl(chainId, bundlerUrl) {
|
|
1317
|
-
if (!this.rpcOverrides.has(chainId)) {
|
|
1318
|
-
throw new Error(`Chain ${chainId} is not configured`);
|
|
1319
|
-
}
|
|
1320
|
-
this.bundlerUrls.set(chainId, bundlerUrl);
|
|
1321
|
-
}
|
|
1322
1331
|
/** Replaces the rebalancing config at runtime; trigger checks read it live. */
|
|
1323
1332
|
setRebalancing(rebalancing) {
|
|
1324
1333
|
if (this.fillerConfig) this.fillerConfig.rebalancing = rebalancing;
|
|
@@ -1495,7 +1504,7 @@ var FillerConfigService = class {
|
|
|
1495
1504
|
return this.fillerConfig?.substratePrivateKey;
|
|
1496
1505
|
}
|
|
1497
1506
|
getEntryPointAddress(chain) {
|
|
1498
|
-
return this.chainConfigService.
|
|
1507
|
+
return this.chainConfigService.getEntryPointAddress(chain);
|
|
1499
1508
|
}
|
|
1500
1509
|
getSolverAccountContractAddress(chain) {
|
|
1501
1510
|
return this.chainConfigService.getSolverAccountAddress(chain);
|
|
@@ -1680,9 +1689,6 @@ function validateConfig(config, cliWatchOnly = false) {
|
|
|
1680
1689
|
if (!Array.isArray(chain.rpcUrls) || chain.rpcUrls.length === 0 || chain.rpcUrls.some((u) => !u)) {
|
|
1681
1690
|
throw new Error("Each chain configuration must have rpcUrls (a non-empty array of strings)");
|
|
1682
1691
|
}
|
|
1683
|
-
if (!chain.bundlerUrl) {
|
|
1684
|
-
throw new Error("Each chain configuration must have bundlerUrl");
|
|
1685
|
-
}
|
|
1686
1692
|
}
|
|
1687
1693
|
const concurrent = config.simplex.maxConcurrentOrders;
|
|
1688
1694
|
if (concurrent !== void 0) {
|
|
@@ -2572,7 +2578,8 @@ var INIT_CHAINS = [
|
|
|
2572
2578
|
network: "mainnet",
|
|
2573
2579
|
alchemySubdomain: "eth-mainnet",
|
|
2574
2580
|
explorerUrl: external_links_default.chainExplorers.ethereum,
|
|
2575
|
-
defaultRpcUrls: FREE_RPC_URLS[1]
|
|
2581
|
+
defaultRpcUrls: FREE_RPC_URLS[1],
|
|
2582
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[1]
|
|
2576
2583
|
},
|
|
2577
2584
|
{
|
|
2578
2585
|
chainId: 42161,
|
|
@@ -2581,7 +2588,8 @@ var INIT_CHAINS = [
|
|
|
2581
2588
|
network: "mainnet",
|
|
2582
2589
|
alchemySubdomain: "arb-mainnet",
|
|
2583
2590
|
explorerUrl: external_links_default.chainExplorers.arbitrum,
|
|
2584
|
-
defaultRpcUrls: FREE_RPC_URLS[42161]
|
|
2591
|
+
defaultRpcUrls: FREE_RPC_URLS[42161],
|
|
2592
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[42161]
|
|
2585
2593
|
},
|
|
2586
2594
|
{
|
|
2587
2595
|
chainId: 8453,
|
|
@@ -2590,7 +2598,8 @@ var INIT_CHAINS = [
|
|
|
2590
2598
|
network: "mainnet",
|
|
2591
2599
|
alchemySubdomain: "base-mainnet",
|
|
2592
2600
|
explorerUrl: external_links_default.chainExplorers.base,
|
|
2593
|
-
defaultRpcUrls: FREE_RPC_URLS[8453]
|
|
2601
|
+
defaultRpcUrls: FREE_RPC_URLS[8453],
|
|
2602
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[8453]
|
|
2594
2603
|
},
|
|
2595
2604
|
{
|
|
2596
2605
|
chainId: 137,
|
|
@@ -2599,7 +2608,8 @@ var INIT_CHAINS = [
|
|
|
2599
2608
|
network: "mainnet",
|
|
2600
2609
|
alchemySubdomain: "polygon-mainnet",
|
|
2601
2610
|
explorerUrl: external_links_default.chainExplorers.polygon,
|
|
2602
|
-
defaultRpcUrls: FREE_RPC_URLS[137]
|
|
2611
|
+
defaultRpcUrls: FREE_RPC_URLS[137],
|
|
2612
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[137]
|
|
2603
2613
|
},
|
|
2604
2614
|
{
|
|
2605
2615
|
chainId: 56,
|
|
@@ -2608,7 +2618,8 @@ var INIT_CHAINS = [
|
|
|
2608
2618
|
network: "mainnet",
|
|
2609
2619
|
alchemySubdomain: "bnb-mainnet",
|
|
2610
2620
|
explorerUrl: external_links_default.chainExplorers.bnb,
|
|
2611
|
-
defaultRpcUrls: FREE_RPC_URLS[56]
|
|
2621
|
+
defaultRpcUrls: FREE_RPC_URLS[56],
|
|
2622
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[56]
|
|
2612
2623
|
},
|
|
2613
2624
|
{
|
|
2614
2625
|
chainId: 11155111,
|
|
@@ -2640,7 +2651,8 @@ var INIT_CHAINS = [
|
|
|
2640
2651
|
label: "Polygon Amoy",
|
|
2641
2652
|
network: "testnet",
|
|
2642
2653
|
alchemySubdomain: "polygon-amoy",
|
|
2643
|
-
explorerUrl: external_links_default.chainExplorers.polygonAmoy
|
|
2654
|
+
explorerUrl: external_links_default.chainExplorers.polygonAmoy,
|
|
2655
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[80002]
|
|
2644
2656
|
},
|
|
2645
2657
|
{
|
|
2646
2658
|
chainId: 97,
|
|
@@ -2648,7 +2660,8 @@ var INIT_CHAINS = [
|
|
|
2648
2660
|
label: "BSC Chapel",
|
|
2649
2661
|
network: "testnet",
|
|
2650
2662
|
alchemySubdomain: "bnb-testnet",
|
|
2651
|
-
explorerUrl: external_links_default.chainExplorers.bnbChapel
|
|
2663
|
+
explorerUrl: external_links_default.chainExplorers.bnbChapel,
|
|
2664
|
+
hyperbridgeBundlerUrl: HYPERBRIDGE_BUNDLER_URLS[97]
|
|
2652
2665
|
}
|
|
2653
2666
|
];
|
|
2654
2667
|
function chainByChainId(chainId) {
|
|
@@ -3122,6 +3135,8 @@ var LimitOrderService = class {
|
|
|
3122
3135
|
this.delegationService = delegationService;
|
|
3123
3136
|
this.startingNonce = startingNonce;
|
|
3124
3137
|
this.vaultBalances = vaultBalances;
|
|
3138
|
+
/** Re-signs in flight, by order id. A fill's resize waits one out rather than reposting alongside it. */
|
|
3139
|
+
this.resigning = /* @__PURE__ */ new Map();
|
|
3125
3140
|
this.logger = loggers.get("limit-orders");
|
|
3126
3141
|
}
|
|
3127
3142
|
/**
|
|
@@ -3215,6 +3230,7 @@ var LimitOrderService = class {
|
|
|
3215
3230
|
bookExpiresAt: null,
|
|
3216
3231
|
bookPrice: null,
|
|
3217
3232
|
orderNonce: order.orderNonce,
|
|
3233
|
+
entryPoint: null,
|
|
3218
3234
|
status: "cancelled",
|
|
3219
3235
|
lastError: null
|
|
3220
3236
|
}),
|
|
@@ -3481,24 +3497,32 @@ var LimitOrderService = class {
|
|
|
3481
3497
|
async resize(remaining, delivered) {
|
|
3482
3498
|
const id = remaining.id;
|
|
3483
3499
|
const floor = await this.dustFloor(remaining);
|
|
3484
|
-
|
|
3500
|
+
for (let resigning = this.resigning.get(id); resigning; resigning = this.resigning.get(id)) await resigning;
|
|
3501
|
+
const order = await this.store.setStatus(id, "resizing", null, POSTABLE);
|
|
3502
|
+
if (!order) return null;
|
|
3503
|
+
if (BigInt(order.remaining) < floor) {
|
|
3485
3504
|
this.logger.info(
|
|
3486
|
-
{ id, remaining:
|
|
3505
|
+
{ id, remaining: order.remaining, floor: floor.toString() },
|
|
3487
3506
|
"Limit order worked down past the dust floor; closing it"
|
|
3488
3507
|
);
|
|
3489
|
-
if (
|
|
3490
|
-
const closed = await this.store.setPosting(
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3508
|
+
if (order.commitment) await this.withdraw(order.commitment);
|
|
3509
|
+
const closed = await this.store.setPosting(
|
|
3510
|
+
id,
|
|
3511
|
+
{
|
|
3512
|
+
commitment: null,
|
|
3513
|
+
bookExpiresAt: null,
|
|
3514
|
+
bookPrice: null,
|
|
3515
|
+
orderNonce: order.orderNonce,
|
|
3516
|
+
entryPoint: null,
|
|
3517
|
+
status: "filled",
|
|
3518
|
+
lastError: null
|
|
3519
|
+
},
|
|
3520
|
+
POSTABLE
|
|
3521
|
+
);
|
|
3498
3522
|
if (closed) this.report({ kind: "filled", order: closed });
|
|
3499
3523
|
return closed;
|
|
3500
3524
|
}
|
|
3501
|
-
const resized = await this.repost(
|
|
3525
|
+
const resized = await this.repost(order);
|
|
3502
3526
|
if (resized) this.report({ kind: "resized", order: resized, delivered });
|
|
3503
3527
|
return resized;
|
|
3504
3528
|
}
|
|
@@ -3511,7 +3535,7 @@ var LimitOrderService = class {
|
|
|
3511
3535
|
*/
|
|
3512
3536
|
async repost(order) {
|
|
3513
3537
|
if (!order) return null;
|
|
3514
|
-
if (isLocal(order)) return this.store.setStatus(order.id, "open");
|
|
3538
|
+
if (isLocal(order)) return this.store.setStatus(order.id, "open", null, POSTABLE);
|
|
3515
3539
|
if (hasExpired(order.expiresAt, /* @__PURE__ */ new Date())) return this.retire(order);
|
|
3516
3540
|
if (order.commitment) {
|
|
3517
3541
|
const withdrawn = await this.withdraw(order.commitment);
|
|
@@ -3522,10 +3546,13 @@ var LimitOrderService = class {
|
|
|
3522
3546
|
{ id: order.id, commitment: order.commitment, err: message },
|
|
3523
3547
|
"Could not clear the old entry; leaving the posting alone rather than adding a second"
|
|
3524
3548
|
);
|
|
3525
|
-
return this.store.setStatus(order.id, "open", message);
|
|
3549
|
+
return this.store.setStatus(order.id, "open", message, POSTABLE);
|
|
3526
3550
|
}
|
|
3527
3551
|
}
|
|
3528
|
-
await this.store.setStatus(order.id, "resizing")
|
|
3552
|
+
if (!await this.store.setStatus(order.id, "resizing", null, POSTABLE)) {
|
|
3553
|
+
this.logger.info({ id: order.id }, "Limit order closed while it was being reposted; leaving it closed");
|
|
3554
|
+
return null;
|
|
3555
|
+
}
|
|
3529
3556
|
const nonce = (BigInt(order.orderNonce) + 1n).toString();
|
|
3530
3557
|
return (await this.post({ ...order, commitment: null, orderNonce: nonce })).order;
|
|
3531
3558
|
}
|
|
@@ -3568,7 +3595,9 @@ var LimitOrderService = class {
|
|
|
3568
3595
|
* They drift apart when a request never got an answer or the process died
|
|
3569
3596
|
* between two of them: an entry the orderbook still lists that nothing here
|
|
3570
3597
|
* owns, an order here whose entry has gone, or an entry the orderbook has cut
|
|
3571
|
-
* down because the solver cannot cover what it quoted.
|
|
3598
|
+
* down because the solver cannot cover what it quoted. A posted op is also
|
|
3599
|
+
* signed over one EntryPoint, so when the fill chain moves to another, every
|
|
3600
|
+
* entry signed before the move is withdrawn and posted again.
|
|
3572
3601
|
*/
|
|
3573
3602
|
async reconcile(now = /* @__PURE__ */ new Date()) {
|
|
3574
3603
|
const [entries, live] = await Promise.all([this.postedOrders(), this.live()]);
|
|
@@ -3595,7 +3624,17 @@ var LimitOrderService = class {
|
|
|
3595
3624
|
}
|
|
3596
3625
|
for (const order of live) {
|
|
3597
3626
|
if (isLocal(order)) continue;
|
|
3598
|
-
if (order.commitment && found.has(order.commitment.toLowerCase()))
|
|
3627
|
+
if (order.commitment && found.has(order.commitment.toLowerCase())) {
|
|
3628
|
+
try {
|
|
3629
|
+
if (await this.resign(order)) report.reposted++;
|
|
3630
|
+
} catch (err) {
|
|
3631
|
+
this.logger.error(
|
|
3632
|
+
{ id: order.id, err },
|
|
3633
|
+
"Could not sign the limit order again for its chain's EntryPoint"
|
|
3634
|
+
);
|
|
3635
|
+
}
|
|
3636
|
+
continue;
|
|
3637
|
+
}
|
|
3599
3638
|
if (sinceMs(order.updatedAt, now) < POST_GRACE_MS) continue;
|
|
3600
3639
|
this.logger.warn({ id: order.id }, "Limit order has no entry on the orderbook; posting it again");
|
|
3601
3640
|
try {
|
|
@@ -3607,6 +3646,45 @@ var LimitOrderService = class {
|
|
|
3607
3646
|
}
|
|
3608
3647
|
return report;
|
|
3609
3648
|
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Withdraws a posting signed for an EntryPoint its fill chain no longer uses,
|
|
3651
|
+
* and posts it again signed for the one it uses now. Answers whether a new
|
|
3652
|
+
* posting landed.
|
|
3653
|
+
*
|
|
3654
|
+
* The row is read again and claimed out of `open` first: a fill can have worked
|
|
3655
|
+
* it down or started its own repost since reconciliation listed it, and that
|
|
3656
|
+
* write is the one to follow. A fill that lands once the claim is taken waits
|
|
3657
|
+
* in `resize` for this to finish. The EntryPoint is only written with a posting
|
|
3658
|
+
* that landed, so a pass cut short anywhere leaves a row the next pass still
|
|
3659
|
+
* reads as stale, or as having no entry, and finishes the job.
|
|
3660
|
+
*/
|
|
3661
|
+
async resign(listed) {
|
|
3662
|
+
const entryPoint = this.configService.getEntryPointAddress(listed.fillChain);
|
|
3663
|
+
if (!entryPoint || sameAddress(signedFor(listed), entryPoint)) return false;
|
|
3664
|
+
const order = await this.store.get(listed.id);
|
|
3665
|
+
if (!order || order.status !== "open" || order.commitment !== listed.commitment) return false;
|
|
3666
|
+
if (sameAddress(signedFor(order), entryPoint) || this.resigning.has(order.id)) return false;
|
|
3667
|
+
const resigned = this.signAgain(order, entryPoint);
|
|
3668
|
+
this.resigning.set(
|
|
3669
|
+
order.id,
|
|
3670
|
+
resigned.catch(() => false)
|
|
3671
|
+
);
|
|
3672
|
+
try {
|
|
3673
|
+
return await resigned;
|
|
3674
|
+
} finally {
|
|
3675
|
+
this.resigning.delete(order.id);
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
async signAgain(order, entryPoint) {
|
|
3679
|
+
const claimed = await this.store.setStatus(order.id, "resizing", null, ["open"]);
|
|
3680
|
+
if (!claimed) return false;
|
|
3681
|
+
this.logger.warn(
|
|
3682
|
+
{ id: order.id, commitment: order.commitment, signedFor: signedFor(order), entryPoint },
|
|
3683
|
+
"Limit order was signed for an EntryPoint its chain no longer uses; signing it again"
|
|
3684
|
+
);
|
|
3685
|
+
const posted = await this.repost(claimed);
|
|
3686
|
+
return !!posted?.commitment && sameAddress(signedFor(posted), entryPoint);
|
|
3687
|
+
}
|
|
3610
3688
|
/**
|
|
3611
3689
|
* Lowers every open order's `remaining` to what the solver's account will still
|
|
3612
3690
|
* pay for it, and puts the order back on the book at that size.
|
|
@@ -3688,6 +3766,7 @@ var LimitOrderService = class {
|
|
|
3688
3766
|
bookExpiresAt: null,
|
|
3689
3767
|
bookPrice: null,
|
|
3690
3768
|
orderNonce: order.orderNonce,
|
|
3769
|
+
entryPoint: null,
|
|
3691
3770
|
status: "expired",
|
|
3692
3771
|
lastError: null
|
|
3693
3772
|
});
|
|
@@ -3744,11 +3823,11 @@ var LimitOrderService = class {
|
|
|
3744
3823
|
}
|
|
3745
3824
|
/** Builds and submits the posting, then writes the orderbook's answer onto the row. */
|
|
3746
3825
|
async post(order) {
|
|
3747
|
-
const { result, orderNonce } = await this.submit(order);
|
|
3826
|
+
const { result, orderNonce, entryPoint } = await this.submit(order);
|
|
3748
3827
|
if (result.kind === "accepted" || result.kind === "unchanged") {
|
|
3749
3828
|
const posted = result.order;
|
|
3750
3829
|
this.logger.info(
|
|
3751
|
-
{ id: order.id, commitment: posted.commitment, price: posted.price },
|
|
3830
|
+
{ id: order.id, commitment: posted.commitment, price: posted.price, entryPoint },
|
|
3752
3831
|
"Limit order posted to the orderbook"
|
|
3753
3832
|
);
|
|
3754
3833
|
const stored = await this.store.setPosting(
|
|
@@ -3758,6 +3837,10 @@ var LimitOrderService = class {
|
|
|
3758
3837
|
bookExpiresAt: posted.expiresAt,
|
|
3759
3838
|
bookPrice: posted.price,
|
|
3760
3839
|
orderNonce: orderNonce.toString(),
|
|
3840
|
+
// An entry the orderbook already held was signed by an earlier attempt,
|
|
3841
|
+
// for whichever EntryPoint was current then. Recorded as unknown, which
|
|
3842
|
+
// reconciliation reads as v0.8 and signs again once the chain is past it.
|
|
3843
|
+
entryPoint: result.kind === "accepted" ? entryPoint : null,
|
|
3761
3844
|
status: "open",
|
|
3762
3845
|
lastError: null
|
|
3763
3846
|
},
|
|
@@ -3787,6 +3870,7 @@ var LimitOrderService = class {
|
|
|
3787
3870
|
bookExpiresAt: null,
|
|
3788
3871
|
bookPrice: null,
|
|
3789
3872
|
orderNonce: orderNonce.toString(),
|
|
3873
|
+
entryPoint: null,
|
|
3790
3874
|
status: "open",
|
|
3791
3875
|
lastError: message
|
|
3792
3876
|
}),
|
|
@@ -3809,22 +3893,30 @@ var LimitOrderService = class {
|
|
|
3809
3893
|
* only the second one.
|
|
3810
3894
|
*/
|
|
3811
3895
|
async submit(order) {
|
|
3896
|
+
const entryPoint = this.entryPointFor(order.fillChain);
|
|
3812
3897
|
const orderNonce = BigInt(order.orderNonce);
|
|
3813
|
-
const attempt = await this.buildAndSubmit(order, orderNonce);
|
|
3898
|
+
const attempt = await this.buildAndSubmit(order, orderNonce, entryPoint);
|
|
3814
3899
|
const first = attempt.result;
|
|
3815
3900
|
if (first.kind !== "rejected" || first.code !== "REPLAYED" && first.code !== "ORDER_EXISTS") {
|
|
3816
|
-
return { result: first, orderNonce };
|
|
3901
|
+
return { result: first, orderNonce, entryPoint };
|
|
3817
3902
|
}
|
|
3818
3903
|
if (first.code === "ORDER_EXISTS" && attempt.commitment) {
|
|
3819
3904
|
const live = await this.entryAt(order, attempt.commitment);
|
|
3820
3905
|
if (live) {
|
|
3821
3906
|
this.logger.info({ id: order.id, commitment: live.commitment }, "This posting is already on the orderbook");
|
|
3822
|
-
return { result: { kind: "unchanged", order: live }, orderNonce };
|
|
3907
|
+
return { result: { kind: "unchanged", order: live }, orderNonce, entryPoint };
|
|
3823
3908
|
}
|
|
3824
3909
|
}
|
|
3825
3910
|
this.logger.warn({ id: order.id, code: first.code }, "Orderbook has seen this op before; reposting on a new nonce");
|
|
3826
3911
|
const retried = orderNonce + 1n;
|
|
3827
|
-
|
|
3912
|
+
const { result } = await this.buildAndSubmit(order, retried, entryPoint);
|
|
3913
|
+
return { result, orderNonce: retried, entryPoint };
|
|
3914
|
+
}
|
|
3915
|
+
/** The EntryPoint a posting on `chain` is signed for now. */
|
|
3916
|
+
entryPointFor(chain) {
|
|
3917
|
+
const entryPoint = this.configService.getEntryPointAddress(chain);
|
|
3918
|
+
if (!entryPoint) throw new LimitOrderValidationError(`No EntryPoint is configured for ${chain}`);
|
|
3919
|
+
return entryPoint;
|
|
3828
3920
|
}
|
|
3829
3921
|
/**
|
|
3830
3922
|
* The entry the orderbook holds at `commitment`, if it holds one. A lookup that
|
|
@@ -3840,8 +3932,8 @@ var LimitOrderService = class {
|
|
|
3840
3932
|
}
|
|
3841
3933
|
}
|
|
3842
3934
|
/** The orderbook's answer, and the commitment the op we sent hashes to. */
|
|
3843
|
-
async buildAndSubmit(order, orderNonce) {
|
|
3844
|
-
const { commitment, userOp } = await this.buildUserOp(order, orderNonce);
|
|
3935
|
+
async buildAndSubmit(order, orderNonce, entryPoint) {
|
|
3936
|
+
const { commitment, userOp } = await this.buildUserOp(order, orderNonce, entryPoint);
|
|
3845
3937
|
try {
|
|
3846
3938
|
return { result: await this.client.submitOrder(userOp), commitment };
|
|
3847
3939
|
} catch (err) {
|
|
@@ -3851,7 +3943,7 @@ var LimitOrderService = class {
|
|
|
3851
3943
|
throw err;
|
|
3852
3944
|
}
|
|
3853
3945
|
}
|
|
3854
|
-
async buildUserOp(order, orderNonce) {
|
|
3946
|
+
async buildUserOp(order, orderNonce, entryPointAddress) {
|
|
3855
3947
|
const baseToken = this.assetRegistry.getAddress(order.base, order.fillChain);
|
|
3856
3948
|
const quoteToken = this.assetRegistry.getAddress(order.quote, order.fillChain);
|
|
3857
3949
|
const [baseDecimals, quoteDecimals] = await Promise.all([
|
|
@@ -3866,10 +3958,6 @@ var LimitOrderService = class {
|
|
|
3866
3958
|
baseDecimals,
|
|
3867
3959
|
quoteDecimals
|
|
3868
3960
|
});
|
|
3869
|
-
const entryPointAddress = this.configService.getEntryPointAddress(order.fillChain);
|
|
3870
|
-
if (!entryPointAddress) {
|
|
3871
|
-
throw new LimitOrderValidationError(`No EntryPoint is configured for ${order.fillChain}`);
|
|
3872
|
-
}
|
|
3873
3961
|
return this.contractService.prepareLimitOrderUserOp({
|
|
3874
3962
|
fillChain: order.fillChain,
|
|
3875
3963
|
entryPointAddress,
|
|
@@ -3900,6 +3988,12 @@ function hasExpired(expiresAt, now) {
|
|
|
3900
3988
|
const at = Date.parse(expiresAt);
|
|
3901
3989
|
return !Number.isNaN(at) && at <= now.getTime();
|
|
3902
3990
|
}
|
|
3991
|
+
function signedFor(order) {
|
|
3992
|
+
return order.entryPoint ?? import_sdk4.ENTRY_POINT_V08;
|
|
3993
|
+
}
|
|
3994
|
+
function sameAddress(a, b) {
|
|
3995
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
3996
|
+
}
|
|
3903
3997
|
function sinceMs(updatedAt, now) {
|
|
3904
3998
|
const written = Date.parse(`${updatedAt.replace(" ", "T")}Z`);
|
|
3905
3999
|
return Number.isNaN(written) ? Number.POSITIVE_INFINITY : now.getTime() - written;
|
|
@@ -17527,9 +17621,9 @@ var INTENT_GATEWAY_V2_ABI = [
|
|
|
17527
17621
|
internalType: "bytes32"
|
|
17528
17622
|
},
|
|
17529
17623
|
{
|
|
17530
|
-
name: "
|
|
17531
|
-
type: "
|
|
17532
|
-
internalType: "
|
|
17624
|
+
name: "userOpHash",
|
|
17625
|
+
type: "bytes32",
|
|
17626
|
+
internalType: "bytes32"
|
|
17533
17627
|
},
|
|
17534
17628
|
{
|
|
17535
17629
|
name: "signature",
|
|
@@ -18904,10 +18998,13 @@ var LIMIT_ORDER_CALL_GAS_LIMIT = 500000n;
|
|
|
18904
18998
|
var LIMIT_ORDER_VERIFICATION_GAS_LIMIT = 150000n;
|
|
18905
18999
|
var LIMIT_ORDER_PRE_VERIFICATION_GAS = 50000n;
|
|
18906
19000
|
var BID_DISCOVERY_PAD_SECONDS = 30;
|
|
19001
|
+
var BLOCK_TIME_SAMPLE_BLOCKS = 1000n;
|
|
19002
|
+
var FALLBACK_BLOCK_TIME_SECONDS = 2;
|
|
18907
19003
|
var ContractInteractionService = class {
|
|
18908
19004
|
constructor(clientManager, configService, signer, sharedCacheService) {
|
|
18909
19005
|
this.clientManager = clientManager;
|
|
18910
19006
|
this.sdkHelperCache = /* @__PURE__ */ new Map();
|
|
19007
|
+
this.blockTimes = /* @__PURE__ */ new Map();
|
|
18911
19008
|
this.logger = moduleLogger(configService.loggers, "contract-service");
|
|
18912
19009
|
this.configService = configService;
|
|
18913
19010
|
this.cacheService = sharedCacheService || new CacheService();
|
|
@@ -18967,11 +19064,20 @@ var ContractInteractionService = class {
|
|
|
18967
19064
|
try {
|
|
18968
19065
|
const chainIds = this.configService.getConfiguredChainIds();
|
|
18969
19066
|
const chainNames = chainIds.map((id) => `EVM-${id}`);
|
|
18970
|
-
|
|
19067
|
+
await Promise.all(
|
|
19068
|
+
chainNames.map(async (chainName2) => {
|
|
19069
|
+
try {
|
|
19070
|
+
await this.blockTimeSeconds(chainName2);
|
|
19071
|
+
} catch (err) {
|
|
19072
|
+
this.logger.warn({ err, chain: chainName2 }, "Could not measure the block time at startup");
|
|
19073
|
+
}
|
|
19074
|
+
})
|
|
19075
|
+
);
|
|
19076
|
+
for (const chainName2 of chainNames) {
|
|
18971
19077
|
try {
|
|
18972
|
-
await this.getFeeTokenWithDecimals(
|
|
19078
|
+
await this.getFeeTokenWithDecimals(chainName2);
|
|
18973
19079
|
} catch (err) {
|
|
18974
|
-
this.logger.warn({ err, chain:
|
|
19080
|
+
this.logger.warn({ err, chain: chainName2 }, "Could not prewarm fee token decimals");
|
|
18975
19081
|
}
|
|
18976
19082
|
}
|
|
18977
19083
|
for (const destChain of chainNames) {
|
|
@@ -19556,18 +19662,59 @@ var ContractInteractionService = class {
|
|
|
19556
19662
|
*
|
|
19557
19663
|
* Operators configure seconds because that is the unit the risk is actually in, but the
|
|
19558
19664
|
* contract compares against block numbers so `order.deadline` and this read the same
|
|
19559
|
-
* clock. The conversion uses the chain's
|
|
19560
|
-
* up, since erring long costs a slightly stale quote while erring
|
|
19561
|
-
* winnable bids.
|
|
19665
|
+
* clock. The conversion uses the chain's block time from {@link blockTimeSeconds}; it is
|
|
19666
|
+
* deliberately rounded up, since erring long costs a slightly stale quote while erring
|
|
19667
|
+
* short silently drops winnable bids.
|
|
19562
19668
|
*/
|
|
19563
19669
|
async bidValidUntilBlock(chain) {
|
|
19564
19670
|
const client = this.clientManager.getPublicClient(chain);
|
|
19565
|
-
const currentBlock = await client.getBlockNumber();
|
|
19566
|
-
const blockTimeMs = client.chain?.blockTime;
|
|
19567
|
-
const blockTimeSec = blockTimeMs ? blockTimeMs / 1e3 : 2;
|
|
19671
|
+
const [currentBlock, blockTimeSec] = await Promise.all([client.getBlockNumber(), this.blockTimeSeconds(chain)]);
|
|
19568
19672
|
const windowSec = this.configService.getBidValiditySeconds() + BID_DISCOVERY_PAD_SECONDS;
|
|
19569
19673
|
return currentBlock + BigInt(Math.ceil(windowSec / blockTimeSec));
|
|
19570
19674
|
}
|
|
19675
|
+
/**
|
|
19676
|
+
* Seconds per block on `chain`. Measured once, when simplex starts ({@link initCache}), and
|
|
19677
|
+
* kept until it stops. A chain with no measurement yet, because it was added later or its
|
|
19678
|
+
* blocks could not be read at startup, is measured at its first bid.
|
|
19679
|
+
*
|
|
19680
|
+
* The nominal `Chain.blockTime` cannot be relied on alone: it goes stale when a chain
|
|
19681
|
+
* speeds up (BSC makes a block every 0.45s against a declared 0.75s) and some chains
|
|
19682
|
+
* declare none. Converting at a slower block time than the chain's ends a bid early, so
|
|
19683
|
+
* the faster of the measured and the declared figure is used, and a stalled or idle
|
|
19684
|
+
* stretch in the sample never shortens a bid.
|
|
19685
|
+
*
|
|
19686
|
+
* Blocks that cannot be read fall back to the declared figure, then to
|
|
19687
|
+
* {@link FALLBACK_BLOCK_TIME_SECONDS}. That answer is not kept.
|
|
19688
|
+
*/
|
|
19689
|
+
blockTimeSeconds(chain) {
|
|
19690
|
+
const known = this.blockTimes.get(chain);
|
|
19691
|
+
if (known) return known;
|
|
19692
|
+
const client = this.clientManager.getPublicClient(chain);
|
|
19693
|
+
const declaredMs = client.chain?.blockTime;
|
|
19694
|
+
const declared = declaredMs ? declaredMs / 1e3 : void 0;
|
|
19695
|
+
const seconds = this.measureBlockTimeSeconds(client).then((measured) => declared ? Math.min(measured, declared) : measured).catch((err) => {
|
|
19696
|
+
this.blockTimes.delete(chain);
|
|
19697
|
+
const fallback2 = declared ?? FALLBACK_BLOCK_TIME_SECONDS;
|
|
19698
|
+
this.logger.warn(
|
|
19699
|
+
{ err, chain, seconds: fallback2 },
|
|
19700
|
+
"Could not measure the block time; using the declared one"
|
|
19701
|
+
);
|
|
19702
|
+
return fallback2;
|
|
19703
|
+
});
|
|
19704
|
+
this.blockTimes.set(chain, seconds);
|
|
19705
|
+
return seconds;
|
|
19706
|
+
}
|
|
19707
|
+
/** The average block time over the chain's last {@link BLOCK_TIME_SAMPLE_BLOCKS} blocks, in seconds. */
|
|
19708
|
+
async measureBlockTimeSeconds(client) {
|
|
19709
|
+
const head = await client.getBlock({ blockTag: "latest" });
|
|
19710
|
+
const span = head.number < BLOCK_TIME_SAMPLE_BLOCKS ? head.number : BLOCK_TIME_SAMPLE_BLOCKS;
|
|
19711
|
+
const tail = await client.getBlock({ blockNumber: head.number - span });
|
|
19712
|
+
const measured = Number(head.timestamp - tail.timestamp) / Number(span);
|
|
19713
|
+
if (!Number.isFinite(measured) || measured <= 0) {
|
|
19714
|
+
throw new Error(`${span} blocks span ${head.timestamp - tail.timestamp}s`);
|
|
19715
|
+
}
|
|
19716
|
+
return measured;
|
|
19717
|
+
}
|
|
19571
19718
|
/**
|
|
19572
19719
|
* Builds the signed, never-executed UserOperation that carries one limit order
|
|
19573
19720
|
* to the HyperFX orderbook.
|
|
@@ -19747,6 +19894,8 @@ var import_viem24 = require("viem");
|
|
|
19747
19894
|
var FALLBACK_VERIFICATION_GAS_LIMIT = 250000n;
|
|
19748
19895
|
var FALLBACK_CALL_GAS_LIMIT = 1500000n;
|
|
19749
19896
|
var FALLBACK_PRE_VERIFICATION_GAS = 150000n;
|
|
19897
|
+
var PRIORITY_FEE_BUMP_PERCENT = 8n;
|
|
19898
|
+
var MAX_FEE_BUMP_PERCENT = 10n;
|
|
19750
19899
|
var UserOpSender = class {
|
|
19751
19900
|
constructor(clientManager, configService, signer) {
|
|
19752
19901
|
this.clientManager = clientManager;
|
|
@@ -19773,7 +19922,7 @@ var UserOpSender = class {
|
|
|
19773
19922
|
let auth;
|
|
19774
19923
|
let fees;
|
|
19775
19924
|
try {
|
|
19776
|
-
fees = await this.getGasPrice(bundlerUrl, publicClient
|
|
19925
|
+
fees = await this.getGasPrice(bundlerUrl, publicClient);
|
|
19777
19926
|
const gasForPrefund = gas ?? {
|
|
19778
19927
|
verificationGasLimit: FALLBACK_VERIFICATION_GAS_LIMIT,
|
|
19779
19928
|
callGasLimit: FALLBACK_CALL_GAS_LIMIT,
|
|
@@ -19927,31 +20076,27 @@ var UserOpSender = class {
|
|
|
19927
20076
|
);
|
|
19928
20077
|
return userOp;
|
|
19929
20078
|
}
|
|
19930
|
-
/**
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
|
|
19935
|
-
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
19942
|
-
|
|
19943
|
-
|
|
19944
|
-
|
|
19945
|
-
|
|
19946
|
-
|
|
19947
|
-
|
|
19948
|
-
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
return { maxFeePerGas: bufferedBaseFee + maxPriorityFeePerGas, maxPriorityFeePerGas };
|
|
19952
|
-
}
|
|
19953
|
-
const gasPrice = await publicClient.getGasPrice();
|
|
19954
|
-
return { maxFeePerGas: gasPrice + gasPrice * 10n / 100n, maxPriorityFeePerGas: gasPrice + gasPrice * 8n / 100n };
|
|
20079
|
+
/**
|
|
20080
|
+
* Prices the op for the rundler bundler, as the fill path does: its suggested fees, or the
|
|
20081
|
+
* chain's gas price raised to the priority fee it requires where it suggests none.
|
|
20082
|
+
*/
|
|
20083
|
+
async getGasPrice(bundlerUrl, publicClient) {
|
|
20084
|
+
const [gasPrice, latestBlock] = await Promise.all([
|
|
20085
|
+
publicClient.getGasPrice(),
|
|
20086
|
+
publicClient.getBlock({ blockTag: "latest" })
|
|
20087
|
+
]);
|
|
20088
|
+
return (0, import_sdk8.rundlerUserOperationFees)(
|
|
20089
|
+
bundlerUrl,
|
|
20090
|
+
{
|
|
20091
|
+
maxFeePerGas: gasPrice + gasPrice * MAX_FEE_BUMP_PERCENT / 100n,
|
|
20092
|
+
maxPriorityFeePerGas: gasPrice + gasPrice * PRIORITY_FEE_BUMP_PERCENT / 100n
|
|
20093
|
+
},
|
|
20094
|
+
{
|
|
20095
|
+
baseFeePerGas: latestBlock.baseFeePerGas ?? gasPrice,
|
|
20096
|
+
priorityFeeBumpPercent: PRIORITY_FEE_BUMP_PERCENT,
|
|
20097
|
+
maxFeeBumpPercent: MAX_FEE_BUMP_PERCENT
|
|
20098
|
+
}
|
|
20099
|
+
);
|
|
19955
20100
|
}
|
|
19956
20101
|
async sendBundlerRpc(bundlerUrl, method, params) {
|
|
19957
20102
|
const response = await fetch(bundlerUrl, {
|
|
@@ -23672,6 +23817,82 @@ var TokenSender = class {
|
|
|
23672
23817
|
}
|
|
23673
23818
|
};
|
|
23674
23819
|
|
|
23820
|
+
// src/services/bundler-preflight.ts
|
|
23821
|
+
var BUNDLER_PREFLIGHT_TIMEOUT_MS = 1e4;
|
|
23822
|
+
async function assertBundlersServeEntryPoint(chains, configService, options = {}) {
|
|
23823
|
+
const timeoutMs = options.timeoutMs ?? BUNDLER_PREFLIGHT_TIMEOUT_MS;
|
|
23824
|
+
const logger = moduleLogger(configService.loggers, "bundler-preflight");
|
|
23825
|
+
const failures = await Promise.all(
|
|
23826
|
+
chains.map(async ({ chainId, bundlerUrl }) => {
|
|
23827
|
+
if (options.watchOnly?.[chainId] === true) return void 0;
|
|
23828
|
+
if (!bundlerUrl?.trim()) {
|
|
23829
|
+
return `Hyperbridge runs no bundler for ${chainName(chainId)}, so simplex cannot fill there; set it watch-only`;
|
|
23830
|
+
}
|
|
23831
|
+
const entryPoint = configService.getEntryPointAddress(formatChainKey(chainId));
|
|
23832
|
+
if (!entryPoint) return void 0;
|
|
23833
|
+
return checkBundler(chainId, bundlerUrl.trim(), entryPoint, timeoutMs, logger);
|
|
23834
|
+
})
|
|
23835
|
+
);
|
|
23836
|
+
const messages = failures.filter((failure) => failure !== void 0);
|
|
23837
|
+
if (messages.length > 0) throw new Error(messages.join("; "));
|
|
23838
|
+
}
|
|
23839
|
+
function servesEntryPoint(listed, entryPoint) {
|
|
23840
|
+
return Array.isArray(listed) && listed.some((address) => String(address).toLowerCase() === entryPoint.toLowerCase());
|
|
23841
|
+
}
|
|
23842
|
+
function describeEntryPoints(listed) {
|
|
23843
|
+
return Array.isArray(listed) && listed.length > 0 ? listed.map(String).join(", ") : "none";
|
|
23844
|
+
}
|
|
23845
|
+
function bundlerHost(url) {
|
|
23846
|
+
try {
|
|
23847
|
+
return new URL(url).host || "an unparseable URL";
|
|
23848
|
+
} catch {
|
|
23849
|
+
return "an unparseable URL";
|
|
23850
|
+
}
|
|
23851
|
+
}
|
|
23852
|
+
function chainName(chainId) {
|
|
23853
|
+
const label = chainByChainId(chainId)?.label;
|
|
23854
|
+
return label ? `${label} (${formatChainKey(chainId)})` : formatChainKey(chainId);
|
|
23855
|
+
}
|
|
23856
|
+
async function checkBundler(chainId, bundlerUrl, entryPoint, timeoutMs, logger) {
|
|
23857
|
+
const host = bundlerHost(bundlerUrl);
|
|
23858
|
+
const subject = `Bundler ${host} for ${chainName(chainId)}`;
|
|
23859
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
23860
|
+
let listed;
|
|
23861
|
+
try {
|
|
23862
|
+
listed = await supportedEntryPoints(bundlerUrl, signal);
|
|
23863
|
+
} catch (err) {
|
|
23864
|
+
const reason = signal.aborted ? `timed out after ${timeoutMs}ms` : errorReason(err);
|
|
23865
|
+
logger.warn(
|
|
23866
|
+
{ chain: formatChainKey(chainId), bundler: host },
|
|
23867
|
+
`${subject} did not answer eth_supportedEntryPoints: ${reason.split(bundlerUrl).join(host)}. Continuing without confirming it serves EntryPoint ${entryPoint}`
|
|
23868
|
+
);
|
|
23869
|
+
return void 0;
|
|
23870
|
+
}
|
|
23871
|
+
if (servesEntryPoint(listed, entryPoint)) return void 0;
|
|
23872
|
+
return `${subject} does not support EntryPoint ${entryPoint}; it lists ${describeEntryPoints(listed)}`;
|
|
23873
|
+
}
|
|
23874
|
+
async function supportedEntryPoints(bundlerUrl, signal) {
|
|
23875
|
+
const response = await fetch(bundlerUrl, {
|
|
23876
|
+
method: "POST",
|
|
23877
|
+
headers: { "Content-Type": "application/json" },
|
|
23878
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_supportedEntryPoints", params: [] }),
|
|
23879
|
+
signal
|
|
23880
|
+
});
|
|
23881
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
23882
|
+
const json = await response.json();
|
|
23883
|
+
if (json.error) throw new Error(json.error.message ?? JSON.stringify(json.error));
|
|
23884
|
+
const { result } = json;
|
|
23885
|
+
if (!Array.isArray(result) || !result.every((address) => typeof address === "string")) {
|
|
23886
|
+
throw new Error("the result is not a list of addresses");
|
|
23887
|
+
}
|
|
23888
|
+
return result;
|
|
23889
|
+
}
|
|
23890
|
+
function errorReason(err) {
|
|
23891
|
+
if (!(err instanceof Error)) return String(err);
|
|
23892
|
+
const { cause } = err;
|
|
23893
|
+
return cause instanceof Error ? `${err.message} (${cause.message})` : err.message;
|
|
23894
|
+
}
|
|
23895
|
+
|
|
23675
23896
|
// src/orderbook/lifecycle.ts
|
|
23676
23897
|
var EXPIRY_SWEEP_MS = 3e4;
|
|
23677
23898
|
var LimitOrderLifecycle = class {
|
|
@@ -24482,8 +24703,8 @@ async function bootFiller(config, options) {
|
|
|
24482
24703
|
};
|
|
24483
24704
|
const configService = new FillerConfigService(resolvedChains, fillerConfigForService, options.loggers);
|
|
24484
24705
|
const chainConfigs = resolvedChains.map((chain) => {
|
|
24485
|
-
const
|
|
24486
|
-
return configService.getChainConfig(
|
|
24706
|
+
const chainName2 = `EVM-${chain.chainId}`;
|
|
24707
|
+
return configService.getChainConfig(chainName2);
|
|
24487
24708
|
});
|
|
24488
24709
|
let watchOnlyConfig;
|
|
24489
24710
|
const globalWatchOnly = options.watchOnlyOverride === true || config.simplex.watchOnly === true;
|
|
@@ -24521,6 +24742,8 @@ async function bootFiller(config, options) {
|
|
|
24521
24742
|
"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]."
|
|
24522
24743
|
);
|
|
24523
24744
|
}
|
|
24745
|
+
logger.info("Checking each bundler serves its chain's EntryPoint...");
|
|
24746
|
+
await assertBundlersServeEntryPoint(resolvedChains, configService, { watchOnly: watchOnlyConfig });
|
|
24524
24747
|
const chainClientManager = new ChainClientManager(configService, options.signer);
|
|
24525
24748
|
const runtimeSigner = chainClientManager.getSigner();
|
|
24526
24749
|
if (options.signer) {
|
|
@@ -24711,10 +24934,10 @@ async function bootFiller(config, options) {
|
|
|
24711
24934
|
const token1 = {};
|
|
24712
24935
|
for (const pair of config.pairs ?? []) {
|
|
24713
24936
|
if (normalizeSymbol(pair.token0) === normalizeSymbol(pair.token1)) continue;
|
|
24714
|
-
for (const
|
|
24715
|
-
const address = assetRegistry.getAddress(pair.token1,
|
|
24937
|
+
for (const chainName2 of configuredChainNames) {
|
|
24938
|
+
const address = assetRegistry.getAddress(pair.token1, chainName2);
|
|
24716
24939
|
if (!address) continue;
|
|
24717
|
-
const list = token1[
|
|
24940
|
+
const list = token1[chainName2] ?? (token1[chainName2] = []);
|
|
24718
24941
|
if (!list.includes(address)) list.push(address);
|
|
24719
24942
|
}
|
|
24720
24943
|
}
|
|
@@ -25110,6 +25333,7 @@ var MemoryLimitOrderStore = class {
|
|
|
25110
25333
|
status: "open",
|
|
25111
25334
|
commitment: null,
|
|
25112
25335
|
orderNonce: order.orderNonce ?? "0",
|
|
25336
|
+
entryPoint: null,
|
|
25113
25337
|
bookExpiresAt: null,
|
|
25114
25338
|
bookPrice: null,
|
|
25115
25339
|
lastError: null,
|
|
@@ -25745,7 +25969,7 @@ var OrderScanner = class _OrderScanner {
|
|
|
25745
25969
|
async addChainNow(chain) {
|
|
25746
25970
|
if (this.closed) throw new Error("This OrderScanner is closed");
|
|
25747
25971
|
const rpcUrls = validateRpcUrls(chain.rpcUrls);
|
|
25748
|
-
const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls
|
|
25972
|
+
const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls }], {
|
|
25749
25973
|
loggers: this.loggers,
|
|
25750
25974
|
tolerateUnreachable: true
|
|
25751
25975
|
}))[0].chainId;
|
|
@@ -26095,10 +26319,10 @@ var PairController = class {
|
|
|
26095
26319
|
if (assets && Object.keys(assets).length > 0) config.assets = nextAssets;
|
|
26096
26320
|
if (normalizeSymbol(pair.token0) !== normalizeSymbol(pair.token1)) {
|
|
26097
26321
|
for (const chain of this.runtime.resolvedChains) {
|
|
26098
|
-
const
|
|
26099
|
-
const address = assetRegistry.getAddress(pair.token1,
|
|
26322
|
+
const chainName2 = formatChainKey(chain.chainId);
|
|
26323
|
+
const address = assetRegistry.getAddress(pair.token1, chainName2);
|
|
26100
26324
|
if (!address) continue;
|
|
26101
|
-
const list = balanceTokens[
|
|
26325
|
+
const list = balanceTokens[chainName2] ?? (balanceTokens[chainName2] = []);
|
|
26102
26326
|
if (!list.includes(address)) list.push(address);
|
|
26103
26327
|
}
|
|
26104
26328
|
}
|
|
@@ -26177,10 +26401,7 @@ var ChainController = class {
|
|
|
26177
26401
|
async add(chain) {
|
|
26178
26402
|
return this.serialise(async () => {
|
|
26179
26403
|
validateRpcUrls(chain.rpcUrls);
|
|
26180
|
-
|
|
26181
|
-
throw new Error("A bundler URL is required to submit fill UserOperations");
|
|
26182
|
-
}
|
|
26183
|
-
const [resolved] = await resolveChainConfigs([{ rpcUrls: chain.rpcUrls, bundlerUrl: chain.bundlerUrl }]);
|
|
26404
|
+
const [resolved] = await resolveChainConfigs([{ rpcUrls: chain.rpcUrls }]);
|
|
26184
26405
|
const { chainId } = resolved;
|
|
26185
26406
|
const { configService, config, intentFiller } = this.runtime;
|
|
26186
26407
|
if (configService.getConfiguredChainIds().includes(chainId)) {
|
|
@@ -26203,12 +26424,13 @@ var ChainController = class {
|
|
|
26203
26424
|
`Chain ${chainId} cannot be added as filling: this solver was started without a signer. Restart with \`signer\` to fill.`
|
|
26204
26425
|
);
|
|
26205
26426
|
}
|
|
26206
|
-
configService.addChain(resolved);
|
|
26207
26427
|
const watchOnly = chain.watchOnly ?? (this.runtime.signerless || this.runtime.globalWatchOnly);
|
|
26428
|
+
await assertBundlersServeEntryPoint([resolved], configService, { watchOnly: { [chainId]: watchOnly } });
|
|
26429
|
+
configService.addChain(resolved);
|
|
26208
26430
|
if (watchOnly) intentFiller.setWatchOnly(chainId, true);
|
|
26209
26431
|
try {
|
|
26210
26432
|
if (!inScanner) {
|
|
26211
|
-
await this.scanner.addChain({ rpcUrls: chain.rpcUrls,
|
|
26433
|
+
await this.scanner.addChain({ rpcUrls: chain.rpcUrls, chainId });
|
|
26212
26434
|
}
|
|
26213
26435
|
this.runtime.chainClientManager.invalidate(formatChainKey(chainId));
|
|
26214
26436
|
await intentFiller.addChain(configService.getChainConfig(formatChainKey(chainId)));
|
|
@@ -26225,7 +26447,7 @@ var ChainController = class {
|
|
|
26225
26447
|
this.runtime.confirmationPolicy.adopt(chainId, resolvedPolicy);
|
|
26226
26448
|
}
|
|
26227
26449
|
this.runtime.resolvedChains.push(resolved);
|
|
26228
|
-
config.chains = [...config.chains, { rpcUrls: chain.rpcUrls
|
|
26450
|
+
config.chains = [...config.chains, { rpcUrls: chain.rpcUrls }];
|
|
26229
26451
|
if (Object.keys(confirmationPolicies).length > 0) config.confirmationPolicies = confirmationPolicies;
|
|
26230
26452
|
this.syncWatchOnlyToConfig();
|
|
26231
26453
|
await this.persist();
|
|
@@ -26270,9 +26492,7 @@ var ChainController = class {
|
|
|
26270
26492
|
validateRpcUrls(rpcUrls);
|
|
26271
26493
|
const index = this.runtime.resolvedChains.findIndex((chain) => chain.chainId === chainId);
|
|
26272
26494
|
if (index < 0) throw new Error(`Chain ${chainId} is not configured`);
|
|
26273
|
-
const [probed] = await resolveChainConfigs([
|
|
26274
|
-
{ rpcUrls, bundlerUrl: this.runtime.resolvedChains[index].bundlerUrl ?? "" }
|
|
26275
|
-
]);
|
|
26495
|
+
const [probed] = await resolveChainConfigs([{ rpcUrls }]);
|
|
26276
26496
|
if (probed.chainId !== chainId) {
|
|
26277
26497
|
throw new Error(`Those endpoints answer for chain ${probed.chainId}, not ${chainId}`);
|
|
26278
26498
|
}
|
|
@@ -26291,17 +26511,6 @@ var ChainController = class {
|
|
|
26291
26511
|
await this.persist();
|
|
26292
26512
|
});
|
|
26293
26513
|
}
|
|
26294
|
-
async setBundlerUrl(chainId, bundlerUrl) {
|
|
26295
|
-
return this.serialise(async () => {
|
|
26296
|
-
if (!bundlerUrl?.trim()) throw new Error("A bundler URL is required");
|
|
26297
|
-
const index = this.runtime.resolvedChains.findIndex((chain) => chain.chainId === chainId);
|
|
26298
|
-
if (index < 0) throw new Error(`Chain ${chainId} is not configured`);
|
|
26299
|
-
this.runtime.configService.setBundlerUrl(chainId, bundlerUrl);
|
|
26300
|
-
this.runtime.resolvedChains[index].bundlerUrl = bundlerUrl;
|
|
26301
|
-
this.runtime.config.chains[index].bundlerUrl = bundlerUrl;
|
|
26302
|
-
await this.persist();
|
|
26303
|
-
});
|
|
26304
|
-
}
|
|
26305
26514
|
/** Monitor without filling. Takes effect on the next order. */
|
|
26306
26515
|
async setWatchOnly(chainId, watchOnly) {
|
|
26307
26516
|
return this.serialise(async () => {
|
|
@@ -26310,6 +26519,10 @@ var ChainController = class {
|
|
|
26310
26519
|
"Watch-only cannot be disabled: this solver was started without a signer. Restart with `signer` to fill."
|
|
26311
26520
|
);
|
|
26312
26521
|
}
|
|
26522
|
+
if (!watchOnly) {
|
|
26523
|
+
const chain = this.runtime.resolvedChains.find((resolved) => resolved.chainId === chainId);
|
|
26524
|
+
if (chain) await assertBundlersServeEntryPoint([chain], this.runtime.configService);
|
|
26525
|
+
}
|
|
26313
26526
|
this.runtime.intentFiller.setWatchOnly(chainId, watchOnly);
|
|
26314
26527
|
this.syncWatchOnlyToConfig();
|
|
26315
26528
|
await this.persist();
|