@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.js
CHANGED
|
@@ -2402,6 +2402,7 @@ function closedKey(chain, vault) {
|
|
|
2402
2402
|
// src/config/external-links.json
|
|
2403
2403
|
var external_links_default = {
|
|
2404
2404
|
hyperfxApp: "https://app.hyperfx.finance",
|
|
2405
|
+
alchemyDashboard: "https://dashboard.alchemy.com/",
|
|
2405
2406
|
hyperbridgeExplorers: {
|
|
2406
2407
|
mainnet: "https://nexus.statescan.io",
|
|
2407
2408
|
testnet: "https://gargantua.statescan.io"
|
|
@@ -2421,6 +2422,52 @@ var external_links_default = {
|
|
|
2421
2422
|
};
|
|
2422
2423
|
|
|
2423
2424
|
// src/cli/init/chains.ts
|
|
2425
|
+
var FREE_RPC_URLS = {
|
|
2426
|
+
1: [
|
|
2427
|
+
"https://mainnet.gateway.tenderly.co",
|
|
2428
|
+
"https://rpc.mevblocker.io",
|
|
2429
|
+
"https://ethereum-rpc.publicnode.com",
|
|
2430
|
+
"https://ethereum.public.blockpi.network/v1/rpc/public",
|
|
2431
|
+
"https://eth.blockrazor.xyz",
|
|
2432
|
+
"https://eth.api.pocket.network",
|
|
2433
|
+
"https://ethereum-public.nodies.app",
|
|
2434
|
+
"https://eth-mainnet.public.blastapi.io",
|
|
2435
|
+
"https://public-eth.nownodes.io"
|
|
2436
|
+
],
|
|
2437
|
+
56: [
|
|
2438
|
+
"https://bsc-rpc.publicnode.com",
|
|
2439
|
+
"https://public-bsc-mainnet.fastnode.io",
|
|
2440
|
+
"https://public-bsc.nownodes.io",
|
|
2441
|
+
"https://bsc.blockrazor.xyz",
|
|
2442
|
+
"https://rpc-bsc.48.club",
|
|
2443
|
+
"https://bsc.api.pocket.network"
|
|
2444
|
+
],
|
|
2445
|
+
137: [
|
|
2446
|
+
"https://polygon-bor-rpc.publicnode.com",
|
|
2447
|
+
"https://polygon.drpc.org",
|
|
2448
|
+
"https://polygon.gateway.tenderly.co",
|
|
2449
|
+
"https://rpc.private.mev-x.com/polygon",
|
|
2450
|
+
"https://poly.api.pocket.network"
|
|
2451
|
+
],
|
|
2452
|
+
8453: [
|
|
2453
|
+
"https://base-rpc.publicnode.com",
|
|
2454
|
+
"https://base.gateway.tenderly.co",
|
|
2455
|
+
"https://base.public.blockpi.network/v1/rpc/public",
|
|
2456
|
+
"https://base.rpc.blxrbdn.com",
|
|
2457
|
+
"https://rpc.baseazul.dev",
|
|
2458
|
+
"https://mainnet.base.org",
|
|
2459
|
+
"https://base-public.nodies.app"
|
|
2460
|
+
],
|
|
2461
|
+
42161: [
|
|
2462
|
+
"https://arbitrum-one-rpc.publicnode.com",
|
|
2463
|
+
"https://arbitrum.gateway.tenderly.co",
|
|
2464
|
+
"https://arbitrum.drpc.org",
|
|
2465
|
+
"https://public-arb-mainnet.fastnode.io",
|
|
2466
|
+
"https://arb-one.api.pocket.network",
|
|
2467
|
+
"https://arbitrum-one-public.nodies.app",
|
|
2468
|
+
"https://arb1.arbitrum.io/rpc"
|
|
2469
|
+
]
|
|
2470
|
+
};
|
|
2424
2471
|
var CHAIN_NATIVE_SYMBOLS = {
|
|
2425
2472
|
1: "ETH",
|
|
2426
2473
|
56: "BNB",
|
|
@@ -2448,7 +2495,8 @@ var INIT_CHAINS = [
|
|
|
2448
2495
|
label: "Ethereum",
|
|
2449
2496
|
network: "mainnet",
|
|
2450
2497
|
alchemySubdomain: "eth-mainnet",
|
|
2451
|
-
explorerUrl: external_links_default.chainExplorers.ethereum
|
|
2498
|
+
explorerUrl: external_links_default.chainExplorers.ethereum,
|
|
2499
|
+
defaultRpcUrls: FREE_RPC_URLS[1]
|
|
2452
2500
|
},
|
|
2453
2501
|
{
|
|
2454
2502
|
chainId: 42161,
|
|
@@ -2456,7 +2504,8 @@ var INIT_CHAINS = [
|
|
|
2456
2504
|
label: "Arbitrum",
|
|
2457
2505
|
network: "mainnet",
|
|
2458
2506
|
alchemySubdomain: "arb-mainnet",
|
|
2459
|
-
explorerUrl: external_links_default.chainExplorers.arbitrum
|
|
2507
|
+
explorerUrl: external_links_default.chainExplorers.arbitrum,
|
|
2508
|
+
defaultRpcUrls: FREE_RPC_URLS[42161]
|
|
2460
2509
|
},
|
|
2461
2510
|
{
|
|
2462
2511
|
chainId: 8453,
|
|
@@ -2464,7 +2513,8 @@ var INIT_CHAINS = [
|
|
|
2464
2513
|
label: "Base",
|
|
2465
2514
|
network: "mainnet",
|
|
2466
2515
|
alchemySubdomain: "base-mainnet",
|
|
2467
|
-
explorerUrl: external_links_default.chainExplorers.base
|
|
2516
|
+
explorerUrl: external_links_default.chainExplorers.base,
|
|
2517
|
+
defaultRpcUrls: FREE_RPC_URLS[8453]
|
|
2468
2518
|
},
|
|
2469
2519
|
{
|
|
2470
2520
|
chainId: 137,
|
|
@@ -2472,7 +2522,8 @@ var INIT_CHAINS = [
|
|
|
2472
2522
|
label: "Polygon",
|
|
2473
2523
|
network: "mainnet",
|
|
2474
2524
|
alchemySubdomain: "polygon-mainnet",
|
|
2475
|
-
explorerUrl: external_links_default.chainExplorers.polygon
|
|
2525
|
+
explorerUrl: external_links_default.chainExplorers.polygon,
|
|
2526
|
+
defaultRpcUrls: FREE_RPC_URLS[137]
|
|
2476
2527
|
},
|
|
2477
2528
|
{
|
|
2478
2529
|
chainId: 56,
|
|
@@ -2480,7 +2531,8 @@ var INIT_CHAINS = [
|
|
|
2480
2531
|
label: "BNB Chain",
|
|
2481
2532
|
network: "mainnet",
|
|
2482
2533
|
alchemySubdomain: "bnb-mainnet",
|
|
2483
|
-
explorerUrl: external_links_default.chainExplorers.bnb
|
|
2534
|
+
explorerUrl: external_links_default.chainExplorers.bnb,
|
|
2535
|
+
defaultRpcUrls: FREE_RPC_URLS[56]
|
|
2484
2536
|
},
|
|
2485
2537
|
{
|
|
2486
2538
|
chainId: 11155111,
|
|
@@ -2597,10 +2649,16 @@ var CANCEL_ORDER_MUTATION = `
|
|
|
2597
2649
|
`;
|
|
2598
2650
|
var OrderbookRequestError = class extends Error {
|
|
2599
2651
|
};
|
|
2652
|
+
function graphqlEndpoint(url) {
|
|
2653
|
+
const trimmed = url.trim();
|
|
2654
|
+
if (trimmed.includes("?")) return trimmed;
|
|
2655
|
+
const withoutTrailingSlash = trimmed.replace(/\/+$/, "");
|
|
2656
|
+
return withoutTrailingSlash.endsWith("/graphql") ? withoutTrailingSlash : `${withoutTrailingSlash}/graphql`;
|
|
2657
|
+
}
|
|
2600
2658
|
var OrderbookClient = class {
|
|
2601
2659
|
constructor(url, requestTimeoutMs, loggers = defaultLoggerContext()) {
|
|
2602
|
-
this.url = url;
|
|
2603
2660
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
2661
|
+
this.url = graphqlEndpoint(url);
|
|
2604
2662
|
this.logger = loggers.get("orderbook");
|
|
2605
2663
|
}
|
|
2606
2664
|
/** The limits to validate against before posting, plus the books on offer. */
|
|
@@ -2698,7 +2756,9 @@ var OrderbookClient = class {
|
|
|
2698
2756
|
clearTimeout(timer);
|
|
2699
2757
|
}
|
|
2700
2758
|
if (!response.ok) {
|
|
2701
|
-
throw new OrderbookRequestError(
|
|
2759
|
+
throw new OrderbookRequestError(
|
|
2760
|
+
`Orderbook at ${this.url} returned HTTP ${response.status} ${response.statusText}`
|
|
2761
|
+
);
|
|
2702
2762
|
}
|
|
2703
2763
|
const body = await response.json().catch((err) => {
|
|
2704
2764
|
throw new OrderbookRequestError(`Orderbook returned a body that is not JSON: ${describe(err)}`);
|
|
@@ -2813,7 +2873,7 @@ var HEARTBEAT_TYPES = {
|
|
|
2813
2873
|
var LimitOrderValidationError = class extends Error {
|
|
2814
2874
|
};
|
|
2815
2875
|
var LimitOrderService = class {
|
|
2816
|
-
constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce) {
|
|
2876
|
+
constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce, vaultBalances) {
|
|
2817
2877
|
this.store = store;
|
|
2818
2878
|
this.client = client;
|
|
2819
2879
|
this.contractService = contractService;
|
|
@@ -2823,6 +2883,7 @@ var LimitOrderService = class {
|
|
|
2823
2883
|
this.defaultTtlSecs = defaultTtlSecs;
|
|
2824
2884
|
this.delegationService = delegationService;
|
|
2825
2885
|
this.startingNonce = startingNonce;
|
|
2886
|
+
this.vaultBalances = vaultBalances;
|
|
2826
2887
|
this.logger = loggers.get("limit-orders");
|
|
2827
2888
|
}
|
|
2828
2889
|
/**
|
|
@@ -2958,6 +3019,18 @@ var LimitOrderService = class {
|
|
|
2958
3019
|
throw err;
|
|
2959
3020
|
}
|
|
2960
3021
|
}
|
|
3022
|
+
/**
|
|
3023
|
+
* The pairs an order can be written against, the smallest payout each token may carry, and
|
|
3024
|
+
* the tokens the orderbook registers on each chain.
|
|
3025
|
+
*
|
|
3026
|
+
* The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
|
|
3027
|
+
* operator is offered exactly these and no combination of symbols they could not post. The
|
|
3028
|
+
* chains do the same for the source chains an order can accept.
|
|
3029
|
+
*/
|
|
3030
|
+
async books() {
|
|
3031
|
+
const limits = await this.limits();
|
|
3032
|
+
return { books: limits.books, minOrderSizes: limits.serverInfo.minOrderSizes, chains: limits.chains ?? [] };
|
|
3033
|
+
}
|
|
2961
3034
|
/**
|
|
2962
3035
|
* Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
|
|
2963
3036
|
*
|
|
@@ -3038,6 +3111,18 @@ var LimitOrderService = class {
|
|
|
3038
3111
|
);
|
|
3039
3112
|
}
|
|
3040
3113
|
}
|
|
3114
|
+
if (book.base !== book.quote) {
|
|
3115
|
+
const input = normalizeSymbol(request.tokenIn);
|
|
3116
|
+
const lacking = sources.filter((source) => {
|
|
3117
|
+
const listed = limits.chains?.find((chain) => chain.id === source);
|
|
3118
|
+
return listed && !listed.tokens.some((token) => normalizeSymbol(token.symbol) === input);
|
|
3119
|
+
});
|
|
3120
|
+
if (lacking.length > 0) {
|
|
3121
|
+
throw new LimitOrderValidationError(
|
|
3122
|
+
`The orderbook has no ${request.tokenIn} on ${lacking.join(", ")}, so swaps from there cannot pay this order`
|
|
3123
|
+
);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3041
3126
|
if (ttlSecs < limits.serverInfo.minOrderTtlSecs) {
|
|
3042
3127
|
throw new LimitOrderValidationError(
|
|
3043
3128
|
`ttlSecs must be at least the orderbook's minimum of ${limits.serverInfo.minOrderTtlSecs}; got ${ttlSecs}`
|
|
@@ -3065,28 +3150,61 @@ var LimitOrderService = class {
|
|
|
3065
3150
|
* balance on the fill chain, so the operator hears it while they are creating
|
|
3066
3151
|
* the order.
|
|
3067
3152
|
*
|
|
3153
|
+
* A fill pays out of the wallet and then withdraws any shortfall from the
|
|
3154
|
+
* configured ERC-4626 vaults in the same batch, so what those vaults hold of
|
|
3155
|
+
* the payout token backs the order too and is counted here. Without it an
|
|
3156
|
+
* operator who sweeps inventory into a vault is refused orders their filler
|
|
3157
|
+
* would have filled.
|
|
3158
|
+
*
|
|
3068
3159
|
* Each order is checked against the whole balance, not against what is left of
|
|
3069
3160
|
* it after the others. One balance backs every order resting on it — that is
|
|
3070
3161
|
* what quoting both sides of a book is — and the orderbook says as much: it
|
|
3071
3162
|
* advertises each entry at `min(quoted, balance)` rather than dividing the
|
|
3072
3163
|
* balance between them. Whichever order fills first draws the inventory down,
|
|
3073
3164
|
* and the rest are cut to what is left. Netting them here would refuse the
|
|
3074
|
-
* second side of every book.
|
|
3165
|
+
* second side of every book. Vault positions are counted the same way: the
|
|
3166
|
+
* whole position, not what is left after reservations for fills in flight.
|
|
3075
3167
|
*/
|
|
3076
3168
|
async assertWalletCanPay(symbol, chain, payout) {
|
|
3077
3169
|
const token = this.assetRegistry.getAddress(symbol, chain);
|
|
3078
3170
|
if (!token) return;
|
|
3079
3171
|
const decimals = await this.decimalsFor(symbol, token, chain);
|
|
3080
|
-
const
|
|
3172
|
+
const wallet = toScaled(
|
|
3081
3173
|
await this.contractService.getTokenBalance(chain, token, this.signer.address),
|
|
3082
3174
|
decimals
|
|
3083
3175
|
);
|
|
3084
|
-
|
|
3176
|
+
const vaults = await this.vaultHoldings(token, chain);
|
|
3177
|
+
if (wallet + vaults < payout) {
|
|
3178
|
+
const held = vaults > 0n ? `${toHuman(wallet)} in the wallet and ${toHuman(vaults)} in vaults` : toHuman(wallet);
|
|
3085
3179
|
throw new LimitOrderValidationError(
|
|
3086
|
-
`The wallet holds ${
|
|
3180
|
+
`The wallet holds ${held} ${symbol} on ${chain}, which cannot pay out ${toHuman(payout)}`
|
|
3087
3181
|
);
|
|
3088
3182
|
}
|
|
3089
3183
|
}
|
|
3184
|
+
/**
|
|
3185
|
+
* What the configured vaults hold of `token` on `chain`, in scaled units.
|
|
3186
|
+
*
|
|
3187
|
+
* Zero when no vault is configured. A snapshot that cannot be read is also
|
|
3188
|
+
* zero: the wallet alone then has to cover the order, which refuses one the
|
|
3189
|
+
* filler might have managed rather than accepting one it could not.
|
|
3190
|
+
*/
|
|
3191
|
+
async vaultHoldings(token, chain) {
|
|
3192
|
+
if (!this.vaultBalances) return 0n;
|
|
3193
|
+
let positions;
|
|
3194
|
+
try {
|
|
3195
|
+
positions = await this.vaultBalances.getBalanceSnapshot(chain);
|
|
3196
|
+
} catch (err) {
|
|
3197
|
+
this.logger.warn({ chain, token, err }, "Could not read vault balances; backing the order on the wallet alone");
|
|
3198
|
+
return 0n;
|
|
3199
|
+
}
|
|
3200
|
+
const wanted = token.toLowerCase();
|
|
3201
|
+
let total = 0n;
|
|
3202
|
+
for (const position of positions) {
|
|
3203
|
+
if (position.chain !== chain || position.asset.toLowerCase() !== wanted) continue;
|
|
3204
|
+
total += toScaled(position.positionAssets, position.decimals);
|
|
3205
|
+
}
|
|
3206
|
+
return total;
|
|
3207
|
+
}
|
|
3090
3208
|
/**
|
|
3091
3209
|
* Works a limit order down by what a fill delivered, and puts the rest back on
|
|
3092
3210
|
* the orderbook.
|
|
@@ -12890,14 +13008,10 @@ var RATE_LIMIT_RPC_CODES = /* @__PURE__ */ new Set([-32005, -32097, -32016, 429]
|
|
|
12890
13008
|
var RATE_LIMIT_SUSPENSION_MS = 5 * 6e4;
|
|
12891
13009
|
var SCAN_BUDGET = {
|
|
12892
13010
|
timeoutMs: 5e3,
|
|
12893
|
-
retryCount: 1,
|
|
12894
|
-
retryDelayMs: 500,
|
|
12895
13011
|
deadlineMs: 12e3
|
|
12896
13012
|
};
|
|
12897
13013
|
var CONFIRMATION_BUDGET = {
|
|
12898
13014
|
timeoutMs: 1e4,
|
|
12899
|
-
retryCount: 1,
|
|
12900
|
-
retryDelayMs: 500,
|
|
12901
13015
|
deadlineMs: 3e4
|
|
12902
13016
|
};
|
|
12903
13017
|
var MALFORMED_RESPONSE_SUSPENSION_MS = 3e4;
|
|
@@ -12989,8 +13103,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
12989
13103
|
chain,
|
|
12990
13104
|
transport: http(url, {
|
|
12991
13105
|
timeout: budget.timeoutMs,
|
|
12992
|
-
|
|
12993
|
-
|
|
13106
|
+
// No transport-level retry. viem's retry sleeps for the provider's
|
|
13107
|
+
// `Retry-After` verbatim and uncapped, which reaches the socket
|
|
13108
|
+
// before {@link suspensionMsFor} can apply its clamp — and even
|
|
13109
|
+
// without that header, retrying inside the task is what makes a
|
|
13110
|
+
// task's worst case (timeout, backoff, timeout) approach the
|
|
13111
|
+
// deadline that is supposed to bound it. One attempt per endpoint
|
|
13112
|
+
// per call; `retryPromise` above the quorum owns the retry policy.
|
|
13113
|
+
retryCount: 0
|
|
12994
13114
|
})
|
|
12995
13115
|
})
|
|
12996
13116
|
);
|
|
@@ -13004,11 +13124,10 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13004
13124
|
return this.rpcUrls.filter((url) => (_QuorumPublicClient.benchedUntil.get(url) ?? 0) > now);
|
|
13005
13125
|
}
|
|
13006
13126
|
/**
|
|
13007
|
-
* The endpoints this call queries
|
|
13008
|
-
*
|
|
13009
|
-
*
|
|
13010
|
-
*
|
|
13011
|
-
* queried again; there is nobody left to prefer.
|
|
13127
|
+
* The endpoints this call queries: everyone not benched. A benched endpoint is
|
|
13128
|
+
* dropped outright: it answers nothing while throttled, so asking it could only
|
|
13129
|
+
* cost a request. With every endpoint benched, everyone is queried again; there
|
|
13130
|
+
* is nobody left to prefer.
|
|
13012
13131
|
*/
|
|
13013
13132
|
participants() {
|
|
13014
13133
|
const now = Date.now();
|
|
@@ -13016,12 +13135,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13016
13135
|
const active = all.filter(({ idx }) => (_QuorumPublicClient.benchedUntil.get(this.rpcUrls[idx]) ?? 0) <= now);
|
|
13017
13136
|
const allBenched = active.length === 0;
|
|
13018
13137
|
const queried = allBenched ? all : active;
|
|
13019
|
-
return {
|
|
13020
|
-
queried,
|
|
13021
|
-
skipped: all.length - queried.length,
|
|
13022
|
-
threshold: quorumThreshold(queried.length),
|
|
13023
|
-
allBenched
|
|
13024
|
-
};
|
|
13138
|
+
return { queried, skipped: all.length - queried.length, allBenched };
|
|
13025
13139
|
}
|
|
13026
13140
|
/**
|
|
13027
13141
|
* Records a task failure against its endpoint. Only the failures
|
|
@@ -13066,17 +13180,21 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13066
13180
|
* retries would otherwise stall every confirmation poll). Stragglers settle
|
|
13067
13181
|
* out of band and are ignored. `tryDecide` returns undefined to keep
|
|
13068
13182
|
* waiting; once every task has settled, `finalize` must decide (it throws
|
|
13069
|
-
* the QuorumError when the full response set still has no quorum).
|
|
13070
|
-
*
|
|
13071
|
-
*
|
|
13183
|
+
* the QuorumError when the full response set still has no quorum).
|
|
13184
|
+
*
|
|
13185
|
+
* Both are handed the bar: the BFT threshold over the endpoints that have not
|
|
13186
|
+
* failed, answered or still out. A failure lowers it rather than counting
|
|
13187
|
+
* against it. Counting the pending endpoints keeps an early decision to a bar
|
|
13188
|
+
* no lower than the full response set would get, so a vote it did not wait for
|
|
13189
|
+
* could never have reversed it.
|
|
13072
13190
|
*
|
|
13073
13191
|
* `deadlineMs` bounds the other direction: when the bar is NOT reachable
|
|
13074
13192
|
* from the fast responders, the call used to wait out every straggler, and
|
|
13075
13193
|
* the caller's own retry loop multiplied that into minutes of a chain not
|
|
13076
13194
|
* being scanned at all, silently. At the deadline the outstanding endpoints
|
|
13077
13195
|
* are treated as failures — named in the QuorumError, so the operator sees
|
|
13078
|
-
* which ones held the call up — and `finalize` decides
|
|
13079
|
-
*
|
|
13196
|
+
* which ones held the call up — and `finalize` decides over the endpoints
|
|
13197
|
+
* that answered.
|
|
13080
13198
|
*/
|
|
13081
13199
|
settleUntilQuorum(tasks, tryDecide, finalize) {
|
|
13082
13200
|
return new Promise((resolve, reject) => {
|
|
@@ -13094,13 +13212,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13094
13212
|
const evaluate = () => {
|
|
13095
13213
|
if (done) return;
|
|
13096
13214
|
try {
|
|
13097
|
-
const
|
|
13215
|
+
const threshold = quorumThreshold(tasks.length - failures.length);
|
|
13216
|
+
const early = tryDecide(fulfilled, threshold);
|
|
13098
13217
|
if (early !== void 0) {
|
|
13099
13218
|
finish(() => resolve(early));
|
|
13100
13219
|
return;
|
|
13101
13220
|
}
|
|
13102
13221
|
if (outstanding === 0) {
|
|
13103
|
-
const value = finalize(fulfilled, failures);
|
|
13222
|
+
const value = finalize(fulfilled, failures, threshold);
|
|
13104
13223
|
finish(() => resolve(value));
|
|
13105
13224
|
}
|
|
13106
13225
|
} catch (error) {
|
|
@@ -13118,7 +13237,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13118
13237
|
error: new Error(`no answer within the ${this.deadlineMs}ms quorum deadline`)
|
|
13119
13238
|
}));
|
|
13120
13239
|
try {
|
|
13121
|
-
const value = finalize(fulfilled, [...failures, ...stragglers]);
|
|
13240
|
+
const value = finalize(fulfilled, [...failures, ...stragglers], quorumThreshold(fulfilled.length));
|
|
13122
13241
|
finish(() => resolve(value));
|
|
13123
13242
|
} catch (error) {
|
|
13124
13243
|
finish(() => reject(error));
|
|
@@ -13145,22 +13264,22 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13145
13264
|
});
|
|
13146
13265
|
}
|
|
13147
13266
|
/**
|
|
13148
|
-
* Highest block head backed by a
|
|
13149
|
-
*
|
|
13267
|
+
* Highest block head backed by a quorum of the endpoints that answer. Throws
|
|
13268
|
+
* {@link QuorumError} when none do.
|
|
13150
13269
|
*/
|
|
13151
13270
|
async getBlockNumber() {
|
|
13152
|
-
const { queried, skipped,
|
|
13271
|
+
const { queried, skipped, allBenched } = this.participants();
|
|
13153
13272
|
return this.settleUntilQuorum(
|
|
13154
13273
|
queried.map(({ idx, client }) => ({ idx, task: client.getBlockNumber() })),
|
|
13155
13274
|
// Early exit as soon as a quorum is satisfiable. Late voters could
|
|
13156
13275
|
// only raise the reported head; the earlier (lower) head is
|
|
13157
13276
|
// conservative for every consumer (fewer confirmations counted,
|
|
13158
13277
|
// smaller scan windows).
|
|
13159
|
-
(fulfilled) => this.quorumHead(
|
|
13278
|
+
(fulfilled, threshold) => this.quorumHead(
|
|
13160
13279
|
fulfilled.map((f) => f.value),
|
|
13161
13280
|
threshold
|
|
13162
13281
|
) ?? void 0,
|
|
13163
|
-
(fulfilled, failures) => {
|
|
13282
|
+
(fulfilled, failures, threshold) => {
|
|
13164
13283
|
const head = this.quorumHead(
|
|
13165
13284
|
fulfilled.map((f) => f.value),
|
|
13166
13285
|
threshold
|
|
@@ -13201,7 +13320,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13201
13320
|
}
|
|
13202
13321
|
return views;
|
|
13203
13322
|
};
|
|
13204
|
-
const { queried, skipped,
|
|
13323
|
+
const { queried, skipped, allBenched } = this.participants();
|
|
13205
13324
|
return this.settleUntilQuorum(
|
|
13206
13325
|
queried.map(({ idx, client }) => ({
|
|
13207
13326
|
idx,
|
|
@@ -13220,8 +13339,8 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13220
13339
|
// Early exit only on a POSITIVE quorum: an agreeing inclusion group can
|
|
13221
13340
|
// only grow, so the first satisfiable quorum is final. Not-found votes
|
|
13222
13341
|
// never decide early — "not confirmed" needs the full response set.
|
|
13223
|
-
(fulfilled) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
|
|
13224
|
-
(fulfilled, failures) => {
|
|
13342
|
+
(fulfilled, threshold) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
|
|
13343
|
+
(fulfilled, failures, threshold) => {
|
|
13225
13344
|
const confirmations = aggregateConfirmations(toViews(fulfilled), threshold);
|
|
13226
13345
|
if (confirmations === null) {
|
|
13227
13346
|
throw new QuorumError(
|
|
@@ -13250,7 +13369,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13250
13369
|
}
|
|
13251
13370
|
return groups;
|
|
13252
13371
|
};
|
|
13253
|
-
const { queried, skipped,
|
|
13372
|
+
const { queried, skipped, allBenched } = this.participants();
|
|
13254
13373
|
return this.settleUntilQuorum(
|
|
13255
13374
|
queried.map(({ idx, client }) => ({
|
|
13256
13375
|
idx,
|
|
@@ -13262,13 +13381,13 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13262
13381
|
// two disjoint groups can't both hold one), so the first satisfiable
|
|
13263
13382
|
// agreement is final — no need to wait out stragglers that could
|
|
13264
13383
|
// only join or lose.
|
|
13265
|
-
(fulfilled) => {
|
|
13384
|
+
(fulfilled, threshold) => {
|
|
13266
13385
|
for (const group of groupOf(fulfilled).values()) {
|
|
13267
13386
|
if (group.providerIdxs.length >= threshold) return group.result;
|
|
13268
13387
|
}
|
|
13269
13388
|
return void 0;
|
|
13270
13389
|
},
|
|
13271
|
-
(fulfilled, failures) => {
|
|
13390
|
+
(fulfilled, failures, threshold) => {
|
|
13272
13391
|
const groups = groupOf(fulfilled);
|
|
13273
13392
|
for (const group of groups.values()) {
|
|
13274
13393
|
if (group.providerIdxs.length >= threshold) return group.result;
|
|
@@ -21224,8 +21343,15 @@ var IntentFiller = class {
|
|
|
21224
21343
|
const evalDurationSec = (Date.now() - evalStartMs) / 1e3;
|
|
21225
21344
|
this.monitor.emit("orderTiming", { orderId: order.id, phase: "evaluation", durationSec: evalDurationSec });
|
|
21226
21345
|
if (validStrategies.length === 0) {
|
|
21227
|
-
|
|
21228
|
-
this.
|
|
21346
|
+
const priced = eligibleStrategies.some((s) => s !== null);
|
|
21347
|
+
this.logger.warn(
|
|
21348
|
+
{ orderId: order.id },
|
|
21349
|
+
priced ? "No profitable strategy found for order" : "No strategy can fill this order"
|
|
21350
|
+
);
|
|
21351
|
+
this.monitor.emit("orderSkipped", {
|
|
21352
|
+
orderId: order.id,
|
|
21353
|
+
reason: priced ? "No profitable strategy" : "No strategy can fill this order"
|
|
21354
|
+
});
|
|
21229
21355
|
return null;
|
|
21230
21356
|
}
|
|
21231
21357
|
this.logger.info(
|
|
@@ -21672,16 +21798,63 @@ function byBestOfferFirst(a, b) {
|
|
|
21672
21798
|
if (a.available !== b.available) return a.available > b.available ? -1 : 1;
|
|
21673
21799
|
return a.order.id < b.order.id ? -1 : 1;
|
|
21674
21800
|
}
|
|
21801
|
+
function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
21802
|
+
let furthest = -1;
|
|
21803
|
+
for (const order of orders) {
|
|
21804
|
+
const failed = firstFailed(order, incoming, resolve, now);
|
|
21805
|
+
furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
|
|
21806
|
+
}
|
|
21807
|
+
const { source, destination, inputSymbol } = incoming;
|
|
21808
|
+
if (furthest < CHECKS.length) {
|
|
21809
|
+
switch (CHECKS[Math.max(furthest, 0)]) {
|
|
21810
|
+
case "open":
|
|
21811
|
+
return "no limit order is open";
|
|
21812
|
+
case "chain":
|
|
21813
|
+
return `no open limit order fills on ${destination}`;
|
|
21814
|
+
case "input":
|
|
21815
|
+
return `no limit order on ${destination} takes ${inputSymbol} in`;
|
|
21816
|
+
case "output":
|
|
21817
|
+
return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
|
|
21818
|
+
case "source":
|
|
21819
|
+
return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
|
|
21820
|
+
}
|
|
21821
|
+
}
|
|
21822
|
+
const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
|
|
21823
|
+
order,
|
|
21824
|
+
offer: offerFor({
|
|
21825
|
+
side: order.side,
|
|
21826
|
+
inputAmount: incoming.inputNet,
|
|
21827
|
+
price: BigInt(order.price),
|
|
21828
|
+
outputDecimals: incoming.outputDecimals
|
|
21829
|
+
})
|
|
21830
|
+
}));
|
|
21831
|
+
if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
|
|
21832
|
+
return "the limit orders that meet its rate have nothing left to pay out";
|
|
21833
|
+
}
|
|
21834
|
+
const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
|
|
21835
|
+
const symbol = limitOrderLegs(best.order).output;
|
|
21836
|
+
return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
|
|
21837
|
+
}
|
|
21838
|
+
var CHECKS = ["open", "chain", "input", "output", "source"];
|
|
21675
21839
|
function serves(order, incoming, resolve, now) {
|
|
21676
|
-
|
|
21677
|
-
|
|
21678
|
-
|
|
21840
|
+
return firstFailed(order, incoming, resolve, now) === null;
|
|
21841
|
+
}
|
|
21842
|
+
function firstFailed(order, incoming, resolve, now) {
|
|
21843
|
+
if (order.status !== "open") return "open";
|
|
21844
|
+
if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
|
|
21845
|
+
if (order.fillChain !== incoming.destination) return "chain";
|
|
21679
21846
|
const legs = limitOrderLegs(order);
|
|
21680
|
-
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return
|
|
21847
|
+
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
|
|
21681
21848
|
const outputToken = resolve(legs.output, order.fillChain);
|
|
21682
|
-
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return
|
|
21683
|
-
if (incoming.source === incoming.destination) return
|
|
21684
|
-
return order.acceptedSources.includes(incoming.source);
|
|
21849
|
+
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
|
|
21850
|
+
if (incoming.source === incoming.destination) return null;
|
|
21851
|
+
return order.acceptedSources.includes(incoming.source) ? null : "source";
|
|
21852
|
+
}
|
|
21853
|
+
function readable(amount) {
|
|
21854
|
+
const [whole, fraction] = toHuman(amount).split(".");
|
|
21855
|
+
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
21856
|
+
const cut = fraction?.slice(0, 6).replace(/0+$/, "");
|
|
21857
|
+
return cut ? `${grouped}.${cut}` : grouped;
|
|
21685
21858
|
}
|
|
21686
21859
|
|
|
21687
21860
|
// src/orderbook/usd.ts
|
|
@@ -21823,15 +21996,20 @@ var FXFiller = class _FXFiller {
|
|
|
21823
21996
|
}
|
|
21824
21997
|
try {
|
|
21825
21998
|
if (order.inputs.length !== order.output.assets.length) {
|
|
21826
|
-
this.logger.
|
|
21827
|
-
{ inputs: order.inputs.length, outputs: order.output.assets.length },
|
|
21999
|
+
this.logger.info(
|
|
22000
|
+
{ orderId: order.id, inputs: order.inputs.length, outputs: order.output.assets.length },
|
|
21828
22001
|
"Order input/output length mismatch or empty"
|
|
21829
22002
|
);
|
|
21830
22003
|
return false;
|
|
21831
22004
|
}
|
|
21832
22005
|
if ((await this.matchOrder(order)).length === 0) {
|
|
21833
|
-
this.logger.
|
|
21834
|
-
{
|
|
22006
|
+
this.logger.info(
|
|
22007
|
+
{
|
|
22008
|
+
orderId: order.id,
|
|
22009
|
+
sourceChain: order.source,
|
|
22010
|
+
destChain: order.destination,
|
|
22011
|
+
reason: await this.explainUnmatched(order)
|
|
22012
|
+
},
|
|
21835
22013
|
"No limit order matches this order"
|
|
21836
22014
|
);
|
|
21837
22015
|
return false;
|
|
@@ -22373,30 +22551,53 @@ var FXFiller = class _FXFiller {
|
|
|
22373
22551
|
*/
|
|
22374
22552
|
async matchLeg(order, leg) {
|
|
22375
22553
|
if (!this.limitOrders) return [];
|
|
22554
|
+
const incoming = await this.incomingFor(order, leg);
|
|
22555
|
+
if (!incoming) return [];
|
|
22556
|
+
return matchLimitOrders(
|
|
22557
|
+
await this.limitOrders.open(),
|
|
22558
|
+
incoming,
|
|
22559
|
+
(symbol, chain) => this.registry.getAddress(symbol, chain)
|
|
22560
|
+
);
|
|
22561
|
+
}
|
|
22562
|
+
/** Why no limit order serves the order, leg by leg, for the line that says it was passed over. */
|
|
22563
|
+
async explainUnmatched(order) {
|
|
22564
|
+
if (!this.limitOrders) return "limit orders are not loaded";
|
|
22565
|
+
const open = await this.limitOrders.open();
|
|
22566
|
+
const reasons = [];
|
|
22567
|
+
for (let leg = 0; leg < order.inputs.length; leg++) {
|
|
22568
|
+
const incoming = await this.incomingFor(order, leg);
|
|
22569
|
+
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}`;
|
|
22570
|
+
reasons.push(order.inputs.length > 1 ? `leg ${leg}: ${reason}` : reason);
|
|
22571
|
+
}
|
|
22572
|
+
return reasons.join("; ");
|
|
22573
|
+
}
|
|
22574
|
+
/**
|
|
22575
|
+
* One leg as the matcher sees it, or null when its input token is not one the asset
|
|
22576
|
+
* registry knows on the source chain.
|
|
22577
|
+
*
|
|
22578
|
+
* Amounts cross into the matcher at 1e18, the unit limit orders are kept in.
|
|
22579
|
+
*/
|
|
22580
|
+
async incomingFor(order, leg) {
|
|
22376
22581
|
const input = order.inputs[leg];
|
|
22377
22582
|
const output = order.output.assets[leg];
|
|
22378
|
-
if (!input || !output) return
|
|
22583
|
+
if (!input || !output) return null;
|
|
22379
22584
|
const inputToken = bytes32ToBytes204(input.token);
|
|
22380
22585
|
const outputToken = bytes32ToBytes204(output.token);
|
|
22381
22586
|
const inputSymbol = this.registry.symbolFor(inputToken, order.source);
|
|
22382
|
-
if (!inputSymbol) return
|
|
22587
|
+
if (!inputSymbol) return null;
|
|
22383
22588
|
const [inputDecimals, outputDecimals] = await Promise.all([
|
|
22384
22589
|
this.contractService.getTokenDecimals(inputToken, order.source),
|
|
22385
22590
|
this.contractService.getTokenDecimals(outputToken, order.destination)
|
|
22386
22591
|
]);
|
|
22387
|
-
return
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
|
|
22395
|
-
|
|
22396
|
-
outputDecimals
|
|
22397
|
-
},
|
|
22398
|
-
(symbol, chain) => this.registry.getAddress(symbol, chain)
|
|
22399
|
-
);
|
|
22592
|
+
return {
|
|
22593
|
+
source: order.source,
|
|
22594
|
+
destination: order.destination,
|
|
22595
|
+
inputSymbol,
|
|
22596
|
+
outputToken,
|
|
22597
|
+
inputNet: toScaled(input.amount, inputDecimals),
|
|
22598
|
+
requestedOutput: toScaled(output.amount, outputDecimals),
|
|
22599
|
+
outputDecimals
|
|
22600
|
+
};
|
|
22400
22601
|
}
|
|
22401
22602
|
/**
|
|
22402
22603
|
* The order's input in **USD**, or null when no limit order connects its input
|
|
@@ -23563,7 +23764,10 @@ async function bootFiller(config, options) {
|
|
|
23563
23764
|
runtimeSigner,
|
|
23564
23765
|
config.orderbook.defaultTtlSecs ?? DEFAULT_LIMIT_ORDER_TTL_SECONDS,
|
|
23565
23766
|
new DelegationService(chainClientManager, configService, runtimeSigner),
|
|
23566
|
-
options.loggers
|
|
23767
|
+
options.loggers,
|
|
23768
|
+
initialOrderNonce,
|
|
23769
|
+
// A fill withdraws a payout shortfall from the vaults, so what they hold backs an order too.
|
|
23770
|
+
vaultVenue
|
|
23567
23771
|
);
|
|
23568
23772
|
intentFiller.setLimitOrderService(limitOrderService);
|
|
23569
23773
|
const lifecycle = new LimitOrderLifecycle(
|
|
@@ -24106,6 +24310,8 @@ var MAX_BLOCK_RANGE = 1000n;
|
|
|
24106
24310
|
var PUBLISH_CHUNK = 250;
|
|
24107
24311
|
var yieldToConsumers = () => new Promise((resolve) => setImmediate(resolve));
|
|
24108
24312
|
var STOP_DRAIN_TIMEOUT_MS = 5e3;
|
|
24313
|
+
var STALL_THRESHOLD_MS = 45e3;
|
|
24314
|
+
var STALL_REPORT_INTERVAL_MS = 6e4;
|
|
24109
24315
|
var ChainScanner = class {
|
|
24110
24316
|
constructor(target, loggers = defaultLoggerContext(), scanIntervalMs = DEFAULT_SCAN_INTERVAL_MS) {
|
|
24111
24317
|
this.target = target;
|
|
@@ -24119,6 +24325,18 @@ var ChainScanner = class {
|
|
|
24119
24325
|
this.errorHandler = () => {
|
|
24120
24326
|
};
|
|
24121
24327
|
this.stopped = false;
|
|
24328
|
+
/**
|
|
24329
|
+
* How far past the cursor the next pass reads, as `toBlock - fromBlock`:
|
|
24330
|
+
* halved after a failed read, doubled after a good one, between 0 (one block)
|
|
24331
|
+
* and {@link MAX_BLOCK_RANGE}.
|
|
24332
|
+
*
|
|
24333
|
+
* Free endpoints cap `eth_getLogs` ranges far below the full span — on
|
|
24334
|
+
* Arbitrum three of seven refused 100 blocks — and the cursor never moves
|
|
24335
|
+
* past a range it failed to read. At a fixed span a catch-up that outgrew
|
|
24336
|
+
* those caps was stuck for good: the range only grew, so the capped endpoints
|
|
24337
|
+
* never rejoined and the quorum never formed.
|
|
24338
|
+
*/
|
|
24339
|
+
this.span = MAX_BLOCK_RANGE;
|
|
24122
24340
|
this.logger = loggers.get("chain-scanner");
|
|
24123
24341
|
this.quorumClient = new QuorumPublicClient(target.chainId, target.rpcUrls, loggers);
|
|
24124
24342
|
if (this.quorumClient.size > 1) {
|
|
@@ -24161,8 +24379,9 @@ var ChainScanner = class {
|
|
|
24161
24379
|
if (this.timer || this.stopped) return;
|
|
24162
24380
|
this.logger.info({ chainId: this.target.chainId }, "Block scanner started");
|
|
24163
24381
|
this.timer = setInterval(() => {
|
|
24164
|
-
if (this.mutex.isLocked()) return;
|
|
24382
|
+
if (this.mutex.isLocked()) return this.reportStall();
|
|
24165
24383
|
void this.mutex.runExclusive(async () => {
|
|
24384
|
+
this.pass = { phase: "head", startedAt: Date.now(), reportedAt: 0 };
|
|
24166
24385
|
try {
|
|
24167
24386
|
await this.scan();
|
|
24168
24387
|
} catch (error) {
|
|
@@ -24176,10 +24395,66 @@ var ChainScanner = class {
|
|
|
24176
24395
|
"Error in block scanner"
|
|
24177
24396
|
);
|
|
24178
24397
|
this.errorHandler(error);
|
|
24398
|
+
} finally {
|
|
24399
|
+
this.notePassEnd();
|
|
24179
24400
|
}
|
|
24180
24401
|
});
|
|
24181
24402
|
}, this.scanIntervalMs);
|
|
24182
24403
|
}
|
|
24404
|
+
/**
|
|
24405
|
+
* Records which await the current pass is sitting on, for the watchdog.
|
|
24406
|
+
*
|
|
24407
|
+
* A no-op when no pass is registered — `scan()` is driven directly by tests
|
|
24408
|
+
* and by nothing else, so there is no watchdog to inform.
|
|
24409
|
+
*/
|
|
24410
|
+
enter(phase) {
|
|
24411
|
+
if (this.pass) this.pass.phase = phase;
|
|
24412
|
+
}
|
|
24413
|
+
/**
|
|
24414
|
+
* Says that a pass has outlived the quorum budgets and names the phase it is
|
|
24415
|
+
* stuck in, then repeats every {@link STALL_REPORT_INTERVAL_MS} until it ends.
|
|
24416
|
+
*
|
|
24417
|
+
* Observation only: the pass is left alone. Aborting it would need a cancel
|
|
24418
|
+
* path through the quorum client that does not exist, and the cursor is safe
|
|
24419
|
+
* either way — a pass that never completes never advances it.
|
|
24420
|
+
*/
|
|
24421
|
+
reportStall() {
|
|
24422
|
+
const pass = this.pass;
|
|
24423
|
+
if (!pass) return;
|
|
24424
|
+
const now = Date.now();
|
|
24425
|
+
const heldMs = now - pass.startedAt;
|
|
24426
|
+
if (heldMs < STALL_THRESHOLD_MS) return;
|
|
24427
|
+
if (pass.reportedAt !== 0 && now - pass.reportedAt < STALL_REPORT_INTERVAL_MS) return;
|
|
24428
|
+
pass.reportedAt = now;
|
|
24429
|
+
this.logger.warn(
|
|
24430
|
+
{
|
|
24431
|
+
chainId: this.target.chainId,
|
|
24432
|
+
phase: pass.phase,
|
|
24433
|
+
heldMs,
|
|
24434
|
+
cursor: this.cursor?.toString(),
|
|
24435
|
+
rpcUrls: this.target.rpcUrls
|
|
24436
|
+
},
|
|
24437
|
+
"Block scan pass has outlived its budget \u2014 this chain is not being scanned"
|
|
24438
|
+
);
|
|
24439
|
+
}
|
|
24440
|
+
/**
|
|
24441
|
+
* Closes out a pass, and says so if the watchdog complained about it — without
|
|
24442
|
+
* this the log shows a chain going quiet and never shows it coming back.
|
|
24443
|
+
*/
|
|
24444
|
+
notePassEnd() {
|
|
24445
|
+
const pass = this.pass;
|
|
24446
|
+
this.pass = void 0;
|
|
24447
|
+
if (!pass || pass.reportedAt === 0) return;
|
|
24448
|
+
this.logger.warn(
|
|
24449
|
+
{
|
|
24450
|
+
chainId: this.target.chainId,
|
|
24451
|
+
phase: pass.phase,
|
|
24452
|
+
heldMs: Date.now() - pass.startedAt,
|
|
24453
|
+
cursor: this.cursor?.toString()
|
|
24454
|
+
},
|
|
24455
|
+
"Block scan pass finished after outliving its budget \u2014 scanning has resumed"
|
|
24456
|
+
);
|
|
24457
|
+
}
|
|
24183
24458
|
/**
|
|
24184
24459
|
* Points this loop at different endpoints, keeping the cursor.
|
|
24185
24460
|
*
|
|
@@ -24221,11 +24496,12 @@ var ChainScanner = class {
|
|
|
24221
24496
|
}
|
|
24222
24497
|
if (currentBlock <= this.cursor) return;
|
|
24223
24498
|
const fromBlock = this.cursor + 1n;
|
|
24224
|
-
const toBlock = fromBlock +
|
|
24499
|
+
const toBlock = fromBlock + this.span > currentBlock ? currentBlock : fromBlock + this.span;
|
|
24225
24500
|
this.logger.debug(
|
|
24226
24501
|
{ chainId: this.target.chainId, fromBlock, toBlock, gap: Number(toBlock - fromBlock) },
|
|
24227
24502
|
"Scanning blocks"
|
|
24228
24503
|
);
|
|
24504
|
+
this.enter("logs");
|
|
24229
24505
|
let logs;
|
|
24230
24506
|
try {
|
|
24231
24507
|
logs = await retryPromise3(
|
|
@@ -24247,6 +24523,7 @@ var ChainScanner = class {
|
|
|
24247
24523
|
);
|
|
24248
24524
|
} catch (error) {
|
|
24249
24525
|
if (isBlockRangeError(error)) return;
|
|
24526
|
+
this.narrow(toBlock - fromBlock);
|
|
24250
24527
|
throw error;
|
|
24251
24528
|
}
|
|
24252
24529
|
if (this.stopped) return;
|
|
@@ -24257,6 +24534,7 @@ var ChainScanner = class {
|
|
|
24257
24534
|
{ chainId: this.target.chainId, fromBlock, toBlock, eventCount: placed.length },
|
|
24258
24535
|
"Found OrderPlaced events in block scan"
|
|
24259
24536
|
);
|
|
24537
|
+
this.enter("publish-orders");
|
|
24260
24538
|
await this.publishOrders(placed);
|
|
24261
24539
|
}
|
|
24262
24540
|
if (filled.length > 0) {
|
|
@@ -24264,10 +24542,36 @@ var ChainScanner = class {
|
|
|
24264
24542
|
{ chainId: this.target.chainId, fromBlock, toBlock, eventCount: filled.length },
|
|
24265
24543
|
"Found OrderFilled events in block scan"
|
|
24266
24544
|
);
|
|
24545
|
+
this.enter("publish-fills");
|
|
24267
24546
|
await this.publishFills(filled);
|
|
24268
24547
|
}
|
|
24269
24548
|
if (this.stopped) return;
|
|
24270
24549
|
this.cursor = toBlock;
|
|
24550
|
+
this.widen();
|
|
24551
|
+
}
|
|
24552
|
+
/**
|
|
24553
|
+
* Halves the span below the range that just failed, so the next pass asks
|
|
24554
|
+
* for something a range-capped endpoint will answer. Halving the attempted
|
|
24555
|
+
* range rather than the span matters near the head, where a pass reads fewer
|
|
24556
|
+
* blocks than the span allows.
|
|
24557
|
+
*/
|
|
24558
|
+
narrow(attempted) {
|
|
24559
|
+
const next = attempted / 2n;
|
|
24560
|
+
if (next >= this.span) return;
|
|
24561
|
+
this.span = next;
|
|
24562
|
+
this.logger.warn(
|
|
24563
|
+
{ chainId: this.target.chainId, span: Number(next) },
|
|
24564
|
+
"Narrowing the block scan range after a failed read"
|
|
24565
|
+
);
|
|
24566
|
+
}
|
|
24567
|
+
/** Doubles the span back toward {@link MAX_BLOCK_RANGE} after a good read. */
|
|
24568
|
+
widen() {
|
|
24569
|
+
if (this.span >= MAX_BLOCK_RANGE) return;
|
|
24570
|
+
const next = this.span === 0n ? 1n : this.span * 2n;
|
|
24571
|
+
this.span = next > MAX_BLOCK_RANGE ? MAX_BLOCK_RANGE : next;
|
|
24572
|
+
if (this.span === MAX_BLOCK_RANGE) {
|
|
24573
|
+
this.logger.info({ chainId: this.target.chainId }, "Block scan range back to full");
|
|
24574
|
+
}
|
|
24271
24575
|
}
|
|
24272
24576
|
async publishOrders(logs) {
|
|
24273
24577
|
const rebuilt = reconstructOrdersFromLogs(logs, {
|
|
@@ -24634,6 +24938,10 @@ var LimitOrderController = class {
|
|
|
24634
24938
|
]);
|
|
24635
24939
|
return { order, fills, bids };
|
|
24636
24940
|
}
|
|
24941
|
+
/** The pairs the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
|
|
24942
|
+
books() {
|
|
24943
|
+
return this.service.books();
|
|
24944
|
+
}
|
|
24637
24945
|
/** Creates the order, posts it, and reports what the orderbook made of it. */
|
|
24638
24946
|
async create(request) {
|
|
24639
24947
|
const posted = await this.service.create(request);
|