@hyperbridge/simplex 0.16.5 → 0.16.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/simplex.js +603 -186
- package/dist/bin/simplex.js.map +1 -1
- package/dist/index.cjs +387 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +110 -50
- package/dist/index.d.ts +110 -50
- package/dist/index.js +387 -79
- package/dist/index.js.map +1 -1
- package/dist/ui/assets/index-Be446eYK.js +106 -0
- package/dist/ui/assets/index-C3T-gubp.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -2
- package/dist/ui/assets/index-Bv9S1oj4.js +0 -106
- package/dist/ui/assets/index-DwJCXJ6a.css +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2459,6 +2459,7 @@ function closedKey(chain, vault) {
|
|
|
2459
2459
|
// src/config/external-links.json
|
|
2460
2460
|
var external_links_default = {
|
|
2461
2461
|
hyperfxApp: "https://app.hyperfx.finance",
|
|
2462
|
+
alchemyDashboard: "https://dashboard.alchemy.com/",
|
|
2462
2463
|
hyperbridgeExplorers: {
|
|
2463
2464
|
mainnet: "https://nexus.statescan.io",
|
|
2464
2465
|
testnet: "https://gargantua.statescan.io"
|
|
@@ -2478,6 +2479,52 @@ var external_links_default = {
|
|
|
2478
2479
|
};
|
|
2479
2480
|
|
|
2480
2481
|
// src/cli/init/chains.ts
|
|
2482
|
+
var FREE_RPC_URLS = {
|
|
2483
|
+
1: [
|
|
2484
|
+
"https://mainnet.gateway.tenderly.co",
|
|
2485
|
+
"https://rpc.mevblocker.io",
|
|
2486
|
+
"https://ethereum-rpc.publicnode.com",
|
|
2487
|
+
"https://ethereum.public.blockpi.network/v1/rpc/public",
|
|
2488
|
+
"https://eth.blockrazor.xyz",
|
|
2489
|
+
"https://eth.api.pocket.network",
|
|
2490
|
+
"https://ethereum-public.nodies.app",
|
|
2491
|
+
"https://eth-mainnet.public.blastapi.io",
|
|
2492
|
+
"https://public-eth.nownodes.io"
|
|
2493
|
+
],
|
|
2494
|
+
56: [
|
|
2495
|
+
"https://bsc-rpc.publicnode.com",
|
|
2496
|
+
"https://public-bsc-mainnet.fastnode.io",
|
|
2497
|
+
"https://public-bsc.nownodes.io",
|
|
2498
|
+
"https://bsc.blockrazor.xyz",
|
|
2499
|
+
"https://rpc-bsc.48.club",
|
|
2500
|
+
"https://bsc.api.pocket.network"
|
|
2501
|
+
],
|
|
2502
|
+
137: [
|
|
2503
|
+
"https://polygon-bor-rpc.publicnode.com",
|
|
2504
|
+
"https://polygon.drpc.org",
|
|
2505
|
+
"https://polygon.gateway.tenderly.co",
|
|
2506
|
+
"https://rpc.private.mev-x.com/polygon",
|
|
2507
|
+
"https://poly.api.pocket.network"
|
|
2508
|
+
],
|
|
2509
|
+
8453: [
|
|
2510
|
+
"https://base-rpc.publicnode.com",
|
|
2511
|
+
"https://base.gateway.tenderly.co",
|
|
2512
|
+
"https://base.public.blockpi.network/v1/rpc/public",
|
|
2513
|
+
"https://base.rpc.blxrbdn.com",
|
|
2514
|
+
"https://rpc.baseazul.dev",
|
|
2515
|
+
"https://mainnet.base.org",
|
|
2516
|
+
"https://base-public.nodies.app"
|
|
2517
|
+
],
|
|
2518
|
+
42161: [
|
|
2519
|
+
"https://arbitrum-one-rpc.publicnode.com",
|
|
2520
|
+
"https://arbitrum.gateway.tenderly.co",
|
|
2521
|
+
"https://arbitrum.drpc.org",
|
|
2522
|
+
"https://public-arb-mainnet.fastnode.io",
|
|
2523
|
+
"https://arb-one.api.pocket.network",
|
|
2524
|
+
"https://arbitrum-one-public.nodies.app",
|
|
2525
|
+
"https://arb1.arbitrum.io/rpc"
|
|
2526
|
+
]
|
|
2527
|
+
};
|
|
2481
2528
|
var CHAIN_NATIVE_SYMBOLS = {
|
|
2482
2529
|
1: "ETH",
|
|
2483
2530
|
56: "BNB",
|
|
@@ -2505,7 +2552,8 @@ var INIT_CHAINS = [
|
|
|
2505
2552
|
label: "Ethereum",
|
|
2506
2553
|
network: "mainnet",
|
|
2507
2554
|
alchemySubdomain: "eth-mainnet",
|
|
2508
|
-
explorerUrl: external_links_default.chainExplorers.ethereum
|
|
2555
|
+
explorerUrl: external_links_default.chainExplorers.ethereum,
|
|
2556
|
+
defaultRpcUrls: FREE_RPC_URLS[1]
|
|
2509
2557
|
},
|
|
2510
2558
|
{
|
|
2511
2559
|
chainId: 42161,
|
|
@@ -2513,7 +2561,8 @@ var INIT_CHAINS = [
|
|
|
2513
2561
|
label: "Arbitrum",
|
|
2514
2562
|
network: "mainnet",
|
|
2515
2563
|
alchemySubdomain: "arb-mainnet",
|
|
2516
|
-
explorerUrl: external_links_default.chainExplorers.arbitrum
|
|
2564
|
+
explorerUrl: external_links_default.chainExplorers.arbitrum,
|
|
2565
|
+
defaultRpcUrls: FREE_RPC_URLS[42161]
|
|
2517
2566
|
},
|
|
2518
2567
|
{
|
|
2519
2568
|
chainId: 8453,
|
|
@@ -2521,7 +2570,8 @@ var INIT_CHAINS = [
|
|
|
2521
2570
|
label: "Base",
|
|
2522
2571
|
network: "mainnet",
|
|
2523
2572
|
alchemySubdomain: "base-mainnet",
|
|
2524
|
-
explorerUrl: external_links_default.chainExplorers.base
|
|
2573
|
+
explorerUrl: external_links_default.chainExplorers.base,
|
|
2574
|
+
defaultRpcUrls: FREE_RPC_URLS[8453]
|
|
2525
2575
|
},
|
|
2526
2576
|
{
|
|
2527
2577
|
chainId: 137,
|
|
@@ -2529,7 +2579,8 @@ var INIT_CHAINS = [
|
|
|
2529
2579
|
label: "Polygon",
|
|
2530
2580
|
network: "mainnet",
|
|
2531
2581
|
alchemySubdomain: "polygon-mainnet",
|
|
2532
|
-
explorerUrl: external_links_default.chainExplorers.polygon
|
|
2582
|
+
explorerUrl: external_links_default.chainExplorers.polygon,
|
|
2583
|
+
defaultRpcUrls: FREE_RPC_URLS[137]
|
|
2533
2584
|
},
|
|
2534
2585
|
{
|
|
2535
2586
|
chainId: 56,
|
|
@@ -2537,7 +2588,8 @@ var INIT_CHAINS = [
|
|
|
2537
2588
|
label: "BNB Chain",
|
|
2538
2589
|
network: "mainnet",
|
|
2539
2590
|
alchemySubdomain: "bnb-mainnet",
|
|
2540
|
-
explorerUrl: external_links_default.chainExplorers.bnb
|
|
2591
|
+
explorerUrl: external_links_default.chainExplorers.bnb,
|
|
2592
|
+
defaultRpcUrls: FREE_RPC_URLS[56]
|
|
2541
2593
|
},
|
|
2542
2594
|
{
|
|
2543
2595
|
chainId: 11155111,
|
|
@@ -2654,10 +2706,16 @@ var CANCEL_ORDER_MUTATION = `
|
|
|
2654
2706
|
`;
|
|
2655
2707
|
var OrderbookRequestError = class extends Error {
|
|
2656
2708
|
};
|
|
2709
|
+
function graphqlEndpoint(url) {
|
|
2710
|
+
const trimmed = url.trim();
|
|
2711
|
+
if (trimmed.includes("?")) return trimmed;
|
|
2712
|
+
const withoutTrailingSlash = trimmed.replace(/\/+$/, "");
|
|
2713
|
+
return withoutTrailingSlash.endsWith("/graphql") ? withoutTrailingSlash : `${withoutTrailingSlash}/graphql`;
|
|
2714
|
+
}
|
|
2657
2715
|
var OrderbookClient = class {
|
|
2658
2716
|
constructor(url, requestTimeoutMs, loggers = defaultLoggerContext()) {
|
|
2659
|
-
this.url = url;
|
|
2660
2717
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
2718
|
+
this.url = graphqlEndpoint(url);
|
|
2661
2719
|
this.logger = loggers.get("orderbook");
|
|
2662
2720
|
}
|
|
2663
2721
|
/** The limits to validate against before posting, plus the books on offer. */
|
|
@@ -2755,7 +2813,9 @@ var OrderbookClient = class {
|
|
|
2755
2813
|
clearTimeout(timer);
|
|
2756
2814
|
}
|
|
2757
2815
|
if (!response.ok) {
|
|
2758
|
-
throw new OrderbookRequestError(
|
|
2816
|
+
throw new OrderbookRequestError(
|
|
2817
|
+
`Orderbook at ${this.url} returned HTTP ${response.status} ${response.statusText}`
|
|
2818
|
+
);
|
|
2759
2819
|
}
|
|
2760
2820
|
const body = await response.json().catch((err) => {
|
|
2761
2821
|
throw new OrderbookRequestError(`Orderbook returned a body that is not JSON: ${describe(err)}`);
|
|
@@ -2870,7 +2930,7 @@ var HEARTBEAT_TYPES = {
|
|
|
2870
2930
|
var LimitOrderValidationError = class extends Error {
|
|
2871
2931
|
};
|
|
2872
2932
|
var LimitOrderService = class {
|
|
2873
|
-
constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce) {
|
|
2933
|
+
constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce, vaultBalances) {
|
|
2874
2934
|
this.store = store;
|
|
2875
2935
|
this.client = client;
|
|
2876
2936
|
this.contractService = contractService;
|
|
@@ -2880,6 +2940,7 @@ var LimitOrderService = class {
|
|
|
2880
2940
|
this.defaultTtlSecs = defaultTtlSecs;
|
|
2881
2941
|
this.delegationService = delegationService;
|
|
2882
2942
|
this.startingNonce = startingNonce;
|
|
2943
|
+
this.vaultBalances = vaultBalances;
|
|
2883
2944
|
this.logger = loggers.get("limit-orders");
|
|
2884
2945
|
}
|
|
2885
2946
|
/**
|
|
@@ -3015,6 +3076,18 @@ var LimitOrderService = class {
|
|
|
3015
3076
|
throw err;
|
|
3016
3077
|
}
|
|
3017
3078
|
}
|
|
3079
|
+
/**
|
|
3080
|
+
* The pairs an order can be written against, the smallest payout each token may carry, and
|
|
3081
|
+
* the tokens the orderbook registers on each chain.
|
|
3082
|
+
*
|
|
3083
|
+
* The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
|
|
3084
|
+
* operator is offered exactly these and no combination of symbols they could not post. The
|
|
3085
|
+
* chains do the same for the source chains an order can accept.
|
|
3086
|
+
*/
|
|
3087
|
+
async books() {
|
|
3088
|
+
const limits = await this.limits();
|
|
3089
|
+
return { books: limits.books, minOrderSizes: limits.serverInfo.minOrderSizes, chains: limits.chains ?? [] };
|
|
3090
|
+
}
|
|
3018
3091
|
/**
|
|
3019
3092
|
* Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
|
|
3020
3093
|
*
|
|
@@ -3095,6 +3168,18 @@ var LimitOrderService = class {
|
|
|
3095
3168
|
);
|
|
3096
3169
|
}
|
|
3097
3170
|
}
|
|
3171
|
+
if (book.base !== book.quote) {
|
|
3172
|
+
const input = normalizeSymbol(request.tokenIn);
|
|
3173
|
+
const lacking = sources.filter((source) => {
|
|
3174
|
+
const listed = limits.chains?.find((chain) => chain.id === source);
|
|
3175
|
+
return listed && !listed.tokens.some((token) => normalizeSymbol(token.symbol) === input);
|
|
3176
|
+
});
|
|
3177
|
+
if (lacking.length > 0) {
|
|
3178
|
+
throw new LimitOrderValidationError(
|
|
3179
|
+
`The orderbook has no ${request.tokenIn} on ${lacking.join(", ")}, so swaps from there cannot pay this order`
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3098
3183
|
if (ttlSecs < limits.serverInfo.minOrderTtlSecs) {
|
|
3099
3184
|
throw new LimitOrderValidationError(
|
|
3100
3185
|
`ttlSecs must be at least the orderbook's minimum of ${limits.serverInfo.minOrderTtlSecs}; got ${ttlSecs}`
|
|
@@ -3122,28 +3207,61 @@ var LimitOrderService = class {
|
|
|
3122
3207
|
* balance on the fill chain, so the operator hears it while they are creating
|
|
3123
3208
|
* the order.
|
|
3124
3209
|
*
|
|
3210
|
+
* A fill pays out of the wallet and then withdraws any shortfall from the
|
|
3211
|
+
* configured ERC-4626 vaults in the same batch, so what those vaults hold of
|
|
3212
|
+
* the payout token backs the order too and is counted here. Without it an
|
|
3213
|
+
* operator who sweeps inventory into a vault is refused orders their filler
|
|
3214
|
+
* would have filled.
|
|
3215
|
+
*
|
|
3125
3216
|
* Each order is checked against the whole balance, not against what is left of
|
|
3126
3217
|
* it after the others. One balance backs every order resting on it — that is
|
|
3127
3218
|
* what quoting both sides of a book is — and the orderbook says as much: it
|
|
3128
3219
|
* advertises each entry at `min(quoted, balance)` rather than dividing the
|
|
3129
3220
|
* balance between them. Whichever order fills first draws the inventory down,
|
|
3130
3221
|
* and the rest are cut to what is left. Netting them here would refuse the
|
|
3131
|
-
* second side of every book.
|
|
3222
|
+
* second side of every book. Vault positions are counted the same way: the
|
|
3223
|
+
* whole position, not what is left after reservations for fills in flight.
|
|
3132
3224
|
*/
|
|
3133
3225
|
async assertWalletCanPay(symbol, chain, payout) {
|
|
3134
3226
|
const token = this.assetRegistry.getAddress(symbol, chain);
|
|
3135
3227
|
if (!token) return;
|
|
3136
3228
|
const decimals = await this.decimalsFor(symbol, token, chain);
|
|
3137
|
-
const
|
|
3229
|
+
const wallet = toScaled(
|
|
3138
3230
|
await this.contractService.getTokenBalance(chain, token, this.signer.address),
|
|
3139
3231
|
decimals
|
|
3140
3232
|
);
|
|
3141
|
-
|
|
3233
|
+
const vaults = await this.vaultHoldings(token, chain);
|
|
3234
|
+
if (wallet + vaults < payout) {
|
|
3235
|
+
const held = vaults > 0n ? `${toHuman(wallet)} in the wallet and ${toHuman(vaults)} in vaults` : toHuman(wallet);
|
|
3142
3236
|
throw new LimitOrderValidationError(
|
|
3143
|
-
`The wallet holds ${
|
|
3237
|
+
`The wallet holds ${held} ${symbol} on ${chain}, which cannot pay out ${toHuman(payout)}`
|
|
3144
3238
|
);
|
|
3145
3239
|
}
|
|
3146
3240
|
}
|
|
3241
|
+
/**
|
|
3242
|
+
* What the configured vaults hold of `token` on `chain`, in scaled units.
|
|
3243
|
+
*
|
|
3244
|
+
* Zero when no vault is configured. A snapshot that cannot be read is also
|
|
3245
|
+
* zero: the wallet alone then has to cover the order, which refuses one the
|
|
3246
|
+
* filler might have managed rather than accepting one it could not.
|
|
3247
|
+
*/
|
|
3248
|
+
async vaultHoldings(token, chain) {
|
|
3249
|
+
if (!this.vaultBalances) return 0n;
|
|
3250
|
+
let positions;
|
|
3251
|
+
try {
|
|
3252
|
+
positions = await this.vaultBalances.getBalanceSnapshot(chain);
|
|
3253
|
+
} catch (err) {
|
|
3254
|
+
this.logger.warn({ chain, token, err }, "Could not read vault balances; backing the order on the wallet alone");
|
|
3255
|
+
return 0n;
|
|
3256
|
+
}
|
|
3257
|
+
const wanted = token.toLowerCase();
|
|
3258
|
+
let total = 0n;
|
|
3259
|
+
for (const position of positions) {
|
|
3260
|
+
if (position.chain !== chain || position.asset.toLowerCase() !== wanted) continue;
|
|
3261
|
+
total += toScaled(position.positionAssets, position.decimals);
|
|
3262
|
+
}
|
|
3263
|
+
return total;
|
|
3264
|
+
}
|
|
3147
3265
|
/**
|
|
3148
3266
|
* Works a limit order down by what a fill delivered, and puts the rest back on
|
|
3149
3267
|
* the orderbook.
|
|
@@ -12932,14 +13050,10 @@ var RATE_LIMIT_RPC_CODES = /* @__PURE__ */ new Set([-32005, -32097, -32016, 429]
|
|
|
12932
13050
|
var RATE_LIMIT_SUSPENSION_MS = 5 * 6e4;
|
|
12933
13051
|
var SCAN_BUDGET = {
|
|
12934
13052
|
timeoutMs: 5e3,
|
|
12935
|
-
retryCount: 1,
|
|
12936
|
-
retryDelayMs: 500,
|
|
12937
13053
|
deadlineMs: 12e3
|
|
12938
13054
|
};
|
|
12939
13055
|
var CONFIRMATION_BUDGET = {
|
|
12940
13056
|
timeoutMs: 1e4,
|
|
12941
|
-
retryCount: 1,
|
|
12942
|
-
retryDelayMs: 500,
|
|
12943
13057
|
deadlineMs: 3e4
|
|
12944
13058
|
};
|
|
12945
13059
|
var MALFORMED_RESPONSE_SUSPENSION_MS = 3e4;
|
|
@@ -13031,8 +13145,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13031
13145
|
chain,
|
|
13032
13146
|
transport: (0, import_viem14.http)(url, {
|
|
13033
13147
|
timeout: budget.timeoutMs,
|
|
13034
|
-
|
|
13035
|
-
|
|
13148
|
+
// No transport-level retry. viem's retry sleeps for the provider's
|
|
13149
|
+
// `Retry-After` verbatim and uncapped, which reaches the socket
|
|
13150
|
+
// before {@link suspensionMsFor} can apply its clamp — and even
|
|
13151
|
+
// without that header, retrying inside the task is what makes a
|
|
13152
|
+
// task's worst case (timeout, backoff, timeout) approach the
|
|
13153
|
+
// deadline that is supposed to bound it. One attempt per endpoint
|
|
13154
|
+
// per call; `retryPromise` above the quorum owns the retry policy.
|
|
13155
|
+
retryCount: 0
|
|
13036
13156
|
})
|
|
13037
13157
|
})
|
|
13038
13158
|
);
|
|
@@ -13046,11 +13166,10 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13046
13166
|
return this.rpcUrls.filter((url) => (_QuorumPublicClient.benchedUntil.get(url) ?? 0) > now);
|
|
13047
13167
|
}
|
|
13048
13168
|
/**
|
|
13049
|
-
* The endpoints this call queries
|
|
13050
|
-
*
|
|
13051
|
-
*
|
|
13052
|
-
*
|
|
13053
|
-
* queried again; there is nobody left to prefer.
|
|
13169
|
+
* The endpoints this call queries: everyone not benched. A benched endpoint is
|
|
13170
|
+
* dropped outright: it answers nothing while throttled, so asking it could only
|
|
13171
|
+
* cost a request. With every endpoint benched, everyone is queried again; there
|
|
13172
|
+
* is nobody left to prefer.
|
|
13054
13173
|
*/
|
|
13055
13174
|
participants() {
|
|
13056
13175
|
const now = Date.now();
|
|
@@ -13058,12 +13177,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13058
13177
|
const active = all.filter(({ idx }) => (_QuorumPublicClient.benchedUntil.get(this.rpcUrls[idx]) ?? 0) <= now);
|
|
13059
13178
|
const allBenched = active.length === 0;
|
|
13060
13179
|
const queried = allBenched ? all : active;
|
|
13061
|
-
return {
|
|
13062
|
-
queried,
|
|
13063
|
-
skipped: all.length - queried.length,
|
|
13064
|
-
threshold: quorumThreshold(queried.length),
|
|
13065
|
-
allBenched
|
|
13066
|
-
};
|
|
13180
|
+
return { queried, skipped: all.length - queried.length, allBenched };
|
|
13067
13181
|
}
|
|
13068
13182
|
/**
|
|
13069
13183
|
* Records a task failure against its endpoint. Only the failures
|
|
@@ -13108,17 +13222,21 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13108
13222
|
* retries would otherwise stall every confirmation poll). Stragglers settle
|
|
13109
13223
|
* out of band and are ignored. `tryDecide` returns undefined to keep
|
|
13110
13224
|
* waiting; once every task has settled, `finalize` must decide (it throws
|
|
13111
|
-
* the QuorumError when the full response set still has no quorum).
|
|
13112
|
-
*
|
|
13113
|
-
*
|
|
13225
|
+
* the QuorumError when the full response set still has no quorum).
|
|
13226
|
+
*
|
|
13227
|
+
* Both are handed the bar: the BFT threshold over the endpoints that have not
|
|
13228
|
+
* failed, answered or still out. A failure lowers it rather than counting
|
|
13229
|
+
* against it. Counting the pending endpoints keeps an early decision to a bar
|
|
13230
|
+
* no lower than the full response set would get, so a vote it did not wait for
|
|
13231
|
+
* could never have reversed it.
|
|
13114
13232
|
*
|
|
13115
13233
|
* `deadlineMs` bounds the other direction: when the bar is NOT reachable
|
|
13116
13234
|
* from the fast responders, the call used to wait out every straggler, and
|
|
13117
13235
|
* the caller's own retry loop multiplied that into minutes of a chain not
|
|
13118
13236
|
* being scanned at all, silently. At the deadline the outstanding endpoints
|
|
13119
13237
|
* are treated as failures — named in the QuorumError, so the operator sees
|
|
13120
|
-
* which ones held the call up — and `finalize` decides
|
|
13121
|
-
*
|
|
13238
|
+
* which ones held the call up — and `finalize` decides over the endpoints
|
|
13239
|
+
* that answered.
|
|
13122
13240
|
*/
|
|
13123
13241
|
settleUntilQuorum(tasks, tryDecide, finalize) {
|
|
13124
13242
|
return new Promise((resolve, reject) => {
|
|
@@ -13136,13 +13254,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13136
13254
|
const evaluate = () => {
|
|
13137
13255
|
if (done) return;
|
|
13138
13256
|
try {
|
|
13139
|
-
const
|
|
13257
|
+
const threshold = quorumThreshold(tasks.length - failures.length);
|
|
13258
|
+
const early = tryDecide(fulfilled, threshold);
|
|
13140
13259
|
if (early !== void 0) {
|
|
13141
13260
|
finish(() => resolve(early));
|
|
13142
13261
|
return;
|
|
13143
13262
|
}
|
|
13144
13263
|
if (outstanding === 0) {
|
|
13145
|
-
const value = finalize(fulfilled, failures);
|
|
13264
|
+
const value = finalize(fulfilled, failures, threshold);
|
|
13146
13265
|
finish(() => resolve(value));
|
|
13147
13266
|
}
|
|
13148
13267
|
} catch (error) {
|
|
@@ -13160,7 +13279,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13160
13279
|
error: new Error(`no answer within the ${this.deadlineMs}ms quorum deadline`)
|
|
13161
13280
|
}));
|
|
13162
13281
|
try {
|
|
13163
|
-
const value = finalize(fulfilled, [...failures, ...stragglers]);
|
|
13282
|
+
const value = finalize(fulfilled, [...failures, ...stragglers], quorumThreshold(fulfilled.length));
|
|
13164
13283
|
finish(() => resolve(value));
|
|
13165
13284
|
} catch (error) {
|
|
13166
13285
|
finish(() => reject(error));
|
|
@@ -13187,22 +13306,22 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13187
13306
|
});
|
|
13188
13307
|
}
|
|
13189
13308
|
/**
|
|
13190
|
-
* Highest block head backed by a
|
|
13191
|
-
*
|
|
13309
|
+
* Highest block head backed by a quorum of the endpoints that answer. Throws
|
|
13310
|
+
* {@link QuorumError} when none do.
|
|
13192
13311
|
*/
|
|
13193
13312
|
async getBlockNumber() {
|
|
13194
|
-
const { queried, skipped,
|
|
13313
|
+
const { queried, skipped, allBenched } = this.participants();
|
|
13195
13314
|
return this.settleUntilQuorum(
|
|
13196
13315
|
queried.map(({ idx, client }) => ({ idx, task: client.getBlockNumber() })),
|
|
13197
13316
|
// Early exit as soon as a quorum is satisfiable. Late voters could
|
|
13198
13317
|
// only raise the reported head; the earlier (lower) head is
|
|
13199
13318
|
// conservative for every consumer (fewer confirmations counted,
|
|
13200
13319
|
// smaller scan windows).
|
|
13201
|
-
(fulfilled) => this.quorumHead(
|
|
13320
|
+
(fulfilled, threshold) => this.quorumHead(
|
|
13202
13321
|
fulfilled.map((f) => f.value),
|
|
13203
13322
|
threshold
|
|
13204
13323
|
) ?? void 0,
|
|
13205
|
-
(fulfilled, failures) => {
|
|
13324
|
+
(fulfilled, failures, threshold) => {
|
|
13206
13325
|
const head = this.quorumHead(
|
|
13207
13326
|
fulfilled.map((f) => f.value),
|
|
13208
13327
|
threshold
|
|
@@ -13243,7 +13362,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13243
13362
|
}
|
|
13244
13363
|
return views;
|
|
13245
13364
|
};
|
|
13246
|
-
const { queried, skipped,
|
|
13365
|
+
const { queried, skipped, allBenched } = this.participants();
|
|
13247
13366
|
return this.settleUntilQuorum(
|
|
13248
13367
|
queried.map(({ idx, client }) => ({
|
|
13249
13368
|
idx,
|
|
@@ -13262,8 +13381,8 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13262
13381
|
// Early exit only on a POSITIVE quorum: an agreeing inclusion group can
|
|
13263
13382
|
// only grow, so the first satisfiable quorum is final. Not-found votes
|
|
13264
13383
|
// never decide early — "not confirmed" needs the full response set.
|
|
13265
|
-
(fulfilled) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
|
|
13266
|
-
(fulfilled, failures) => {
|
|
13384
|
+
(fulfilled, threshold) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
|
|
13385
|
+
(fulfilled, failures, threshold) => {
|
|
13267
13386
|
const confirmations = aggregateConfirmations(toViews(fulfilled), threshold);
|
|
13268
13387
|
if (confirmations === null) {
|
|
13269
13388
|
throw new QuorumError(
|
|
@@ -13292,7 +13411,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13292
13411
|
}
|
|
13293
13412
|
return groups;
|
|
13294
13413
|
};
|
|
13295
|
-
const { queried, skipped,
|
|
13414
|
+
const { queried, skipped, allBenched } = this.participants();
|
|
13296
13415
|
return this.settleUntilQuorum(
|
|
13297
13416
|
queried.map(({ idx, client }) => ({
|
|
13298
13417
|
idx,
|
|
@@ -13304,13 +13423,13 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13304
13423
|
// two disjoint groups can't both hold one), so the first satisfiable
|
|
13305
13424
|
// agreement is final — no need to wait out stragglers that could
|
|
13306
13425
|
// only join or lose.
|
|
13307
|
-
(fulfilled) => {
|
|
13426
|
+
(fulfilled, threshold) => {
|
|
13308
13427
|
for (const group of groupOf(fulfilled).values()) {
|
|
13309
13428
|
if (group.providerIdxs.length >= threshold) return group.result;
|
|
13310
13429
|
}
|
|
13311
13430
|
return void 0;
|
|
13312
13431
|
},
|
|
13313
|
-
(fulfilled, failures) => {
|
|
13432
|
+
(fulfilled, failures, threshold) => {
|
|
13314
13433
|
const groups = groupOf(fulfilled);
|
|
13315
13434
|
for (const group of groups.values()) {
|
|
13316
13435
|
if (group.providerIdxs.length >= threshold) return group.result;
|
|
@@ -21223,8 +21342,15 @@ var IntentFiller = class {
|
|
|
21223
21342
|
const evalDurationSec = (Date.now() - evalStartMs) / 1e3;
|
|
21224
21343
|
this.monitor.emit("orderTiming", { orderId: order.id, phase: "evaluation", durationSec: evalDurationSec });
|
|
21225
21344
|
if (validStrategies.length === 0) {
|
|
21226
|
-
|
|
21227
|
-
this.
|
|
21345
|
+
const priced = eligibleStrategies.some((s) => s !== null);
|
|
21346
|
+
this.logger.warn(
|
|
21347
|
+
{ orderId: order.id },
|
|
21348
|
+
priced ? "No profitable strategy found for order" : "No strategy can fill this order"
|
|
21349
|
+
);
|
|
21350
|
+
this.monitor.emit("orderSkipped", {
|
|
21351
|
+
orderId: order.id,
|
|
21352
|
+
reason: priced ? "No profitable strategy" : "No strategy can fill this order"
|
|
21353
|
+
});
|
|
21228
21354
|
return null;
|
|
21229
21355
|
}
|
|
21230
21356
|
this.logger.info(
|
|
@@ -21668,16 +21794,63 @@ function byBestOfferFirst(a, b) {
|
|
|
21668
21794
|
if (a.available !== b.available) return a.available > b.available ? -1 : 1;
|
|
21669
21795
|
return a.order.id < b.order.id ? -1 : 1;
|
|
21670
21796
|
}
|
|
21797
|
+
function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
21798
|
+
let furthest = -1;
|
|
21799
|
+
for (const order of orders) {
|
|
21800
|
+
const failed = firstFailed(order, incoming, resolve, now);
|
|
21801
|
+
furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
|
|
21802
|
+
}
|
|
21803
|
+
const { source, destination, inputSymbol } = incoming;
|
|
21804
|
+
if (furthest < CHECKS.length) {
|
|
21805
|
+
switch (CHECKS[Math.max(furthest, 0)]) {
|
|
21806
|
+
case "open":
|
|
21807
|
+
return "no limit order is open";
|
|
21808
|
+
case "chain":
|
|
21809
|
+
return `no open limit order fills on ${destination}`;
|
|
21810
|
+
case "input":
|
|
21811
|
+
return `no limit order on ${destination} takes ${inputSymbol} in`;
|
|
21812
|
+
case "output":
|
|
21813
|
+
return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
|
|
21814
|
+
case "source":
|
|
21815
|
+
return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
|
|
21816
|
+
}
|
|
21817
|
+
}
|
|
21818
|
+
const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
|
|
21819
|
+
order,
|
|
21820
|
+
offer: offerFor({
|
|
21821
|
+
side: order.side,
|
|
21822
|
+
inputAmount: incoming.inputNet,
|
|
21823
|
+
price: BigInt(order.price),
|
|
21824
|
+
outputDecimals: incoming.outputDecimals
|
|
21825
|
+
})
|
|
21826
|
+
}));
|
|
21827
|
+
if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
|
|
21828
|
+
return "the limit orders that meet its rate have nothing left to pay out";
|
|
21829
|
+
}
|
|
21830
|
+
const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
|
|
21831
|
+
const symbol = limitOrderLegs(best.order).output;
|
|
21832
|
+
return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
|
|
21833
|
+
}
|
|
21834
|
+
var CHECKS = ["open", "chain", "input", "output", "source"];
|
|
21671
21835
|
function serves(order, incoming, resolve, now) {
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21836
|
+
return firstFailed(order, incoming, resolve, now) === null;
|
|
21837
|
+
}
|
|
21838
|
+
function firstFailed(order, incoming, resolve, now) {
|
|
21839
|
+
if (order.status !== "open") return "open";
|
|
21840
|
+
if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
|
|
21841
|
+
if (order.fillChain !== incoming.destination) return "chain";
|
|
21675
21842
|
const legs = limitOrderLegs(order);
|
|
21676
|
-
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return
|
|
21843
|
+
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
|
|
21677
21844
|
const outputToken = resolve(legs.output, order.fillChain);
|
|
21678
|
-
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return
|
|
21679
|
-
if (incoming.source === incoming.destination) return
|
|
21680
|
-
return order.acceptedSources.includes(incoming.source);
|
|
21845
|
+
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
|
|
21846
|
+
if (incoming.source === incoming.destination) return null;
|
|
21847
|
+
return order.acceptedSources.includes(incoming.source) ? null : "source";
|
|
21848
|
+
}
|
|
21849
|
+
function readable(amount) {
|
|
21850
|
+
const [whole, fraction] = toHuman(amount).split(".");
|
|
21851
|
+
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
21852
|
+
const cut = fraction?.slice(0, 6).replace(/0+$/, "");
|
|
21853
|
+
return cut ? `${grouped}.${cut}` : grouped;
|
|
21681
21854
|
}
|
|
21682
21855
|
|
|
21683
21856
|
// src/orderbook/usd.ts
|
|
@@ -21819,15 +21992,20 @@ var FXFiller = class _FXFiller {
|
|
|
21819
21992
|
}
|
|
21820
21993
|
try {
|
|
21821
21994
|
if (order.inputs.length !== order.output.assets.length) {
|
|
21822
|
-
this.logger.
|
|
21823
|
-
{ inputs: order.inputs.length, outputs: order.output.assets.length },
|
|
21995
|
+
this.logger.info(
|
|
21996
|
+
{ orderId: order.id, inputs: order.inputs.length, outputs: order.output.assets.length },
|
|
21824
21997
|
"Order input/output length mismatch or empty"
|
|
21825
21998
|
);
|
|
21826
21999
|
return false;
|
|
21827
22000
|
}
|
|
21828
22001
|
if ((await this.matchOrder(order)).length === 0) {
|
|
21829
|
-
this.logger.
|
|
21830
|
-
{
|
|
22002
|
+
this.logger.info(
|
|
22003
|
+
{
|
|
22004
|
+
orderId: order.id,
|
|
22005
|
+
sourceChain: order.source,
|
|
22006
|
+
destChain: order.destination,
|
|
22007
|
+
reason: await this.explainUnmatched(order)
|
|
22008
|
+
},
|
|
21831
22009
|
"No limit order matches this order"
|
|
21832
22010
|
);
|
|
21833
22011
|
return false;
|
|
@@ -22369,30 +22547,53 @@ var FXFiller = class _FXFiller {
|
|
|
22369
22547
|
*/
|
|
22370
22548
|
async matchLeg(order, leg) {
|
|
22371
22549
|
if (!this.limitOrders) return [];
|
|
22550
|
+
const incoming = await this.incomingFor(order, leg);
|
|
22551
|
+
if (!incoming) return [];
|
|
22552
|
+
return matchLimitOrders(
|
|
22553
|
+
await this.limitOrders.open(),
|
|
22554
|
+
incoming,
|
|
22555
|
+
(symbol, chain) => this.registry.getAddress(symbol, chain)
|
|
22556
|
+
);
|
|
22557
|
+
}
|
|
22558
|
+
/** Why no limit order serves the order, leg by leg, for the line that says it was passed over. */
|
|
22559
|
+
async explainUnmatched(order) {
|
|
22560
|
+
if (!this.limitOrders) return "limit orders are not loaded";
|
|
22561
|
+
const open = await this.limitOrders.open();
|
|
22562
|
+
const reasons = [];
|
|
22563
|
+
for (let leg = 0; leg < order.inputs.length; leg++) {
|
|
22564
|
+
const incoming = await this.incomingFor(order, leg);
|
|
22565
|
+
const reason = incoming ? whyUnmatched(open, incoming, (symbol, chain) => this.registry.getAddress(symbol, chain)) : `its input token is not in the asset registry on ${order.source}`;
|
|
22566
|
+
reasons.push(order.inputs.length > 1 ? `leg ${leg}: ${reason}` : reason);
|
|
22567
|
+
}
|
|
22568
|
+
return reasons.join("; ");
|
|
22569
|
+
}
|
|
22570
|
+
/**
|
|
22571
|
+
* One leg as the matcher sees it, or null when its input token is not one the asset
|
|
22572
|
+
* registry knows on the source chain.
|
|
22573
|
+
*
|
|
22574
|
+
* Amounts cross into the matcher at 1e18, the unit limit orders are kept in.
|
|
22575
|
+
*/
|
|
22576
|
+
async incomingFor(order, leg) {
|
|
22372
22577
|
const input = order.inputs[leg];
|
|
22373
22578
|
const output = order.output.assets[leg];
|
|
22374
|
-
if (!input || !output) return
|
|
22579
|
+
if (!input || !output) return null;
|
|
22375
22580
|
const inputToken = (0, import_sdk15.bytes32ToBytes20)(input.token);
|
|
22376
22581
|
const outputToken = (0, import_sdk15.bytes32ToBytes20)(output.token);
|
|
22377
22582
|
const inputSymbol = this.registry.symbolFor(inputToken, order.source);
|
|
22378
|
-
if (!inputSymbol) return
|
|
22583
|
+
if (!inputSymbol) return null;
|
|
22379
22584
|
const [inputDecimals, outputDecimals] = await Promise.all([
|
|
22380
22585
|
this.contractService.getTokenDecimals(inputToken, order.source),
|
|
22381
22586
|
this.contractService.getTokenDecimals(outputToken, order.destination)
|
|
22382
22587
|
]);
|
|
22383
|
-
return
|
|
22384
|
-
|
|
22385
|
-
|
|
22386
|
-
|
|
22387
|
-
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
outputDecimals
|
|
22393
|
-
},
|
|
22394
|
-
(symbol, chain) => this.registry.getAddress(symbol, chain)
|
|
22395
|
-
);
|
|
22588
|
+
return {
|
|
22589
|
+
source: order.source,
|
|
22590
|
+
destination: order.destination,
|
|
22591
|
+
inputSymbol,
|
|
22592
|
+
outputToken,
|
|
22593
|
+
inputNet: toScaled(input.amount, inputDecimals),
|
|
22594
|
+
requestedOutput: toScaled(output.amount, outputDecimals),
|
|
22595
|
+
outputDecimals
|
|
22596
|
+
};
|
|
22396
22597
|
}
|
|
22397
22598
|
/**
|
|
22398
22599
|
* The order's input in **USD**, or null when no limit order connects its input
|
|
@@ -23559,7 +23760,10 @@ async function bootFiller(config, options) {
|
|
|
23559
23760
|
runtimeSigner,
|
|
23560
23761
|
config.orderbook.defaultTtlSecs ?? DEFAULT_LIMIT_ORDER_TTL_SECONDS,
|
|
23561
23762
|
new DelegationService(chainClientManager, configService, runtimeSigner),
|
|
23562
|
-
options.loggers
|
|
23763
|
+
options.loggers,
|
|
23764
|
+
initialOrderNonce,
|
|
23765
|
+
// A fill withdraws a payout shortfall from the vaults, so what they hold backs an order too.
|
|
23766
|
+
vaultVenue
|
|
23563
23767
|
);
|
|
23564
23768
|
intentFiller.setLimitOrderService(limitOrderService);
|
|
23565
23769
|
const lifecycle = new LimitOrderLifecycle(
|
|
@@ -24102,6 +24306,8 @@ var MAX_BLOCK_RANGE = 1000n;
|
|
|
24102
24306
|
var PUBLISH_CHUNK = 250;
|
|
24103
24307
|
var yieldToConsumers = () => new Promise((resolve) => setImmediate(resolve));
|
|
24104
24308
|
var STOP_DRAIN_TIMEOUT_MS = 5e3;
|
|
24309
|
+
var STALL_THRESHOLD_MS = 45e3;
|
|
24310
|
+
var STALL_REPORT_INTERVAL_MS = 6e4;
|
|
24105
24311
|
var ChainScanner = class {
|
|
24106
24312
|
constructor(target, loggers = defaultLoggerContext(), scanIntervalMs = DEFAULT_SCAN_INTERVAL_MS) {
|
|
24107
24313
|
this.target = target;
|
|
@@ -24115,6 +24321,18 @@ var ChainScanner = class {
|
|
|
24115
24321
|
this.errorHandler = () => {
|
|
24116
24322
|
};
|
|
24117
24323
|
this.stopped = false;
|
|
24324
|
+
/**
|
|
24325
|
+
* How far past the cursor the next pass reads, as `toBlock - fromBlock`:
|
|
24326
|
+
* halved after a failed read, doubled after a good one, between 0 (one block)
|
|
24327
|
+
* and {@link MAX_BLOCK_RANGE}.
|
|
24328
|
+
*
|
|
24329
|
+
* Free endpoints cap `eth_getLogs` ranges far below the full span — on
|
|
24330
|
+
* Arbitrum three of seven refused 100 blocks — and the cursor never moves
|
|
24331
|
+
* past a range it failed to read. At a fixed span a catch-up that outgrew
|
|
24332
|
+
* those caps was stuck for good: the range only grew, so the capped endpoints
|
|
24333
|
+
* never rejoined and the quorum never formed.
|
|
24334
|
+
*/
|
|
24335
|
+
this.span = MAX_BLOCK_RANGE;
|
|
24118
24336
|
this.logger = loggers.get("chain-scanner");
|
|
24119
24337
|
this.quorumClient = new QuorumPublicClient(target.chainId, target.rpcUrls, loggers);
|
|
24120
24338
|
if (this.quorumClient.size > 1) {
|
|
@@ -24157,8 +24375,9 @@ var ChainScanner = class {
|
|
|
24157
24375
|
if (this.timer || this.stopped) return;
|
|
24158
24376
|
this.logger.info({ chainId: this.target.chainId }, "Block scanner started");
|
|
24159
24377
|
this.timer = setInterval(() => {
|
|
24160
|
-
if (this.mutex.isLocked()) return;
|
|
24378
|
+
if (this.mutex.isLocked()) return this.reportStall();
|
|
24161
24379
|
void this.mutex.runExclusive(async () => {
|
|
24380
|
+
this.pass = { phase: "head", startedAt: Date.now(), reportedAt: 0 };
|
|
24162
24381
|
try {
|
|
24163
24382
|
await this.scan();
|
|
24164
24383
|
} catch (error) {
|
|
@@ -24172,10 +24391,66 @@ var ChainScanner = class {
|
|
|
24172
24391
|
"Error in block scanner"
|
|
24173
24392
|
);
|
|
24174
24393
|
this.errorHandler(error);
|
|
24394
|
+
} finally {
|
|
24395
|
+
this.notePassEnd();
|
|
24175
24396
|
}
|
|
24176
24397
|
});
|
|
24177
24398
|
}, this.scanIntervalMs);
|
|
24178
24399
|
}
|
|
24400
|
+
/**
|
|
24401
|
+
* Records which await the current pass is sitting on, for the watchdog.
|
|
24402
|
+
*
|
|
24403
|
+
* A no-op when no pass is registered — `scan()` is driven directly by tests
|
|
24404
|
+
* and by nothing else, so there is no watchdog to inform.
|
|
24405
|
+
*/
|
|
24406
|
+
enter(phase) {
|
|
24407
|
+
if (this.pass) this.pass.phase = phase;
|
|
24408
|
+
}
|
|
24409
|
+
/**
|
|
24410
|
+
* Says that a pass has outlived the quorum budgets and names the phase it is
|
|
24411
|
+
* stuck in, then repeats every {@link STALL_REPORT_INTERVAL_MS} until it ends.
|
|
24412
|
+
*
|
|
24413
|
+
* Observation only: the pass is left alone. Aborting it would need a cancel
|
|
24414
|
+
* path through the quorum client that does not exist, and the cursor is safe
|
|
24415
|
+
* either way — a pass that never completes never advances it.
|
|
24416
|
+
*/
|
|
24417
|
+
reportStall() {
|
|
24418
|
+
const pass = this.pass;
|
|
24419
|
+
if (!pass) return;
|
|
24420
|
+
const now = Date.now();
|
|
24421
|
+
const heldMs = now - pass.startedAt;
|
|
24422
|
+
if (heldMs < STALL_THRESHOLD_MS) return;
|
|
24423
|
+
if (pass.reportedAt !== 0 && now - pass.reportedAt < STALL_REPORT_INTERVAL_MS) return;
|
|
24424
|
+
pass.reportedAt = now;
|
|
24425
|
+
this.logger.warn(
|
|
24426
|
+
{
|
|
24427
|
+
chainId: this.target.chainId,
|
|
24428
|
+
phase: pass.phase,
|
|
24429
|
+
heldMs,
|
|
24430
|
+
cursor: this.cursor?.toString(),
|
|
24431
|
+
rpcUrls: this.target.rpcUrls
|
|
24432
|
+
},
|
|
24433
|
+
"Block scan pass has outlived its budget \u2014 this chain is not being scanned"
|
|
24434
|
+
);
|
|
24435
|
+
}
|
|
24436
|
+
/**
|
|
24437
|
+
* Closes out a pass, and says so if the watchdog complained about it — without
|
|
24438
|
+
* this the log shows a chain going quiet and never shows it coming back.
|
|
24439
|
+
*/
|
|
24440
|
+
notePassEnd() {
|
|
24441
|
+
const pass = this.pass;
|
|
24442
|
+
this.pass = void 0;
|
|
24443
|
+
if (!pass || pass.reportedAt === 0) return;
|
|
24444
|
+
this.logger.warn(
|
|
24445
|
+
{
|
|
24446
|
+
chainId: this.target.chainId,
|
|
24447
|
+
phase: pass.phase,
|
|
24448
|
+
heldMs: Date.now() - pass.startedAt,
|
|
24449
|
+
cursor: this.cursor?.toString()
|
|
24450
|
+
},
|
|
24451
|
+
"Block scan pass finished after outliving its budget \u2014 scanning has resumed"
|
|
24452
|
+
);
|
|
24453
|
+
}
|
|
24179
24454
|
/**
|
|
24180
24455
|
* Points this loop at different endpoints, keeping the cursor.
|
|
24181
24456
|
*
|
|
@@ -24217,11 +24492,12 @@ var ChainScanner = class {
|
|
|
24217
24492
|
}
|
|
24218
24493
|
if (currentBlock <= this.cursor) return;
|
|
24219
24494
|
const fromBlock = this.cursor + 1n;
|
|
24220
|
-
const toBlock = fromBlock +
|
|
24495
|
+
const toBlock = fromBlock + this.span > currentBlock ? currentBlock : fromBlock + this.span;
|
|
24221
24496
|
this.logger.debug(
|
|
24222
24497
|
{ chainId: this.target.chainId, fromBlock, toBlock, gap: Number(toBlock - fromBlock) },
|
|
24223
24498
|
"Scanning blocks"
|
|
24224
24499
|
);
|
|
24500
|
+
this.enter("logs");
|
|
24225
24501
|
let logs;
|
|
24226
24502
|
try {
|
|
24227
24503
|
logs = await (0, import_sdk17.retryPromise)(
|
|
@@ -24243,6 +24519,7 @@ var ChainScanner = class {
|
|
|
24243
24519
|
);
|
|
24244
24520
|
} catch (error) {
|
|
24245
24521
|
if (isBlockRangeError(error)) return;
|
|
24522
|
+
this.narrow(toBlock - fromBlock);
|
|
24246
24523
|
throw error;
|
|
24247
24524
|
}
|
|
24248
24525
|
if (this.stopped) return;
|
|
@@ -24253,6 +24530,7 @@ var ChainScanner = class {
|
|
|
24253
24530
|
{ chainId: this.target.chainId, fromBlock, toBlock, eventCount: placed.length },
|
|
24254
24531
|
"Found OrderPlaced events in block scan"
|
|
24255
24532
|
);
|
|
24533
|
+
this.enter("publish-orders");
|
|
24256
24534
|
await this.publishOrders(placed);
|
|
24257
24535
|
}
|
|
24258
24536
|
if (filled.length > 0) {
|
|
@@ -24260,10 +24538,36 @@ var ChainScanner = class {
|
|
|
24260
24538
|
{ chainId: this.target.chainId, fromBlock, toBlock, eventCount: filled.length },
|
|
24261
24539
|
"Found OrderFilled events in block scan"
|
|
24262
24540
|
);
|
|
24541
|
+
this.enter("publish-fills");
|
|
24263
24542
|
await this.publishFills(filled);
|
|
24264
24543
|
}
|
|
24265
24544
|
if (this.stopped) return;
|
|
24266
24545
|
this.cursor = toBlock;
|
|
24546
|
+
this.widen();
|
|
24547
|
+
}
|
|
24548
|
+
/**
|
|
24549
|
+
* Halves the span below the range that just failed, so the next pass asks
|
|
24550
|
+
* for something a range-capped endpoint will answer. Halving the attempted
|
|
24551
|
+
* range rather than the span matters near the head, where a pass reads fewer
|
|
24552
|
+
* blocks than the span allows.
|
|
24553
|
+
*/
|
|
24554
|
+
narrow(attempted) {
|
|
24555
|
+
const next = attempted / 2n;
|
|
24556
|
+
if (next >= this.span) return;
|
|
24557
|
+
this.span = next;
|
|
24558
|
+
this.logger.warn(
|
|
24559
|
+
{ chainId: this.target.chainId, span: Number(next) },
|
|
24560
|
+
"Narrowing the block scan range after a failed read"
|
|
24561
|
+
);
|
|
24562
|
+
}
|
|
24563
|
+
/** Doubles the span back toward {@link MAX_BLOCK_RANGE} after a good read. */
|
|
24564
|
+
widen() {
|
|
24565
|
+
if (this.span >= MAX_BLOCK_RANGE) return;
|
|
24566
|
+
const next = this.span === 0n ? 1n : this.span * 2n;
|
|
24567
|
+
this.span = next > MAX_BLOCK_RANGE ? MAX_BLOCK_RANGE : next;
|
|
24568
|
+
if (this.span === MAX_BLOCK_RANGE) {
|
|
24569
|
+
this.logger.info({ chainId: this.target.chainId }, "Block scan range back to full");
|
|
24570
|
+
}
|
|
24267
24571
|
}
|
|
24268
24572
|
async publishOrders(logs) {
|
|
24269
24573
|
const rebuilt = reconstructOrdersFromLogs(logs, {
|
|
@@ -24630,6 +24934,10 @@ var LimitOrderController = class {
|
|
|
24630
24934
|
]);
|
|
24631
24935
|
return { order, fills, bids };
|
|
24632
24936
|
}
|
|
24937
|
+
/** The pairs the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
|
|
24938
|
+
books() {
|
|
24939
|
+
return this.service.books();
|
|
24940
|
+
}
|
|
24633
24941
|
/** Creates the order, posts it, and reports what the orderbook made of it. */
|
|
24634
24942
|
async create(request) {
|
|
24635
24943
|
const posted = await this.service.create(request);
|