@morpho-dev/router 0.10.0 → 0.11.0
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/cli.js +4698 -3654
- package/dist/drizzle/migrations/0031_sell-takeable-reindex.sql +254 -0
- package/dist/drizzle/migrations/0032_callback-type.sql +3 -0
- package/dist/drizzle/migrations/0033_obligation-id-bytes20.sql +255 -0
- package/dist/drizzle/migrations/meta/0031_snapshot.json +1652 -0
- package/dist/drizzle/migrations/meta/0033_snapshot.json +1658 -0
- package/dist/drizzle/migrations/meta/_journal.json +21 -0
- package/dist/evm/bytecode/morpho.txt +1 -1
- package/dist/index.browser.d.mts +315 -129
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.mjs +1005 -401
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +378 -175
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.mjs +2003 -969
- package/dist/index.node.mjs.map +1 -1
- package/dist/register-otel-hook.js +7 -0
- package/package.json +31 -27
- package/dist/index.browser.d.ts +0 -5007
- package/dist/index.browser.d.ts.map +0 -1
- package/dist/index.browser.js +0 -5825
- package/dist/index.browser.js.map +0 -1
- package/dist/index.node.d.ts +0 -8263
- package/dist/index.node.d.ts.map +0 -1
- package/dist/index.node.js +0 -12566
- package/dist/index.node.js.map +0 -1
package/dist/index.node.mjs
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-Bo1DHCg-.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { getBlockNumber, getLogs, multicall } from "viem/actions";
|
|
3
|
+
import { SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
3
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
5
|
import { bytesToHex, concatHex, decodeAbiParameters, encodeAbiParameters, erc20Abi, getAddress, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, numberToHex, pad, parseAbi, parseEventLogs, publicActions, recoverAddress, stringify, zeroAddress } from "viem";
|
|
5
|
-
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
6
6
|
import "@opentelemetry/exporter-trace-otlp-proto";
|
|
7
|
-
import "@opentelemetry/id-generator-aws-xray";
|
|
8
7
|
import "@opentelemetry/instrumentation";
|
|
9
8
|
import "@opentelemetry/instrumentation-http";
|
|
10
9
|
import "@opentelemetry/instrumentation-pg";
|
|
11
|
-
import "@opentelemetry/propagator-aws-xray";
|
|
12
10
|
import "@opentelemetry/resources";
|
|
13
11
|
import "@opentelemetry/sdk-trace-node";
|
|
14
12
|
import "@opentelemetry/semantic-conventions";
|
|
@@ -21,11 +19,11 @@ import { bigint, boolean, foreignKey, index, integer, numeric, pgSchema, primary
|
|
|
21
19
|
import { serve } from "@hono/node-server";
|
|
22
20
|
import { Hono } from "hono";
|
|
23
21
|
import { cors } from "hono/cors";
|
|
22
|
+
import crypto, { createHash, randomUUID } from "node:crypto";
|
|
24
23
|
import { z } from "zod/v4";
|
|
25
24
|
import "reflect-metadata";
|
|
26
25
|
import { generateDocument } from "openapi-metadata";
|
|
27
26
|
import { ApiBody, ApiOperation, ApiParam, ApiProperty, ApiQuery, ApiResponse, ApiTags } from "openapi-metadata/decorators";
|
|
28
|
-
import crypto, { createHash } from "node:crypto";
|
|
29
27
|
import { existsSync } from "node:fs";
|
|
30
28
|
import { readFile } from "node:fs/promises";
|
|
31
29
|
import { dirname, resolve } from "node:path";
|
|
@@ -39,11 +37,75 @@ import { drizzle as drizzle$1 } from "drizzle-orm/pglite";
|
|
|
39
37
|
import { migrate as migrate$1 } from "drizzle-orm/pglite/migrator";
|
|
40
38
|
import { Pool } from "pg";
|
|
41
39
|
|
|
40
|
+
//#region src/observability/Events.ts
|
|
41
|
+
const eventNamePattern = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/;
|
|
42
|
+
/**
|
|
43
|
+
* Canonical telemetry event names used by router logs.
|
|
44
|
+
* Every value must stay unique and follow dot-separated snake_case segments.
|
|
45
|
+
*/
|
|
46
|
+
const Events = defineEvents({
|
|
47
|
+
API_GET_BOOK_FAILED: "api.get_book.failed",
|
|
48
|
+
API_GET_HEALTH_CHAINS_FAILED: "api.get_health_chains.failed",
|
|
49
|
+
API_GET_HEALTH_COLLECTORS_FAILED: "api.get_health_collectors.failed",
|
|
50
|
+
API_GET_HEALTH_FAILED: "api.get_health.failed",
|
|
51
|
+
API_GET_OBLIGATION_FAILED: "api.get_obligation.failed",
|
|
52
|
+
API_GET_OBLIGATIONS_FAILED: "api.get_obligations.failed",
|
|
53
|
+
API_GET_OFFERS_FAILED: "api.get_offers.failed",
|
|
54
|
+
API_GET_USER_POSITIONS_FAILED: "api.get_user_positions.failed",
|
|
55
|
+
API_VALIDATE_OFFERS_FAILED: "api.validate_offers.failed",
|
|
56
|
+
HTTP_REQUEST_COMPLETED: "http.request.completed",
|
|
57
|
+
HTTP_REQUEST_FAILED: "http.request.failed",
|
|
58
|
+
INDEXER_COLLECTOR_EVENTS_INDEXED: "indexer.collector.events_indexed",
|
|
59
|
+
INDEXER_COLLECTOR_FAILED: "indexer.collector.failed",
|
|
60
|
+
INDEXER_COLLECTOR_FINALIZED_BLOCK_FAILED: "indexer.collector.finalized_block_failed",
|
|
61
|
+
INDEXER_COLLECTOR_GATEKEEPER_VALIDATION_FAILED: "indexer.collector.gatekeeper_validation_failed",
|
|
62
|
+
INDEXER_COLLECTOR_INVALID_CHAIN_CONFIG: "indexer.collector.invalid_chain_config",
|
|
63
|
+
INDEXER_COLLECTOR_LOG_SKIPPED: "indexer.collector.log_skipped",
|
|
64
|
+
INDEXER_COLLECTOR_MAX_BLOCK_REACHED: "indexer.collector.max_block_reached",
|
|
65
|
+
INDEXER_COLLECTOR_MISSING_BLOCKS: "indexer.collector.missing_blocks",
|
|
66
|
+
INDEXER_COLLECTOR_OFFERS_INDEXED: "indexer.collector.offers_indexed",
|
|
67
|
+
INDEXER_COLLECTOR_OFFER_TREE_DECODE_FAILED: "indexer.collector.offer_tree_decode_failed",
|
|
68
|
+
INDEXER_COLLECTOR_OFFER_TREE_REJECTED: "indexer.collector.offer_tree_rejected",
|
|
69
|
+
INDEXER_COLLECTOR_ORACLES_INDEXED: "indexer.collector.oracles_indexed",
|
|
70
|
+
INDEXER_COLLECTOR_ORACLE_FETCH_FAILED: "indexer.collector.oracle_fetch_failed",
|
|
71
|
+
INDEXER_COLLECTOR_POLL_FAILED: "indexer.collector.poll_failed",
|
|
72
|
+
INDEXER_COLLECTOR_POSITIONS_INDEXED: "indexer.collector.positions_indexed",
|
|
73
|
+
INDEXER_COLLECTOR_POSITIONS_INSERT_FAILED: "indexer.collector.positions_insert_failed",
|
|
74
|
+
INDEXER_COLLECTOR_POSITIONS_SNAPSHOT_FAILED: "indexer.collector.positions_snapshot_failed",
|
|
75
|
+
INDEXER_COLLECTOR_POSITION_CONVERSION_FAILED: "indexer.collector.position_conversion_failed",
|
|
76
|
+
INDEXER_COLLECTOR_REORG_COMPENSATED: "indexer.collector.reorg_compensated",
|
|
77
|
+
INDEXER_COLLECTOR_REORG_COMPENSATION_FAILED: "indexer.collector.reorg_compensation_failed",
|
|
78
|
+
INDEXER_COLLECTOR_REORG_DETECTED: "indexer.collector.reorg_detected",
|
|
79
|
+
INDEXER_COLLECTOR_RPC_QUERY_INVALID_RANGE: "indexer.collector.rpc_query_invalid_range",
|
|
80
|
+
INDEXER_COLLECTOR_STARTED: "indexer.collector.started",
|
|
81
|
+
INDEXER_COLLECTOR_TRANSFERS_INDEXED: "indexer.collector.transfers_indexed",
|
|
82
|
+
INDEXER_COLLECTOR_TRANSFERS_INSERT_FAILED: "indexer.collector.transfers_insert_failed"
|
|
83
|
+
});
|
|
84
|
+
const { API_GET_BOOK_FAILED, API_GET_HEALTH_CHAINS_FAILED, API_GET_HEALTH_COLLECTORS_FAILED, API_GET_HEALTH_FAILED, API_GET_OBLIGATION_FAILED, API_GET_OBLIGATIONS_FAILED, API_GET_OFFERS_FAILED, API_GET_USER_POSITIONS_FAILED, API_VALIDATE_OFFERS_FAILED, HTTP_REQUEST_COMPLETED, HTTP_REQUEST_FAILED, INDEXER_COLLECTOR_EVENTS_INDEXED, INDEXER_COLLECTOR_FAILED, INDEXER_COLLECTOR_FINALIZED_BLOCK_FAILED, INDEXER_COLLECTOR_GATEKEEPER_VALIDATION_FAILED, INDEXER_COLLECTOR_INVALID_CHAIN_CONFIG, INDEXER_COLLECTOR_LOG_SKIPPED, INDEXER_COLLECTOR_MAX_BLOCK_REACHED, INDEXER_COLLECTOR_MISSING_BLOCKS, INDEXER_COLLECTOR_OFFERS_INDEXED, INDEXER_COLLECTOR_OFFER_TREE_DECODE_FAILED, INDEXER_COLLECTOR_OFFER_TREE_REJECTED, INDEXER_COLLECTOR_ORACLES_INDEXED, INDEXER_COLLECTOR_ORACLE_FETCH_FAILED, INDEXER_COLLECTOR_POLL_FAILED, INDEXER_COLLECTOR_POSITIONS_INDEXED, INDEXER_COLLECTOR_POSITIONS_INSERT_FAILED, INDEXER_COLLECTOR_POSITIONS_SNAPSHOT_FAILED, INDEXER_COLLECTOR_POSITION_CONVERSION_FAILED, INDEXER_COLLECTOR_REORG_COMPENSATED, INDEXER_COLLECTOR_REORG_COMPENSATION_FAILED, INDEXER_COLLECTOR_REORG_DETECTED, INDEXER_COLLECTOR_RPC_QUERY_INVALID_RANGE, INDEXER_COLLECTOR_STARTED, INDEXER_COLLECTOR_TRANSFERS_INDEXED, INDEXER_COLLECTOR_TRANSFERS_INSERT_FAILED } = Events;
|
|
85
|
+
function defineEvents(events) {
|
|
86
|
+
const names = Object.values(events);
|
|
87
|
+
validateUnique(names);
|
|
88
|
+
validatePattern(names);
|
|
89
|
+
return events;
|
|
90
|
+
}
|
|
91
|
+
function validateUnique(names) {
|
|
92
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93
|
+
for (const name of names) {
|
|
94
|
+
if (seen.has(name)) throw new Error(`Duplicate telemetry event name: ${name}`);
|
|
95
|
+
seen.add(name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function validatePattern(names) {
|
|
99
|
+
for (const name of names) if (!eventNamePattern.test(name)) throw new Error(`Invalid telemetry event name: ${name}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
42
103
|
//#region src/logger/Logger.ts
|
|
43
104
|
var Logger_exports = /* @__PURE__ */ __exportAll({
|
|
44
105
|
LogLevelValues: () => LogLevelValues,
|
|
45
106
|
defaultLogger: () => defaultLogger,
|
|
46
107
|
getLogger: () => getLogger,
|
|
108
|
+
runWithLogContext: () => runWithLogContext,
|
|
47
109
|
runWithLogger: () => runWithLogger,
|
|
48
110
|
silentLogger: () => silentLogger
|
|
49
111
|
});
|
|
@@ -65,15 +127,16 @@ function defaultLogger(minLevel, pretty) {
|
|
|
65
127
|
}, {});
|
|
66
128
|
const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
|
|
67
129
|
const wrap = (consoleMethod, methodLevel) => isEnabled(methodLevel) ? (entry) => {
|
|
130
|
+
const normalizedEntry = normalizeLogEntry(entry);
|
|
68
131
|
if (!prettyEnabled) {
|
|
69
132
|
console[consoleMethod](stringify({
|
|
70
133
|
level: methodLevel,
|
|
71
|
-
...
|
|
134
|
+
...normalizedEntry
|
|
72
135
|
}));
|
|
73
136
|
return;
|
|
74
137
|
}
|
|
75
|
-
const { msg, ...rest } =
|
|
76
|
-
const stack = typeof rest.stack === "string" ? rest.stack :
|
|
138
|
+
const { msg, ...rest } = normalizedEntry;
|
|
139
|
+
const stack = typeof rest.stack === "string" ? rest.stack : getErrorStack(rest.err);
|
|
77
140
|
if (stack) delete rest.stack;
|
|
78
141
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
79
142
|
const level = methodLevel.toUpperCase();
|
|
@@ -103,11 +166,40 @@ function silentLogger() {
|
|
|
103
166
|
};
|
|
104
167
|
}
|
|
105
168
|
const loggerContext = new AsyncLocalStorage();
|
|
169
|
+
const logEntryContext = new AsyncLocalStorage();
|
|
106
170
|
function runWithLogger(logger, fn) {
|
|
107
171
|
return loggerContext.run(logger, fn);
|
|
108
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Run a function with additional context fields attached to every log entry emitted via {@link getLogger}.
|
|
175
|
+
* Nested calls merge context (inner keys override outer keys).
|
|
176
|
+
* @param context - Static fields added to all log entries in this scope.
|
|
177
|
+
* @param fn - Async function to run with the scoped logging context.
|
|
178
|
+
* @returns The result of the function.
|
|
179
|
+
*/
|
|
180
|
+
function runWithLogContext(context, fn) {
|
|
181
|
+
const inheritedContext = logEntryContext.getStore() ?? {};
|
|
182
|
+
return logEntryContext.run({
|
|
183
|
+
...inheritedContext,
|
|
184
|
+
...context
|
|
185
|
+
}, fn);
|
|
186
|
+
}
|
|
109
187
|
function getLogger() {
|
|
110
|
-
|
|
188
|
+
const scopedLogger = loggerContext.getStore() ?? defaultLogger();
|
|
189
|
+
const scopedContext = logEntryContext.getStore();
|
|
190
|
+
if (!scopedContext || Object.keys(scopedContext).length === 0) return scopedLogger;
|
|
191
|
+
const withContext = (logFn) => (entry) => logFn({
|
|
192
|
+
...scopedContext,
|
|
193
|
+
...entry
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
trace: withContext(scopedLogger.trace),
|
|
197
|
+
debug: withContext(scopedLogger.debug),
|
|
198
|
+
info: withContext(scopedLogger.info),
|
|
199
|
+
warn: withContext(scopedLogger.warn),
|
|
200
|
+
error: withContext(scopedLogger.error),
|
|
201
|
+
fatal: withContext(scopedLogger.fatal)
|
|
202
|
+
};
|
|
111
203
|
}
|
|
112
204
|
function formatValue(value) {
|
|
113
205
|
if (value === null || value === void 0 || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") return String(value);
|
|
@@ -125,6 +217,112 @@ function formatValue(value) {
|
|
|
125
217
|
}
|
|
126
218
|
}
|
|
127
219
|
}
|
|
220
|
+
function normalizeLogEntry(entry) {
|
|
221
|
+
return normalizeUnknown(entry, /* @__PURE__ */ new WeakSet());
|
|
222
|
+
}
|
|
223
|
+
function normalizeUnknown(value, path) {
|
|
224
|
+
if (value instanceof Error) return serializeError(value, path);
|
|
225
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
226
|
+
if (Array.isArray(value)) {
|
|
227
|
+
if (path.has(value)) return "[Circular]";
|
|
228
|
+
path.add(value);
|
|
229
|
+
try {
|
|
230
|
+
return value.map((item) => normalizeUnknown(item, path));
|
|
231
|
+
} finally {
|
|
232
|
+
path.delete(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (value && typeof value === "object") {
|
|
236
|
+
if (path.has(value)) return "[Circular]";
|
|
237
|
+
path.add(value);
|
|
238
|
+
try {
|
|
239
|
+
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, normalizeUnknown(nested, path)]));
|
|
240
|
+
} finally {
|
|
241
|
+
path.delete(value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
function serializeError(error, path) {
|
|
247
|
+
if (path.has(error)) return {
|
|
248
|
+
name: error.name,
|
|
249
|
+
message: error.message,
|
|
250
|
+
circular: true
|
|
251
|
+
};
|
|
252
|
+
path.add(error);
|
|
253
|
+
try {
|
|
254
|
+
const serialized = {
|
|
255
|
+
name: error.name,
|
|
256
|
+
message: error.message
|
|
257
|
+
};
|
|
258
|
+
if (typeof error.stack === "string") serialized.stack = error.stack;
|
|
259
|
+
if ("cause" in error && error.cause !== void 0) serialized.cause = normalizeUnknown(error.cause, path);
|
|
260
|
+
for (const [key, value] of Object.entries(error)) {
|
|
261
|
+
if (key === "name" || key === "message" || key === "stack" || key === "cause") continue;
|
|
262
|
+
serialized[key] = normalizeUnknown(value, path);
|
|
263
|
+
}
|
|
264
|
+
return serialized;
|
|
265
|
+
} finally {
|
|
266
|
+
path.delete(error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function getErrorStack(errorValue) {
|
|
270
|
+
if (!errorValue || typeof errorValue !== "object") return void 0;
|
|
271
|
+
const nestedStack = errorValue.stack;
|
|
272
|
+
return typeof nestedStack === "string" ? nestedStack : void 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/observability/Telemetry.ts
|
|
277
|
+
const invalidTraceId = "0".repeat(32);
|
|
278
|
+
const invalidSpanId = "0".repeat(16);
|
|
279
|
+
/**
|
|
280
|
+
* Read active trace and span identifiers when a span is currently active.
|
|
281
|
+
* @returns Active identifiers when available.
|
|
282
|
+
*/
|
|
283
|
+
function getActiveTraceIdentifiers() {
|
|
284
|
+
const activeSpan = trace.getActiveSpan();
|
|
285
|
+
if (!activeSpan) return {};
|
|
286
|
+
const spanContext = activeSpan.spanContext();
|
|
287
|
+
return getTraceIdentifiersFromContext(spanContext.traceId, spanContext.spanId);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Build log-safe trace identifiers from OpenTelemetry span context values.
|
|
291
|
+
* @param traceId - OpenTelemetry trace identifier.
|
|
292
|
+
* @param spanId - OpenTelemetry span identifier.
|
|
293
|
+
* @returns Trace identifiers when both ids are valid and non-noop.
|
|
294
|
+
*/
|
|
295
|
+
function getTraceIdentifiersFromContext(traceId, spanId) {
|
|
296
|
+
if (!isValidOtelIdentifier(traceId, invalidTraceId) || !isValidOtelIdentifier(spanId, invalidSpanId)) return {};
|
|
297
|
+
return {
|
|
298
|
+
trace_id: traceId,
|
|
299
|
+
span_id: spanId
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Build a logger that enriches each entry with shared telemetry context.
|
|
304
|
+
* @param context - Static fields attached to every log entry.
|
|
305
|
+
* @returns Context-aware logger.
|
|
306
|
+
*/
|
|
307
|
+
function getLoggerWithContext(context) {
|
|
308
|
+
const scopedLogger = getLogger();
|
|
309
|
+
const withContext = (logFn) => (entry) => logFn({
|
|
310
|
+
...context,
|
|
311
|
+
...getActiveTraceIdentifiers(),
|
|
312
|
+
...entry
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
trace: withContext(scopedLogger.trace),
|
|
316
|
+
debug: withContext(scopedLogger.debug),
|
|
317
|
+
info: withContext(scopedLogger.info),
|
|
318
|
+
warn: withContext(scopedLogger.warn),
|
|
319
|
+
error: withContext(scopedLogger.error),
|
|
320
|
+
fatal: withContext(scopedLogger.fatal)
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function isValidOtelIdentifier(identifier, invalidValue) {
|
|
324
|
+
return identifier.length === invalidValue.length && identifier !== invalidValue;
|
|
325
|
+
}
|
|
128
326
|
|
|
129
327
|
//#endregion
|
|
130
328
|
//#region src/tracer/Tracer.ts
|
|
@@ -566,14 +764,18 @@ var utils_exports = /* @__PURE__ */ __exportAll({
|
|
|
566
764
|
|
|
567
765
|
//#endregion
|
|
568
766
|
//#region src/indexer/collectors/Admin.ts
|
|
569
|
-
function create$
|
|
767
|
+
function create$22(parameters) {
|
|
570
768
|
const collector = "admin";
|
|
571
769
|
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
572
770
|
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
573
771
|
let finalizedBlock = null;
|
|
574
772
|
let unfinalizedBlocks = [];
|
|
575
773
|
let initialized = false;
|
|
576
|
-
const logger =
|
|
774
|
+
const logger = getLoggerWithContext({
|
|
775
|
+
component: "indexer.collector.admin",
|
|
776
|
+
collector,
|
|
777
|
+
chain_id: client.chain.id
|
|
778
|
+
});
|
|
577
779
|
let isMaxBlockNumberReached = false;
|
|
578
780
|
let tick = 0;
|
|
579
781
|
return { syncBlock: async () => {
|
|
@@ -593,9 +795,8 @@ function create$21(parameters) {
|
|
|
593
795
|
const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
594
796
|
if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
|
|
595
797
|
logger.info({
|
|
798
|
+
event: INDEXER_COLLECTOR_MAX_BLOCK_REACHED,
|
|
596
799
|
msg: `Head is greater than max block number`,
|
|
597
|
-
collector,
|
|
598
|
-
chainId: client.chain.id,
|
|
599
800
|
block_number: head.number,
|
|
600
801
|
max_block_number: maxBlockNumber
|
|
601
802
|
});
|
|
@@ -611,7 +812,6 @@ function create$21(parameters) {
|
|
|
611
812
|
tick,
|
|
612
813
|
client,
|
|
613
814
|
logger,
|
|
614
|
-
collector,
|
|
615
815
|
unfinalizedBlocks,
|
|
616
816
|
previousFinalizedBlock: finalizedBlock
|
|
617
817
|
});
|
|
@@ -622,7 +822,6 @@ function create$21(parameters) {
|
|
|
622
822
|
unfinalizedBlocks,
|
|
623
823
|
finalizedBlock,
|
|
624
824
|
logger,
|
|
625
|
-
collector,
|
|
626
825
|
maxBatchSize
|
|
627
826
|
});
|
|
628
827
|
unfinalizedBlocks = newUnfinalizedBlocks;
|
|
@@ -648,7 +847,7 @@ const commonAncestor = (block, unfinalizedBlocks) => {
|
|
|
648
847
|
return null;
|
|
649
848
|
};
|
|
650
849
|
const fetchFinalizedBlock = async (parameters) => {
|
|
651
|
-
let { tick, client, logger,
|
|
850
|
+
let { tick, client, logger, unfinalizedBlocks, previousFinalizedBlock } = parameters;
|
|
652
851
|
let finalizedBlock = previousFinalizedBlock;
|
|
653
852
|
if (tick % 20 === 0 || previousFinalizedBlock === null) {
|
|
654
853
|
finalizedBlock = await client.getBlock({
|
|
@@ -658,8 +857,7 @@ const fetchFinalizedBlock = async (parameters) => {
|
|
|
658
857
|
if (finalizedBlock === null || finalizedBlock.number === null) {
|
|
659
858
|
const msg = "Failed to get finalized block";
|
|
660
859
|
logger.fatal({
|
|
661
|
-
|
|
662
|
-
chainId: client.chain.id,
|
|
860
|
+
event: INDEXER_COLLECTOR_FINALIZED_BLOCK_FAILED,
|
|
663
861
|
msg
|
|
664
862
|
});
|
|
665
863
|
throw new Error(msg);
|
|
@@ -669,8 +867,7 @@ const fetchFinalizedBlock = async (parameters) => {
|
|
|
669
867
|
if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
|
|
670
868
|
const msg = "Failed to get finalized block";
|
|
671
869
|
logger.fatal({
|
|
672
|
-
|
|
673
|
-
chainId: client.chain.id,
|
|
870
|
+
event: INDEXER_COLLECTOR_FINALIZED_BLOCK_FAILED,
|
|
674
871
|
msg
|
|
675
872
|
});
|
|
676
873
|
throw new Error(msg);
|
|
@@ -682,8 +879,7 @@ const fetchFinalizedBlock = async (parameters) => {
|
|
|
682
879
|
};
|
|
683
880
|
};
|
|
684
881
|
const reconcile = async (parameters) => {
|
|
685
|
-
let { client, block, unfinalizedBlocks, finalizedBlock, logger,
|
|
686
|
-
const chain = client.chain;
|
|
882
|
+
let { client, block, unfinalizedBlocks, finalizedBlock, logger, maxBatchSize } = parameters;
|
|
687
883
|
if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
|
|
688
884
|
const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
|
|
689
885
|
if (latestBlock === void 0) {
|
|
@@ -707,9 +903,8 @@ const reconcile = async (parameters) => {
|
|
|
707
903
|
if (latestBlock.number >= block.number) {
|
|
708
904
|
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
709
905
|
logger.info({
|
|
906
|
+
event: INDEXER_COLLECTOR_REORG_DETECTED,
|
|
710
907
|
msg: `Reorg detected, latestBlock.number >= block.number`,
|
|
711
|
-
collector,
|
|
712
|
-
chain_id: chain.id,
|
|
713
908
|
ancestor: ancestor.number,
|
|
714
909
|
latest_block_number: latestBlock.number,
|
|
715
910
|
block_number: block.number
|
|
@@ -723,8 +918,7 @@ const reconcile = async (parameters) => {
|
|
|
723
918
|
}
|
|
724
919
|
if (latestBlock.number + 1n < block.number) {
|
|
725
920
|
logger.debug({
|
|
726
|
-
|
|
727
|
-
chain_id: chain.id,
|
|
921
|
+
event: INDEXER_COLLECTOR_MISSING_BLOCKS,
|
|
728
922
|
block_range: [latestBlock.number, block.number],
|
|
729
923
|
msg: `Missing blocks`
|
|
730
924
|
});
|
|
@@ -749,7 +943,6 @@ const reconcile = async (parameters) => {
|
|
|
749
943
|
unfinalizedBlocks,
|
|
750
944
|
finalizedBlock,
|
|
751
945
|
logger,
|
|
752
|
-
collector,
|
|
753
946
|
maxBatchSize
|
|
754
947
|
});
|
|
755
948
|
if (returnedBlock.number !== missingBlock.number) return {
|
|
@@ -764,16 +957,14 @@ const reconcile = async (parameters) => {
|
|
|
764
957
|
unfinalizedBlocks,
|
|
765
958
|
finalizedBlock,
|
|
766
959
|
logger,
|
|
767
|
-
collector,
|
|
768
960
|
maxBatchSize
|
|
769
961
|
});
|
|
770
962
|
}
|
|
771
963
|
if (block.parentHash !== latestBlock.hash) {
|
|
772
964
|
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
773
965
|
logger.info({
|
|
966
|
+
event: INDEXER_COLLECTOR_REORG_DETECTED,
|
|
774
967
|
msg: `Reorg detected, block parent hash !== latest block hash`,
|
|
775
|
-
collector,
|
|
776
|
-
chain_id: chain.id,
|
|
777
968
|
ancestor: ancestor.number,
|
|
778
969
|
latest_block_number: latestBlock.number,
|
|
779
970
|
block_number: block.number
|
|
@@ -806,31 +997,37 @@ const names = [
|
|
|
806
997
|
"positions",
|
|
807
998
|
"prices"
|
|
808
999
|
];
|
|
809
|
-
function create$
|
|
810
|
-
const admin = create$
|
|
1000
|
+
function create$21({ name, collect, client, db, options }) {
|
|
1001
|
+
const admin = create$22({
|
|
811
1002
|
client,
|
|
812
1003
|
db,
|
|
813
1004
|
options
|
|
814
1005
|
});
|
|
1006
|
+
const chain = client.chain;
|
|
1007
|
+
const interval = options.interval ?? 1e4;
|
|
815
1008
|
return {
|
|
816
1009
|
name,
|
|
817
|
-
chain
|
|
1010
|
+
chain,
|
|
818
1011
|
client,
|
|
819
1012
|
db,
|
|
820
|
-
interval
|
|
1013
|
+
interval,
|
|
821
1014
|
collect: async function* () {
|
|
822
1015
|
const collector = name;
|
|
823
1016
|
const chain = client.chain;
|
|
824
|
-
const logger =
|
|
1017
|
+
const logger = getLoggerWithContext({
|
|
1018
|
+
component: "indexer.collector",
|
|
1019
|
+
collector,
|
|
1020
|
+
chain_id: chain.id
|
|
1021
|
+
});
|
|
825
1022
|
const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
|
|
826
1023
|
const tracer = getTracer(`router.${collectorId}`);
|
|
827
1024
|
logger.info({
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
chain_id: chain.id
|
|
1025
|
+
event: INDEXER_COLLECTOR_STARTED,
|
|
1026
|
+
msg: `Collector started`
|
|
831
1027
|
});
|
|
832
1028
|
let iterator = null;
|
|
833
1029
|
let lastBlockNumber;
|
|
1030
|
+
let retryAttempt = 0;
|
|
834
1031
|
while (true) try {
|
|
835
1032
|
if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
|
|
836
1033
|
const { collector: collectorBlock } = await db.blocks.init({
|
|
@@ -859,20 +1056,36 @@ function create$20({ name, collect, client, db, options }) {
|
|
|
859
1056
|
done
|
|
860
1057
|
};
|
|
861
1058
|
});
|
|
862
|
-
if (done)
|
|
863
|
-
|
|
1059
|
+
if (done) {
|
|
1060
|
+
iterator = null;
|
|
1061
|
+
retryAttempt = 0;
|
|
1062
|
+
} else {
|
|
864
1063
|
lastBlockNumber = blockNumber;
|
|
1064
|
+
retryAttempt = 0;
|
|
865
1065
|
yield blockNumber;
|
|
866
1066
|
}
|
|
867
1067
|
} catch (err) {
|
|
868
|
-
const
|
|
1068
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1069
|
+
const invalidRange = getInvalidBlockRange(error);
|
|
1070
|
+
if (invalidRange !== null) logger.warn({
|
|
1071
|
+
event: INDEXER_COLLECTOR_RPC_QUERY_INVALID_RANGE,
|
|
1072
|
+
msg: "Invalid eth_getLogs block range rejected",
|
|
1073
|
+
rpc_method: "eth_getLogs",
|
|
1074
|
+
action: "reset_iterator",
|
|
1075
|
+
from_block: invalidRange.fromBlock,
|
|
1076
|
+
to_block: invalidRange.toBlock
|
|
1077
|
+
});
|
|
1078
|
+
retryAttempt += 1;
|
|
1079
|
+
const retryDelayMs = Math.min(interval, 1e3 * 2 ** Math.min(retryAttempt - 1, 3));
|
|
1080
|
+
iterator = null;
|
|
869
1081
|
logger.error({
|
|
1082
|
+
event: INDEXER_COLLECTOR_FAILED,
|
|
870
1083
|
msg: "Collector error",
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
stack: isError ? err.stack : void 0
|
|
1084
|
+
err: error,
|
|
1085
|
+
retry_attempt: retryAttempt,
|
|
1086
|
+
retry_delay_ms: retryDelayMs
|
|
875
1087
|
});
|
|
1088
|
+
await wait(retryDelayMs);
|
|
876
1089
|
}
|
|
877
1090
|
}
|
|
878
1091
|
};
|
|
@@ -884,7 +1097,11 @@ function create$20({ name, collect, client, db, options }) {
|
|
|
884
1097
|
function start(collector) {
|
|
885
1098
|
let stopped = false;
|
|
886
1099
|
const it = collector.collect();
|
|
887
|
-
const logger =
|
|
1100
|
+
const logger = getLoggerWithContext({
|
|
1101
|
+
component: "indexer.collector",
|
|
1102
|
+
collector: collector.name,
|
|
1103
|
+
chain_id: collector.chain.id
|
|
1104
|
+
});
|
|
888
1105
|
const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
|
|
889
1106
|
const tracer = getTracer(`router.${collectorId}`);
|
|
890
1107
|
const blocks = collector.db.blocks;
|
|
@@ -918,9 +1135,8 @@ function start(collector) {
|
|
|
918
1135
|
} catch (err) {
|
|
919
1136
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
920
1137
|
logger.error({
|
|
1138
|
+
event: INDEXER_COLLECTOR_POLL_FAILED,
|
|
921
1139
|
msg: "Collector polling error",
|
|
922
|
-
collector: collector.name,
|
|
923
|
-
chain_id: collector.chain.id,
|
|
924
1140
|
err: error
|
|
925
1141
|
});
|
|
926
1142
|
}
|
|
@@ -930,6 +1146,20 @@ function start(collector) {
|
|
|
930
1146
|
stopped = true;
|
|
931
1147
|
};
|
|
932
1148
|
}
|
|
1149
|
+
const getInvalidBlockRange = (error) => {
|
|
1150
|
+
if (error.name !== "Chain.InvalidBlockRangeError") return null;
|
|
1151
|
+
const candidate = error;
|
|
1152
|
+
if (typeof candidate.fromBlock === "bigint" && typeof candidate.toBlock === "bigint") return {
|
|
1153
|
+
fromBlock: candidate.fromBlock.toString(),
|
|
1154
|
+
toBlock: candidate.toBlock.toString()
|
|
1155
|
+
};
|
|
1156
|
+
const match = error.message.match(/From block (\d+) to block (\d+)\./);
|
|
1157
|
+
if (!match) return null;
|
|
1158
|
+
return {
|
|
1159
|
+
fromBlock: match[1],
|
|
1160
|
+
toBlock: match[2]
|
|
1161
|
+
};
|
|
1162
|
+
};
|
|
933
1163
|
|
|
934
1164
|
//#endregion
|
|
935
1165
|
//#region src/core/Abi/MetaMorpho.ts
|
|
@@ -953,55 +1183,57 @@ const MetaMorphoFactory = parseAbi(["event CreateMetaMorpho(address indexed meta
|
|
|
953
1183
|
//#region src/core/Abi/MorphoV2.ts
|
|
954
1184
|
const MorphoV2 = parseAbi([
|
|
955
1185
|
"constructor()",
|
|
956
|
-
"function collateralOf(
|
|
1186
|
+
"function collateralOf(bytes20 id, address user, uint256 collateralIndex) view returns (uint128)",
|
|
957
1187
|
"function consume(bytes32 group, uint256 amount)",
|
|
958
1188
|
"function consumed(address user, bytes32 group) view returns (uint256)",
|
|
959
|
-
"function debtOf(
|
|
1189
|
+
"function debtOf(bytes20 id, address user) view returns (uint256)",
|
|
960
1190
|
"function defaultFees(address loanToken, uint256 index) view returns (uint16)",
|
|
961
1191
|
"function feeSetter() view returns (address)",
|
|
962
|
-
"function fees(
|
|
1192
|
+
"function fees(bytes20 id) view returns (uint16[6])",
|
|
963
1193
|
"function flashLoan(address token, uint256 assets, address callback, bytes data)",
|
|
964
|
-
"function isHealthy((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation,
|
|
965
|
-
"function liquidate((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation,
|
|
1194
|
+
"function isHealthy((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation, bytes20 id, address borrower) view returns (bool)",
|
|
1195
|
+
"function liquidate((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation, uint256 collateralIndex, uint256 seizedAssets, uint256 repaidUnits, address borrower, bytes data) returns (uint256, uint256)",
|
|
966
1196
|
"function multicall(bytes[] calls)",
|
|
967
|
-
"function obligationCreated(
|
|
968
|
-
"function obligationState(
|
|
1197
|
+
"function obligationCreated(bytes20 id) view returns (bool)",
|
|
1198
|
+
"function obligationState(bytes20 id) view returns (uint128 totalUnits, uint128 totalShares, uint256 withdrawable, bool created, uint16[6] fees)",
|
|
969
1199
|
"function owner() view returns (address)",
|
|
970
|
-
"function repay((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, address onBehalf)",
|
|
1200
|
+
"function repay((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation, uint256 obligationUnits, address onBehalf)",
|
|
971
1201
|
"function session(address user) view returns (bytes32)",
|
|
972
1202
|
"function setDefaultTradingFee(address loanToken, uint256 index, uint256 newTradingFee)",
|
|
973
1203
|
"function setFeeSetter(address newFeeSetter)",
|
|
974
|
-
"function setObligationTradingFee(
|
|
1204
|
+
"function setObligationTradingFee(bytes20 id, uint256 index, uint256 newTradingFee)",
|
|
975
1205
|
"function setOwner(address newOwner)",
|
|
976
1206
|
"function setTradingFeeRecipient(address feeRecipient)",
|
|
977
|
-
"function sharesOf(
|
|
1207
|
+
"function sharesOf(bytes20 id, address user) view returns (uint256)",
|
|
978
1208
|
"function shuffleSession()",
|
|
979
|
-
"function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation,
|
|
980
|
-
"function take(uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, address taker, address takerCallback, bytes takerCallbackData, address receiverIfTakerIsSeller, ((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, bool buy, address maker, uint256 assets, uint256 obligationUnits, uint256 obligationShares, uint256 start, uint256 expiry, uint256 tick, bytes32 group, bytes32 session, address callback, bytes callbackData, address receiverIfMakerIsSeller) offer, (uint8 v, bytes32 r, bytes32 s) sig, bytes32 root, bytes32[] proof) returns (uint256, uint256, uint256, uint256)",
|
|
981
|
-
"function
|
|
982
|
-
"function
|
|
983
|
-
"function
|
|
984
|
-
"function
|
|
1209
|
+
"function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation, uint256 collateralIndex, uint256 assets, address onBehalf)",
|
|
1210
|
+
"function take(uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, address taker, address takerCallback, bytes takerCallbackData, address receiverIfTakerIsSeller, ((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation, bool buy, address maker, uint256 assets, uint256 obligationUnits, uint256 obligationShares, uint256 start, uint256 expiry, uint256 tick, bytes32 group, bytes32 session, address callback, bytes callbackData, address receiverIfMakerIsSeller) offer, (uint8 v, bytes32 r, bytes32 s) sig, bytes32 root, bytes32[] proof) returns (uint256, uint256, uint256, uint256)",
|
|
1211
|
+
"function toId((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation) view returns (bytes20)",
|
|
1212
|
+
"function toObligation(bytes20 id) view returns ((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue))",
|
|
1213
|
+
"function totalShares(bytes20 id) view returns (uint256)",
|
|
1214
|
+
"function totalUnits(bytes20 id) view returns (uint256)",
|
|
1215
|
+
"function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation) returns (bytes20)",
|
|
1216
|
+
"function tradingFee(bytes20 id, uint256 timeToMaturity) view returns (uint256)",
|
|
985
1217
|
"function tradingFeeRecipient() view returns (address)",
|
|
986
|
-
"function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf, address receiver) returns (uint256, uint256)",
|
|
987
|
-
"function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation,
|
|
988
|
-
"function withdrawable(
|
|
1218
|
+
"function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation, uint256 obligationUnits, uint256 shares, address onBehalf, address receiver) returns (uint256, uint256)",
|
|
1219
|
+
"function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation, uint256 collateralIndex, uint256 assets, address onBehalf, address receiver)",
|
|
1220
|
+
"function withdrawable(bytes20 id) view returns (uint256)",
|
|
989
1221
|
"event Constructor(address indexed owner)",
|
|
990
1222
|
"event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
|
|
991
1223
|
"event FlashLoan(address indexed caller, address indexed token, uint256 assets)",
|
|
992
|
-
"event Liquidate(address indexed caller,
|
|
993
|
-
"event ObligationCreated(
|
|
994
|
-
"event Repay(address indexed caller,
|
|
1224
|
+
"event Liquidate(address indexed caller, bytes20 indexed id_, uint256 collateralIndex, uint256 seizedAssets, uint256 repaidUnits, address indexed borrower, uint256 badDebt)",
|
|
1225
|
+
"event ObligationCreated(bytes20 indexed id_, (address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity, uint256 minCollatValue) obligation)",
|
|
1226
|
+
"event Repay(address indexed caller, bytes20 indexed id_, uint256 obligationUnits, address indexed onBehalf)",
|
|
995
1227
|
"event SetDefaultTradingFee(address indexed loanToken, uint256 indexed index, uint256 newTradingFee)",
|
|
996
1228
|
"event SetFeeSetter(address indexed feeSetter)",
|
|
997
|
-
"event SetObligationTradingFee(
|
|
1229
|
+
"event SetObligationTradingFee(bytes20 indexed id_, uint256 indexed index, uint256 newTradingFee)",
|
|
998
1230
|
"event SetOwner(address indexed owner)",
|
|
999
1231
|
"event SetTradingFeeRecipient(address indexed feeRecipient)",
|
|
1000
1232
|
"event ShuffleSession(address indexed user, bytes32 session)",
|
|
1001
|
-
"event SupplyCollateral(address caller,
|
|
1002
|
-
"event Take(address caller,
|
|
1003
|
-
"event Withdraw(address caller,
|
|
1004
|
-
"event WithdrawCollateral(address caller,
|
|
1233
|
+
"event SupplyCollateral(address caller, bytes20 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf)",
|
|
1234
|
+
"event Take(address caller, bytes20 indexed id_, address indexed maker, address indexed taker, bool offerIsBuy, uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, bool buyerIsLender, bool sellerIsBorrower, address sellerReceiver, bytes32 group, uint256 consumed)",
|
|
1235
|
+
"event Withdraw(address caller, bytes20 indexed id_, uint256 obligationUnits, uint256 shares, address indexed onBehalf, address indexed receiver)",
|
|
1236
|
+
"event WithdrawCollateral(address caller, bytes20 indexed id_, address indexed collateral, uint256 assets, address indexed onBehalf, address receiver)"
|
|
1005
1237
|
]);
|
|
1006
1238
|
|
|
1007
1239
|
//#endregion
|
|
@@ -1159,6 +1391,7 @@ const Morpho = [
|
|
|
1159
1391
|
//#endregion
|
|
1160
1392
|
//#region src/core/Callback.ts
|
|
1161
1393
|
var Callback_exports = /* @__PURE__ */ __exportAll({
|
|
1394
|
+
CallbackType: () => CallbackType,
|
|
1162
1395
|
Type: () => Type$1,
|
|
1163
1396
|
isEmptyCallback: () => isEmptyCallback
|
|
1164
1397
|
});
|
|
@@ -1167,6 +1400,10 @@ let Type$1 = /* @__PURE__ */ function(Type) {
|
|
|
1167
1400
|
Type["SellWithEmptyCallback"] = "sell_with_empty_callback";
|
|
1168
1401
|
return Type;
|
|
1169
1402
|
}({});
|
|
1403
|
+
let CallbackType = /* @__PURE__ */ function(CallbackType) {
|
|
1404
|
+
CallbackType["Empty"] = "empty";
|
|
1405
|
+
return CallbackType;
|
|
1406
|
+
}({});
|
|
1170
1407
|
const isEmptyCallback = (offer) => offer.callback.data === "0x";
|
|
1171
1408
|
|
|
1172
1409
|
//#endregion
|
|
@@ -1177,6 +1414,7 @@ var Chain_exports = /* @__PURE__ */ __exportAll({
|
|
|
1177
1414
|
InvalidBlockRangeError: () => InvalidBlockRangeError,
|
|
1178
1415
|
InvalidBlockWindowError: () => InvalidBlockWindowError,
|
|
1179
1416
|
MissingBlockNumberError: () => MissingBlockNumberError,
|
|
1417
|
+
UnrecoverableLogsResponseSizeError: () => UnrecoverableLogsResponseSizeError,
|
|
1180
1418
|
chainIds: () => chainIds,
|
|
1181
1419
|
chainNames: () => chainNames,
|
|
1182
1420
|
chains: () => chains$1,
|
|
@@ -1243,8 +1481,8 @@ const chains$1 = {
|
|
|
1243
1481
|
name: "base",
|
|
1244
1482
|
custom: {
|
|
1245
1483
|
morpho: {
|
|
1246
|
-
address: "
|
|
1247
|
-
blockCreated:
|
|
1484
|
+
address: "0x4C752Cdc4b13c9A6a933CbecfE050eC0BA0B45f9",
|
|
1485
|
+
blockCreated: 42365274
|
|
1248
1486
|
},
|
|
1249
1487
|
morphoBlue: {
|
|
1250
1488
|
address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
|
|
@@ -1273,8 +1511,8 @@ const chains$1 = {
|
|
|
1273
1511
|
name: "ethereum-virtual-testnet",
|
|
1274
1512
|
custom: {
|
|
1275
1513
|
morpho: {
|
|
1276
|
-
address: "
|
|
1277
|
-
blockCreated:
|
|
1514
|
+
address: "0x9ac49a344376964291f7289663beb78e2952de44",
|
|
1515
|
+
blockCreated: 23229385
|
|
1278
1516
|
},
|
|
1279
1517
|
morphoBlue: {
|
|
1280
1518
|
address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
|
|
@@ -1332,33 +1570,72 @@ const MAX_BATCH_SIZE = 1e4;
|
|
|
1332
1570
|
const DEFAULT_BATCH_SIZE$2 = 2500;
|
|
1333
1571
|
const MAX_BLOCK_WINDOW = 1e4;
|
|
1334
1572
|
const DEFAULT_BLOCK_WINDOW = 8e3;
|
|
1573
|
+
const MIN_BLOCK_WINDOW = 0n;
|
|
1574
|
+
const oversizedLogsErrorPatterns = [
|
|
1575
|
+
"cannot create a string longer than",
|
|
1576
|
+
"response is too big",
|
|
1577
|
+
"response size exceeded",
|
|
1578
|
+
"log response size exceeded",
|
|
1579
|
+
"query returned more than",
|
|
1580
|
+
"too many results"
|
|
1581
|
+
];
|
|
1582
|
+
const getLatestBlockNumber = async (client) => {
|
|
1583
|
+
return await getBlockNumber(client, { cacheTime: 0 });
|
|
1584
|
+
};
|
|
1335
1585
|
async function* streamLogs(parameters) {
|
|
1336
1586
|
const { client, contractAddress, event, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$2, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
|
|
1337
1587
|
if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
|
|
1338
1588
|
if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
|
|
1339
1589
|
if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1590
|
+
const ascendingLowerBound = order === "asc" ? BigInt(blockNumberGte) : void 0;
|
|
1591
|
+
const latestBlock = await getLatestBlockNumber(client);
|
|
1592
|
+
let upperBound = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
|
|
1593
|
+
const lowerBound = BigInt(blockNumberGte || 0);
|
|
1594
|
+
const configuredBlockWindow = BigInt(blockWindow);
|
|
1595
|
+
let adaptiveBlockWindow = configuredBlockWindow;
|
|
1344
1596
|
let toBlock = 0n;
|
|
1345
|
-
if (order === "asc") toBlock = min(
|
|
1346
|
-
if (order === "desc") toBlock =
|
|
1597
|
+
if (order === "asc") toBlock = min(ascendingLowerBound + adaptiveBlockWindow, upperBound);
|
|
1598
|
+
if (order === "desc") toBlock = upperBound;
|
|
1347
1599
|
let fromBlock = 0n;
|
|
1348
|
-
if (order === "asc") fromBlock =
|
|
1349
|
-
if (order === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock -
|
|
1350
|
-
if (order === "asc") toBlock = min(toBlock, fromBlock +
|
|
1351
|
-
if (order === "desc") fromBlock = max$1(fromBlock, toBlock -
|
|
1600
|
+
if (order === "asc") fromBlock = ascendingLowerBound;
|
|
1601
|
+
if (order === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - adaptiveBlockWindow), 0n);
|
|
1602
|
+
if (order === "asc") toBlock = min(toBlock, fromBlock + adaptiveBlockWindow);
|
|
1603
|
+
if (order === "desc") fromBlock = max$1(fromBlock, toBlock - adaptiveBlockWindow);
|
|
1352
1604
|
if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
|
|
1353
1605
|
let streaming = true;
|
|
1354
1606
|
while (streaming) {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1607
|
+
let logs;
|
|
1608
|
+
try {
|
|
1609
|
+
logs = await getLogs(client, {
|
|
1610
|
+
address: contractAddress,
|
|
1611
|
+
event,
|
|
1612
|
+
fromBlock,
|
|
1613
|
+
toBlock
|
|
1614
|
+
});
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
if (order === "asc" && isBlockOutOfRangeError(err)) {
|
|
1617
|
+
const previousUpperBound = upperBound;
|
|
1618
|
+
const previousFromBlock = fromBlock;
|
|
1619
|
+
const previousToBlock = toBlock;
|
|
1620
|
+
const latestBlockOnRetry = await getLatestBlockNumber(client);
|
|
1621
|
+
upperBound = min(upperBound, latestBlockOnRetry);
|
|
1622
|
+
if (upperBound < ascendingLowerBound) throw new InvalidBlockRangeError(ascendingLowerBound, upperBound);
|
|
1623
|
+
toBlock = min(toBlock, upperBound);
|
|
1624
|
+
fromBlock = max$1(min(fromBlock, upperBound), ascendingLowerBound);
|
|
1625
|
+
if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
|
|
1626
|
+
if (!(upperBound < previousUpperBound || fromBlock < previousFromBlock || toBlock < previousToBlock)) throw err;
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
if (isOversizedLogsError(err)) {
|
|
1630
|
+
if (fromBlock === toBlock) throw new UnrecoverableLogsResponseSizeError(fromBlock, err);
|
|
1631
|
+
adaptiveBlockWindow = max$1((toBlock - fromBlock) / 2n, MIN_BLOCK_WINDOW);
|
|
1632
|
+
if (order === "asc") toBlock = min(fromBlock + adaptiveBlockWindow, upperBound);
|
|
1633
|
+
else fromBlock = max$1(toBlock - adaptiveBlockWindow, lowerBound);
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
throw err;
|
|
1637
|
+
}
|
|
1638
|
+
streaming = order === "asc" ? toBlock < upperBound : fromBlock > lowerBound;
|
|
1362
1639
|
if (logs.length === 0 && !streaming) break;
|
|
1363
1640
|
if (logs.length === 0 && streaming) yield {
|
|
1364
1641
|
logs: [],
|
|
@@ -1373,17 +1650,19 @@ async function* streamLogs(parameters) {
|
|
|
1373
1650
|
logs: logBatch,
|
|
1374
1651
|
blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1375
1652
|
};
|
|
1653
|
+
if (adaptiveBlockWindow < configuredBlockWindow) {
|
|
1654
|
+
const expandedBlockWindow = adaptiveBlockWindow === 0n ? 1n : adaptiveBlockWindow * 2n;
|
|
1655
|
+
adaptiveBlockWindow = min(expandedBlockWindow, configuredBlockWindow);
|
|
1656
|
+
}
|
|
1376
1657
|
if (order === "asc") {
|
|
1377
|
-
const upperBound = BigInt(blockNumberLte || latestBlock);
|
|
1378
1658
|
const nextFromBlock = min(BigInt(toBlock) + 1n, upperBound);
|
|
1379
|
-
const nextToBlock = min(toBlock +
|
|
1659
|
+
const nextToBlock = min(toBlock + adaptiveBlockWindow + 1n, upperBound);
|
|
1380
1660
|
fromBlock = nextFromBlock;
|
|
1381
1661
|
toBlock = nextToBlock;
|
|
1382
1662
|
}
|
|
1383
1663
|
if (order === "desc") {
|
|
1384
|
-
const lowerBound = BigInt(blockNumberGte || 0);
|
|
1385
1664
|
const nextToBlock = max$1(fromBlock - 1n, lowerBound);
|
|
1386
|
-
const nextFromBlock = max$1(fromBlock -
|
|
1665
|
+
const nextFromBlock = max$1(fromBlock - adaptiveBlockWindow - 1n, lowerBound);
|
|
1387
1666
|
toBlock = nextToBlock;
|
|
1388
1667
|
fromBlock = nextFromBlock;
|
|
1389
1668
|
}
|
|
@@ -1393,10 +1672,23 @@ async function* streamLogs(parameters) {
|
|
|
1393
1672
|
blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1394
1673
|
};
|
|
1395
1674
|
}
|
|
1675
|
+
const isBlockOutOfRangeError = (error) => {
|
|
1676
|
+
let cause = error;
|
|
1677
|
+
while (cause && typeof cause === "object") {
|
|
1678
|
+
const candidate = cause;
|
|
1679
|
+
if (typeof candidate.message === "string" && candidate.message.includes("BlockOutOfRangeError") || typeof candidate.details === "string" && candidate.details.includes("BlockOutOfRangeError")) return true;
|
|
1680
|
+
cause = candidate.cause;
|
|
1681
|
+
}
|
|
1682
|
+
return false;
|
|
1683
|
+
};
|
|
1396
1684
|
var InvalidBlockRangeError = class extends BaseError {
|
|
1397
1685
|
name = "Chain.InvalidBlockRangeError";
|
|
1686
|
+
fromBlock;
|
|
1687
|
+
toBlock;
|
|
1398
1688
|
constructor(fromBlock, toBlock) {
|
|
1399
1689
|
super(`Invalid block range while streaming data from chain. From block ${fromBlock} to block ${toBlock}.`);
|
|
1690
|
+
this.fromBlock = fromBlock;
|
|
1691
|
+
this.toBlock = toBlock;
|
|
1400
1692
|
}
|
|
1401
1693
|
};
|
|
1402
1694
|
var InvalidBlockWindowError = class extends BaseError {
|
|
@@ -1417,16 +1709,35 @@ var MissingBlockNumberError = class extends BaseError {
|
|
|
1417
1709
|
super("Missing block number when streaming data from chain in ascending order.");
|
|
1418
1710
|
}
|
|
1419
1711
|
};
|
|
1712
|
+
var UnrecoverableLogsResponseSizeError = class extends BaseError {
|
|
1713
|
+
name = "Chain.UnrecoverableLogsResponseSizeError";
|
|
1714
|
+
constructor(blockNumber, cause) {
|
|
1715
|
+
const rootCause = cause instanceof Error ? cause : cause === void 0 ? void 0 : new Error(String(cause));
|
|
1716
|
+
super(`Failed to stream logs because even a single-block query exceeded the RPC response size limit at block ${blockNumber}.`, { cause: rootCause });
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
function isOversizedLogsError(err) {
|
|
1720
|
+
return oversizedLogsErrorPatterns.some((pattern) => collectErrorMessages(err).includes(pattern));
|
|
1721
|
+
}
|
|
1722
|
+
function collectErrorMessages(err) {
|
|
1723
|
+
if (!(err instanceof Error)) return "";
|
|
1724
|
+
const fragments = [err.message];
|
|
1725
|
+
const candidate = err;
|
|
1726
|
+
if (typeof candidate.details === "string") fragments.push(candidate.details);
|
|
1727
|
+
if (typeof candidate.shortMessage === "string") fragments.push(candidate.shortMessage);
|
|
1728
|
+
if (candidate.cause instanceof Error) fragments.push(collectErrorMessages(candidate.cause));
|
|
1729
|
+
return fragments.join(" ").toLowerCase();
|
|
1730
|
+
}
|
|
1420
1731
|
|
|
1421
1732
|
//#endregion
|
|
1422
1733
|
//#region src/core/ChainRegistry.ts
|
|
1423
|
-
var ChainRegistry_exports = /* @__PURE__ */ __exportAll({ create: () => create$
|
|
1734
|
+
var ChainRegistry_exports = /* @__PURE__ */ __exportAll({ create: () => create$20 });
|
|
1424
1735
|
/**
|
|
1425
1736
|
* Creates a chain registry from a list of chains.
|
|
1426
1737
|
* @param chains - Array of chain objects to register.
|
|
1427
1738
|
* @returns A registry for looking up chains by ID. {@link ChainRegistry}
|
|
1428
1739
|
*/
|
|
1429
|
-
function create$
|
|
1740
|
+
function create$20(chains) {
|
|
1430
1741
|
const byId = /* @__PURE__ */ new Map();
|
|
1431
1742
|
for (const chain of chains) byId.set(chain.id, chain);
|
|
1432
1743
|
return {
|
|
@@ -1717,9 +2028,9 @@ const MaturitySchema = z$1.number().int().refine((maturity) => {
|
|
|
1717
2028
|
}
|
|
1718
2029
|
}, { error: (issue) => {
|
|
1719
2030
|
try {
|
|
1720
|
-
return `The maturity is set to ${/* @__PURE__ */ new Date(issue.input * 1e3)}. It must
|
|
2031
|
+
return `The maturity is set to ${/* @__PURE__ */ new Date(issue.input * 1e3)}. It must be at 15:00:00 UTC.`;
|
|
1721
2032
|
} catch (_) {
|
|
1722
|
-
return `The maturity is set to ${issue.input}. It must
|
|
2033
|
+
return `The maturity is set to ${issue.input}. It must be at 15:00:00 UTC.`;
|
|
1723
2034
|
}
|
|
1724
2035
|
} }).transform((maturity) => maturity);
|
|
1725
2036
|
let MaturityType = /* @__PURE__ */ function(MaturityType) {
|
|
@@ -1751,9 +2062,14 @@ function from$16(ts) {
|
|
|
1751
2062
|
throw new InvalidOptionError(ts);
|
|
1752
2063
|
}
|
|
1753
2064
|
if (typeof ts === "number" && ts > 0xe8d4a51000) throw new InvalidFormatError();
|
|
1754
|
-
if (!
|
|
2065
|
+
if (!isAt15UTC(ts)) throw new InvalidDateError(ts);
|
|
1755
2066
|
return ts;
|
|
1756
2067
|
}
|
|
2068
|
+
/** Checks whether a timestamp (in seconds) falls at exactly 15:00:00 UTC. */
|
|
2069
|
+
function isAt15UTC(ts) {
|
|
2070
|
+
const date = /* @__PURE__ */ new Date(ts * 1e3);
|
|
2071
|
+
return date.getUTCHours() === 15 && date.getUTCMinutes() === 0 && date.getUTCSeconds() === 0 && date.getUTCMilliseconds() === 0;
|
|
2072
|
+
}
|
|
1757
2073
|
/** Returns the end of the current week (friday at 15:00:00 UTC) */
|
|
1758
2074
|
const endOfWeek = () => fridayOfWeek(0);
|
|
1759
2075
|
/** Returns the end of the next week (friday at 15:00:00 UTC) */
|
|
@@ -1814,7 +2130,7 @@ var InvalidFormatError = class extends BaseError {
|
|
|
1814
2130
|
var InvalidDateError = class extends BaseError {
|
|
1815
2131
|
name = "Maturity.InvalidDateError";
|
|
1816
2132
|
constructor(input) {
|
|
1817
|
-
super(`Invalid maturity date. Input: "${input}".
|
|
2133
|
+
super(`Invalid maturity date. Input: "${input}". Maturity must be at 15:00:00 UTC.`);
|
|
1818
2134
|
}
|
|
1819
2135
|
};
|
|
1820
2136
|
var InvalidOptionError = class extends BaseError {
|
|
@@ -1841,7 +2157,8 @@ var Obligation_exports = /* @__PURE__ */ __exportAll({
|
|
|
1841
2157
|
const ObligationSchema = z$1.object({
|
|
1842
2158
|
loanToken: z$1.string().transform(transformAddress),
|
|
1843
2159
|
collaterals: CollateralsSchema,
|
|
1844
|
-
maturity: MaturitySchema
|
|
2160
|
+
maturity: MaturitySchema,
|
|
2161
|
+
minCollatValue: z$1.bigint({ coerce: true }).min(0n).optional().default(0n)
|
|
1845
2162
|
});
|
|
1846
2163
|
const abi = [
|
|
1847
2164
|
{
|
|
@@ -1856,6 +2173,10 @@ const abi = [
|
|
|
1856
2173
|
{
|
|
1857
2174
|
type: "uint256",
|
|
1858
2175
|
name: "maturity"
|
|
2176
|
+
},
|
|
2177
|
+
{
|
|
2178
|
+
type: "uint256",
|
|
2179
|
+
name: "minCollatValue"
|
|
1859
2180
|
}
|
|
1860
2181
|
];
|
|
1861
2182
|
const tupleAbi = [{
|
|
@@ -1893,7 +2214,8 @@ function from$15(parameters) {
|
|
|
1893
2214
|
return {
|
|
1894
2215
|
loanToken: parsedObligation.loanToken.toLowerCase(),
|
|
1895
2216
|
collaterals: parsedObligation.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
1896
|
-
maturity: parsedObligation.maturity
|
|
2217
|
+
maturity: parsedObligation.maturity,
|
|
2218
|
+
minCollatValue: parsedObligation.minCollatValue
|
|
1897
2219
|
};
|
|
1898
2220
|
} catch (error) {
|
|
1899
2221
|
throw new InvalidObligationError(error);
|
|
@@ -1910,7 +2232,8 @@ function fromSnakeCase$2(input) {
|
|
|
1910
2232
|
}
|
|
1911
2233
|
/**
|
|
1912
2234
|
* Calculates a canonical key for an obligation payload.
|
|
1913
|
-
* The key is computed as keccak256(abi.encode(loanToken, collaterals, maturity)).
|
|
2235
|
+
* The key is computed as keccak256(abi.encode(loanToken, collaterals, maturity, minCollatValue)).
|
|
2236
|
+
* If omitted, `minCollatValue` defaults to `0`.
|
|
1914
2237
|
* @throws If the collaterals are not sorted alphabetically by address. {@link CollateralsAreNotSortedError}
|
|
1915
2238
|
* @param parameters - {@link key.Parameters}
|
|
1916
2239
|
* @returns The obligation key as a 32-byte hex string. {@link key.ReturnType}
|
|
@@ -1936,7 +2259,8 @@ function key(parameters) {
|
|
|
1936
2259
|
lltv: c.lltv,
|
|
1937
2260
|
oracle: c.oracle.toLowerCase()
|
|
1938
2261
|
})),
|
|
1939
|
-
BigInt(parameters.maturity)
|
|
2262
|
+
BigInt(parameters.maturity),
|
|
2263
|
+
parameters.minCollatValue ?? 0n
|
|
1940
2264
|
]));
|
|
1941
2265
|
}
|
|
1942
2266
|
/**
|
|
@@ -1988,39 +2312,42 @@ var Id_exports = /* @__PURE__ */ __exportAll({
|
|
|
1988
2312
|
creationCode: () => creationCode,
|
|
1989
2313
|
toId: () => toId
|
|
1990
2314
|
});
|
|
1991
|
-
const CREATION_CODE_PREFIX = "
|
|
2315
|
+
const CREATION_CODE_PREFIX = "0x600b380380600b5f395ff3";
|
|
1992
2316
|
/**
|
|
1993
2317
|
* Builds the same creation code as `IdLib.creationCode` in Solidity.
|
|
1994
2318
|
*
|
|
1995
|
-
* Layout: `prefix (11 bytes) +
|
|
2319
|
+
* Layout: `prefix (11 bytes) + abi.encode(obligation)`.
|
|
1996
2320
|
*
|
|
1997
2321
|
* @param parameters - {@link creationCode.Parameters}
|
|
1998
2322
|
* @returns The CREATE2 init code bytes. {@link creationCode.ReturnType}
|
|
1999
2323
|
*/
|
|
2000
2324
|
function creationCode(parameters) {
|
|
2001
|
-
|
|
2325
|
+
return concatHex([CREATION_CODE_PREFIX, encodeAbiParameters(tupleAbi, [{
|
|
2002
2326
|
loanToken: parameters.obligation.loanToken.toLowerCase(),
|
|
2003
2327
|
collaterals: parameters.obligation.collaterals.map((collateral) => ({
|
|
2004
2328
|
token: collateral.asset.toLowerCase(),
|
|
2005
2329
|
lltv: collateral.lltv,
|
|
2006
2330
|
oracle: collateral.oracle.toLowerCase()
|
|
2007
2331
|
})),
|
|
2008
|
-
maturity: BigInt(parameters.obligation.maturity)
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
CREATION_CODE_PREFIX,
|
|
2012
|
-
numberToHex(BigInt(parameters.chainId), { size: 32 }),
|
|
2013
|
-
parameters.morphoV2.toLowerCase(),
|
|
2014
|
-
encodedObligation
|
|
2015
|
-
]);
|
|
2332
|
+
maturity: BigInt(parameters.obligation.maturity),
|
|
2333
|
+
minCollatValue: 0n
|
|
2334
|
+
}])]);
|
|
2016
2335
|
}
|
|
2017
2336
|
/**
|
|
2018
|
-
* Computes the same id as `IdLib.toId` in Solidity
|
|
2337
|
+
* Computes the same id as `IdLib.toId` in Solidity using the CREATE2 preimage:
|
|
2338
|
+
* `keccak256(0xff ++ morphoV2 ++ chainId ++ keccak256(creationCode))`,
|
|
2339
|
+
* then truncates to the lower 20 bytes.
|
|
2340
|
+
*
|
|
2019
2341
|
* @param parameters - {@link toId.Parameters}
|
|
2020
2342
|
* @returns The obligation id. {@link toId.ReturnType}
|
|
2021
2343
|
*/
|
|
2022
2344
|
function toId(parameters) {
|
|
2023
|
-
return keccak256(
|
|
2345
|
+
return `0x${keccak256(concatHex([
|
|
2346
|
+
"0xff",
|
|
2347
|
+
parameters.morphoV2.toLowerCase(),
|
|
2348
|
+
numberToHex(BigInt(parameters.chainId), { size: 32 }),
|
|
2349
|
+
keccak256(creationCode(parameters))
|
|
2350
|
+
])).slice(-40)}`;
|
|
2024
2351
|
}
|
|
2025
2352
|
|
|
2026
2353
|
//#endregion
|
|
@@ -2317,7 +2644,7 @@ function hash(offer) {
|
|
|
2317
2644
|
* The id is computed with {@link Id.toId}.
|
|
2318
2645
|
* @param offer - The offer to calculate the obligation id for.
|
|
2319
2646
|
* @param parameters - The chain context used by the onchain id function.
|
|
2320
|
-
* @returns The obligation id as a
|
|
2647
|
+
* @returns The obligation id as a 20-byte hex string.
|
|
2321
2648
|
*/
|
|
2322
2649
|
function obligationId(offer, parameters) {
|
|
2323
2650
|
return toId({
|
|
@@ -2480,10 +2807,10 @@ const takeEvent = {
|
|
|
2480
2807
|
internalType: "address"
|
|
2481
2808
|
},
|
|
2482
2809
|
{
|
|
2483
|
-
name: "
|
|
2484
|
-
type: "
|
|
2810
|
+
name: "id_",
|
|
2811
|
+
type: "bytes20",
|
|
2485
2812
|
indexed: true,
|
|
2486
|
-
internalType: "
|
|
2813
|
+
internalType: "bytes20"
|
|
2487
2814
|
},
|
|
2488
2815
|
{
|
|
2489
2816
|
name: "maker",
|
|
@@ -2602,10 +2929,10 @@ const repayEvent = {
|
|
|
2602
2929
|
internalType: "address"
|
|
2603
2930
|
},
|
|
2604
2931
|
{
|
|
2605
|
-
name: "
|
|
2606
|
-
type: "
|
|
2932
|
+
name: "id_",
|
|
2933
|
+
type: "bytes20",
|
|
2607
2934
|
indexed: true,
|
|
2608
|
-
internalType: "
|
|
2935
|
+
internalType: "bytes20"
|
|
2609
2936
|
},
|
|
2610
2937
|
{
|
|
2611
2938
|
name: "obligationUnits",
|
|
@@ -2636,46 +2963,35 @@ const liquidateEvent = {
|
|
|
2636
2963
|
internalType: "address"
|
|
2637
2964
|
},
|
|
2638
2965
|
{
|
|
2639
|
-
name: "
|
|
2640
|
-
type: "
|
|
2966
|
+
name: "id_",
|
|
2967
|
+
type: "bytes20",
|
|
2641
2968
|
indexed: true,
|
|
2642
|
-
internalType: "
|
|
2969
|
+
internalType: "bytes20"
|
|
2643
2970
|
},
|
|
2644
2971
|
{
|
|
2645
|
-
name: "
|
|
2646
|
-
type: "
|
|
2972
|
+
name: "collateralIndex",
|
|
2973
|
+
type: "uint256",
|
|
2647
2974
|
indexed: false,
|
|
2648
|
-
internalType: "
|
|
2649
|
-
components: [
|
|
2650
|
-
{
|
|
2651
|
-
name: "collateralIndex",
|
|
2652
|
-
type: "uint256",
|
|
2653
|
-
internalType: "uint256"
|
|
2654
|
-
},
|
|
2655
|
-
{
|
|
2656
|
-
name: "repaid",
|
|
2657
|
-
type: "uint256",
|
|
2658
|
-
internalType: "uint256"
|
|
2659
|
-
},
|
|
2660
|
-
{
|
|
2661
|
-
name: "seized",
|
|
2662
|
-
type: "uint256",
|
|
2663
|
-
internalType: "uint256"
|
|
2664
|
-
}
|
|
2665
|
-
]
|
|
2975
|
+
internalType: "uint256"
|
|
2666
2976
|
},
|
|
2667
2977
|
{
|
|
2668
|
-
name: "
|
|
2669
|
-
type: "
|
|
2670
|
-
indexed:
|
|
2671
|
-
internalType: "
|
|
2978
|
+
name: "seizedAssets",
|
|
2979
|
+
type: "uint256",
|
|
2980
|
+
indexed: false,
|
|
2981
|
+
internalType: "uint256"
|
|
2672
2982
|
},
|
|
2673
2983
|
{
|
|
2674
|
-
name: "
|
|
2984
|
+
name: "repaidUnits",
|
|
2675
2985
|
type: "uint256",
|
|
2676
2986
|
indexed: false,
|
|
2677
2987
|
internalType: "uint256"
|
|
2678
2988
|
},
|
|
2989
|
+
{
|
|
2990
|
+
name: "borrower",
|
|
2991
|
+
type: "address",
|
|
2992
|
+
indexed: true,
|
|
2993
|
+
internalType: "address"
|
|
2994
|
+
},
|
|
2679
2995
|
{
|
|
2680
2996
|
name: "badDebt",
|
|
2681
2997
|
type: "uint256",
|
|
@@ -2699,10 +3015,10 @@ const supplyCollateralEvent = {
|
|
|
2699
3015
|
internalType: "address"
|
|
2700
3016
|
},
|
|
2701
3017
|
{
|
|
2702
|
-
name: "
|
|
2703
|
-
type: "
|
|
3018
|
+
name: "id_",
|
|
3019
|
+
type: "bytes20",
|
|
2704
3020
|
indexed: true,
|
|
2705
|
-
internalType: "
|
|
3021
|
+
internalType: "bytes20"
|
|
2706
3022
|
},
|
|
2707
3023
|
{
|
|
2708
3024
|
name: "collateral",
|
|
@@ -2739,10 +3055,10 @@ const withdrawCollateralEvent = {
|
|
|
2739
3055
|
internalType: "address"
|
|
2740
3056
|
},
|
|
2741
3057
|
{
|
|
2742
|
-
name: "
|
|
2743
|
-
type: "
|
|
3058
|
+
name: "id_",
|
|
3059
|
+
type: "bytes20",
|
|
2744
3060
|
indexed: true,
|
|
2745
|
-
internalType: "
|
|
3061
|
+
internalType: "bytes20"
|
|
2746
3062
|
},
|
|
2747
3063
|
{
|
|
2748
3064
|
name: "collateral",
|
|
@@ -3587,11 +3903,12 @@ const BrandTypeId = Symbol.for("mempool/Brand");
|
|
|
3587
3903
|
|
|
3588
3904
|
//#endregion
|
|
3589
3905
|
//#region src/database/drizzle/VERSION.ts
|
|
3590
|
-
const VERSION = "router_v1.
|
|
3906
|
+
const VERSION = "router_v1.13";
|
|
3591
3907
|
|
|
3592
3908
|
//#endregion
|
|
3593
3909
|
//#region src/database/drizzle/schema.ts
|
|
3594
3910
|
var schema_exports = /* @__PURE__ */ __exportAll({
|
|
3911
|
+
CallbackTypes: () => CallbackTypes,
|
|
3595
3912
|
PositionTypes: () => PositionTypes,
|
|
3596
3913
|
StatusCode: () => StatusCode,
|
|
3597
3914
|
TABLE_NAMES: () => TABLE_NAMES,
|
|
@@ -3649,7 +3966,7 @@ const obligations = s.table(EnumTableName.OBLIGATIONS, {
|
|
|
3649
3966
|
maturity: integer("maturity").notNull()
|
|
3650
3967
|
});
|
|
3651
3968
|
const obligationIdKeys = s.table(EnumTableName.OBLIGATION_ID_KEYS, {
|
|
3652
|
-
obligationId: varchar("obligation_id", { length:
|
|
3969
|
+
obligationId: varchar("obligation_id", { length: 42 }).primaryKey(),
|
|
3653
3970
|
obligationKey: varchar("obligation_key", { length: 66 }).notNull().references(() => obligations.obligationKey, { onDelete: "cascade" }),
|
|
3654
3971
|
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3655
3972
|
morphoV2: varchar("morpho_v2", { length: 42 }).notNull()
|
|
@@ -3730,7 +4047,7 @@ const oracles$1 = s.table(EnumTableName.ORACLES, {
|
|
|
3730
4047
|
})]);
|
|
3731
4048
|
const offers = s.table(EnumTableName.OFFERS, {
|
|
3732
4049
|
hash: varchar("hash", { length: 66 }).notNull(),
|
|
3733
|
-
obligationId: varchar("obligation_id", { length:
|
|
4050
|
+
obligationId: varchar("obligation_id", { length: 42 }).notNull().references(() => obligationIdKeys.obligationId, { onDelete: "cascade" }),
|
|
3734
4051
|
assets: numeric("assets", {
|
|
3735
4052
|
precision: 78,
|
|
3736
4053
|
scale: 0
|
|
@@ -3781,7 +4098,7 @@ const offers = s.table(EnumTableName.OFFERS, {
|
|
|
3781
4098
|
]);
|
|
3782
4099
|
const offersCallbacks = s.table(EnumTableName.OFFERS_CALLBACKS, {
|
|
3783
4100
|
offerHash: varchar("offer_hash", { length: 66 }).notNull(),
|
|
3784
|
-
obligationId: varchar("obligation_id", { length:
|
|
4101
|
+
obligationId: varchar("obligation_id", { length: 42 }).notNull(),
|
|
3785
4102
|
callbackId: varchar("callback_id", { length: 66 })
|
|
3786
4103
|
}, (table) => [foreignKey({
|
|
3787
4104
|
columns: [table.offerHash, table.obligationId],
|
|
@@ -3795,20 +4112,18 @@ const offersCallbacks = s.table(EnumTableName.OFFERS_CALLBACKS, {
|
|
|
3795
4112
|
],
|
|
3796
4113
|
name: "offers_callbacks_pk"
|
|
3797
4114
|
})]);
|
|
4115
|
+
const CallbackTypes = s.enum("callback_type", Object.values(CallbackType));
|
|
3798
4116
|
const callbacks = s.table(EnumTableName.CALLBACKS, {
|
|
3799
4117
|
id: varchar("id", { length: 66 }).primaryKey(),
|
|
3800
4118
|
positionChainId: bigint("position_chain_id", { mode: "number" }).$type().notNull(),
|
|
3801
|
-
positionContract: varchar("position_contract", { length:
|
|
4119
|
+
positionContract: varchar("position_contract", { length: 66 }).notNull(),
|
|
3802
4120
|
positionUser: varchar("position_user", { length: 42 }).notNull(),
|
|
3803
4121
|
positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
|
|
3804
|
-
|
|
3805
|
-
precision: 78,
|
|
3806
|
-
scale: 0
|
|
3807
|
-
})
|
|
4122
|
+
type: CallbackTypes("type").notNull()
|
|
3808
4123
|
});
|
|
3809
4124
|
const lotsPositions = s.table(EnumTableName.LOTS_POSITIONS, {
|
|
3810
4125
|
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3811
|
-
contract: varchar("contract", { length:
|
|
4126
|
+
contract: varchar("contract", { length: 66 }).notNull(),
|
|
3812
4127
|
user: varchar("user", { length: 42 }).notNull(),
|
|
3813
4128
|
positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" })
|
|
3814
4129
|
}, (table) => [primaryKey({
|
|
@@ -3822,9 +4137,9 @@ const lotsPositions = s.table(EnumTableName.LOTS_POSITIONS, {
|
|
|
3822
4137
|
const lots = s.table(EnumTableName.LOTS, {
|
|
3823
4138
|
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3824
4139
|
user: varchar("user", { length: 42 }).notNull(),
|
|
3825
|
-
contract: varchar("contract", { length:
|
|
4140
|
+
contract: varchar("contract", { length: 66 }).notNull(),
|
|
3826
4141
|
group: varchar("group", { length: 66 }).notNull(),
|
|
3827
|
-
obligationId: varchar("obligation_id", { length:
|
|
4142
|
+
obligationId: varchar("obligation_id", { length: 42 }).notNull(),
|
|
3828
4143
|
lower: numeric("lower", {
|
|
3829
4144
|
precision: 78,
|
|
3830
4145
|
scale: 0
|
|
@@ -3874,9 +4189,9 @@ const lots = s.table(EnumTableName.LOTS, {
|
|
|
3874
4189
|
const offsets = s.table(EnumTableName.OFFSETS, {
|
|
3875
4190
|
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3876
4191
|
user: varchar("user", { length: 42 }).notNull(),
|
|
3877
|
-
contract: varchar("contract", { length:
|
|
4192
|
+
contract: varchar("contract", { length: 66 }).notNull(),
|
|
3878
4193
|
group: varchar("group", { length: 66 }).notNull(),
|
|
3879
|
-
obligationId: varchar("obligation_id", { length:
|
|
4194
|
+
obligationId: varchar("obligation_id", { length: 42 }).notNull(),
|
|
3880
4195
|
value: numeric("value", {
|
|
3881
4196
|
precision: 78,
|
|
3882
4197
|
scale: 0
|
|
@@ -3989,7 +4304,7 @@ const status = s.table("status", {
|
|
|
3989
4304
|
});
|
|
3990
4305
|
const validations = s.table("validations", {
|
|
3991
4306
|
offerHash: varchar("offer_hash", { length: 66 }).notNull(),
|
|
3992
|
-
obligationId: varchar("obligation_id", { length:
|
|
4307
|
+
obligationId: varchar("obligation_id", { length: 42 }).notNull(),
|
|
3993
4308
|
statusId: integer("status_id").notNull().references(() => status.id, { onDelete: "no action" }),
|
|
3994
4309
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3995
4310
|
}, (table) => [primaryKey({
|
|
@@ -4026,7 +4341,7 @@ const trees = s.table(EnumTableName.TREES, {
|
|
|
4026
4341
|
});
|
|
4027
4342
|
const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
4028
4343
|
offerHash: varchar("offer_hash", { length: 66 }).notNull(),
|
|
4029
|
-
obligationId: varchar("obligation_id", { length:
|
|
4344
|
+
obligationId: varchar("obligation_id", { length: 42 }).notNull(),
|
|
4030
4345
|
treeRoot: varchar("tree_root", { length: 66 }).notNull().references(() => trees.root, { onDelete: "cascade" }),
|
|
4031
4346
|
proofNodes: text("proof_nodes").notNull(),
|
|
4032
4347
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
@@ -4043,6 +4358,54 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
|
4043
4358
|
index("merkle_paths_tree_root_idx").on(table.treeRoot)
|
|
4044
4359
|
]);
|
|
4045
4360
|
|
|
4361
|
+
//#endregion
|
|
4362
|
+
//#region src/indexer/collectors/CollectFunctions/IndexedEventTelemetry.ts
|
|
4363
|
+
/**
|
|
4364
|
+
* Resolve the chain head block metadata used by indexed collector telemetry events.
|
|
4365
|
+
* @param parameters - Block lookup parameters.
|
|
4366
|
+
* @returns Head block number and timestamp (seconds since epoch).
|
|
4367
|
+
*/
|
|
4368
|
+
async function getIndexedEventHeadContext(parameters) {
|
|
4369
|
+
const { client, headBlockNumber } = parameters;
|
|
4370
|
+
const fallbackHeadTimestampS = Math.floor(Date.now() / 1e3);
|
|
4371
|
+
const getBlock = client.getBlock;
|
|
4372
|
+
if (typeof getBlock !== "function") return {
|
|
4373
|
+
head_block_number: headBlockNumber,
|
|
4374
|
+
head_block_timestamp_s: fallbackHeadTimestampS
|
|
4375
|
+
};
|
|
4376
|
+
try {
|
|
4377
|
+
const headBlock = await getBlock({
|
|
4378
|
+
blockNumber: BigInt(headBlockNumber),
|
|
4379
|
+
includeTransactions: false
|
|
4380
|
+
});
|
|
4381
|
+
return {
|
|
4382
|
+
head_block_number: headBlockNumber,
|
|
4383
|
+
head_block_timestamp_s: Number(headBlock.timestamp)
|
|
4384
|
+
};
|
|
4385
|
+
} catch {
|
|
4386
|
+
return {
|
|
4387
|
+
head_block_number: headBlockNumber,
|
|
4388
|
+
head_block_timestamp_s: fallbackHeadTimestampS
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
/**
|
|
4393
|
+
* Build wide-event telemetry fields used by indexed collector logs.
|
|
4394
|
+
* @param parameters - Indexed event counts and head metadata.
|
|
4395
|
+
* @returns Computed latency and log counters.
|
|
4396
|
+
*/
|
|
4397
|
+
function buildIndexedEventTelemetry(parameters) {
|
|
4398
|
+
const insertedAtMs = parameters.insertedAtMs ?? Date.now();
|
|
4399
|
+
const headTimestampMs = parameters.head_block_timestamp_s * 1e3;
|
|
4400
|
+
return {
|
|
4401
|
+
head_block_number: parameters.head_block_number,
|
|
4402
|
+
head_block_timestamp_s: parameters.head_block_timestamp_s,
|
|
4403
|
+
head_to_db_insert_latency_ms: Math.max(insertedAtMs - headTimestampMs, 0),
|
|
4404
|
+
logs_ingested_count: Math.max(parameters.logsIngestedCount, 0),
|
|
4405
|
+
logs_indexed_count: Math.max(parameters.logsIndexedCount, 0)
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4046
4409
|
//#endregion
|
|
4047
4410
|
//#region src/indexer/collectors/CollectFunctions/processors/processCollateralSeizures.ts
|
|
4048
4411
|
/**
|
|
@@ -4058,39 +4421,40 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
|
4058
4421
|
*/
|
|
4059
4422
|
function processCollateralSeizures(parameters) {
|
|
4060
4423
|
const { logs, chainId } = parameters;
|
|
4061
|
-
const logger =
|
|
4424
|
+
const logger = getLoggerWithContext({
|
|
4425
|
+
component: "indexer.collector.processor",
|
|
4426
|
+
chain_id: chainId
|
|
4427
|
+
});
|
|
4062
4428
|
const seizureEvents = [];
|
|
4063
4429
|
for (const rawLog of logs) {
|
|
4064
4430
|
if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
|
|
4065
4431
|
logger.debug({
|
|
4066
|
-
|
|
4432
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4067
4433
|
msg: "Skipping collateral log because it is missing required fields"
|
|
4068
4434
|
});
|
|
4069
4435
|
continue;
|
|
4070
4436
|
}
|
|
4071
4437
|
if (rawLog.eventName !== liquidateEvent.name) continue;
|
|
4072
4438
|
const args = rawLog.args;
|
|
4073
|
-
|
|
4439
|
+
const obligationId = args?.id_;
|
|
4440
|
+
if (obligationId === void 0 || args?.borrower === void 0 || args?.collateralIndex === void 0 || args?.seizedAssets === void 0) {
|
|
4074
4441
|
logger.debug({
|
|
4075
|
-
|
|
4442
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4076
4443
|
msg: "Skipping Liquidate log for collateral because it is missing required args"
|
|
4077
4444
|
});
|
|
4078
4445
|
continue;
|
|
4079
4446
|
}
|
|
4447
|
+
if (args.seizedAssets === 0n) continue;
|
|
4080
4448
|
const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
amount: seizure.seized,
|
|
4091
|
-
blockNumber: Number(rawLog.blockNumber)
|
|
4092
|
-
});
|
|
4093
|
-
}
|
|
4449
|
+
seizureEvents.push({
|
|
4450
|
+
id: `${baseId}-collateral-liquidate`,
|
|
4451
|
+
chainId,
|
|
4452
|
+
obligationId,
|
|
4453
|
+
collateralIndex: Number(args.collateralIndex),
|
|
4454
|
+
user: args.borrower,
|
|
4455
|
+
amount: args.seizedAssets,
|
|
4456
|
+
blockNumber: Number(rawLog.blockNumber)
|
|
4457
|
+
});
|
|
4094
4458
|
}
|
|
4095
4459
|
return seizureEvents;
|
|
4096
4460
|
}
|
|
@@ -4113,12 +4477,15 @@ function processCollateralSeizures(parameters) {
|
|
|
4113
4477
|
*/
|
|
4114
4478
|
function processCollateralTransfers(parameters) {
|
|
4115
4479
|
const { logs, chainId } = parameters;
|
|
4116
|
-
const logger =
|
|
4480
|
+
const logger = getLoggerWithContext({
|
|
4481
|
+
component: "indexer.collector.processor",
|
|
4482
|
+
chain_id: chainId
|
|
4483
|
+
});
|
|
4117
4484
|
const transfers = [];
|
|
4118
4485
|
for (const rawLog of logs) {
|
|
4119
4486
|
if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
|
|
4120
4487
|
logger.debug({
|
|
4121
|
-
|
|
4488
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4122
4489
|
msg: "Skipping collateral log because it is missing required fields"
|
|
4123
4490
|
});
|
|
4124
4491
|
continue;
|
|
@@ -4127,9 +4494,10 @@ function processCollateralTransfers(parameters) {
|
|
|
4127
4494
|
const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
|
|
4128
4495
|
if (eventName === supplyCollateralEvent.name) {
|
|
4129
4496
|
const args = rawLog.args;
|
|
4130
|
-
|
|
4497
|
+
const obligationId = args?.id_;
|
|
4498
|
+
if (obligationId === void 0 || args?.collateral === void 0 || args?.assets === void 0 || args?.onBehalf === void 0) {
|
|
4131
4499
|
logger.debug({
|
|
4132
|
-
|
|
4500
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4133
4501
|
msg: "Skipping SupplyCollateral log because it is missing required args"
|
|
4134
4502
|
});
|
|
4135
4503
|
continue;
|
|
@@ -4138,7 +4506,7 @@ function processCollateralTransfers(parameters) {
|
|
|
4138
4506
|
transfers.push(from$9({
|
|
4139
4507
|
id: `${baseId}-collateral-supply`,
|
|
4140
4508
|
chainId,
|
|
4141
|
-
contract:
|
|
4509
|
+
contract: obligationId,
|
|
4142
4510
|
from: zeroAddress,
|
|
4143
4511
|
to: args.onBehalf,
|
|
4144
4512
|
value: args.assets,
|
|
@@ -4150,9 +4518,10 @@ function processCollateralTransfers(parameters) {
|
|
|
4150
4518
|
}
|
|
4151
4519
|
if (eventName === withdrawCollateralEvent.name) {
|
|
4152
4520
|
const args = rawLog.args;
|
|
4153
|
-
|
|
4521
|
+
const obligationId = args?.id_;
|
|
4522
|
+
if (obligationId === void 0 || args?.collateral === void 0 || args?.assets === void 0 || args?.onBehalf === void 0) {
|
|
4154
4523
|
logger.debug({
|
|
4155
|
-
|
|
4524
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4156
4525
|
msg: "Skipping WithdrawCollateral log because it is missing required args"
|
|
4157
4526
|
});
|
|
4158
4527
|
continue;
|
|
@@ -4161,7 +4530,7 @@ function processCollateralTransfers(parameters) {
|
|
|
4161
4530
|
transfers.push(from$9({
|
|
4162
4531
|
id: `${baseId}-collateral-withdraw`,
|
|
4163
4532
|
chainId,
|
|
4164
|
-
contract:
|
|
4533
|
+
contract: obligationId,
|
|
4165
4534
|
from: args.onBehalf,
|
|
4166
4535
|
to: zeroAddress,
|
|
4167
4536
|
value: args.assets,
|
|
@@ -4187,12 +4556,15 @@ const buildGroupKey = (parameters) => {
|
|
|
4187
4556
|
*/
|
|
4188
4557
|
function processConsumedLogs(parameters) {
|
|
4189
4558
|
const { logs, chainId } = parameters;
|
|
4190
|
-
const logger =
|
|
4559
|
+
const logger = getLoggerWithContext({
|
|
4560
|
+
component: "indexer.collector.processor",
|
|
4561
|
+
chain_id: chainId
|
|
4562
|
+
});
|
|
4191
4563
|
const consumedEvents = [];
|
|
4192
4564
|
for (const rawLog of logs) {
|
|
4193
4565
|
if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
|
|
4194
4566
|
logger.debug({
|
|
4195
|
-
|
|
4567
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4196
4568
|
msg: "Skipping log because it is missing required fields"
|
|
4197
4569
|
});
|
|
4198
4570
|
continue;
|
|
@@ -4202,7 +4574,7 @@ function processConsumedLogs(parameters) {
|
|
|
4202
4574
|
const consumeArgs = rawLog.args;
|
|
4203
4575
|
if (consumeArgs?.user === void 0 || consumeArgs?.group === void 0 || consumeArgs?.amount === void 0) {
|
|
4204
4576
|
logger.debug({
|
|
4205
|
-
|
|
4577
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4206
4578
|
msg: "Skipping Consume log because it is missing required args"
|
|
4207
4579
|
});
|
|
4208
4580
|
continue;
|
|
@@ -4222,7 +4594,7 @@ function processConsumedLogs(parameters) {
|
|
|
4222
4594
|
const takeArgs = rawLog.args;
|
|
4223
4595
|
if (takeArgs?.maker === void 0 || takeArgs?.group === void 0 || takeArgs?.consumed === void 0) {
|
|
4224
4596
|
logger.debug({
|
|
4225
|
-
|
|
4597
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4226
4598
|
msg: "Skipping Take log because it is missing required args for consumed"
|
|
4227
4599
|
});
|
|
4228
4600
|
continue;
|
|
@@ -4260,7 +4632,7 @@ function processConsumedLogs(parameters) {
|
|
|
4260
4632
|
* | false | false | from: 0x0 → to: buyer | none |
|
|
4261
4633
|
*
|
|
4262
4634
|
* **Repay**: from: 0x0 → to: onBehalf
|
|
4263
|
-
* **Liquidate**: from: 0x0 → to: borrower (value =
|
|
4635
|
+
* **Liquidate**: from: 0x0 → to: borrower (value = repaidUnits + badDebt)
|
|
4264
4636
|
*
|
|
4265
4637
|
* @param parameters - The parsed event logs and chain ID.
|
|
4266
4638
|
* @param parameters.logs - Parsed event logs from MorphoV2 (Take, Repay, Liquidate events).
|
|
@@ -4269,12 +4641,15 @@ function processConsumedLogs(parameters) {
|
|
|
4269
4641
|
*/
|
|
4270
4642
|
function processDebtTransfers(parameters) {
|
|
4271
4643
|
const { logs, chainId } = parameters;
|
|
4272
|
-
const logger =
|
|
4644
|
+
const logger = getLoggerWithContext({
|
|
4645
|
+
component: "indexer.collector.processor",
|
|
4646
|
+
chain_id: chainId
|
|
4647
|
+
});
|
|
4273
4648
|
const transfers = [];
|
|
4274
4649
|
for (const rawLog of logs) {
|
|
4275
4650
|
if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
|
|
4276
4651
|
logger.debug({
|
|
4277
|
-
|
|
4652
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4278
4653
|
msg: "Skipping debt log because it is missing required fields"
|
|
4279
4654
|
});
|
|
4280
4655
|
continue;
|
|
@@ -4283,9 +4658,10 @@ function processDebtTransfers(parameters) {
|
|
|
4283
4658
|
const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
|
|
4284
4659
|
if (eventName === takeEvent.name) {
|
|
4285
4660
|
const args = rawLog.args;
|
|
4286
|
-
|
|
4661
|
+
const obligationId = args?.id_;
|
|
4662
|
+
if (obligationId === void 0 || args?.maker === void 0 || args?.taker === void 0 || args?.offerIsBuy === void 0 || args?.obligationUnits === void 0 || args?.buyerIsLender === void 0 || args?.sellerIsBorrower === void 0) {
|
|
4287
4663
|
logger.debug({
|
|
4288
|
-
|
|
4664
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4289
4665
|
msg: "Skipping Take log because it is missing required args for debt"
|
|
4290
4666
|
});
|
|
4291
4667
|
continue;
|
|
@@ -4297,7 +4673,7 @@ function processDebtTransfers(parameters) {
|
|
|
4297
4673
|
if (!args.buyerIsLender) transfers.push(from$9({
|
|
4298
4674
|
id: `${baseId}-debt-buyer`,
|
|
4299
4675
|
chainId,
|
|
4300
|
-
contract:
|
|
4676
|
+
contract: obligationId,
|
|
4301
4677
|
from: zeroAddress,
|
|
4302
4678
|
to: buyer,
|
|
4303
4679
|
value: args.obligationUnits,
|
|
@@ -4308,7 +4684,7 @@ function processDebtTransfers(parameters) {
|
|
|
4308
4684
|
if (args.sellerIsBorrower) transfers.push(from$9({
|
|
4309
4685
|
id: `${baseId}-debt-seller`,
|
|
4310
4686
|
chainId,
|
|
4311
|
-
contract:
|
|
4687
|
+
contract: obligationId,
|
|
4312
4688
|
from: seller,
|
|
4313
4689
|
to: zeroAddress,
|
|
4314
4690
|
value: args.obligationUnits,
|
|
@@ -4320,9 +4696,10 @@ function processDebtTransfers(parameters) {
|
|
|
4320
4696
|
}
|
|
4321
4697
|
if (eventName === repayEvent.name) {
|
|
4322
4698
|
const args = rawLog.args;
|
|
4323
|
-
|
|
4699
|
+
const obligationId = args?.id_;
|
|
4700
|
+
if (obligationId === void 0 || args?.obligationUnits === void 0 || args?.onBehalf === void 0) {
|
|
4324
4701
|
logger.debug({
|
|
4325
|
-
|
|
4702
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4326
4703
|
msg: "Skipping Repay log because it is missing required args"
|
|
4327
4704
|
});
|
|
4328
4705
|
continue;
|
|
@@ -4331,7 +4708,7 @@ function processDebtTransfers(parameters) {
|
|
|
4331
4708
|
transfers.push(from$9({
|
|
4332
4709
|
id: `${baseId}-debt-repay`,
|
|
4333
4710
|
chainId,
|
|
4334
|
-
contract:
|
|
4711
|
+
contract: obligationId,
|
|
4335
4712
|
from: zeroAddress,
|
|
4336
4713
|
to: args.onBehalf,
|
|
4337
4714
|
value: args.obligationUnits,
|
|
@@ -4343,19 +4720,20 @@ function processDebtTransfers(parameters) {
|
|
|
4343
4720
|
}
|
|
4344
4721
|
if (eventName === liquidateEvent.name) {
|
|
4345
4722
|
const args = rawLog.args;
|
|
4346
|
-
|
|
4723
|
+
const obligationId = args?.id_;
|
|
4724
|
+
if (obligationId === void 0 || args?.borrower === void 0 || args?.repaidUnits === void 0 || args?.badDebt === void 0) {
|
|
4347
4725
|
logger.debug({
|
|
4348
|
-
|
|
4726
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4349
4727
|
msg: "Skipping Liquidate log because it is missing required args"
|
|
4350
4728
|
});
|
|
4351
4729
|
continue;
|
|
4352
4730
|
}
|
|
4353
|
-
const totalReduction = args.
|
|
4731
|
+
const totalReduction = args.repaidUnits + args.badDebt;
|
|
4354
4732
|
if (totalReduction === 0n) continue;
|
|
4355
4733
|
transfers.push(from$9({
|
|
4356
4734
|
id: `${baseId}-debt-liquidate`,
|
|
4357
4735
|
chainId,
|
|
4358
|
-
contract:
|
|
4736
|
+
contract: obligationId,
|
|
4359
4737
|
from: zeroAddress,
|
|
4360
4738
|
to: args.borrower,
|
|
4361
4739
|
value: totalReduction,
|
|
@@ -4372,10 +4750,18 @@ function processDebtTransfers(parameters) {
|
|
|
4372
4750
|
//#region src/indexer/collectors/CollectFunctions/collectMorphoV2.ts
|
|
4373
4751
|
async function* collectMorphoV2(parameters) {
|
|
4374
4752
|
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
4375
|
-
const logger =
|
|
4753
|
+
const logger = getLoggerWithContext({
|
|
4754
|
+
component: "indexer.collector",
|
|
4755
|
+
collector,
|
|
4756
|
+
chain_id: client.chain.id
|
|
4757
|
+
});
|
|
4376
4758
|
let startBlock = blockNumber;
|
|
4377
4759
|
let reorgDetected = false;
|
|
4378
4760
|
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
4761
|
+
const indexedEventHeadContext = await getIndexedEventHeadContext({
|
|
4762
|
+
client,
|
|
4763
|
+
headBlockNumber: latestBlockNumberChain
|
|
4764
|
+
});
|
|
4379
4765
|
const stream = streamLogs({
|
|
4380
4766
|
client,
|
|
4381
4767
|
contractAddress: client.chain.custom.morpho.address,
|
|
@@ -4423,8 +4809,6 @@ async function* collectMorphoV2(parameters) {
|
|
|
4423
4809
|
}), resolveConsumedEvents({
|
|
4424
4810
|
dbTx,
|
|
4425
4811
|
consumedEvents,
|
|
4426
|
-
chainId: client.chain.id,
|
|
4427
|
-
collector,
|
|
4428
4812
|
logger
|
|
4429
4813
|
})]);
|
|
4430
4814
|
if (debtTransfers.length > 0) {
|
|
@@ -4437,14 +4821,23 @@ async function* collectMorphoV2(parameters) {
|
|
|
4437
4821
|
await dbTx.transfers.create(allCollateralTransfers);
|
|
4438
4822
|
}
|
|
4439
4823
|
await dbTx.consumed.create(resolvedConsumedEvents);
|
|
4440
|
-
|
|
4824
|
+
const logsIndexedCount = countIndexedLogs({
|
|
4825
|
+
resolvedConsumedEvents,
|
|
4826
|
+
debtTransfers,
|
|
4827
|
+
collateralTransfers: allCollateralTransfers
|
|
4828
|
+
});
|
|
4829
|
+
logger.info({
|
|
4830
|
+
event: INDEXER_COLLECTOR_EVENTS_INDEXED,
|
|
4441
4831
|
msg: "Events indexed",
|
|
4442
|
-
collector,
|
|
4443
4832
|
consumed_count: resolvedConsumedEvents.length,
|
|
4444
4833
|
debt_transfer_count: debtTransfers.length,
|
|
4445
4834
|
collateral_transfer_count: allCollateralTransfers.length,
|
|
4446
|
-
|
|
4447
|
-
|
|
4835
|
+
block_range: [startBlock, lastStreamBlockNumber],
|
|
4836
|
+
...buildIndexedEventTelemetry({
|
|
4837
|
+
...indexedEventHeadContext,
|
|
4838
|
+
logsIngestedCount: parsedLogs.length,
|
|
4839
|
+
logsIndexedCount
|
|
4840
|
+
})
|
|
4448
4841
|
});
|
|
4449
4842
|
blockNumber = lastStreamBlockNumber;
|
|
4450
4843
|
try {
|
|
@@ -4476,8 +4869,7 @@ async function* collectMorphoV2(parameters) {
|
|
|
4476
4869
|
positionTypeId: positionTypeId(Type.COLLATERAL_OF)
|
|
4477
4870
|
});
|
|
4478
4871
|
logger.info({
|
|
4479
|
-
|
|
4480
|
-
chain_id: client.chain.id,
|
|
4872
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATED,
|
|
4481
4873
|
msg: "Reorg detected, events deleted",
|
|
4482
4874
|
consumed_deleted: deletedConsumed,
|
|
4483
4875
|
debt_transfers_deleted: deletedDebtTransfers,
|
|
@@ -4493,13 +4885,13 @@ async function* collectMorphoV2(parameters) {
|
|
|
4493
4885
|
reorgDetected = true;
|
|
4494
4886
|
} catch (err) {
|
|
4495
4887
|
const msg = "Failed to delete events when handling reorg.";
|
|
4888
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4496
4889
|
logger.error({
|
|
4497
|
-
|
|
4498
|
-
chainId: client.chain.id,
|
|
4890
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATION_FAILED,
|
|
4499
4891
|
msg,
|
|
4500
|
-
err
|
|
4892
|
+
err: error
|
|
4501
4893
|
});
|
|
4502
|
-
throw new Error(msg, { cause:
|
|
4894
|
+
throw new Error(msg, { cause: error });
|
|
4503
4895
|
}
|
|
4504
4896
|
}
|
|
4505
4897
|
});
|
|
@@ -4527,8 +4919,7 @@ async function* collectMorphoV2(parameters) {
|
|
|
4527
4919
|
positionTypeId: positionTypeId(Type.COLLATERAL_OF)
|
|
4528
4920
|
});
|
|
4529
4921
|
if (deletedConsumed > 0 || deletedDebtTransfers > 0 || deletedCollateralTransfers > 0) logger.info({
|
|
4530
|
-
|
|
4531
|
-
chain_id: client.chain.id,
|
|
4922
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATED,
|
|
4532
4923
|
msg: "Reorg detected, events deleted",
|
|
4533
4924
|
consumed_deleted: deletedConsumed,
|
|
4534
4925
|
debt_transfers_deleted: deletedDebtTransfers,
|
|
@@ -4568,7 +4959,7 @@ async function resolveSeizureTransfers(parameters) {
|
|
|
4568
4959
|
return resolvedSeizureTransfers;
|
|
4569
4960
|
}
|
|
4570
4961
|
async function resolveConsumedEvents(parameters) {
|
|
4571
|
-
const { dbTx, consumedEvents,
|
|
4962
|
+
const { dbTx, consumedEvents, logger } = parameters;
|
|
4572
4963
|
if (consumedEvents.length === 0) return [];
|
|
4573
4964
|
const [existingConsumedIds, consumedByGroup] = await Promise.all([getExistingConsumedIds({
|
|
4574
4965
|
dbTx,
|
|
@@ -4601,8 +4992,7 @@ async function resolveConsumedEvents(parameters) {
|
|
|
4601
4992
|
const delta = log.consumed - previousConsumed;
|
|
4602
4993
|
if (delta <= 0n) {
|
|
4603
4994
|
logger.debug({
|
|
4604
|
-
|
|
4605
|
-
chainId,
|
|
4995
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
4606
4996
|
msg: "Skipping Take log because consumed did not increase",
|
|
4607
4997
|
previous_consumed: previousConsumed.toString(),
|
|
4608
4998
|
consumed: log.consumed.toString()
|
|
@@ -4621,6 +5011,15 @@ async function resolveConsumedEvents(parameters) {
|
|
|
4621
5011
|
}
|
|
4622
5012
|
return resolvedEvents;
|
|
4623
5013
|
}
|
|
5014
|
+
function countIndexedLogs(parameters) {
|
|
5015
|
+
const sourceLogIds = /* @__PURE__ */ new Set();
|
|
5016
|
+
for (const consumedEvent of parameters.resolvedConsumedEvents) sourceLogIds.add(consumedEvent.id);
|
|
5017
|
+
for (const transfer of [...parameters.debtTransfers, ...parameters.collateralTransfers]) sourceLogIds.add(toSourceLogId(transfer.id));
|
|
5018
|
+
return sourceLogIds.size;
|
|
5019
|
+
}
|
|
5020
|
+
function toSourceLogId(derivedId) {
|
|
5021
|
+
return derivedId.replace(/-(?:debt-(?:buyer|seller|repay|liquidate)|collateral-(?:supply|withdraw|liquidate))$/, "");
|
|
5022
|
+
}
|
|
4624
5023
|
async function getExistingConsumedIds(parameters) {
|
|
4625
5024
|
const { dbTx, consumedEvents: consumedEvents$1 } = parameters;
|
|
4626
5025
|
const existingConsumedIds = /* @__PURE__ */ new Set();
|
|
@@ -4638,21 +5037,21 @@ async function getExistingConsumedIds(parameters) {
|
|
|
4638
5037
|
}
|
|
4639
5038
|
async function getConsumedByGroup(parameters) {
|
|
4640
5039
|
const { dbTx, consumedEvents } = parameters;
|
|
4641
|
-
const groups$
|
|
5040
|
+
const groups$4 = /* @__PURE__ */ new Map();
|
|
4642
5041
|
for (const event of consumedEvents) {
|
|
4643
5042
|
const key = buildGroupKey({
|
|
4644
5043
|
chainId: event.chainId,
|
|
4645
5044
|
maker: event.maker,
|
|
4646
5045
|
group: event.group
|
|
4647
5046
|
});
|
|
4648
|
-
if (!groups$
|
|
5047
|
+
if (!groups$4.has(key)) groups$4.set(key, {
|
|
4649
5048
|
chainId: event.chainId,
|
|
4650
5049
|
maker: event.maker,
|
|
4651
5050
|
group: event.group
|
|
4652
5051
|
});
|
|
4653
5052
|
}
|
|
4654
5053
|
const consumedByGroup = /* @__PURE__ */ new Map();
|
|
4655
|
-
const groupList = Array.from(groups$
|
|
5054
|
+
const groupList = Array.from(groups$4.values());
|
|
4656
5055
|
for (let index = 0; index < groupList.length; index += 500) {
|
|
4657
5056
|
const slice = groupList.slice(index, index + 500);
|
|
4658
5057
|
const { rows } = await dbTx.execute(sql`
|
|
@@ -4710,17 +5109,20 @@ function transfersToPositions(transfers) {
|
|
|
4710
5109
|
|
|
4711
5110
|
//#endregion
|
|
4712
5111
|
//#region src/indexer/collectors/CollectFunctions/collectOffers.ts
|
|
4713
|
-
const ERC20_TYPE_ID = Object.values(Type).indexOf(Type.ERC20) + 1;
|
|
4714
5112
|
async function* collectOffersV2(parameters) {
|
|
4715
5113
|
let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
4716
|
-
const logger =
|
|
5114
|
+
const logger = getLoggerWithContext({
|
|
5115
|
+
component: "indexer.collector",
|
|
5116
|
+
collector,
|
|
5117
|
+
chain_id: client.chain.id
|
|
5118
|
+
});
|
|
4717
5119
|
let startBlock = blockNumber;
|
|
4718
5120
|
let reorgDetected = false;
|
|
4719
5121
|
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
4720
5122
|
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
4721
5123
|
logger.error({
|
|
4722
|
-
|
|
4723
|
-
|
|
5124
|
+
event: INDEXER_COLLECTOR_INVALID_CHAIN_CONFIG,
|
|
5125
|
+
msg
|
|
4724
5126
|
});
|
|
4725
5127
|
throw new Error(msg);
|
|
4726
5128
|
}
|
|
@@ -4730,6 +5132,10 @@ async function* collectOffersV2(parameters) {
|
|
|
4730
5132
|
verifyingContract: morphoV2
|
|
4731
5133
|
};
|
|
4732
5134
|
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5135
|
+
const indexedEventHeadContext = await getIndexedEventHeadContext({
|
|
5136
|
+
client,
|
|
5137
|
+
headBlockNumber: latestBlockNumberChain
|
|
5138
|
+
});
|
|
4733
5139
|
const stream = streamLogs({
|
|
4734
5140
|
client,
|
|
4735
5141
|
contractAddress: client.chain.custom.mempool.address,
|
|
@@ -4763,11 +5169,11 @@ async function* collectOffersV2(parameters) {
|
|
|
4763
5169
|
const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
|
|
4764
5170
|
if (signerMismatch) {
|
|
4765
5171
|
logger.debug({
|
|
5172
|
+
event: INDEXER_COLLECTOR_OFFER_TREE_REJECTED,
|
|
4766
5173
|
msg: "Tree rejected: signer mismatch",
|
|
4767
5174
|
reason: "signer_mismatch",
|
|
4768
5175
|
signer,
|
|
4769
|
-
maker: signerMismatch.maker
|
|
4770
|
-
chain_id: client.chain.id
|
|
5176
|
+
maker: signerMismatch.maker
|
|
4771
5177
|
});
|
|
4772
5178
|
continue;
|
|
4773
5179
|
}
|
|
@@ -4780,80 +5186,81 @@ async function* collectOffersV2(parameters) {
|
|
|
4780
5186
|
} catch (err) {
|
|
4781
5187
|
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
4782
5188
|
logger.debug({
|
|
5189
|
+
event: INDEXER_COLLECTOR_OFFER_TREE_DECODE_FAILED,
|
|
4783
5190
|
msg: "Tree decode failed",
|
|
4784
5191
|
reason,
|
|
4785
|
-
chain_id: client.chain.id,
|
|
4786
5192
|
err: err instanceof Error ? err.message : String(err)
|
|
4787
5193
|
});
|
|
4788
5194
|
}
|
|
4789
5195
|
}
|
|
4790
|
-
await db.
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
if (
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
issues_count: allowedResults.issues.length
|
|
4810
|
-
});
|
|
4811
|
-
} else if (hasBlockWindowViolation) logger.debug({
|
|
4812
|
-
msg: "Tree rejected: offers outside block window",
|
|
4813
|
-
reason: "block_window",
|
|
4814
|
-
chain_id: client.chain.id
|
|
5196
|
+
const { blockNumber: latestBlockNumber } = await db.blocks.getChain(client.chain.id);
|
|
5197
|
+
const treesToInsert = [];
|
|
5198
|
+
const pathsToInsert = [];
|
|
5199
|
+
let totalValidOffers = 0;
|
|
5200
|
+
const offersWithBlock = [];
|
|
5201
|
+
for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
|
|
5202
|
+
const allowedResults = await gatekeeper.isAllowed({
|
|
5203
|
+
offers: tree.offers,
|
|
5204
|
+
chainId: client.chain.id
|
|
5205
|
+
});
|
|
5206
|
+
const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
|
|
5207
|
+
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
5208
|
+
if (allowedResults.issues.length > 0) {
|
|
5209
|
+
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
5210
|
+
logger.debug({
|
|
5211
|
+
event: INDEXER_COLLECTOR_OFFER_TREE_REJECTED,
|
|
5212
|
+
msg: "Tree offers rejected by gatekeeper",
|
|
5213
|
+
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
5214
|
+
issues_count: allowedResults.issues.length
|
|
4815
5215
|
});
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
signature
|
|
5216
|
+
} else if (hasBlockWindowViolation) logger.debug({
|
|
5217
|
+
event: INDEXER_COLLECTOR_OFFER_TREE_REJECTED,
|
|
5218
|
+
msg: "Tree rejected: offers outside block window",
|
|
5219
|
+
reason: "block_window"
|
|
4821
5220
|
});
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
msg: "Gatekeeper validation failed",
|
|
4853
|
-
chain_id: client.chain.id
|
|
5221
|
+
continue;
|
|
5222
|
+
}
|
|
5223
|
+
treesToInsert.push({
|
|
5224
|
+
root: tree.root,
|
|
5225
|
+
signature
|
|
5226
|
+
});
|
|
5227
|
+
totalValidOffers += tree.offers.length;
|
|
5228
|
+
const obligationIdsByOfferHash = /* @__PURE__ */ new Map();
|
|
5229
|
+
for (const offer of tree.offers) {
|
|
5230
|
+
const offerHash = hash(offer).toLowerCase();
|
|
5231
|
+
const obligationId$1 = obligationId(offer, {
|
|
5232
|
+
chainId: client.chain.id,
|
|
5233
|
+
morphoV2
|
|
5234
|
+
}).toLowerCase();
|
|
5235
|
+
offersWithBlock.push({
|
|
5236
|
+
offer,
|
|
5237
|
+
blockNumber: treeBlockNumber,
|
|
5238
|
+
obligationId: obligationId$1
|
|
5239
|
+
});
|
|
5240
|
+
obligationIdsByOfferHash.set(offerHash, obligationId$1);
|
|
5241
|
+
}
|
|
5242
|
+
for (const proof of proofs(tree)) {
|
|
5243
|
+
const offerHash = hash(proof.offer).toLowerCase();
|
|
5244
|
+
const obligationId = obligationIdsByOfferHash.get(offerHash);
|
|
5245
|
+
if (obligationId === void 0) continue;
|
|
5246
|
+
pathsToInsert.push({
|
|
5247
|
+
offerHash,
|
|
5248
|
+
obligationId,
|
|
5249
|
+
treeRoot: tree.root,
|
|
5250
|
+
proof: proof.path
|
|
4854
5251
|
});
|
|
4855
|
-
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
4856
5252
|
}
|
|
5253
|
+
} catch (err) {
|
|
5254
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5255
|
+
logger.error({
|
|
5256
|
+
event: INDEXER_COLLECTOR_GATEKEEPER_VALIDATION_FAILED,
|
|
5257
|
+
err: error,
|
|
5258
|
+
msg: "Gatekeeper validation failed"
|
|
5259
|
+
});
|
|
5260
|
+
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
5261
|
+
}
|
|
5262
|
+
await db.transaction(async (dbTx) => {
|
|
5263
|
+
const { epoch } = await dbTx.blocks.getChain(client.chain.id);
|
|
4857
5264
|
const dependencies = buildOfferDependencies({
|
|
4858
5265
|
offers: offersWithBlock,
|
|
4859
5266
|
chainId: client.chain.id,
|
|
@@ -4882,13 +5289,17 @@ async function* collectOffersV2(parameters) {
|
|
|
4882
5289
|
blockNumber,
|
|
4883
5290
|
epoch
|
|
4884
5291
|
});
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
5292
|
+
logger.info({
|
|
5293
|
+
event: INDEXER_COLLECTOR_OFFERS_INDEXED,
|
|
5294
|
+
msg: "Offers batch processed",
|
|
4888
5295
|
count: totalValidOffers,
|
|
4889
5296
|
trees_count: treesToInsert.length,
|
|
4890
|
-
|
|
4891
|
-
|
|
5297
|
+
block_range: [startBlock, lastStreamBlockNumber],
|
|
5298
|
+
...buildIndexedEventTelemetry({
|
|
5299
|
+
...indexedEventHeadContext,
|
|
5300
|
+
logsIngestedCount: logs.length,
|
|
5301
|
+
logsIndexedCount: treesToInsert.length
|
|
5302
|
+
})
|
|
4892
5303
|
});
|
|
4893
5304
|
} catch (_) {
|
|
4894
5305
|
try {
|
|
@@ -4902,8 +5313,7 @@ async function* collectOffersV2(parameters) {
|
|
|
4902
5313
|
chainId: client.chain.id
|
|
4903
5314
|
});
|
|
4904
5315
|
logger.info({
|
|
4905
|
-
|
|
4906
|
-
chain_id: client.chain.id,
|
|
5316
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATED,
|
|
4907
5317
|
msg: `Reorg detected, offers deleted`,
|
|
4908
5318
|
count: deleted,
|
|
4909
5319
|
block_number: blockNumber
|
|
@@ -4917,11 +5327,11 @@ async function* collectOffersV2(parameters) {
|
|
|
4917
5327
|
reorgDetected = true;
|
|
4918
5328
|
} catch (err) {
|
|
4919
5329
|
const msg = "Failed to delete offers when handling reorg.";
|
|
5330
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4920
5331
|
logger.error({
|
|
4921
|
-
|
|
4922
|
-
chainId: client.chain.id,
|
|
5332
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATION_FAILED,
|
|
4923
5333
|
msg,
|
|
4924
|
-
err
|
|
5334
|
+
err: error
|
|
4925
5335
|
});
|
|
4926
5336
|
throw new Error(msg);
|
|
4927
5337
|
}
|
|
@@ -4943,37 +5353,62 @@ function decodeCallbacks(parameters) {
|
|
|
4943
5353
|
const positions = [];
|
|
4944
5354
|
const lots = [];
|
|
4945
5355
|
for (const { offer, blockNumber: offerBlockNumber, obligationId } of offers) {
|
|
4946
|
-
if (!offer.buy) continue;
|
|
4947
5356
|
if (!isEmptyCallback(offer)) continue;
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
user: offer.maker,
|
|
4953
|
-
type: Type.ERC20,
|
|
4954
|
-
asset: loanToken,
|
|
4955
|
-
blockNumber: offerBlockNumber
|
|
4956
|
-
}));
|
|
4957
|
-
lots.push({
|
|
4958
|
-
positionChainId: chainId,
|
|
4959
|
-
positionContract: loanToken,
|
|
4960
|
-
positionUser: offer.maker,
|
|
4961
|
-
positionTypeId: ERC20_TYPE_ID,
|
|
4962
|
-
group: offer.group,
|
|
4963
|
-
obligationId,
|
|
4964
|
-
size: offer.assets
|
|
4965
|
-
});
|
|
4966
|
-
callbacks.push({
|
|
4967
|
-
offerHash: hash(offer),
|
|
4968
|
-
obligationId,
|
|
4969
|
-
callbacks: [{
|
|
5357
|
+
if (offer.buy) {
|
|
5358
|
+
const loanToken = offer.loanToken.toLowerCase();
|
|
5359
|
+
const positionTypeId$3 = positionTypeId(Type.ERC20);
|
|
5360
|
+
positions.push(from$12({
|
|
4970
5361
|
chainId,
|
|
4971
5362
|
contract: loanToken,
|
|
4972
5363
|
user: offer.maker,
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
5364
|
+
type: Type.ERC20,
|
|
5365
|
+
asset: loanToken,
|
|
5366
|
+
blockNumber: offerBlockNumber
|
|
5367
|
+
}));
|
|
5368
|
+
lots.push({
|
|
5369
|
+
positionChainId: chainId,
|
|
5370
|
+
positionContract: loanToken,
|
|
5371
|
+
positionUser: offer.maker,
|
|
5372
|
+
positionTypeId: positionTypeId$3,
|
|
5373
|
+
group: offer.group,
|
|
5374
|
+
obligationId,
|
|
5375
|
+
size: offer.assets
|
|
5376
|
+
});
|
|
5377
|
+
callbacks.push({
|
|
5378
|
+
offerHash: hash(offer),
|
|
5379
|
+
obligationId,
|
|
5380
|
+
callbacks: [{
|
|
5381
|
+
chainId,
|
|
5382
|
+
contract: loanToken,
|
|
5383
|
+
user: offer.maker,
|
|
5384
|
+
positionTypeId: positionTypeId$3,
|
|
5385
|
+
type: CallbackType.Empty
|
|
5386
|
+
}]
|
|
5387
|
+
});
|
|
5388
|
+
} else {
|
|
5389
|
+
const contract = obligationId;
|
|
5390
|
+
const positionTypeId$2 = positionTypeId(Type.COLLATERAL_OF);
|
|
5391
|
+
lots.push({
|
|
5392
|
+
positionChainId: chainId,
|
|
5393
|
+
positionContract: contract,
|
|
5394
|
+
positionUser: offer.maker,
|
|
5395
|
+
positionTypeId: positionTypeId$2,
|
|
5396
|
+
group: offer.group,
|
|
5397
|
+
obligationId,
|
|
5398
|
+
size: offer.assets
|
|
5399
|
+
});
|
|
5400
|
+
callbacks.push({
|
|
5401
|
+
offerHash: hash(offer),
|
|
5402
|
+
obligationId,
|
|
5403
|
+
callbacks: [{
|
|
5404
|
+
chainId,
|
|
5405
|
+
contract,
|
|
5406
|
+
user: offer.maker,
|
|
5407
|
+
positionTypeId: positionTypeId$2,
|
|
5408
|
+
type: CallbackType.Empty
|
|
5409
|
+
}]
|
|
5410
|
+
});
|
|
5411
|
+
}
|
|
4977
5412
|
}
|
|
4978
5413
|
return {
|
|
4979
5414
|
callbacks,
|
|
@@ -5149,8 +5584,11 @@ async function snapshotERC20Positions(parameters) {
|
|
|
5149
5584
|
* @returns Positions - {@link snapshotVaultPositions.ReturnType}
|
|
5150
5585
|
*/
|
|
5151
5586
|
async function snapshotVaultPositions(parameters) {
|
|
5152
|
-
const logger = getLogger();
|
|
5153
5587
|
const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5588
|
+
const logger = getLoggerWithContext({
|
|
5589
|
+
component: "indexer.collector.fetcher",
|
|
5590
|
+
chain_id: client.chain.id
|
|
5591
|
+
});
|
|
5154
5592
|
const calls = [];
|
|
5155
5593
|
const contracts = /* @__PURE__ */ new Map();
|
|
5156
5594
|
const positions = structuredClone(oldPositions);
|
|
@@ -5176,8 +5614,8 @@ async function snapshotVaultPositions(parameters) {
|
|
|
5176
5614
|
} catch (err) {
|
|
5177
5615
|
if (err instanceof DenominatorIsZeroError) {
|
|
5178
5616
|
logger.error({
|
|
5617
|
+
event: INDEXER_COLLECTOR_POSITION_CONVERSION_FAILED,
|
|
5179
5618
|
msg: "Failed to convert shares to assets",
|
|
5180
|
-
chain_id: client.chain.id,
|
|
5181
5619
|
block_number: blockNumber,
|
|
5182
5620
|
position_contract: position.contract,
|
|
5183
5621
|
position_user: position.user,
|
|
@@ -5257,11 +5695,18 @@ async function snapshotVaultPositions(parameters) {
|
|
|
5257
5695
|
|
|
5258
5696
|
//#endregion
|
|
5259
5697
|
//#region src/indexer/collectors/CollectFunctions/collectPositions.ts
|
|
5698
|
+
const MAX_POSITIONS_BLOCK_WINDOW = 500;
|
|
5699
|
+
const POSITIONS_PAGE_SIZE = 1e3;
|
|
5260
5700
|
async function* collectPositions(parameters) {
|
|
5261
5701
|
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
|
|
5262
|
-
const logger =
|
|
5702
|
+
const logger = getLoggerWithContext({
|
|
5703
|
+
component: "indexer.collector",
|
|
5704
|
+
collector,
|
|
5705
|
+
chain_id: client.chain.id
|
|
5706
|
+
});
|
|
5263
5707
|
let startBlock = blockNumber;
|
|
5264
5708
|
let reorgDetected = false;
|
|
5709
|
+
const safeBlockWindow = blockWindow === void 0 ? MAX_POSITIONS_BLOCK_WINDOW : Math.min(blockWindow, MAX_POSITIONS_BLOCK_WINDOW);
|
|
5265
5710
|
const TransferEvent = {
|
|
5266
5711
|
type: "event",
|
|
5267
5712
|
name: "Transfer",
|
|
@@ -5284,6 +5729,107 @@ async function* collectPositions(parameters) {
|
|
|
5284
5729
|
]
|
|
5285
5730
|
};
|
|
5286
5731
|
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5732
|
+
const indexedEventHeadContext = await getIndexedEventHeadContext({
|
|
5733
|
+
client,
|
|
5734
|
+
headBlockNumber: latestBlockNumberChain
|
|
5735
|
+
});
|
|
5736
|
+
const hasEmptyPositions = await _hasPositions({
|
|
5737
|
+
db,
|
|
5738
|
+
chainId: client.chain.id,
|
|
5739
|
+
filled: false
|
|
5740
|
+
});
|
|
5741
|
+
const hasFilledPositions = await _hasPositions({
|
|
5742
|
+
db,
|
|
5743
|
+
chainId: client.chain.id,
|
|
5744
|
+
filled: true
|
|
5745
|
+
});
|
|
5746
|
+
let pendingNewPositions = [];
|
|
5747
|
+
if (hasEmptyPositions) {
|
|
5748
|
+
const emptyPositions = await _getPositions({
|
|
5749
|
+
db,
|
|
5750
|
+
chainId: client.chain.id,
|
|
5751
|
+
filled: false
|
|
5752
|
+
});
|
|
5753
|
+
try {
|
|
5754
|
+
pendingNewPositions = (await _snapshot({
|
|
5755
|
+
positions: emptyPositions,
|
|
5756
|
+
blockNumber: latestBlockNumberChain,
|
|
5757
|
+
client,
|
|
5758
|
+
maxBatchSize,
|
|
5759
|
+
retryAttempts,
|
|
5760
|
+
retryDelayMs
|
|
5761
|
+
})).map((position) => ({
|
|
5762
|
+
...position,
|
|
5763
|
+
blockNumber: position.blockNumber + 1
|
|
5764
|
+
}));
|
|
5765
|
+
} catch (err) {
|
|
5766
|
+
logger.error({
|
|
5767
|
+
event: INDEXER_COLLECTOR_POSITIONS_SNAPSHOT_FAILED,
|
|
5768
|
+
msg: "Failed to snapshot new empty positions",
|
|
5769
|
+
block_number: latestBlockNumberChain,
|
|
5770
|
+
err
|
|
5771
|
+
});
|
|
5772
|
+
yield startBlock;
|
|
5773
|
+
return;
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
if (!hasFilledPositions) {
|
|
5777
|
+
blockNumber = latestBlockNumberChain;
|
|
5778
|
+
const positionsToInsert = pendingNewPositions;
|
|
5779
|
+
try {
|
|
5780
|
+
await db.transaction(async (dbTx) => {
|
|
5781
|
+
const insertPositions = async () => {
|
|
5782
|
+
if (positionsToInsert.length === 0) return 0;
|
|
5783
|
+
try {
|
|
5784
|
+
return await dbTx.positions.upsert(positionsToInsert);
|
|
5785
|
+
} catch (err) {
|
|
5786
|
+
throw new InsertPositionsError(err);
|
|
5787
|
+
}
|
|
5788
|
+
};
|
|
5789
|
+
const saveBlockNumber = async () => {
|
|
5790
|
+
try {
|
|
5791
|
+
await dbTx.blocks.advanceCollector({
|
|
5792
|
+
collectorName: collector,
|
|
5793
|
+
chainId: client.chain.id,
|
|
5794
|
+
blockNumber,
|
|
5795
|
+
epoch
|
|
5796
|
+
});
|
|
5797
|
+
} catch (_) {
|
|
5798
|
+
throw new ReorgError(blockNumber);
|
|
5799
|
+
}
|
|
5800
|
+
};
|
|
5801
|
+
const positionsIndexedCount = await insertPositions();
|
|
5802
|
+
logger.info({
|
|
5803
|
+
event: INDEXER_COLLECTOR_POSITIONS_INDEXED,
|
|
5804
|
+
msg: "Positions batch processed",
|
|
5805
|
+
count: positionsIndexedCount,
|
|
5806
|
+
block_number: latestBlockNumberChain,
|
|
5807
|
+
...buildIndexedEventTelemetry({
|
|
5808
|
+
...indexedEventHeadContext,
|
|
5809
|
+
logsIngestedCount: positionsToInsert.length,
|
|
5810
|
+
logsIndexedCount: positionsIndexedCount
|
|
5811
|
+
})
|
|
5812
|
+
});
|
|
5813
|
+
await saveBlockNumber();
|
|
5814
|
+
});
|
|
5815
|
+
} catch (err) {
|
|
5816
|
+
if (err instanceof ReorgError) return;
|
|
5817
|
+
if (err instanceof InsertPositionsError) {
|
|
5818
|
+
logger.error({
|
|
5819
|
+
event: INDEXER_COLLECTOR_POSITIONS_INSERT_FAILED,
|
|
5820
|
+
msg: "Failed to insert positions",
|
|
5821
|
+
count: positionsToInsert.length,
|
|
5822
|
+
block_number: latestBlockNumberChain,
|
|
5823
|
+
err
|
|
5824
|
+
});
|
|
5825
|
+
throw err.cause;
|
|
5826
|
+
}
|
|
5827
|
+
throw err;
|
|
5828
|
+
}
|
|
5829
|
+
startBlock = blockNumber;
|
|
5830
|
+
yield blockNumber;
|
|
5831
|
+
return;
|
|
5832
|
+
}
|
|
5287
5833
|
const stream = streamLogs({
|
|
5288
5834
|
client,
|
|
5289
5835
|
event: TransferEvent,
|
|
@@ -5292,7 +5838,7 @@ async function* collectPositions(parameters) {
|
|
|
5292
5838
|
order: "asc",
|
|
5293
5839
|
options: {
|
|
5294
5840
|
maxBatchSize,
|
|
5295
|
-
blockWindow
|
|
5841
|
+
blockWindow: safeBlockWindow
|
|
5296
5842
|
}
|
|
5297
5843
|
});
|
|
5298
5844
|
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
@@ -5305,8 +5851,7 @@ async function* collectPositions(parameters) {
|
|
|
5305
5851
|
for (const log of parsedLogs) {
|
|
5306
5852
|
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
5307
5853
|
logger.debug({
|
|
5308
|
-
|
|
5309
|
-
chainId: client.chain.id,
|
|
5854
|
+
event: INDEXER_COLLECTOR_LOG_SKIPPED,
|
|
5310
5855
|
msg: "Skipping log because it is missing required fields"
|
|
5311
5856
|
});
|
|
5312
5857
|
continue;
|
|
@@ -5323,63 +5868,21 @@ async function* collectPositions(parameters) {
|
|
|
5323
5868
|
blockNumber: Number(log.blockNumber)
|
|
5324
5869
|
}));
|
|
5325
5870
|
}
|
|
5326
|
-
const
|
|
5327
|
-
chainId: client.chain.id,
|
|
5328
|
-
filled: false,
|
|
5329
|
-
type: Type.ERC20
|
|
5330
|
-
});
|
|
5331
|
-
const newPositions = [];
|
|
5332
|
-
try {
|
|
5333
|
-
newPositions.push(...(await _snapshot({
|
|
5334
|
-
positions,
|
|
5335
|
-
blockNumber: latestBlockNumberChain,
|
|
5336
|
-
client,
|
|
5337
|
-
maxBatchSize,
|
|
5338
|
-
retryAttempts,
|
|
5339
|
-
retryDelayMs
|
|
5340
|
-
})).map((p) => ({
|
|
5341
|
-
...p,
|
|
5342
|
-
blockNumber: p.blockNumber + 1
|
|
5343
|
-
})));
|
|
5344
|
-
} catch (err) {
|
|
5345
|
-
logger.error({
|
|
5346
|
-
msg: "Failed to snapshot new empty positions",
|
|
5347
|
-
collector,
|
|
5348
|
-
chain_id: client.chain.id,
|
|
5349
|
-
block_number: latestBlockNumberChain,
|
|
5350
|
-
err
|
|
5351
|
-
});
|
|
5352
|
-
yield startBlock;
|
|
5353
|
-
return;
|
|
5354
|
-
}
|
|
5871
|
+
const positionsToInsert = pendingNewPositions;
|
|
5355
5872
|
try {
|
|
5356
5873
|
await db.transaction(async (dbTx) => {
|
|
5357
5874
|
const insertPositions = async () => {
|
|
5358
|
-
if (
|
|
5875
|
+
if (positionsToInsert.length === 0) return 0;
|
|
5359
5876
|
try {
|
|
5360
|
-
|
|
5361
|
-
logger.info({
|
|
5362
|
-
msg: `New positions`,
|
|
5363
|
-
collector,
|
|
5364
|
-
count,
|
|
5365
|
-
chain_id: client.chain.id,
|
|
5366
|
-
block_number: latestBlockNumberChain
|
|
5367
|
-
});
|
|
5877
|
+
return await dbTx.positions.upsert(positionsToInsert);
|
|
5368
5878
|
} catch (err) {
|
|
5369
5879
|
throw new InsertPositionsError(err);
|
|
5370
5880
|
}
|
|
5371
5881
|
};
|
|
5372
5882
|
const insertTransfers = async () => {
|
|
5373
|
-
if (transfers.length === 0) return;
|
|
5883
|
+
if (transfers.length === 0) return 0;
|
|
5374
5884
|
try {
|
|
5375
|
-
|
|
5376
|
-
logger.info({
|
|
5377
|
-
msg: `New transfers`,
|
|
5378
|
-
collector,
|
|
5379
|
-
count: created,
|
|
5380
|
-
chain_id: client.chain.id,
|
|
5381
|
-
block_range: [startBlock, blockNumber]
|
|
5382
|
-
});
|
|
5885
|
+
return await dbTx.transfers.create(transfers);
|
|
5383
5886
|
} catch (err) {
|
|
5384
5887
|
throw new InsertTransfersError(err);
|
|
5385
5888
|
}
|
|
@@ -5396,27 +5899,48 @@ async function* collectPositions(parameters) {
|
|
|
5396
5899
|
throw new ReorgError(blockNumber);
|
|
5397
5900
|
}
|
|
5398
5901
|
};
|
|
5399
|
-
await insertPositions();
|
|
5400
|
-
await insertTransfers();
|
|
5902
|
+
const positionsIndexedCount = await insertPositions();
|
|
5903
|
+
const transfersIndexedCount = await insertTransfers();
|
|
5904
|
+
logger.info({
|
|
5905
|
+
event: INDEXER_COLLECTOR_POSITIONS_INDEXED,
|
|
5906
|
+
msg: "Positions batch processed",
|
|
5907
|
+
count: positionsIndexedCount,
|
|
5908
|
+
block_number: latestBlockNumberChain,
|
|
5909
|
+
...buildIndexedEventTelemetry({
|
|
5910
|
+
...indexedEventHeadContext,
|
|
5911
|
+
logsIngestedCount: positionsToInsert.length,
|
|
5912
|
+
logsIndexedCount: positionsIndexedCount
|
|
5913
|
+
})
|
|
5914
|
+
});
|
|
5915
|
+
logger.info({
|
|
5916
|
+
event: INDEXER_COLLECTOR_TRANSFERS_INDEXED,
|
|
5917
|
+
msg: "Transfers batch processed",
|
|
5918
|
+
count: transfersIndexedCount,
|
|
5919
|
+
block_range: [startBlock, blockNumber],
|
|
5920
|
+
...buildIndexedEventTelemetry({
|
|
5921
|
+
...indexedEventHeadContext,
|
|
5922
|
+
logsIngestedCount: transfers.length,
|
|
5923
|
+
logsIndexedCount: transfersIndexedCount
|
|
5924
|
+
})
|
|
5925
|
+
});
|
|
5401
5926
|
await saveBlockNumber();
|
|
5402
5927
|
});
|
|
5928
|
+
pendingNewPositions = [];
|
|
5403
5929
|
} catch (err) {
|
|
5404
5930
|
if (err instanceof ReorgError) {
|
|
5405
5931
|
logger.info({
|
|
5932
|
+
event: INDEXER_COLLECTOR_REORG_DETECTED,
|
|
5406
5933
|
msg: "Reorg detected, positions and transfers insertion aborted",
|
|
5407
|
-
|
|
5408
|
-
count: newPositions.length,
|
|
5409
|
-
chain_id: client.chain.id,
|
|
5934
|
+
count: positionsToInsert.length,
|
|
5410
5935
|
block_number: blockNumber
|
|
5411
5936
|
});
|
|
5412
5937
|
reorgDetected = true;
|
|
5413
5938
|
}
|
|
5414
5939
|
if (err instanceof InsertPositionsError) {
|
|
5415
5940
|
logger.error({
|
|
5941
|
+
event: INDEXER_COLLECTOR_POSITIONS_INSERT_FAILED,
|
|
5416
5942
|
msg: "Failed to insert positions",
|
|
5417
|
-
|
|
5418
|
-
count: newPositions.length,
|
|
5419
|
-
chain_id: client.chain.id,
|
|
5943
|
+
count: positionsToInsert.length,
|
|
5420
5944
|
block_number: latestBlockNumberChain,
|
|
5421
5945
|
err
|
|
5422
5946
|
});
|
|
@@ -5424,10 +5948,9 @@ async function* collectPositions(parameters) {
|
|
|
5424
5948
|
}
|
|
5425
5949
|
if (err instanceof InsertTransfersError) {
|
|
5426
5950
|
logger.error({
|
|
5951
|
+
event: INDEXER_COLLECTOR_TRANSFERS_INSERT_FAILED,
|
|
5427
5952
|
msg: "Failed to insert transfers",
|
|
5428
|
-
collector,
|
|
5429
5953
|
count: transfers.length,
|
|
5430
|
-
chain_id: client.chain.id,
|
|
5431
5954
|
block_number: blockNumber,
|
|
5432
5955
|
err
|
|
5433
5956
|
});
|
|
@@ -5436,7 +5959,7 @@ async function* collectPositions(parameters) {
|
|
|
5436
5959
|
}
|
|
5437
5960
|
if (!reorgDetected) {
|
|
5438
5961
|
startBlock = blockNumber;
|
|
5439
|
-
if (
|
|
5962
|
+
if (positionsToInsert.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
|
|
5440
5963
|
yield blockNumber;
|
|
5441
5964
|
continue;
|
|
5442
5965
|
}
|
|
@@ -5453,10 +5976,9 @@ async function* collectPositions(parameters) {
|
|
|
5453
5976
|
type: Type.ERC20
|
|
5454
5977
|
});
|
|
5455
5978
|
logger.info({
|
|
5979
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATED,
|
|
5456
5980
|
msg: "Reorg detected, positions set to empty",
|
|
5457
|
-
collector,
|
|
5458
5981
|
count: emptied,
|
|
5459
|
-
chain_id: client.chain.id,
|
|
5460
5982
|
block_number_gte: blockNumber + 1
|
|
5461
5983
|
});
|
|
5462
5984
|
await dbTx.blocks.advanceCollector({
|
|
@@ -5467,11 +5989,11 @@ async function* collectPositions(parameters) {
|
|
|
5467
5989
|
});
|
|
5468
5990
|
} catch (err) {
|
|
5469
5991
|
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
5992
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5470
5993
|
logger.error({
|
|
5471
|
-
|
|
5472
|
-
chainId: client.chain.id,
|
|
5994
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATION_FAILED,
|
|
5473
5995
|
msg,
|
|
5474
|
-
err
|
|
5996
|
+
err: error
|
|
5475
5997
|
});
|
|
5476
5998
|
throw new Error(msg);
|
|
5477
5999
|
}
|
|
@@ -5479,6 +6001,34 @@ async function* collectPositions(parameters) {
|
|
|
5479
6001
|
return;
|
|
5480
6002
|
}
|
|
5481
6003
|
}
|
|
6004
|
+
async function _hasPositions(parameters) {
|
|
6005
|
+
const { db, chainId, filled } = parameters;
|
|
6006
|
+
const { positions } = await db.positions.get({
|
|
6007
|
+
chainId,
|
|
6008
|
+
filled,
|
|
6009
|
+
type: Type.ERC20,
|
|
6010
|
+
limit: 1
|
|
6011
|
+
});
|
|
6012
|
+
return positions.length > 0;
|
|
6013
|
+
}
|
|
6014
|
+
async function _getPositions(parameters) {
|
|
6015
|
+
const { db, chainId, filled } = parameters;
|
|
6016
|
+
const positions = [];
|
|
6017
|
+
let cursor;
|
|
6018
|
+
while (true) {
|
|
6019
|
+
const page = await db.positions.get({
|
|
6020
|
+
chainId,
|
|
6021
|
+
filled,
|
|
6022
|
+
type: Type.ERC20,
|
|
6023
|
+
limit: POSITIONS_PAGE_SIZE,
|
|
6024
|
+
cursor
|
|
6025
|
+
});
|
|
6026
|
+
positions.push(...page.positions);
|
|
6027
|
+
if (!page.nextCursor) break;
|
|
6028
|
+
cursor = page.nextCursor;
|
|
6029
|
+
}
|
|
6030
|
+
return positions;
|
|
6031
|
+
}
|
|
5482
6032
|
/**
|
|
5483
6033
|
* @internal
|
|
5484
6034
|
*
|
|
@@ -5547,9 +6097,17 @@ var InsertTransfersError = class extends BaseError {
|
|
|
5547
6097
|
*/
|
|
5548
6098
|
async function* collectPrices(parameters) {
|
|
5549
6099
|
const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5550
|
-
const logger =
|
|
6100
|
+
const logger = getLoggerWithContext({
|
|
6101
|
+
component: "indexer.collector",
|
|
6102
|
+
collector,
|
|
6103
|
+
chain_id: client.chain.id
|
|
6104
|
+
});
|
|
5551
6105
|
let blockNumber = parameters.lastBlockNumber;
|
|
5552
6106
|
const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
|
|
6107
|
+
const indexedEventHeadContext = await getIndexedEventHeadContext({
|
|
6108
|
+
client,
|
|
6109
|
+
headBlockNumber: latestBlockNumberChain
|
|
6110
|
+
});
|
|
5553
6111
|
const updatedOracles = [];
|
|
5554
6112
|
try {
|
|
5555
6113
|
const pricesMap = await fetchOraclePrices({
|
|
@@ -5573,9 +6131,8 @@ async function* collectPrices(parameters) {
|
|
|
5573
6131
|
}
|
|
5574
6132
|
} catch (err) {
|
|
5575
6133
|
logger.error({
|
|
6134
|
+
event: INDEXER_COLLECTOR_ORACLE_FETCH_FAILED,
|
|
5576
6135
|
msg: "Failed to fetch oracle prices",
|
|
5577
|
-
collector,
|
|
5578
|
-
chain_id: client.chain.id,
|
|
5579
6136
|
block_number: latestBlockNumberChain,
|
|
5580
6137
|
err
|
|
5581
6138
|
});
|
|
@@ -5585,16 +6142,18 @@ async function* collectPrices(parameters) {
|
|
|
5585
6142
|
let reorgDetected = false;
|
|
5586
6143
|
try {
|
|
5587
6144
|
await db.transaction(async (dbTx) => {
|
|
5588
|
-
if (updatedOracles.length > 0)
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
6145
|
+
if (updatedOracles.length > 0) await dbTx.oracles.upsert(updatedOracles);
|
|
6146
|
+
logger.info({
|
|
6147
|
+
event: INDEXER_COLLECTOR_ORACLES_INDEXED,
|
|
6148
|
+
msg: "Oracle prices updated",
|
|
6149
|
+
count: updatedOracles.length,
|
|
6150
|
+
block_number: latestBlockNumberChain,
|
|
6151
|
+
...buildIndexedEventTelemetry({
|
|
6152
|
+
...indexedEventHeadContext,
|
|
6153
|
+
logsIngestedCount: 0,
|
|
6154
|
+
logsIndexedCount: 0
|
|
6155
|
+
})
|
|
6156
|
+
});
|
|
5598
6157
|
try {
|
|
5599
6158
|
await dbTx.blocks.advanceCollector({
|
|
5600
6159
|
collectorName: collector,
|
|
@@ -5610,10 +6169,9 @@ async function* collectPrices(parameters) {
|
|
|
5610
6169
|
} catch (err) {
|
|
5611
6170
|
if (err instanceof ReorgError) {
|
|
5612
6171
|
logger.info({
|
|
6172
|
+
event: INDEXER_COLLECTOR_REORG_DETECTED,
|
|
5613
6173
|
msg: "Reorg detected, prices update aborted",
|
|
5614
|
-
collector,
|
|
5615
6174
|
count: updatedOracles.length,
|
|
5616
|
-
chain_id: client.chain.id,
|
|
5617
6175
|
block_number: latestBlockNumberChain
|
|
5618
6176
|
});
|
|
5619
6177
|
reorgDetected = true;
|
|
@@ -5638,11 +6196,11 @@ async function* collectPrices(parameters) {
|
|
|
5638
6196
|
});
|
|
5639
6197
|
} catch (err) {
|
|
5640
6198
|
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6199
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5641
6200
|
logger.error({
|
|
5642
|
-
|
|
5643
|
-
chainId: client.chain.id,
|
|
6201
|
+
event: INDEXER_COLLECTOR_REORG_COMPENSATION_FAILED,
|
|
5644
6202
|
msg,
|
|
5645
|
-
err
|
|
6203
|
+
err: error
|
|
5646
6204
|
});
|
|
5647
6205
|
throw new Error(msg);
|
|
5648
6206
|
}
|
|
@@ -5654,7 +6212,7 @@ async function* collectPrices(parameters) {
|
|
|
5654
6212
|
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
5655
6213
|
function createBuilder(parameters) {
|
|
5656
6214
|
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
|
|
5657
|
-
const createCollector = (name, collect) => create$
|
|
6215
|
+
const createCollector = (name, collect) => create$21({
|
|
5658
6216
|
name,
|
|
5659
6217
|
collect,
|
|
5660
6218
|
client,
|
|
@@ -5665,7 +6223,7 @@ function createBuilder(parameters) {
|
|
|
5665
6223
|
}
|
|
5666
6224
|
});
|
|
5667
6225
|
return {
|
|
5668
|
-
buildOffersCollector: ({ options: { maxBatchSize =
|
|
6226
|
+
buildOffersCollector: ({ options: { maxBatchSize = 1e4 } = {} }) => {
|
|
5669
6227
|
return createCollector("offers", (p) => collectOffersV2({
|
|
5670
6228
|
...p,
|
|
5671
6229
|
gatekeeper,
|
|
@@ -5677,7 +6235,7 @@ function createBuilder(parameters) {
|
|
|
5677
6235
|
}
|
|
5678
6236
|
}));
|
|
5679
6237
|
},
|
|
5680
|
-
buildMorphoV2Collector: ({ options: { maxBatchSize =
|
|
6238
|
+
buildMorphoV2Collector: ({ options: { maxBatchSize = 1e4 } = {} } = {}) => {
|
|
5681
6239
|
return createCollector("morpho_v2", (p) => collectMorphoV2({
|
|
5682
6240
|
...p,
|
|
5683
6241
|
collector: "morpho_v2",
|
|
@@ -5687,7 +6245,7 @@ function createBuilder(parameters) {
|
|
|
5687
6245
|
}
|
|
5688
6246
|
}));
|
|
5689
6247
|
},
|
|
5690
|
-
buildPricesCollector: ({ options: { maxBatchSize =
|
|
6248
|
+
buildPricesCollector: ({ options: { maxBatchSize = 1e4, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
5691
6249
|
return createCollector("prices", (p) => collectPrices({
|
|
5692
6250
|
...p,
|
|
5693
6251
|
collector: "prices",
|
|
@@ -5698,7 +6256,7 @@ function createBuilder(parameters) {
|
|
|
5698
6256
|
}
|
|
5699
6257
|
}));
|
|
5700
6258
|
},
|
|
5701
|
-
buildPositionsCollector: ({ options: { maxBatchSize =
|
|
6259
|
+
buildPositionsCollector: ({ options: { maxBatchSize = 1e4, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
5702
6260
|
return createCollector("positions", (p) => collectPositions({
|
|
5703
6261
|
...p,
|
|
5704
6262
|
collector: "positions",
|
|
@@ -5746,11 +6304,11 @@ const from$7 = (parameters) => {
|
|
|
5746
6304
|
//#endregion
|
|
5747
6305
|
//#region src/indexer/Indexer.ts
|
|
5748
6306
|
var Indexer_exports = /* @__PURE__ */ __exportAll({
|
|
5749
|
-
create: () => create$
|
|
6307
|
+
create: () => create$19,
|
|
5750
6308
|
from: () => from$6
|
|
5751
6309
|
});
|
|
5752
6310
|
function from$6(config) {
|
|
5753
|
-
const { client, gatekeeper, db, interval = 1e4, maxBatchSize =
|
|
6311
|
+
const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e4, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
|
|
5754
6312
|
const { offersCollector, morphoV2Collector, positionsCollector, pricesCollector } = from$7({
|
|
5755
6313
|
client,
|
|
5756
6314
|
db,
|
|
@@ -5762,7 +6320,7 @@ function from$6(config) {
|
|
|
5762
6320
|
retryAttempts,
|
|
5763
6321
|
retryDelayMs
|
|
5764
6322
|
});
|
|
5765
|
-
return create$
|
|
6323
|
+
return create$19({
|
|
5766
6324
|
client,
|
|
5767
6325
|
collectors: [
|
|
5768
6326
|
offersCollector,
|
|
@@ -5772,7 +6330,7 @@ function from$6(config) {
|
|
|
5772
6330
|
]
|
|
5773
6331
|
});
|
|
5774
6332
|
}
|
|
5775
|
-
function create$
|
|
6333
|
+
function create$19(params) {
|
|
5776
6334
|
const { collectors, client } = params;
|
|
5777
6335
|
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
5778
6336
|
const tracer = getTracer(`router.${indexerId}`);
|
|
@@ -5801,12 +6359,12 @@ function create$18(params) {
|
|
|
5801
6359
|
|
|
5802
6360
|
//#endregion
|
|
5803
6361
|
//#region src/api/Health.ts
|
|
5804
|
-
var Health_exports = /* @__PURE__ */ __exportAll({ create: () => create$
|
|
6362
|
+
var Health_exports = /* @__PURE__ */ __exportAll({ create: () => create$18 });
|
|
5805
6363
|
const DEFAULT_MAX_ALLOWED_LAG = 5;
|
|
5806
6364
|
/**
|
|
5807
6365
|
* Create a health service that exposes collector and chain block numbers.
|
|
5808
6366
|
*/
|
|
5809
|
-
function create$
|
|
6367
|
+
function create$18(parameters) {
|
|
5810
6368
|
const { db, maxAllowedLag = DEFAULT_MAX_ALLOWED_LAG, healthClients, chainRegistry } = parameters;
|
|
5811
6369
|
const loadSnapshot = async () => {
|
|
5812
6370
|
const [collectorRows, chainRows, remoteBlockByChainId] = await Promise.all([
|
|
@@ -5821,8 +6379,8 @@ function create$17(parameters) {
|
|
|
5821
6379
|
updatedAt: chain.updatedAt
|
|
5822
6380
|
});
|
|
5823
6381
|
const configuredChainIds = chainRegistry?.list().map((chain) => chain.id) ?? [];
|
|
5824
|
-
const
|
|
5825
|
-
for (const chain of chainRows)
|
|
6382
|
+
const observedChainIds = /* @__PURE__ */ new Set();
|
|
6383
|
+
for (const chain of chainRows) observedChainIds.add(chain.chainId);
|
|
5826
6384
|
const collectorKey = (chainId, name) => `${chainId}:${name}`;
|
|
5827
6385
|
const collectorsByKey = /* @__PURE__ */ new Map();
|
|
5828
6386
|
for (const row of collectorRows) collectorsByKey.set(collectorKey(row.chainId, row.collectorName), {
|
|
@@ -5831,15 +6389,15 @@ function create$17(parameters) {
|
|
|
5831
6389
|
blockNumber: row.blockNumber,
|
|
5832
6390
|
updatedAt: row.updatedAt
|
|
5833
6391
|
});
|
|
5834
|
-
for (const row of collectorRows)
|
|
5835
|
-
const
|
|
5836
|
-
const missingChains =
|
|
5837
|
-
const missingCollectors =
|
|
6392
|
+
for (const row of collectorRows) observedChainIds.add(row.chainId);
|
|
6393
|
+
const scopedChainIds = configuredChainIds.length > 0 ? configuredChainIds : Array.from(observedChainIds);
|
|
6394
|
+
const missingChains = scopedChainIds.filter((chainId) => !chainById.has(chainId)).sort((a, b) => a - b > 0 ? 1 : -1);
|
|
6395
|
+
const missingCollectors = scopedChainIds.flatMap((chainId) => [...names].sort().filter((name) => !collectorsByKey.has(collectorKey(chainId, name))).map((name) => ({
|
|
5838
6396
|
chainId,
|
|
5839
6397
|
name
|
|
5840
6398
|
}))).sort((a, b) => a.chainId === b.chainId ? a.name.localeCompare(b.name) : a.chainId - b.chainId);
|
|
5841
|
-
const initialized =
|
|
5842
|
-
const collectors = Array.from(
|
|
6399
|
+
const initialized = scopedChainIds.length > 0 && missingChains.length === 0 && missingCollectors.length === 0;
|
|
6400
|
+
const collectors = Array.from(scopedChainIds).sort((a, b) => a - b > 0 ? 1 : -1).flatMap((chainId) => [...names].sort().map((name) => {
|
|
5843
6401
|
const row = collectorsByKey.get(collectorKey(chainId, name));
|
|
5844
6402
|
const chain = chainById.get(chainId);
|
|
5845
6403
|
const blockNumber = row?.blockNumber ?? null;
|
|
@@ -5858,7 +6416,7 @@ function create$17(parameters) {
|
|
|
5858
6416
|
initialized: row !== void 0
|
|
5859
6417
|
};
|
|
5860
6418
|
}));
|
|
5861
|
-
const chains = Array.from(
|
|
6419
|
+
const chains = Array.from(scopedChainIds).sort((a, b) => a - b > 0 ? 1 : -1).map((chainId) => {
|
|
5862
6420
|
const chain = chainById.get(chainId);
|
|
5863
6421
|
return {
|
|
5864
6422
|
chainId,
|
|
@@ -5916,6 +6474,152 @@ async function getRemoteBlockNumbers(healthClients) {
|
|
|
5916
6474
|
return new Map(results.map((r) => [r.chainId, r.remoteBlock]));
|
|
5917
6475
|
}
|
|
5918
6476
|
|
|
6477
|
+
//#endregion
|
|
6478
|
+
//#region src/observability/Hono.ts
|
|
6479
|
+
const requestIdContextKey = "request_id";
|
|
6480
|
+
const requestStartedAtContextKey = "request_started_at";
|
|
6481
|
+
const unmatchedRouteLabel = "/*";
|
|
6482
|
+
/**
|
|
6483
|
+
* Install shared request tracing and structured logging on a Hono app.
|
|
6484
|
+
* @param app - Hono app to instrument.
|
|
6485
|
+
* @param parameters - Observability configuration.
|
|
6486
|
+
*/
|
|
6487
|
+
function init(app, parameters) {
|
|
6488
|
+
const requestIdHeader = (parameters.requestIdHeader ?? "x-request-id").toLowerCase();
|
|
6489
|
+
app.use("*", createRequestObservabilityMiddleware({
|
|
6490
|
+
component: parameters.component,
|
|
6491
|
+
tracerName: parameters.tracerName,
|
|
6492
|
+
requestIdHeader
|
|
6493
|
+
}));
|
|
6494
|
+
app.onError(createObservabilityErrorHandler({
|
|
6495
|
+
component: parameters.component,
|
|
6496
|
+
toFailurePayload: parameters.toFailurePayload,
|
|
6497
|
+
adaptFailurePayload: parameters.adaptFailurePayload,
|
|
6498
|
+
requestIdHeader
|
|
6499
|
+
}));
|
|
6500
|
+
}
|
|
6501
|
+
/**
|
|
6502
|
+
* Build a middleware that emits one completion event per request and enriches span attributes.
|
|
6503
|
+
* @param parameters - Request logging configuration.
|
|
6504
|
+
* @returns Hono middleware.
|
|
6505
|
+
*/
|
|
6506
|
+
function createRequestObservabilityMiddleware(parameters) {
|
|
6507
|
+
const tracer = getTracer(parameters.tracerName);
|
|
6508
|
+
return async (c, next) => {
|
|
6509
|
+
const method = c.req.method;
|
|
6510
|
+
const path = c.req.path;
|
|
6511
|
+
const requestId = resolveRequestId(c.req.header(parameters.requestIdHeader));
|
|
6512
|
+
const startedAt = Date.now();
|
|
6513
|
+
c.set(requestIdContextKey, requestId);
|
|
6514
|
+
c.set(requestStartedAtContextKey, startedAt);
|
|
6515
|
+
return startActiveSpan(tracer, `${method} ${getRouteLabel(c.req.routePath)}`, async (span) => {
|
|
6516
|
+
const spanContext = span.spanContext();
|
|
6517
|
+
const traceIdentifiers = getTraceIdentifiersFromContext(spanContext.traceId, spanContext.spanId);
|
|
6518
|
+
span.setAttribute("http.method", method);
|
|
6519
|
+
span.setAttribute("http.target", path);
|
|
6520
|
+
span.setAttribute("http.request_id", requestId);
|
|
6521
|
+
await runWithLogContext({
|
|
6522
|
+
component: parameters.component,
|
|
6523
|
+
request_id: requestId,
|
|
6524
|
+
...traceIdentifiers,
|
|
6525
|
+
method
|
|
6526
|
+
}, async () => {
|
|
6527
|
+
await next();
|
|
6528
|
+
const statusCode = c.res.status;
|
|
6529
|
+
const durationMs = Math.max(Date.now() - startedAt, 0);
|
|
6530
|
+
const routeLabel = getRouteLabel(c.req.routePath);
|
|
6531
|
+
span.updateName(`${method} ${routeLabel}`);
|
|
6532
|
+
span.setAttribute("http.route", routeLabel);
|
|
6533
|
+
span.setAttribute("http.status_code", statusCode);
|
|
6534
|
+
if (statusCode >= 500) span.setStatus({ code: SpanStatusCode.ERROR });
|
|
6535
|
+
c.header(parameters.requestIdHeader, requestId);
|
|
6536
|
+
getLogger().info({
|
|
6537
|
+
event: HTTP_REQUEST_COMPLETED,
|
|
6538
|
+
msg: "HTTP request completed",
|
|
6539
|
+
status_code: statusCode,
|
|
6540
|
+
duration_ms: durationMs,
|
|
6541
|
+
route: routeLabel
|
|
6542
|
+
});
|
|
6543
|
+
});
|
|
6544
|
+
});
|
|
6545
|
+
};
|
|
6546
|
+
}
|
|
6547
|
+
/**
|
|
6548
|
+
* Build a shared unhandled error handler that logs structured failures and serializes payload errors.
|
|
6549
|
+
* @param parameters - Error logging configuration.
|
|
6550
|
+
* @returns Hono error handler.
|
|
6551
|
+
*/
|
|
6552
|
+
function createObservabilityErrorHandler(parameters) {
|
|
6553
|
+
return (err, c) => {
|
|
6554
|
+
const baseFailure = parameters.toFailurePayload(err);
|
|
6555
|
+
const requestId = resolveRequestId(readContextString(c.get(requestIdContextKey)) ?? c.req.header(parameters.requestIdHeader));
|
|
6556
|
+
const routeTemplate = getRouteLabel(c.req.routePath);
|
|
6557
|
+
const adaptedFailure = parameters.adaptFailurePayload?.({
|
|
6558
|
+
err,
|
|
6559
|
+
failure: baseFailure,
|
|
6560
|
+
method: c.req.method,
|
|
6561
|
+
route: routeTemplate
|
|
6562
|
+
});
|
|
6563
|
+
const responseStatusCode = adaptedFailure?.statusCode ?? baseFailure.statusCode;
|
|
6564
|
+
const startedAt = readContextNumber(c.get(requestStartedAtContextKey)) ?? Date.now();
|
|
6565
|
+
const durationMs = Math.max(Date.now() - startedAt, 0);
|
|
6566
|
+
const activeSpan = trace.getActiveSpan();
|
|
6567
|
+
if (activeSpan) {
|
|
6568
|
+
activeSpan.recordException(err);
|
|
6569
|
+
if (responseStatusCode >= 500) activeSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
6570
|
+
else activeSpan.setStatus({ code: SpanStatusCode.UNSET });
|
|
6571
|
+
activeSpan.setAttribute("http.route", routeTemplate);
|
|
6572
|
+
activeSpan.setAttribute("http.status_code", responseStatusCode);
|
|
6573
|
+
activeSpan.setAttribute("http.request_id", requestId);
|
|
6574
|
+
}
|
|
6575
|
+
const traceIdentifiers = getActiveTraceIdentifiers();
|
|
6576
|
+
getLogger().error({
|
|
6577
|
+
event: HTTP_REQUEST_FAILED,
|
|
6578
|
+
msg: "HTTP request failed",
|
|
6579
|
+
err,
|
|
6580
|
+
component: parameters.component,
|
|
6581
|
+
method: c.req.method,
|
|
6582
|
+
route: routeTemplate,
|
|
6583
|
+
request_id: requestId,
|
|
6584
|
+
status_code: responseStatusCode,
|
|
6585
|
+
duration_ms: durationMs,
|
|
6586
|
+
...traceIdentifiers
|
|
6587
|
+
});
|
|
6588
|
+
c.header(parameters.requestIdHeader, requestId);
|
|
6589
|
+
if (adaptedFailure) return c.text(adaptedFailure.body, adaptedFailure.statusCode, {
|
|
6590
|
+
"Content-Type": adaptedFailure.contentType,
|
|
6591
|
+
...adaptedFailure.headers
|
|
6592
|
+
});
|
|
6593
|
+
return c.json(baseFailure.body, baseFailure.statusCode);
|
|
6594
|
+
};
|
|
6595
|
+
}
|
|
6596
|
+
/**
|
|
6597
|
+
* Convert a Hono route path into a stable request route label.
|
|
6598
|
+
* @param routePath - Hono route path from the request.
|
|
6599
|
+
* @returns Route label for logs and trace attributes.
|
|
6600
|
+
*/
|
|
6601
|
+
function getRouteLabel(routePath) {
|
|
6602
|
+
const candidate = routePath?.trim();
|
|
6603
|
+
if (candidate && candidate.length > 0) return candidate;
|
|
6604
|
+
return unmatchedRouteLabel;
|
|
6605
|
+
}
|
|
6606
|
+
/**
|
|
6607
|
+
* Resolve a request ID from incoming headers and generate one when missing/invalid.
|
|
6608
|
+
* @param value - Incoming header value.
|
|
6609
|
+
* @returns Stable request ID value.
|
|
6610
|
+
*/
|
|
6611
|
+
function resolveRequestId(value) {
|
|
6612
|
+
const candidate = value?.trim();
|
|
6613
|
+
if (candidate && candidate.length <= 128) return candidate;
|
|
6614
|
+
return randomUUID();
|
|
6615
|
+
}
|
|
6616
|
+
function readContextString(value) {
|
|
6617
|
+
return typeof value === "string" ? value : void 0;
|
|
6618
|
+
}
|
|
6619
|
+
function readContextNumber(value) {
|
|
6620
|
+
return typeof value === "number" ? value : void 0;
|
|
6621
|
+
}
|
|
6622
|
+
|
|
5919
6623
|
//#endregion
|
|
5920
6624
|
//#region src/api/Schema/BookResponse.ts
|
|
5921
6625
|
var BookResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$5 });
|
|
@@ -6202,7 +6906,7 @@ const offerExample = {
|
|
|
6202
6906
|
receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
|
|
6203
6907
|
},
|
|
6204
6908
|
offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
|
|
6205
|
-
obligation_id: "
|
|
6909
|
+
obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf79",
|
|
6206
6910
|
chain_id: 1,
|
|
6207
6911
|
consumed: "0",
|
|
6208
6912
|
takeable: "369216000000000000000000",
|
|
@@ -6774,7 +7478,7 @@ const positionExample = {
|
|
|
6774
7478
|
chain_id: 1,
|
|
6775
7479
|
contract: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
|
|
6776
7480
|
user: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
|
|
6777
|
-
obligation_id: "
|
|
7481
|
+
obligation_id: "0x12590ae1aee324a005be565f3bcdd16dbf8daf79",
|
|
6778
7482
|
reserved: "200000000000000000000",
|
|
6779
7483
|
block_number: 21345678
|
|
6780
7484
|
};
|
|
@@ -7459,11 +8163,14 @@ function isValidBase64urlJson(val) {
|
|
|
7459
8163
|
function isValidOfferHashCursor(val) {
|
|
7460
8164
|
return /^0x[a-f0-9]{64}$/i.test(val);
|
|
7461
8165
|
}
|
|
8166
|
+
function isValidObligationIdCursor(val) {
|
|
8167
|
+
return /^0x[a-f0-9]{40}$/i.test(val);
|
|
8168
|
+
}
|
|
7462
8169
|
function isValidOfferCursor(val) {
|
|
7463
8170
|
const [hash, obligationId, ...rest] = val.split(":");
|
|
7464
8171
|
if (rest.length !== 0) return false;
|
|
7465
8172
|
if (!hash || !obligationId) return false;
|
|
7466
|
-
return isValidOfferHashCursor(hash) &&
|
|
8173
|
+
return isValidOfferHashCursor(hash) && isValidObligationIdCursor(obligationId);
|
|
7467
8174
|
}
|
|
7468
8175
|
const csvArray = (schema) => z$1.preprocess((value) => {
|
|
7469
8176
|
if (value === void 0) return void 0;
|
|
@@ -7492,10 +8199,14 @@ const ConfigRuleTypes = z$1.enum([
|
|
|
7492
8199
|
"callback",
|
|
7493
8200
|
"loan_token",
|
|
7494
8201
|
"collateral_token",
|
|
7495
|
-
"oracle"
|
|
8202
|
+
"oracle",
|
|
8203
|
+
"group_consistency",
|
|
8204
|
+
"group_immutability",
|
|
8205
|
+
"max_collaterals",
|
|
8206
|
+
"min_duration"
|
|
7496
8207
|
]);
|
|
7497
8208
|
const GetConfigRulesQueryParams = z$1.object({
|
|
7498
|
-
cursor: z$1.string().regex(/^(maturity|callback|loan_token|collateral_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
|
|
8209
|
+
cursor: z$1.string().regex(/^(maturity|callback|loan_token|collateral_token|oracle|group_consistency|group_immutability|max_collaterals|min_duration):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
|
|
7499
8210
|
description: "Pagination cursor in type:chain_id:<value> format",
|
|
7500
8211
|
example: "maturity:1:1730415600:end_of_next_month"
|
|
7501
8212
|
}),
|
|
@@ -7535,9 +8246,9 @@ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend
|
|
|
7535
8246
|
description: "Side of the offer. Required when using obligation_id.",
|
|
7536
8247
|
example: "buy"
|
|
7537
8248
|
}),
|
|
7538
|
-
obligation_id: z$1.string().regex(/^0x[a-fA-F0-9]{
|
|
8249
|
+
obligation_id: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Obligation id must be a valid 20-byte hex string" }).transform((val) => val.toLowerCase()).optional().meta({
|
|
7539
8250
|
description: "Offers obligation id. Required when not using maker.",
|
|
7540
|
-
example: "
|
|
8251
|
+
example: "0x1234567890123456789012345678901234567890"
|
|
7541
8252
|
}),
|
|
7542
8253
|
maker: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Maker must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).optional().meta({
|
|
7543
8254
|
description: "Maker address to filter offers by. Alternative to obligation_id + side.",
|
|
@@ -7622,9 +8333,9 @@ const GetObligationsQueryParams = z$1.object({
|
|
|
7622
8333
|
example: "-ask,bid,maturity"
|
|
7623
8334
|
})
|
|
7624
8335
|
});
|
|
7625
|
-
const GetObligationParams = z$1.object({ obligation_id: z$1.string({ error: "Obligation id is required and must be a valid
|
|
8336
|
+
const GetObligationParams = z$1.object({ obligation_id: z$1.string({ error: "Obligation id is required and must be a valid 20-byte hex string" }).regex(/^0x[a-fA-F0-9]{40}$/, { error: "Obligation id must be a valid 20-byte hex string" }).transform((val) => val.toLowerCase()).meta({
|
|
7626
8337
|
description: "Obligation id",
|
|
7627
|
-
example: "
|
|
8338
|
+
example: "0x1234567890123456789012345678901234567890"
|
|
7628
8339
|
}) });
|
|
7629
8340
|
/** Validate a book cursor format: {side, lastTick, offersCursor} */
|
|
7630
8341
|
function isValidBookCursor(cursorString) {
|
|
@@ -7659,9 +8370,9 @@ const HealthQueryParams = z$1.object({ strict: z$1.enum([
|
|
|
7659
8370
|
}) });
|
|
7660
8371
|
const GetBookParams = z$1.object({
|
|
7661
8372
|
...BookPaginationQueryParams.shape,
|
|
7662
|
-
obligation_id: z$1.string({ error: "Obligation id is required and must be a valid
|
|
8373
|
+
obligation_id: z$1.string({ error: "Obligation id is required and must be a valid 20-byte hex string" }).regex(/^0x[a-fA-F0-9]{40}$/, { error: "Obligation id must be a valid 20-byte hex string" }).transform((val) => val.toLowerCase()).meta({
|
|
7663
8374
|
description: "Obligation id",
|
|
7664
|
-
example: "
|
|
8375
|
+
example: "0x1234567890123456789012345678901234567890"
|
|
7665
8376
|
}),
|
|
7666
8377
|
side: z$1.enum(["buy", "sell"]).meta({
|
|
7667
8378
|
description: "Side of the book (buy or sell).",
|
|
@@ -7745,9 +8456,11 @@ async function getBook(params, db) {
|
|
|
7745
8456
|
} catch (err) {
|
|
7746
8457
|
logger.error({
|
|
7747
8458
|
err,
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
8459
|
+
event: API_GET_BOOK_FAILED,
|
|
8460
|
+
msg: "Failed to get book",
|
|
8461
|
+
endpoint: "get_book",
|
|
8462
|
+
obligation_id: query.obligation_id,
|
|
8463
|
+
side: query.side
|
|
7751
8464
|
});
|
|
7752
8465
|
return failure(err);
|
|
7753
8466
|
}
|
|
@@ -7935,6 +8648,7 @@ const oracles = {
|
|
|
7935
8648
|
],
|
|
7936
8649
|
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
7937
8650
|
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
8651
|
+
"0xEeee770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
7938
8652
|
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
7939
8653
|
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
7940
8654
|
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
@@ -7943,6 +8657,7 @@ const oracles = {
|
|
|
7943
8657
|
],
|
|
7944
8658
|
[ChainId.ANVIL.toString()]: [
|
|
7945
8659
|
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
8660
|
+
"0xEeee770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
7946
8661
|
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
7947
8662
|
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
7948
8663
|
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
@@ -7953,19 +8668,23 @@ const oracles = {
|
|
|
7953
8668
|
const configs = {
|
|
7954
8669
|
ethereum: {
|
|
7955
8670
|
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
7956
|
-
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
|
|
8671
|
+
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek],
|
|
8672
|
+
minDuration: 10
|
|
7957
8673
|
},
|
|
7958
8674
|
base: {
|
|
7959
8675
|
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
7960
|
-
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
|
|
8676
|
+
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek],
|
|
8677
|
+
minDuration: 10
|
|
7961
8678
|
},
|
|
7962
8679
|
"ethereum-virtual-testnet": {
|
|
7963
8680
|
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
7964
|
-
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
|
|
8681
|
+
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek],
|
|
8682
|
+
minDuration: 10
|
|
7965
8683
|
},
|
|
7966
8684
|
anvil: {
|
|
7967
8685
|
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
7968
|
-
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
|
|
8686
|
+
maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek],
|
|
8687
|
+
minDuration: 10
|
|
7969
8688
|
}
|
|
7970
8689
|
};
|
|
7971
8690
|
|
|
@@ -7979,7 +8698,8 @@ const configs = {
|
|
|
7979
8698
|
function buildConfigRules(chains) {
|
|
7980
8699
|
const rules = [];
|
|
7981
8700
|
for (const chain of chains) {
|
|
7982
|
-
const
|
|
8701
|
+
const config = configs[chain.name];
|
|
8702
|
+
const maturities = config.maturities ?? [];
|
|
7983
8703
|
for (const maturityName of maturities) rules.push({
|
|
7984
8704
|
type: "maturity",
|
|
7985
8705
|
chain_id: chain.id,
|
|
@@ -8004,6 +8724,26 @@ function buildConfigRules(chains) {
|
|
|
8004
8724
|
chain_id: chain.id,
|
|
8005
8725
|
address: normalizeAddress(address)
|
|
8006
8726
|
});
|
|
8727
|
+
rules.push({
|
|
8728
|
+
type: "group_consistency",
|
|
8729
|
+
chain_id: chain.id,
|
|
8730
|
+
description: "All offers in a group must have the same loan token, assets amount, and side (buy/sell)"
|
|
8731
|
+
});
|
|
8732
|
+
rules.push({
|
|
8733
|
+
type: "group_immutability",
|
|
8734
|
+
chain_id: chain.id,
|
|
8735
|
+
description: "Cannot add offers to a group after group creation"
|
|
8736
|
+
});
|
|
8737
|
+
rules.push({
|
|
8738
|
+
type: "max_collaterals",
|
|
8739
|
+
chain_id: chain.id,
|
|
8740
|
+
max: 128
|
|
8741
|
+
});
|
|
8742
|
+
if (config.minDuration != null) rules.push({
|
|
8743
|
+
type: "min_duration",
|
|
8744
|
+
chain_id: chain.id,
|
|
8745
|
+
min_seconds: config.minDuration
|
|
8746
|
+
});
|
|
8007
8747
|
}
|
|
8008
8748
|
rules.sort(compareConfigRules);
|
|
8009
8749
|
return rules;
|
|
@@ -8033,6 +8773,22 @@ function buildConfigRulesChecksum(rules) {
|
|
|
8033
8773
|
hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
|
|
8034
8774
|
continue;
|
|
8035
8775
|
}
|
|
8776
|
+
if (rule.type === "group_consistency") {
|
|
8777
|
+
hash.update(`group_consistency:${rule.chain_id}\n`);
|
|
8778
|
+
continue;
|
|
8779
|
+
}
|
|
8780
|
+
if (rule.type === "group_immutability") {
|
|
8781
|
+
hash.update(`group_immutability:${rule.chain_id}\n`);
|
|
8782
|
+
continue;
|
|
8783
|
+
}
|
|
8784
|
+
if (rule.type === "max_collaterals") {
|
|
8785
|
+
hash.update(`max_collaterals:${rule.chain_id}:${rule.max}\n`);
|
|
8786
|
+
continue;
|
|
8787
|
+
}
|
|
8788
|
+
if (rule.type === "min_duration") {
|
|
8789
|
+
hash.update(`min_duration:${rule.chain_id}:${rule.min_seconds}\n`);
|
|
8790
|
+
continue;
|
|
8791
|
+
}
|
|
8036
8792
|
hash.update(`loan_token:${rule.chain_id}:${rule.address}\n`);
|
|
8037
8793
|
}
|
|
8038
8794
|
return hash.digest("hex");
|
|
@@ -8051,6 +8807,8 @@ function compareConfigRules(left, right) {
|
|
|
8051
8807
|
if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
|
|
8052
8808
|
if (left.type === "collateral_token" && right.type === "collateral_token") return left.address.localeCompare(right.address);
|
|
8053
8809
|
if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
|
|
8810
|
+
if (left.type === "max_collaterals" && right.type === "max_collaterals") return left.max - right.max;
|
|
8811
|
+
if (left.type === "min_duration" && right.type === "min_duration") return left.min_seconds - right.min_seconds;
|
|
8054
8812
|
return 0;
|
|
8055
8813
|
}
|
|
8056
8814
|
|
|
@@ -8097,6 +8855,10 @@ function formatCursor$2(rule) {
|
|
|
8097
8855
|
if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
|
|
8098
8856
|
if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
8099
8857
|
if (rule.type === "collateral_token") return `collateral_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
8858
|
+
if (rule.type === "group_consistency") return `group_consistency:${rule.chain_id}:_`;
|
|
8859
|
+
if (rule.type === "group_immutability") return `group_immutability:${rule.chain_id}:_`;
|
|
8860
|
+
if (rule.type === "max_collaterals") return `max_collaterals:${rule.chain_id}:${rule.max}`;
|
|
8861
|
+
if (rule.type === "min_duration") return `min_duration:${rule.chain_id}:${rule.min_seconds}`;
|
|
8100
8862
|
return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
8101
8863
|
}
|
|
8102
8864
|
function parseCursor$2(cursor) {
|
|
@@ -8138,6 +8900,34 @@ function parseCursor$2(cursor) {
|
|
|
8138
8900
|
address: parseAddress(addressValue, "Cursor address")
|
|
8139
8901
|
};
|
|
8140
8902
|
}
|
|
8903
|
+
if (type === "group_consistency") return {
|
|
8904
|
+
type,
|
|
8905
|
+
chain_id,
|
|
8906
|
+
description: "All offers in a group must have the same loan token, assets amount, and side (buy/sell)"
|
|
8907
|
+
};
|
|
8908
|
+
if (type === "group_immutability") return {
|
|
8909
|
+
type,
|
|
8910
|
+
chain_id,
|
|
8911
|
+
description: "Cannot add offers to a group after group creation"
|
|
8912
|
+
};
|
|
8913
|
+
if (type === "max_collaterals") {
|
|
8914
|
+
const maxValue = Number.parseInt(rest[0] ?? "", 10);
|
|
8915
|
+
if (!Number.isFinite(maxValue) || maxValue < 0) throw new BadRequestError$1("Cursor must be in the format max_collaterals:chain_id:max");
|
|
8916
|
+
return {
|
|
8917
|
+
type,
|
|
8918
|
+
chain_id,
|
|
8919
|
+
max: maxValue
|
|
8920
|
+
};
|
|
8921
|
+
}
|
|
8922
|
+
if (type === "min_duration") {
|
|
8923
|
+
const minSecondsValue = Number.parseInt(rest[0] ?? "", 10);
|
|
8924
|
+
if (!Number.isFinite(minSecondsValue) || minSecondsValue < 0) throw new BadRequestError$1("Cursor must be in the format min_duration:chain_id:min_seconds");
|
|
8925
|
+
return {
|
|
8926
|
+
type,
|
|
8927
|
+
chain_id,
|
|
8928
|
+
min_seconds: minSecondsValue
|
|
8929
|
+
};
|
|
8930
|
+
}
|
|
8141
8931
|
throw new BadRequestError$1("Cursor has an invalid rule type");
|
|
8142
8932
|
}
|
|
8143
8933
|
function findStartIndex(rules, cursor) {
|
|
@@ -8156,7 +8946,7 @@ function parseAddress(address, label) {
|
|
|
8156
8946
|
return address.toLowerCase();
|
|
8157
8947
|
}
|
|
8158
8948
|
function isConfigRuleType(value) {
|
|
8159
|
-
return value === "maturity" || value === "callback" || value === "loan_token" || value === "collateral_token" || value === "oracle";
|
|
8949
|
+
return value === "maturity" || value === "callback" || value === "loan_token" || value === "collateral_token" || value === "oracle" || value === "group_consistency" || value === "group_immutability" || value === "max_collaterals" || value === "min_duration";
|
|
8160
8950
|
}
|
|
8161
8951
|
function isMaturityType(value) {
|
|
8162
8952
|
return Object.values(MaturityType).includes(value);
|
|
@@ -8261,7 +9051,7 @@ async function getHealth(query, db, chainRegistry) {
|
|
|
8261
9051
|
try {
|
|
8262
9052
|
const parsed = safeParse("get_health", query);
|
|
8263
9053
|
if (!parsed.success) return failure(parsed.error);
|
|
8264
|
-
const snapshot = await create$
|
|
9054
|
+
const snapshot = await create$18({
|
|
8265
9055
|
db,
|
|
8266
9056
|
chainRegistry
|
|
8267
9057
|
}).getSnapshot();
|
|
@@ -8278,9 +9068,9 @@ async function getHealth(query, db, chainRegistry) {
|
|
|
8278
9068
|
} catch (err) {
|
|
8279
9069
|
logger.error({
|
|
8280
9070
|
err,
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
9071
|
+
event: API_GET_HEALTH_FAILED,
|
|
9072
|
+
msg: "Failed to get health status",
|
|
9073
|
+
endpoint: "get_health"
|
|
8284
9074
|
});
|
|
8285
9075
|
return failure(err);
|
|
8286
9076
|
}
|
|
@@ -8290,7 +9080,7 @@ async function getHealthChains(query, db, healthClients, chainRegistry) {
|
|
|
8290
9080
|
try {
|
|
8291
9081
|
const parsed = safeParse("get_health_chains", query);
|
|
8292
9082
|
if (!parsed.success) return failure(parsed.error);
|
|
8293
|
-
const snapshot = await create$
|
|
9083
|
+
const snapshot = await create$18({
|
|
8294
9084
|
db,
|
|
8295
9085
|
healthClients,
|
|
8296
9086
|
chainRegistry
|
|
@@ -8310,9 +9100,9 @@ async function getHealthChains(query, db, healthClients, chainRegistry) {
|
|
|
8310
9100
|
} catch (err) {
|
|
8311
9101
|
logger.error({
|
|
8312
9102
|
err,
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
9103
|
+
event: API_GET_HEALTH_CHAINS_FAILED,
|
|
9104
|
+
msg: "Failed to get health status for chains",
|
|
9105
|
+
endpoint: "get_health_chains"
|
|
8316
9106
|
});
|
|
8317
9107
|
return failure(err);
|
|
8318
9108
|
}
|
|
@@ -8322,7 +9112,7 @@ async function getHealthCollectors(query, db, chainRegistry) {
|
|
|
8322
9112
|
try {
|
|
8323
9113
|
const parsed = safeParse("get_health_collectors", query);
|
|
8324
9114
|
if (!parsed.success) return failure(parsed.error);
|
|
8325
|
-
const snapshot = await create$
|
|
9115
|
+
const snapshot = await create$18({
|
|
8326
9116
|
db,
|
|
8327
9117
|
chainRegistry
|
|
8328
9118
|
}).getSnapshot();
|
|
@@ -8343,14 +9133,99 @@ async function getHealthCollectors(query, db, chainRegistry) {
|
|
|
8343
9133
|
} catch (err) {
|
|
8344
9134
|
logger.error({
|
|
8345
9135
|
err,
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
9136
|
+
event: API_GET_HEALTH_COLLECTORS_FAILED,
|
|
9137
|
+
msg: "Failed to get health status for collectors",
|
|
9138
|
+
endpoint: "get_health_collectors"
|
|
8349
9139
|
});
|
|
8350
9140
|
return failure(err);
|
|
8351
9141
|
}
|
|
8352
9142
|
}
|
|
8353
9143
|
|
|
9144
|
+
//#endregion
|
|
9145
|
+
//#region src/api/Metrics.ts
|
|
9146
|
+
const COLLECTOR_LAG_BLOCKS_METRIC = "router_collector_lag_blocks";
|
|
9147
|
+
const COLLECTOR_BLOCK_NUMBER_METRIC = "router_collector_block_number";
|
|
9148
|
+
const CHAIN_BLOCK_NUMBER_METRIC = "router_chain_block_number";
|
|
9149
|
+
/**
|
|
9150
|
+
* Create a metrics service that renders collector and chain metrics in Prometheus format.
|
|
9151
|
+
* @param parameters - Service dependencies. {@link MetricsServiceParameters}
|
|
9152
|
+
* @returns The metrics service. {@link MetricsService}
|
|
9153
|
+
*/
|
|
9154
|
+
function create$17(parameters) {
|
|
9155
|
+
const { db, chainRegistry } = parameters;
|
|
9156
|
+
const healthService = create$18({
|
|
9157
|
+
db,
|
|
9158
|
+
chainRegistry
|
|
9159
|
+
});
|
|
9160
|
+
return { async getPrometheusMetrics() {
|
|
9161
|
+
const snapshot = await healthService.getSnapshot();
|
|
9162
|
+
return renderPrometheusMetrics({
|
|
9163
|
+
collectors: snapshot.collectors,
|
|
9164
|
+
chains: snapshot.chains
|
|
9165
|
+
});
|
|
9166
|
+
} };
|
|
9167
|
+
}
|
|
9168
|
+
/**
|
|
9169
|
+
* Render Prometheus exposition text for collector and chain synchronization metrics.
|
|
9170
|
+
* @param parameters - Snapshot state to expose. {@link RenderPrometheusMetricsParameters}
|
|
9171
|
+
* @returns Prometheus metrics text.
|
|
9172
|
+
*/
|
|
9173
|
+
function renderPrometheusMetrics(parameters) {
|
|
9174
|
+
const lines = [
|
|
9175
|
+
`# HELP ${COLLECTOR_LAG_BLOCKS_METRIC} Collector lag in blocks behind the indexed chain head.`,
|
|
9176
|
+
`# TYPE ${COLLECTOR_LAG_BLOCKS_METRIC} gauge`,
|
|
9177
|
+
`# HELP ${COLLECTOR_BLOCK_NUMBER_METRIC} Latest indexed block number per collector.`,
|
|
9178
|
+
`# TYPE ${COLLECTOR_BLOCK_NUMBER_METRIC} gauge`,
|
|
9179
|
+
`# HELP ${CHAIN_BLOCK_NUMBER_METRIC} Latest indexed block number per chain.`,
|
|
9180
|
+
`# TYPE ${CHAIN_BLOCK_NUMBER_METRIC} gauge`
|
|
9181
|
+
];
|
|
9182
|
+
lines.push(`${COLLECTOR_LAG_BLOCKS_METRIC}{collector="__merge_alarm__",chain_id="0"} 9999`);
|
|
9183
|
+
const collectors = [...parameters.collectors].sort((left, right) => left.chainId === right.chainId ? left.name.localeCompare(right.name) : left.chainId - right.chainId);
|
|
9184
|
+
for (const collector of collectors) {
|
|
9185
|
+
const labels = renderCollectorLabels({
|
|
9186
|
+
collector: collector.name,
|
|
9187
|
+
chainId: collector.chainId
|
|
9188
|
+
});
|
|
9189
|
+
if (collector.initialized && collector.lag !== null) lines.push(`${COLLECTOR_LAG_BLOCKS_METRIC}${labels} ${renderGaugeValue(collector.lag)}`);
|
|
9190
|
+
if (collector.initialized && collector.blockNumber !== null) lines.push(`${COLLECTOR_BLOCK_NUMBER_METRIC}${labels} ${renderGaugeValue(collector.blockNumber)}`);
|
|
9191
|
+
}
|
|
9192
|
+
const chains = [...parameters.chains].sort((left, right) => left.chainId - right.chainId);
|
|
9193
|
+
for (const chain of chains) {
|
|
9194
|
+
if (!chain.initialized || chain.localBlockNumber === null) continue;
|
|
9195
|
+
const labels = renderChainLabels({ chainId: chain.chainId });
|
|
9196
|
+
lines.push(`${CHAIN_BLOCK_NUMBER_METRIC}${labels} ${renderGaugeValue(chain.localBlockNumber)}`);
|
|
9197
|
+
}
|
|
9198
|
+
return `${lines.join("\n")}\n`;
|
|
9199
|
+
}
|
|
9200
|
+
function renderCollectorLabels(parameters) {
|
|
9201
|
+
return `{collector="${escapeLabelValue(parameters.collector)}",chain_id="${parameters.chainId}"}`;
|
|
9202
|
+
}
|
|
9203
|
+
function renderChainLabels(parameters) {
|
|
9204
|
+
return `{chain_id="${parameters.chainId}"}`;
|
|
9205
|
+
}
|
|
9206
|
+
function renderGaugeValue(value) {
|
|
9207
|
+
if (!Number.isFinite(value)) throw new Error(`Metrics value must be finite, received ${value}`);
|
|
9208
|
+
return `${value}`;
|
|
9209
|
+
}
|
|
9210
|
+
function escapeLabelValue(value) {
|
|
9211
|
+
return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, "\\\"");
|
|
9212
|
+
}
|
|
9213
|
+
|
|
9214
|
+
//#endregion
|
|
9215
|
+
//#region src/api/Controllers/getMetrics.ts
|
|
9216
|
+
/**
|
|
9217
|
+
* Get router synchronization metrics in Prometheus exposition format.
|
|
9218
|
+
* @param db - Database instance. {@link Database.Database}
|
|
9219
|
+
* @param chainRegistry - Optional chain registry used to scope expected chains.
|
|
9220
|
+
* @returns Prometheus exposition payload.
|
|
9221
|
+
*/
|
|
9222
|
+
async function getMetrics(db, chainRegistry) {
|
|
9223
|
+
return await create$17({
|
|
9224
|
+
db,
|
|
9225
|
+
chainRegistry
|
|
9226
|
+
}).getPrometheusMetrics();
|
|
9227
|
+
}
|
|
9228
|
+
|
|
8354
9229
|
//#endregion
|
|
8355
9230
|
//#region src/database/readers/ObligationsListing.ts
|
|
8356
9231
|
const SORT_FIELDS = [
|
|
@@ -8359,7 +9234,7 @@ const SORT_FIELDS = [
|
|
|
8359
9234
|
"bid",
|
|
8360
9235
|
"maturity"
|
|
8361
9236
|
];
|
|
8362
|
-
const CURSOR_ID_REGEX = /^0x[a-f0-9]{
|
|
9237
|
+
const CURSOR_ID_REGEX = /^0x[a-f0-9]{40}$/i;
|
|
8363
9238
|
const INT32_MIN = -2147483648;
|
|
8364
9239
|
const INT32_MAX = 2147483647;
|
|
8365
9240
|
const INT32_MAX_BIGINT = BigInt(INT32_MAX);
|
|
@@ -8407,7 +9282,7 @@ function create$16(parameters) {
|
|
|
8407
9282
|
bidTick: sql`MAX(${bestBidTick.bidTick})`.as("bid_tick"),
|
|
8408
9283
|
ask: sql`COALESCE(MAX(${bestAskTick.askTick}) + 1, 0)`.as("ask"),
|
|
8409
9284
|
bid: sql`COALESCE(MAX(${bestBidTick.bidTick}) + 1, 0)`.as("bid")
|
|
8410
|
-
}).from(obligationIdKeys).innerJoin(obligations, eq(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(obligationCollateralsV2, eq(obligations.obligationKey, obligationCollateralsV2.obligationKey)).leftJoinLateral(bestAskTick, sql`true`).leftJoinLateral(bestBidTick, sql`true`).groupBy(obligationIdKeys.obligationId, obligationIdKeys.chainId, obligations.loanToken, obligations.maturity).where(and(ids !== void 0 && ids.length > 0 ? inArray(obligationIdKeys.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligationIdKeys.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) :
|
|
9285
|
+
}).from(obligationIdKeys).innerJoin(obligations, eq(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(obligationCollateralsV2, eq(obligations.obligationKey, obligationCollateralsV2.obligationKey)).leftJoinLateral(bestAskTick, sql`true`).leftJoinLateral(bestBidTick, sql`true`).groupBy(obligationIdKeys.obligationId, obligationIdKeys.chainId, obligations.loanToken, obligations.maturity).where(and(ids !== void 0 && ids.length > 0 ? inArray(obligationIdKeys.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligationIdKeys.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) : void 0, collateralFilter)).as("obligations_with_quotes");
|
|
8411
9286
|
const sortColumns = {
|
|
8412
9287
|
id: obligationsWithQuotes.obligationId,
|
|
8413
9288
|
ask: obligationsWithQuotes.ask,
|
|
@@ -8609,9 +9484,10 @@ async function getObligation(params, db) {
|
|
|
8609
9484
|
const payloadError = toPayloadError$1(err);
|
|
8610
9485
|
logger.error({
|
|
8611
9486
|
err: payloadError,
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
9487
|
+
event: API_GET_OBLIGATION_FAILED,
|
|
9488
|
+
msg: "Failed to get obligation",
|
|
9489
|
+
endpoint: "get_obligation",
|
|
9490
|
+
obligation_id: query.obligation_id
|
|
8615
9491
|
});
|
|
8616
9492
|
return failure(payloadError);
|
|
8617
9493
|
}
|
|
@@ -8650,28 +9526,278 @@ async function getObligations$1(queryParameters, db) {
|
|
|
8650
9526
|
const payloadError = toPayloadError(err);
|
|
8651
9527
|
logger.error({
|
|
8652
9528
|
err: payloadError,
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
9529
|
+
event: API_GET_OBLIGATIONS_FAILED,
|
|
9530
|
+
msg: "Failed to get obligations",
|
|
9531
|
+
endpoint: "get_obligations",
|
|
9532
|
+
sort: query.sort,
|
|
9533
|
+
has_cursor: query.cursor != null,
|
|
9534
|
+
limit: query.limit ?? null
|
|
8656
9535
|
});
|
|
8657
9536
|
return failure(payloadError);
|
|
8658
9537
|
}
|
|
8659
9538
|
}
|
|
8660
9539
|
|
|
8661
9540
|
//#endregion
|
|
8662
|
-
//#region src/database/
|
|
9541
|
+
//#region src/database/domains/OfferFormulas.ts
|
|
9542
|
+
const IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
8663
9543
|
/**
|
|
8664
|
-
*
|
|
9544
|
+
* Assert that a value is a valid SQL identifier (lowercase alphanumeric + underscores).
|
|
9545
|
+
* @param value - The value to validate.
|
|
9546
|
+
* @param label - A descriptive label for error messages.
|
|
9547
|
+
*/
|
|
9548
|
+
function assertIdentifier(value, label) {
|
|
9549
|
+
if (!IDENTIFIER_PATTERN.test(value)) throw new Error(`Invalid SQL identifier for ${label}: "${value}". Must match ${IDENTIFIER_PATTERN}.`);
|
|
9550
|
+
}
|
|
9551
|
+
/**
|
|
9552
|
+
* Single position's balance contribution in loan-token units.
|
|
8665
9553
|
*
|
|
8666
|
-
*
|
|
8667
|
-
*
|
|
8668
|
-
*
|
|
8669
|
-
*
|
|
9554
|
+
* - **ERC20 / DEBT_OF**: both price and lltv are NULL (no oracle/obligation
|
|
9555
|
+
* join match). The CASE falls through to the ELSE branch producing
|
|
9556
|
+
* `balance * 1e36 * 1e18 / 1e54 = balance` (identity).
|
|
9557
|
+
* - **COLLATERAL_OF with price**: real price and lltv from oracle/obligation
|
|
9558
|
+
* JOINs → `balance * price * lltv / 1e54`.
|
|
9559
|
+
* - **COLLATERAL_OF with NULL price**: oracle row exists but price has not
|
|
9560
|
+
* been collected yet. lltv IS NOT NULL so COALESCE(price, 0) yields 0,
|
|
9561
|
+
* treating the position as unavailable until the price is fetched.
|
|
8670
9562
|
*
|
|
8671
|
-
*
|
|
8672
|
-
*
|
|
9563
|
+
* @param balance - Position balance column/expression.
|
|
9564
|
+
* @param price - Oracle price column/expression.
|
|
9565
|
+
* @param lltv - Obligation collateral LLTV column/expression.
|
|
8673
9566
|
*/
|
|
8674
|
-
|
|
9567
|
+
function offerBalanceTerm(balance, price, lltv) {
|
|
9568
|
+
return sql`COALESCE(${balance}::numeric, 0) * CASE WHEN ${lltv}::numeric IS NOT NULL THEN COALESCE(${price}::numeric, 0) ELSE 10::numeric ^ 36 END * COALESCE(${lltv}::numeric, 10::numeric ^ 18) / (10::numeric ^ 54)`;
|
|
9569
|
+
}
|
|
9570
|
+
/**
|
|
9571
|
+
* Lot balance: how much liquidity is available to a lot.
|
|
9572
|
+
* @param offerBalance - Aggregated offer balance for the position (from offer_balances CTE).
|
|
9573
|
+
* @param offset - Sum of offsets for this position+obligation.
|
|
9574
|
+
* @param positionConsumed - Sum of consumed capacity across groups for this position+obligation.
|
|
9575
|
+
* @param lotLower - Lot lower bound.
|
|
9576
|
+
* @param lotUpper - Lot upper bound.
|
|
9577
|
+
* @param consumed - Group consumed amount (in loan-token terms).
|
|
9578
|
+
* @param assets - Offer assets (for lot_consumed conversion).
|
|
9579
|
+
*/
|
|
9580
|
+
function lotBalance(offerBalance, offset, positionConsumed, lotLower, lotUpper, consumed, assets) {
|
|
9581
|
+
const lotSize = sql`(COALESCE(${lotUpper}::numeric, 0) - COALESCE(${lotLower}::numeric, 0))`;
|
|
9582
|
+
return sql`GREATEST(0, LEAST(COALESCE(${offerBalance}, 0) + COALESCE(${offset}, 0) + COALESCE(${positionConsumed}, 0) - COALESCE(${lotLower}::numeric, 0), ${lotSize} - ${sql`CASE WHEN ${assets}::numeric > 0 THEN COALESCE(${consumed}::numeric, 0) * ${lotSize} / ${assets}::numeric ELSE 0 END`}))`;
|
|
9583
|
+
}
|
|
9584
|
+
/**
|
|
9585
|
+
* Callback contribution: amount contributed by one callback in loan terms.
|
|
9586
|
+
* Returns 0 when lot_lower IS NULL (no lot exists for this callback).
|
|
9587
|
+
* @param lotBalance - Computed lot balance.
|
|
9588
|
+
* @param lotLower - Lot lower bound (NULL means no lot exists).
|
|
9589
|
+
*/
|
|
9590
|
+
function contribution(lotBalance, lotLower) {
|
|
9591
|
+
return sql`CASE WHEN ${lotLower} IS NULL THEN 0 ELSE ${lotBalance} END`;
|
|
9592
|
+
}
|
|
9593
|
+
/**
|
|
9594
|
+
* Unified takeable for buy and sell offers.
|
|
9595
|
+
* @param assets - Offer assets.
|
|
9596
|
+
* @param consumed - Group consumed amount.
|
|
9597
|
+
* @param available - Total available from callbacks/positions.
|
|
9598
|
+
*/
|
|
9599
|
+
function takeable(assets, consumed, available) {
|
|
9600
|
+
return sql`GREATEST(0, LEAST(${assets}::numeric - ${consumed}::numeric, ${available}))`;
|
|
9601
|
+
}
|
|
9602
|
+
/**
|
|
9603
|
+
* Build a comma-prefixed SQL fragment containing 8 CTEs that compute
|
|
9604
|
+
* `offer_available(hash, obligation_id, available)`.
|
|
9605
|
+
*
|
|
9606
|
+
* Append the result inside a `WITH` clause after the caller's own CTEs.
|
|
9607
|
+
*
|
|
9608
|
+
* Reserved CTE names (callers must not reuse):
|
|
9609
|
+
* `group_winners`, `relevant_positions`, `position_offsets`,
|
|
9610
|
+
* `position_consumed`, `offer_balances`, `callback_contributions`,
|
|
9611
|
+
* `callback_loan_contribution`, `offer_available`.
|
|
9612
|
+
*
|
|
9613
|
+
* @param params - {@link OfferCTEParams}
|
|
9614
|
+
*/
|
|
9615
|
+
function offerAvailabilityCTEs(params) {
|
|
9616
|
+
assertIdentifier(params.sourceTable, "sourceTable");
|
|
9617
|
+
const groupScope = params.groupScope ?? params.sourceTable;
|
|
9618
|
+
if (params.groupScope !== void 0) assertIdentifier(groupScope, "groupScope");
|
|
9619
|
+
const source = sql.raw(params.sourceTable);
|
|
9620
|
+
const scope = sql.raw(groupScope);
|
|
9621
|
+
const { now } = params;
|
|
9622
|
+
return sql`
|
|
9623
|
+
, group_winners AS (
|
|
9624
|
+
SELECT DISTINCT ON (o.group_chain_id, o.group_maker, o.group_group, o.obligation_id, o.buy)
|
|
9625
|
+
o.group_chain_id, o.group_maker, o.group_group, o.obligation_id, o.buy, o.assets
|
|
9626
|
+
FROM ${offers} o
|
|
9627
|
+
LEFT JOIN ${validations} v
|
|
9628
|
+
ON v.offer_hash = o.hash
|
|
9629
|
+
AND v.obligation_id = o.obligation_id
|
|
9630
|
+
LEFT JOIN ${status} s
|
|
9631
|
+
ON s.id = v.status_id
|
|
9632
|
+
WHERE (o.group_chain_id, o.group_maker, o.group_group) IN (
|
|
9633
|
+
SELECT DISTINCT gs.group_chain_id, gs.group_maker, gs.group_group FROM ${scope} gs
|
|
9634
|
+
)
|
|
9635
|
+
AND o.expiry > ${now}
|
|
9636
|
+
AND o.maturity > ${now}
|
|
9637
|
+
AND o.start <= ${now}
|
|
9638
|
+
AND (s.code IS NULL OR s.code = ${Status.VALID})
|
|
9639
|
+
ORDER BY
|
|
9640
|
+
o.group_chain_id, o.group_maker, o.group_group, o.obligation_id, o.buy,
|
|
9641
|
+
CASE WHEN o.buy THEN -o.tick ELSE o.tick END ASC,
|
|
9642
|
+
o.block_number ASC, o.assets DESC, o.hash ASC
|
|
9643
|
+
),
|
|
9644
|
+
relevant_positions AS (
|
|
9645
|
+
SELECT DISTINCT c.position_chain_id, c.position_contract, c.position_user
|
|
9646
|
+
FROM ${source} src
|
|
9647
|
+
JOIN ${offersCallbacks} oc
|
|
9648
|
+
ON oc.offer_hash = src.hash AND oc.obligation_id = src.obligation_id
|
|
9649
|
+
JOIN ${callbacks} c ON c.id = oc.callback_id
|
|
9650
|
+
),
|
|
9651
|
+
position_offsets AS (
|
|
9652
|
+
SELECT chain_id, "user", contract, obligation_id,
|
|
9653
|
+
SUM(value::numeric) AS total_offset
|
|
9654
|
+
FROM ${offsets}
|
|
9655
|
+
WHERE (chain_id, contract, "user") IN (
|
|
9656
|
+
SELECT position_chain_id, position_contract, position_user FROM relevant_positions
|
|
9657
|
+
)
|
|
9658
|
+
GROUP BY chain_id, "user", contract, obligation_id
|
|
9659
|
+
),
|
|
9660
|
+
position_consumed AS (
|
|
9661
|
+
SELECT
|
|
9662
|
+
l.chain_id, l.contract, l."user", l.obligation_id, wo.buy,
|
|
9663
|
+
SUM(
|
|
9664
|
+
CASE
|
|
9665
|
+
WHEN wo.assets::numeric > 0
|
|
9666
|
+
THEN COALESCE(g.consumed::numeric, 0) * (l.upper::numeric - l.lower::numeric) / wo.assets::numeric
|
|
9667
|
+
ELSE 0
|
|
9668
|
+
END
|
|
9669
|
+
) AS consumed
|
|
9670
|
+
FROM ${lots} l
|
|
9671
|
+
JOIN ${groups} g
|
|
9672
|
+
ON g.chain_id = l.chain_id
|
|
9673
|
+
AND g.maker = l."user"
|
|
9674
|
+
AND g."group" = l."group"
|
|
9675
|
+
JOIN group_winners wo
|
|
9676
|
+
ON wo.group_chain_id = g.chain_id
|
|
9677
|
+
AND wo.group_maker = g.maker
|
|
9678
|
+
AND wo.group_group = g."group"
|
|
9679
|
+
AND wo.obligation_id = l.obligation_id
|
|
9680
|
+
GROUP BY l.chain_id, l.contract, l."user", l.obligation_id, wo.buy
|
|
9681
|
+
),
|
|
9682
|
+
offer_balances AS (
|
|
9683
|
+
SELECT
|
|
9684
|
+
pos.chain_id, pos.contract, pos."user",
|
|
9685
|
+
SUM(
|
|
9686
|
+
${offerBalanceTerm(sql`pos.balance`, sql`o.price`, sql`oc_col.lltv`)}
|
|
9687
|
+
) AS offer_balance
|
|
9688
|
+
FROM ${positions} pos
|
|
9689
|
+
LEFT JOIN ${obligationIdKeys} oik
|
|
9690
|
+
ON oik.obligation_id = pos.contract
|
|
9691
|
+
LEFT JOIN ${obligationCollateralsV2} oc_col
|
|
9692
|
+
ON oc_col.obligation_key = oik.obligation_key
|
|
9693
|
+
AND oc_col.asset = pos.asset
|
|
9694
|
+
LEFT JOIN ${oracles$1} o
|
|
9695
|
+
ON o.chain_id = pos.chain_id
|
|
9696
|
+
AND o.address = oc_col.oracle_address
|
|
9697
|
+
WHERE (pos.chain_id, pos.contract, pos."user") IN (
|
|
9698
|
+
SELECT position_chain_id, position_contract, position_user FROM relevant_positions
|
|
9699
|
+
)
|
|
9700
|
+
GROUP BY pos.chain_id, pos.contract, pos."user"
|
|
9701
|
+
),
|
|
9702
|
+
callback_contributions AS (
|
|
9703
|
+
SELECT
|
|
9704
|
+
src.hash,
|
|
9705
|
+
src.obligation_id,
|
|
9706
|
+
src.group_group,
|
|
9707
|
+
src.consumed,
|
|
9708
|
+
src.assets,
|
|
9709
|
+
c.id AS callback_id,
|
|
9710
|
+
c.position_chain_id,
|
|
9711
|
+
c.position_contract,
|
|
9712
|
+
c.position_user,
|
|
9713
|
+
l.lower AS lot_lower,
|
|
9714
|
+
l.upper AS lot_upper,
|
|
9715
|
+
${lotBalance(sql`vb.offer_balance`, sql`pos_offsets.total_offset`, sql`pc.consumed`, sql`l.lower`, sql`l.upper`, sql`src.consumed`, sql`src.assets`)} AS lot_balance
|
|
9716
|
+
FROM ${source} src
|
|
9717
|
+
LEFT JOIN ${offersCallbacks} oc
|
|
9718
|
+
ON oc.offer_hash = src.hash
|
|
9719
|
+
AND oc.obligation_id = src.obligation_id
|
|
9720
|
+
LEFT JOIN ${callbacks} c ON c.id = oc.callback_id
|
|
9721
|
+
LEFT JOIN ${lots} l
|
|
9722
|
+
ON l.chain_id = c.position_chain_id
|
|
9723
|
+
AND l.contract = c.position_contract
|
|
9724
|
+
AND l."user" = c.position_user
|
|
9725
|
+
AND l."group" = src.group_group
|
|
9726
|
+
AND l.obligation_id = src.obligation_id
|
|
9727
|
+
LEFT JOIN offer_balances vb
|
|
9728
|
+
ON vb.chain_id = c.position_chain_id
|
|
9729
|
+
AND vb.contract = c.position_contract
|
|
9730
|
+
AND vb."user" = c.position_user
|
|
9731
|
+
LEFT JOIN position_offsets pos_offsets
|
|
9732
|
+
ON pos_offsets.chain_id = c.position_chain_id
|
|
9733
|
+
AND pos_offsets.contract = c.position_contract
|
|
9734
|
+
AND pos_offsets."user" = c.position_user
|
|
9735
|
+
AND pos_offsets.obligation_id = src.obligation_id
|
|
9736
|
+
LEFT JOIN position_consumed pc
|
|
9737
|
+
ON pc.chain_id = c.position_chain_id
|
|
9738
|
+
AND pc.contract = c.position_contract
|
|
9739
|
+
AND pc."user" = c.position_user
|
|
9740
|
+
AND pc.obligation_id = src.obligation_id
|
|
9741
|
+
AND pc.buy = src.buy
|
|
9742
|
+
),
|
|
9743
|
+
callback_loan_contribution AS (
|
|
9744
|
+
SELECT cc.*,
|
|
9745
|
+
${contribution(sql`cc.lot_balance`, sql`cc.lot_lower`)} AS contribution_in_loan
|
|
9746
|
+
FROM callback_contributions cc
|
|
9747
|
+
),
|
|
9748
|
+
offer_available AS (
|
|
9749
|
+
SELECT hash, obligation_id,
|
|
9750
|
+
SUM(max_contribution) AS available
|
|
9751
|
+
FROM (
|
|
9752
|
+
SELECT hash, obligation_id, position_chain_id, position_contract, position_user,
|
|
9753
|
+
MAX(contribution_in_loan) AS max_contribution
|
|
9754
|
+
FROM callback_loan_contribution
|
|
9755
|
+
WHERE callback_id IS NOT NULL
|
|
9756
|
+
GROUP BY hash, obligation_id, position_chain_id, position_contract, position_user
|
|
9757
|
+
) per_position
|
|
9758
|
+
GROUP BY hash, obligation_id
|
|
9759
|
+
)`;
|
|
9760
|
+
}
|
|
9761
|
+
/**
|
|
9762
|
+
* Build a comma-prefixed SQL fragment containing 9 CTEs that compute
|
|
9763
|
+
* `offer_takeable(hash, obligation_id, available, takeable)`.
|
|
9764
|
+
*
|
|
9765
|
+
* Wraps {@link offerAvailabilityCTEs} and adds one CTE that LEFT JOINs
|
|
9766
|
+
* `offer_available` back to the source table, ensuring every source offer
|
|
9767
|
+
* appears (with `available = 0` when no callbacks exist).
|
|
9768
|
+
*
|
|
9769
|
+
* Reserved CTE names: all from {@link offerAvailabilityCTEs} plus `offer_takeable`.
|
|
9770
|
+
*
|
|
9771
|
+
* @param params - {@link OfferCTEParams}
|
|
9772
|
+
*/
|
|
9773
|
+
function offerTakeabilityCTEs(params) {
|
|
9774
|
+
assertIdentifier(params.sourceTable, "sourceTable");
|
|
9775
|
+
const source = sql.raw(params.sourceTable);
|
|
9776
|
+
return sql`
|
|
9777
|
+
${offerAvailabilityCTEs(params)},
|
|
9778
|
+
offer_takeable AS (
|
|
9779
|
+
SELECT src.hash, src.obligation_id,
|
|
9780
|
+
COALESCE(oa.available, 0) AS available,
|
|
9781
|
+
${takeable(sql`src.assets`, sql`src.consumed`, sql`COALESCE(oa.available, 0)`)} AS takeable
|
|
9782
|
+
FROM ${source} src
|
|
9783
|
+
LEFT JOIN offer_available oa ON oa.hash = src.hash AND oa.obligation_id = src.obligation_id
|
|
9784
|
+
)`;
|
|
9785
|
+
}
|
|
9786
|
+
|
|
9787
|
+
//#endregion
|
|
9788
|
+
//#region src/database/constants.ts
|
|
9789
|
+
/**
|
|
9790
|
+
* Default batch size for bulk database inserts.
|
|
9791
|
+
*
|
|
9792
|
+
* PostgreSQL limits a single query to at most 65,535 parameters
|
|
9793
|
+
* (e.g. $1, $2, ...). In bulk inserts, each row consumes one
|
|
9794
|
+
* parameter per column, so inserting too many rows at once can
|
|
9795
|
+
* exceed this limit.
|
|
9796
|
+
*
|
|
9797
|
+
* Our largest batched insert is into the `offers` table with 15 columns.
|
|
9798
|
+
* 15 cols × 4,000 rows = 60,000 parameters, safely under 65,535.
|
|
9799
|
+
*/
|
|
9800
|
+
const DEFAULT_BATCH_SIZE$1 = 4e3;
|
|
8675
9801
|
|
|
8676
9802
|
//#endregion
|
|
8677
9803
|
//#region src/database/domains/Offers.ts
|
|
@@ -8866,9 +9992,10 @@ function create$15(config) {
|
|
|
8866
9992
|
};
|
|
8867
9993
|
}
|
|
8868
9994
|
const HEX_32$2 = /^0x[a-fA-F0-9]{64}$/;
|
|
9995
|
+
const HEX_20$2 = /^0x[a-fA-F0-9]{40}$/;
|
|
8869
9996
|
function parseCursor$1(cursor) {
|
|
8870
9997
|
const [hash, obligationId] = cursor.split(":");
|
|
8871
|
-
if (!hash || !obligationId || !HEX_32$2.test(hash) || !
|
|
9998
|
+
if (!hash || !obligationId || !HEX_32$2.test(hash) || !HEX_20$2.test(obligationId)) throw new Error("Invalid cursor format");
|
|
8872
9999
|
return {
|
|
8873
10000
|
hash: hash.toLowerCase(),
|
|
8874
10001
|
obligationId: obligationId.toLowerCase()
|
|
@@ -8997,7 +10124,8 @@ function toAttestationKey(offer) {
|
|
|
8997
10124
|
//#endregion
|
|
8998
10125
|
//#region src/api/Controllers/getOffers.ts
|
|
8999
10126
|
/**
|
|
9000
|
-
* Query offers with computed consumed/available/takeable values.
|
|
10127
|
+
* Query offers for a maker with computed consumed/available/takeable values.
|
|
10128
|
+
* Uses the same CTE-based computation as Book.ts via shared OfferFormulas builders.
|
|
9001
10129
|
* @param db - The database client. {@link Database.Core}
|
|
9002
10130
|
* @param parameters - {@link GetOffersQueryParams}
|
|
9003
10131
|
* @returns The offers with pagination cursor.
|
|
@@ -9006,149 +10134,129 @@ async function getOffersQuery(db, parameters) {
|
|
|
9006
10134
|
const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
|
|
9007
10135
|
const rawCursor = parameters?.cursor;
|
|
9008
10136
|
const maker = parameters?.maker;
|
|
9009
|
-
|
|
10137
|
+
if (!maker) throw new Error("getOffersQuery requires a maker parameter");
|
|
10138
|
+
const cursor = rawCursor !== void 0 && rawCursor !== null ? parseMakerCursor(rawCursor) : void 0;
|
|
9010
10139
|
const now = Math.floor((Date.now() - 1) / 1e3);
|
|
9011
|
-
const
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
FROM
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
chainId: obligationIdKeys.chainId,
|
|
9089
|
-
loanToken: obligations.loanToken,
|
|
9090
|
-
callbackAddress: offers.callbackAddress,
|
|
9091
|
-
callbackData: offers.callbackData,
|
|
9092
|
-
receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
|
|
9093
|
-
collaterals: collateralsLateral.collaterals,
|
|
9094
|
-
blockNumber: offers.blockNumber,
|
|
9095
|
-
available: sql`${availableExpr}::numeric`.as("available"),
|
|
9096
|
-
takeable: sql`FLOOR(GREATEST(0,
|
|
9097
|
-
CASE WHEN ${offers.buy} = false
|
|
9098
|
-
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
9099
|
-
ELSE LEAST(
|
|
9100
|
-
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
9101
|
-
${availableExpr}::numeric
|
|
9102
|
-
)
|
|
9103
|
-
END
|
|
9104
|
-
))`.as("takeable")
|
|
9105
|
-
}).from(offers).innerJoin(obligationIdKeys, eq(offers.obligationId, obligationIdKeys.obligationId)).innerJoin(obligations, eq(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== void 0 ? cursor.obligationId === void 0 ? gt(offers.hash, cursor.hash) : or(gt(offers.hash, cursor.hash), and(eq(offers.hash, cursor.hash), gt(offers.obligationId, cursor.obligationId))) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, gte(offers.expiry, now), gte(offers.maturity, now), maker === void 0 ? sql`GREATEST(0,
|
|
9106
|
-
CASE WHEN ${offers.buy} = false
|
|
9107
|
-
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
9108
|
-
ELSE LEAST(
|
|
9109
|
-
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
9110
|
-
${availableExpr}::numeric
|
|
9111
|
-
)
|
|
9112
|
-
END
|
|
9113
|
-
) > 0` : void 0)).orderBy(asc(offers.hash), asc(offers.obligationId)).limit(limit)).map((row) => {
|
|
9114
|
-
const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
|
|
10140
|
+
const rows = (await db.execute(sql`
|
|
10141
|
+
WITH maker_offers AS (
|
|
10142
|
+
SELECT
|
|
10143
|
+
o.*,
|
|
10144
|
+
g.consumed, oik.chain_id, obl.loan_token
|
|
10145
|
+
FROM ${offers} o
|
|
10146
|
+
JOIN ${groups} g
|
|
10147
|
+
ON g.chain_id = o.group_chain_id
|
|
10148
|
+
AND g.maker = o.group_maker
|
|
10149
|
+
AND g."group" = o.group_group
|
|
10150
|
+
JOIN ${obligationIdKeys} oik
|
|
10151
|
+
ON oik.obligation_id = o.obligation_id
|
|
10152
|
+
JOIN ${obligations} obl
|
|
10153
|
+
ON obl.obligation_key = oik.obligation_key
|
|
10154
|
+
WHERE o.group_maker = ${maker.toLowerCase()}
|
|
10155
|
+
AND o.expiry >= ${now}
|
|
10156
|
+
AND o.maturity >= ${now}
|
|
10157
|
+
${cursor !== void 0 ? sql`AND (o.hash, o.obligation_id) > (${cursor.hash}, ${cursor.obligationId})` : sql``}
|
|
10158
|
+
ORDER BY o.hash ASC, o.obligation_id ASC
|
|
10159
|
+
LIMIT ${limit}
|
|
10160
|
+
),
|
|
10161
|
+
maker_groups AS (
|
|
10162
|
+
SELECT DISTINCT o.group_chain_id, o.group_maker, o.group_group
|
|
10163
|
+
FROM ${offers} o
|
|
10164
|
+
WHERE o.group_maker = ${maker.toLowerCase()}
|
|
10165
|
+
AND o.expiry >= ${now}
|
|
10166
|
+
AND o.maturity >= ${now}
|
|
10167
|
+
),
|
|
10168
|
+
collats AS MATERIALIZED (
|
|
10169
|
+
SELECT oia.obligation_id,
|
|
10170
|
+
COALESCE(jsonb_agg(jsonb_build_object(
|
|
10171
|
+
'asset', oc.asset,
|
|
10172
|
+
'oracle', oc.oracle_address,
|
|
10173
|
+
'lltv', oc.lltv
|
|
10174
|
+
) ORDER BY oc.asset), '[]'::jsonb) AS collaterals
|
|
10175
|
+
FROM ${obligationIdKeys} oia
|
|
10176
|
+
JOIN ${obligationCollateralsV2} oc
|
|
10177
|
+
ON oc.obligation_key = oia.obligation_key
|
|
10178
|
+
WHERE oia.obligation_id IN (SELECT DISTINCT mo.obligation_id FROM maker_offers mo)
|
|
10179
|
+
GROUP BY oia.obligation_id
|
|
10180
|
+
)
|
|
10181
|
+
${offerTakeabilityCTEs({
|
|
10182
|
+
sourceTable: "maker_offers",
|
|
10183
|
+
groupScope: "maker_groups",
|
|
10184
|
+
now
|
|
10185
|
+
})}
|
|
10186
|
+
SELECT
|
|
10187
|
+
mo.hash,
|
|
10188
|
+
mo.obligation_id,
|
|
10189
|
+
mo.group_maker,
|
|
10190
|
+
mo.assets,
|
|
10191
|
+
mo.obligation_units,
|
|
10192
|
+
mo.obligation_shares,
|
|
10193
|
+
mo.consumed,
|
|
10194
|
+
mo.tick,
|
|
10195
|
+
mo.maturity,
|
|
10196
|
+
mo.expiry,
|
|
10197
|
+
mo.start,
|
|
10198
|
+
mo.group_group AS "group",
|
|
10199
|
+
mo.buy,
|
|
10200
|
+
mo.chain_id,
|
|
10201
|
+
mo.loan_token,
|
|
10202
|
+
mo.callback_address,
|
|
10203
|
+
mo.callback_data,
|
|
10204
|
+
mo.receiver_if_maker_is_seller,
|
|
10205
|
+
mo.block_number,
|
|
10206
|
+
mo.session,
|
|
10207
|
+
COALESCE(ot.available, 0) AS available,
|
|
10208
|
+
COALESCE(ot.takeable, 0) AS takeable,
|
|
10209
|
+
c.collaterals
|
|
10210
|
+
FROM maker_offers mo
|
|
10211
|
+
LEFT JOIN offer_takeable ot
|
|
10212
|
+
ON ot.hash = mo.hash AND ot.obligation_id = mo.obligation_id
|
|
10213
|
+
LEFT JOIN collats c ON c.obligation_id = mo.obligation_id
|
|
10214
|
+
ORDER BY mo.hash ASC, mo.obligation_id ASC;
|
|
10215
|
+
`)).rows.map((row) => {
|
|
10216
|
+
const receiverIfMakerIsSeller = (row.receiver_if_maker_is_seller ?? row.group_maker).toLowerCase();
|
|
9115
10217
|
return {
|
|
9116
10218
|
hash: row.hash,
|
|
9117
|
-
obligationId: row.
|
|
9118
|
-
maker: row.
|
|
10219
|
+
obligationId: row.obligation_id,
|
|
10220
|
+
maker: row.group_maker,
|
|
9119
10221
|
assets: BigInt(row.assets),
|
|
9120
|
-
obligationUnits: BigInt(row.
|
|
9121
|
-
obligationShares: BigInt(row.
|
|
10222
|
+
obligationUnits: BigInt(row.obligation_units ?? 0),
|
|
10223
|
+
obligationShares: BigInt(row.obligation_shares ?? 0),
|
|
9122
10224
|
tick: row.tick,
|
|
9123
|
-
maturity:
|
|
10225
|
+
maturity: row.maturity,
|
|
9124
10226
|
expiry: row.expiry,
|
|
9125
10227
|
start: row.start,
|
|
9126
10228
|
group: row.group,
|
|
9127
10229
|
session: row.session,
|
|
9128
10230
|
buy: row.buy,
|
|
9129
|
-
chainId: row.
|
|
9130
|
-
loanToken: row.
|
|
10231
|
+
chainId: row.chain_id,
|
|
10232
|
+
loanToken: row.loan_token,
|
|
9131
10233
|
collaterals: row.collaterals.map((c) => ({
|
|
9132
10234
|
asset: c.asset,
|
|
9133
10235
|
oracle: c.oracle,
|
|
9134
10236
|
lltv: BigInt(c.lltv)
|
|
9135
10237
|
})).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
|
|
9136
10238
|
callback: {
|
|
9137
|
-
address: row.
|
|
9138
|
-
data: row.
|
|
10239
|
+
address: row.callback_address,
|
|
10240
|
+
data: row.callback_data
|
|
9139
10241
|
},
|
|
9140
10242
|
receiverIfMakerIsSeller,
|
|
9141
|
-
consumed: BigInt(row.consumed),
|
|
10243
|
+
consumed: BigInt(row.consumed ?? 0),
|
|
9142
10244
|
available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
|
|
9143
10245
|
takeable: BigInt(String(row.takeable ?? "0").split(".")[0] ?? "0"),
|
|
9144
|
-
blockNumber: row.
|
|
10246
|
+
blockNumber: row.block_number
|
|
9145
10247
|
};
|
|
9146
10248
|
});
|
|
9147
10249
|
return {
|
|
9148
10250
|
rows,
|
|
9149
|
-
nextCursor: rows.length === limit ?
|
|
10251
|
+
nextCursor: rows.length === limit ? formatOfferCursor(rows[rows.length - 1]) : null
|
|
9150
10252
|
};
|
|
9151
10253
|
}
|
|
10254
|
+
/**
|
|
10255
|
+
* Get offers with optional maker or obligation+side filter.
|
|
10256
|
+
* @param queryParameters - Raw query parameters from the API request.
|
|
10257
|
+
* @param db - The database client. {@link Database.Database}
|
|
10258
|
+
* @returns The offers response payload.
|
|
10259
|
+
*/
|
|
9152
10260
|
async function getOffers$1(queryParameters, db) {
|
|
9153
10261
|
const logger = getLogger();
|
|
9154
10262
|
const result = safeParse("get_offers", queryParameters, (issue) => issue.message);
|
|
@@ -9187,9 +10295,12 @@ async function getOffers$1(queryParameters, db) {
|
|
|
9187
10295
|
} catch (err) {
|
|
9188
10296
|
logger.error({
|
|
9189
10297
|
err,
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
10298
|
+
event: API_GET_OFFERS_FAILED,
|
|
10299
|
+
msg: "Failed to get offers",
|
|
10300
|
+
endpoint: "get_offers",
|
|
10301
|
+
maker: query.maker ?? null,
|
|
10302
|
+
obligation_id: query.obligation_id ?? null,
|
|
10303
|
+
side: query.side ?? null
|
|
9193
10304
|
});
|
|
9194
10305
|
return failure(err);
|
|
9195
10306
|
}
|
|
@@ -9197,7 +10308,7 @@ async function getOffers$1(queryParameters, db) {
|
|
|
9197
10308
|
function parseMakerCursor(cursor) {
|
|
9198
10309
|
const [rawHash, rawObligationId, ...tail] = cursor.split(":");
|
|
9199
10310
|
if (tail.length > 0) throw new Error("Invalid cursor format");
|
|
9200
|
-
if (!rawHash || !rawObligationId || !HEX_32$1.test(rawHash) || !
|
|
10311
|
+
if (!rawHash || !rawObligationId || !HEX_32$1.test(rawHash) || !HEX_20$1.test(rawObligationId)) throw new Error("Invalid cursor format");
|
|
9201
10312
|
return {
|
|
9202
10313
|
hash: rawHash.toLowerCase(),
|
|
9203
10314
|
obligationId: rawObligationId.toLowerCase()
|
|
@@ -9207,6 +10318,7 @@ function formatOfferCursor(row) {
|
|
|
9207
10318
|
return `${row.hash.toLowerCase()}:${row.obligationId.toLowerCase()}`;
|
|
9208
10319
|
}
|
|
9209
10320
|
const HEX_32$1 = /^0x[a-fA-F0-9]{64}$/;
|
|
10321
|
+
const HEX_20$1 = /^0x[a-fA-F0-9]{40}$/;
|
|
9210
10322
|
|
|
9211
10323
|
//#endregion
|
|
9212
10324
|
//#region src/api/Controllers/getUserPositions.ts
|
|
@@ -9234,9 +10346,12 @@ async function getUserPositions(queryParameters, db) {
|
|
|
9234
10346
|
} catch (err) {
|
|
9235
10347
|
logger.error({
|
|
9236
10348
|
err,
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
10349
|
+
event: API_GET_USER_POSITIONS_FAILED,
|
|
10350
|
+
msg: "Failed to get user positions",
|
|
10351
|
+
endpoint: "get_user_positions",
|
|
10352
|
+
user_address: query.user_address,
|
|
10353
|
+
has_cursor: query.cursor != null,
|
|
10354
|
+
limit: query.limit ?? null
|
|
9240
10355
|
});
|
|
9241
10356
|
return failure(err);
|
|
9242
10357
|
}
|
|
@@ -9297,11 +10412,18 @@ async function validateOffers(body, gatekeeper) {
|
|
|
9297
10412
|
cursor: null
|
|
9298
10413
|
});
|
|
9299
10414
|
} catch (err) {
|
|
10415
|
+
const span = trace.getActiveSpan();
|
|
10416
|
+
if (span) {
|
|
10417
|
+
span.recordException(err);
|
|
10418
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
10419
|
+
}
|
|
9300
10420
|
logger.error({
|
|
9301
10421
|
err,
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
10422
|
+
event: API_VALIDATE_OFFERS_FAILED,
|
|
10423
|
+
msg: "Failed to validate offers",
|
|
10424
|
+
endpoint: "validate_offers",
|
|
10425
|
+
chain_id: chainId,
|
|
10426
|
+
offers_count: parsedOffers.length
|
|
9305
10427
|
});
|
|
9306
10428
|
return failure(err);
|
|
9307
10429
|
}
|
|
@@ -9319,6 +10441,7 @@ var Controllers_exports = /* @__PURE__ */ __exportAll({
|
|
|
9319
10441
|
getHealthChains: () => getHealthChains,
|
|
9320
10442
|
getHealthCollectors: () => getHealthCollectors,
|
|
9321
10443
|
getIntegratorDocsHtml: () => getIntegratorDocsHtml,
|
|
10444
|
+
getMetrics: () => getMetrics,
|
|
9322
10445
|
getObligation: () => getObligation,
|
|
9323
10446
|
getObligations: () => getObligations$1,
|
|
9324
10447
|
getOffers: () => getOffers$1,
|
|
@@ -9343,31 +10466,28 @@ function create$13(params) {
|
|
|
9343
10466
|
return { serve: () => serve$1(params) };
|
|
9344
10467
|
}
|
|
9345
10468
|
/**
|
|
9346
|
-
*
|
|
9347
|
-
* @
|
|
9348
|
-
*
|
|
9349
|
-
* import { RouterApi } from "@morpho-dev/router";
|
|
9350
|
-
* RouterApi.serve({ port: 8081 }); // local router API server running on http://localhost:8081
|
|
9351
|
-
* ```
|
|
10469
|
+
* Create the router API Hono app with all routes and middleware configured.
|
|
10470
|
+
* @param parameters - API construction parameters.
|
|
10471
|
+
* @returns Configured Hono app instance.
|
|
9352
10472
|
*/
|
|
9353
|
-
function
|
|
10473
|
+
function createApp(parameters) {
|
|
9354
10474
|
const { db, gatekeeper, chainRegistry } = parameters;
|
|
9355
|
-
const tracer = getTracer("router.api");
|
|
9356
10475
|
const app = new Hono();
|
|
9357
|
-
app
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
if (c.res.status >= 500) span.setStatus({ code: SpanStatusCode.ERROR });
|
|
9368
|
-
return res;
|
|
9369
|
-
});
|
|
10476
|
+
init(app, {
|
|
10477
|
+
component: "api",
|
|
10478
|
+
tracerName: "router.api",
|
|
10479
|
+
toFailurePayload: failure,
|
|
10480
|
+
adaptFailurePayload: ({ route, failure }) => route === "/metrics" ? {
|
|
10481
|
+
statusCode: failure.statusCode,
|
|
10482
|
+
body: "# Failed to render router metrics\n",
|
|
10483
|
+
contentType: "text/plain; version=0.0.4; charset=utf-8",
|
|
10484
|
+
headers: { "Cache-Control": "no-store" }
|
|
10485
|
+
} : void 0
|
|
9370
10486
|
});
|
|
10487
|
+
app.use("*", cors({
|
|
10488
|
+
origin: "*",
|
|
10489
|
+
exposeHeaders: ["x-request-id"]
|
|
10490
|
+
}));
|
|
9371
10491
|
app.get("/v1/offers", async (c) => {
|
|
9372
10492
|
const { statusCode, body } = await getOffers$1(c.req.query(), db);
|
|
9373
10493
|
return c.json(body, statusCode);
|
|
@@ -9392,14 +10512,9 @@ function serve$1(parameters) {
|
|
|
9392
10512
|
return c.json(body, statusCode);
|
|
9393
10513
|
});
|
|
9394
10514
|
app.post("/v1/validate", async (c) => {
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
return c.json(body, statusCode);
|
|
9399
|
-
} catch (err) {
|
|
9400
|
-
const failure$2 = failure(err);
|
|
9401
|
-
return c.json(failure$2.body, failure$2.statusCode);
|
|
9402
|
-
}
|
|
10515
|
+
const reqBody = await c.req.json();
|
|
10516
|
+
const { statusCode, body } = await gatekeeper.validate(reqBody);
|
|
10517
|
+
return c.json(body, statusCode);
|
|
9403
10518
|
});
|
|
9404
10519
|
app.get("/v1/users/:userAddress/positions", async (c) => {
|
|
9405
10520
|
const query = c.req.query();
|
|
@@ -9422,24 +10537,37 @@ function serve$1(parameters) {
|
|
|
9422
10537
|
const { statusCode, body } = await getHealthChains(c.req.query(), db, void 0, chainRegistry);
|
|
9423
10538
|
return c.json(body, statusCode);
|
|
9424
10539
|
});
|
|
10540
|
+
app.get("/metrics", async (c) => {
|
|
10541
|
+
const body = await getMetrics(db, chainRegistry);
|
|
10542
|
+
return c.text(body, 200, {
|
|
10543
|
+
"Content-Type": "text/plain; version=0.0.4; charset=utf-8",
|
|
10544
|
+
"Cache-Control": "no-store"
|
|
10545
|
+
});
|
|
10546
|
+
});
|
|
9425
10547
|
app.get("/v1/config/contracts", async (c) => {
|
|
9426
10548
|
const { statusCode, body } = await getConfigContracts(c.req.query(), chainRegistry);
|
|
9427
10549
|
return c.json(body, statusCode);
|
|
9428
10550
|
});
|
|
9429
10551
|
app.get("/v1/config/rules", async (c) => {
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
return c.json(body, statusCode);
|
|
9433
|
-
} catch (err) {
|
|
9434
|
-
const failure$1 = failure(err);
|
|
9435
|
-
return c.json(failure$1.body, failure$1.statusCode);
|
|
9436
|
-
}
|
|
10552
|
+
const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
|
|
10553
|
+
return c.json(body, statusCode);
|
|
9437
10554
|
});
|
|
9438
10555
|
app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
|
|
9439
10556
|
app.get("/docs/api", async (c) => c.html(await getDocsHtml(), 200));
|
|
9440
10557
|
app.get("/docs", async (c) => c.html(await getIntegratorDocsHtml(), 200));
|
|
10558
|
+
return app;
|
|
10559
|
+
}
|
|
10560
|
+
/**
|
|
10561
|
+
* Start a local router server.
|
|
10562
|
+
* @example
|
|
10563
|
+
* ```ts
|
|
10564
|
+
* import { RouterApi } from "@morpho-dev/router";
|
|
10565
|
+
* RouterApi.serve({ port: 8081 }); // local router API server running on http://localhost:8081
|
|
10566
|
+
* ```
|
|
10567
|
+
*/
|
|
10568
|
+
function serve$1(parameters) {
|
|
9441
10569
|
serve({
|
|
9442
|
-
fetch:
|
|
10570
|
+
fetch: createApp(parameters).fetch,
|
|
9443
10571
|
port: parameters.port
|
|
9444
10572
|
});
|
|
9445
10573
|
}
|
|
@@ -9467,6 +10595,7 @@ var RouterApi_exports = /* @__PURE__ */ __exportAll({
|
|
|
9467
10595
|
UsersController: () => UsersController,
|
|
9468
10596
|
ValidateController: () => ValidateController,
|
|
9469
10597
|
create: () => create$13,
|
|
10598
|
+
createApp: () => createApp,
|
|
9470
10599
|
from: () => from$1,
|
|
9471
10600
|
parse: () => parse,
|
|
9472
10601
|
safeParse: () => safeParse
|
|
@@ -9655,6 +10784,7 @@ var HttpGetApiFailedError = class extends BaseError {
|
|
|
9655
10784
|
//#endregion
|
|
9656
10785
|
//#region src/database/drizzle/index.ts
|
|
9657
10786
|
var drizzle_exports = /* @__PURE__ */ __exportAll({
|
|
10787
|
+
CallbackTypes: () => CallbackTypes,
|
|
9658
10788
|
PositionTypes: () => PositionTypes,
|
|
9659
10789
|
StatusCode: () => StatusCode,
|
|
9660
10790
|
TABLE_NAMES: () => TABLE_NAMES,
|
|
@@ -10002,13 +11132,19 @@ async function _getOffers(db, params) {
|
|
|
10002
11132
|
ON oia.obligation_id = w.obligation_id
|
|
10003
11133
|
JOIN ${obligations} obl
|
|
10004
11134
|
ON obl.obligation_key = oia.obligation_key
|
|
10005
|
-
)
|
|
10006
|
-
|
|
10007
|
-
|
|
11135
|
+
)
|
|
11136
|
+
${offerTakeabilityCTEs({
|
|
11137
|
+
sourceTable: "enriched",
|
|
11138
|
+
groupScope: "winners",
|
|
11139
|
+
now
|
|
11140
|
+
})}
|
|
11141
|
+
, paged AS (
|
|
11142
|
+
SELECT e.*, COALESCE(ot.available, 0) AS available, ot.takeable
|
|
10008
11143
|
FROM enriched e
|
|
11144
|
+
JOIN offer_takeable ot ON ot.hash = e.hash AND ot.obligation_id = e.obligation_id
|
|
11145
|
+
WHERE ot.takeable > 0
|
|
10009
11146
|
${cursor != null ? sql`
|
|
10010
|
-
|
|
10011
|
-
(e.tick_norm, e.block_norm, e.assets_norm, e.hash_norm)
|
|
11147
|
+
AND (e.tick_norm, e.block_norm, e.assets_norm, e.hash_norm)
|
|
10012
11148
|
> (
|
|
10013
11149
|
CASE WHEN ${priceSortDirection === "asc" ? sql`TRUE` : sql`FALSE`}
|
|
10014
11150
|
THEN ${cursor.tick}::integer ELSE -${cursor.tick}::integer END,
|
|
@@ -10018,224 +11154,38 @@ async function _getOffers(db, params) {
|
|
|
10018
11154
|
)` : sql``}
|
|
10019
11155
|
ORDER BY e.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
|
|
10020
11156
|
LIMIT ${limit}
|
|
10021
|
-
),
|
|
10022
|
-
-- Compute sum of offsets per position and obligation
|
|
10023
|
-
position_offsets AS (
|
|
10024
|
-
SELECT
|
|
10025
|
-
chain_id,
|
|
10026
|
-
"user",
|
|
10027
|
-
contract,
|
|
10028
|
-
obligation_id,
|
|
10029
|
-
SUM(value::numeric) AS total_offset
|
|
10030
|
-
FROM ${offsets}
|
|
10031
|
-
GROUP BY chain_id, "user", contract, obligation_id
|
|
10032
|
-
),
|
|
10033
|
-
-- Compute position_consumed: sum of consumed from all groups with lots on each position+obligation (converted to lot terms)
|
|
10034
|
-
position_consumed AS (
|
|
10035
|
-
SELECT
|
|
10036
|
-
l.chain_id,
|
|
10037
|
-
l.contract,
|
|
10038
|
-
l."user",
|
|
10039
|
-
l.obligation_id,
|
|
10040
|
-
SUM(
|
|
10041
|
-
CASE
|
|
10042
|
-
WHEN wo.assets::numeric > 0
|
|
10043
|
-
THEN COALESCE(g.consumed::numeric, 0) * (l.upper::numeric - l.lower::numeric) / wo.assets::numeric
|
|
10044
|
-
ELSE 0
|
|
10045
|
-
END
|
|
10046
|
-
) AS consumed
|
|
10047
|
-
FROM ${lots} l
|
|
10048
|
-
JOIN ${groups} g
|
|
10049
|
-
ON g.chain_id = l.chain_id
|
|
10050
|
-
AND LOWER(g.maker) = LOWER(l."user")
|
|
10051
|
-
AND g."group" = l."group"
|
|
10052
|
-
JOIN winners wo
|
|
10053
|
-
ON wo.group_chain_id = g.chain_id
|
|
10054
|
-
AND LOWER(wo.group_maker) = LOWER(g.maker)
|
|
10055
|
-
AND wo.group_group = g."group"
|
|
10056
|
-
GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
|
|
10057
|
-
),
|
|
10058
|
-
-- Compute callback contributions with lot balance
|
|
10059
|
-
callback_contributions AS (
|
|
10060
|
-
SELECT
|
|
10061
|
-
p.hash,
|
|
10062
|
-
p.obligation_id,
|
|
10063
|
-
p.assets,
|
|
10064
|
-
p.tick,
|
|
10065
|
-
p.obligation_units,
|
|
10066
|
-
p.obligation_shares,
|
|
10067
|
-
p.maturity,
|
|
10068
|
-
p.expiry,
|
|
10069
|
-
p.start,
|
|
10070
|
-
p.group_group,
|
|
10071
|
-
p.buy,
|
|
10072
|
-
p.callback_address,
|
|
10073
|
-
p.callback_data,
|
|
10074
|
-
p.receiver_if_maker_is_seller,
|
|
10075
|
-
p.block_number,
|
|
10076
|
-
p.group_chain_id,
|
|
10077
|
-
p.group_maker,
|
|
10078
|
-
p.consumed,
|
|
10079
|
-
p.chain_id,
|
|
10080
|
-
p.loan_token,
|
|
10081
|
-
p.session,
|
|
10082
|
-
c.id AS callback_id,
|
|
10083
|
-
c.position_chain_id,
|
|
10084
|
-
c.position_contract,
|
|
10085
|
-
c.position_user,
|
|
10086
|
-
c.amount AS callback_amount,
|
|
10087
|
-
pos.balance AS position_balance,
|
|
10088
|
-
pos.asset AS position_asset,
|
|
10089
|
-
l.lower AS lot_lower,
|
|
10090
|
-
l.upper AS lot_upper,
|
|
10091
|
-
-- Compute lot_balance: min(position_balance + offset + position_consumed - lot.lower, lot.size - lot_consumed)
|
|
10092
|
-
-- lot_consumed is converted from loan token to lot terms: consumed * lot_size / assets
|
|
10093
|
-
GREATEST(0, LEAST(
|
|
10094
|
-
COALESCE(pos.balance::numeric, 0) + COALESCE(pos_offsets.total_offset, 0) + COALESCE(pc.consumed, 0) - COALESCE(l.lower::numeric, 0),
|
|
10095
|
-
(COALESCE(l.upper::numeric, 0) - COALESCE(l.lower::numeric, 0)) -
|
|
10096
|
-
CASE
|
|
10097
|
-
WHEN p.assets::numeric > 0
|
|
10098
|
-
THEN COALESCE(p.consumed::numeric, 0) * (COALESCE(l.upper::numeric, 0) - COALESCE(l.lower::numeric, 0)) / p.assets::numeric
|
|
10099
|
-
ELSE 0
|
|
10100
|
-
END
|
|
10101
|
-
)) AS lot_balance
|
|
10102
|
-
FROM paged p
|
|
10103
|
-
LEFT JOIN ${offersCallbacks} oc
|
|
10104
|
-
ON oc.offer_hash = p.hash
|
|
10105
|
-
AND oc.obligation_id = p.obligation_id
|
|
10106
|
-
LEFT JOIN ${callbacks} c ON c.id = oc.callback_id
|
|
10107
|
-
LEFT JOIN ${lots} l
|
|
10108
|
-
ON l.chain_id = c.position_chain_id
|
|
10109
|
-
AND LOWER(l.contract) = LOWER(c.position_contract)
|
|
10110
|
-
AND LOWER(l."user") = LOWER(c.position_user)
|
|
10111
|
-
AND l."group" = p.group_group
|
|
10112
|
-
AND l.obligation_id = p.obligation_id
|
|
10113
|
-
LEFT JOIN ${positions} pos
|
|
10114
|
-
ON pos.chain_id = c.position_chain_id
|
|
10115
|
-
AND LOWER(pos.contract) = LOWER(c.position_contract)
|
|
10116
|
-
AND LOWER(pos."user") = LOWER(c.position_user)
|
|
10117
|
-
LEFT JOIN position_offsets pos_offsets
|
|
10118
|
-
ON pos_offsets.chain_id = c.position_chain_id
|
|
10119
|
-
AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
|
|
10120
|
-
AND LOWER(pos_offsets."user") = LOWER(c.position_user)
|
|
10121
|
-
AND pos_offsets.obligation_id = p.obligation_id
|
|
10122
|
-
LEFT JOIN position_consumed pc
|
|
10123
|
-
ON pc.chain_id = c.position_chain_id
|
|
10124
|
-
AND LOWER(pc.contract) = LOWER(c.position_contract)
|
|
10125
|
-
AND LOWER(pc."user") = LOWER(c.position_user)
|
|
10126
|
-
AND pc.obligation_id = p.obligation_id
|
|
10127
|
-
),
|
|
10128
|
-
-- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
|
|
10129
|
-
callback_loan_contribution AS (
|
|
10130
|
-
SELECT
|
|
10131
|
-
cc.*,
|
|
10132
|
-
CASE
|
|
10133
|
-
WHEN cc.lot_lower IS NULL THEN 0
|
|
10134
|
-
ELSE LEAST(cc.lot_balance, COALESCE(cc.callback_amount::numeric, cc.lot_balance))
|
|
10135
|
-
END AS contribution_in_loan
|
|
10136
|
-
FROM callback_contributions cc
|
|
10137
|
-
),
|
|
10138
|
-
-- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
|
|
10139
|
-
offer_contributions AS (
|
|
10140
|
-
SELECT
|
|
10141
|
-
hash,
|
|
10142
|
-
obligation_id,
|
|
10143
|
-
assets,
|
|
10144
|
-
tick,
|
|
10145
|
-
obligation_units,
|
|
10146
|
-
obligation_shares,
|
|
10147
|
-
maturity,
|
|
10148
|
-
expiry,
|
|
10149
|
-
start,
|
|
10150
|
-
group_group,
|
|
10151
|
-
buy,
|
|
10152
|
-
callback_address,
|
|
10153
|
-
callback_data,
|
|
10154
|
-
receiver_if_maker_is_seller,
|
|
10155
|
-
block_number,
|
|
10156
|
-
group_chain_id,
|
|
10157
|
-
group_maker,
|
|
10158
|
-
consumed,
|
|
10159
|
-
chain_id,
|
|
10160
|
-
loan_token,
|
|
10161
|
-
session,
|
|
10162
|
-
SUM(contribution_in_loan) AS total_available
|
|
10163
|
-
FROM (
|
|
10164
|
-
-- Take max contribution per position using DISTINCT ON (idiomatic PostgreSQL)
|
|
10165
|
-
SELECT DISTINCT ON (clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user)
|
|
10166
|
-
clc.*
|
|
10167
|
-
FROM callback_loan_contribution clc
|
|
10168
|
-
WHERE clc.callback_id IS NOT NULL
|
|
10169
|
-
ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
|
|
10170
|
-
) deduped
|
|
10171
|
-
GROUP BY hash, obligation_id, assets, tick, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
|
|
10172
|
-
callback_address, callback_data, block_number, group_chain_id, group_maker,
|
|
10173
|
-
consumed, chain_id, loan_token, session, receiver_if_maker_is_seller
|
|
10174
|
-
UNION ALL
|
|
10175
|
-
-- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
|
|
10176
|
-
SELECT
|
|
10177
|
-
p.hash, p.obligation_id, p.assets, p.tick,
|
|
10178
|
-
p.obligation_units, p.obligation_shares,
|
|
10179
|
-
p.maturity, p.expiry, p.start, p.group_group,
|
|
10180
|
-
p.buy, p.callback_address, p.callback_data,
|
|
10181
|
-
p.receiver_if_maker_is_seller,
|
|
10182
|
-
p.block_number, p.group_chain_id, p.group_maker,
|
|
10183
|
-
p.consumed, p.chain_id, p.loan_token, p.session,
|
|
10184
|
-
0 AS total_available
|
|
10185
|
-
FROM paged p
|
|
10186
|
-
WHERE p.buy = false
|
|
10187
|
-
AND NOT EXISTS (
|
|
10188
|
-
SELECT 1 FROM ${offersCallbacks} oc2
|
|
10189
|
-
WHERE oc2.offer_hash = p.hash
|
|
10190
|
-
AND oc2.obligation_id = p.obligation_id
|
|
10191
|
-
)
|
|
10192
11157
|
)
|
|
10193
|
-
-- Final SELECT with inline takeable computation
|
|
10194
11158
|
SELECT
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
CASE WHEN oc.buy = false
|
|
10218
|
-
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
10219
|
-
ELSE GREATEST(0, LEAST(
|
|
10220
|
-
oc.assets::numeric - oc.consumed::numeric,
|
|
10221
|
-
COALESCE(oc.total_available, 0)
|
|
10222
|
-
))
|
|
10223
|
-
END AS takeable,
|
|
11159
|
+
p.hash,
|
|
11160
|
+
p.obligation_id,
|
|
11161
|
+
p.group_maker,
|
|
11162
|
+
p.assets,
|
|
11163
|
+
p.obligation_units,
|
|
11164
|
+
p.obligation_shares,
|
|
11165
|
+
p.consumed,
|
|
11166
|
+
p.tick,
|
|
11167
|
+
p.maturity,
|
|
11168
|
+
p.expiry,
|
|
11169
|
+
p.start,
|
|
11170
|
+
p.group_group AS "group",
|
|
11171
|
+
p.buy,
|
|
11172
|
+
p.chain_id,
|
|
11173
|
+
p.loan_token,
|
|
11174
|
+
p.callback_address,
|
|
11175
|
+
p.callback_data,
|
|
11176
|
+
p.receiver_if_maker_is_seller,
|
|
11177
|
+
p.block_number,
|
|
11178
|
+
p.session,
|
|
11179
|
+
p.available,
|
|
11180
|
+
p.takeable,
|
|
10224
11181
|
c.collaterals
|
|
10225
|
-
FROM
|
|
10226
|
-
LEFT JOIN collats c ON c.obligation_id =
|
|
10227
|
-
WHERE CASE WHEN oc.buy = false
|
|
10228
|
-
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
10229
|
-
ELSE GREATEST(0, LEAST(
|
|
10230
|
-
oc.assets::numeric - oc.consumed::numeric,
|
|
10231
|
-
COALESCE(oc.total_available, 0)
|
|
10232
|
-
))
|
|
10233
|
-
END > 0
|
|
11182
|
+
FROM paged p
|
|
11183
|
+
LEFT JOIN collats c ON c.obligation_id = p.obligation_id
|
|
10234
11184
|
ORDER BY
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
11185
|
+
p.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
|
|
11186
|
+
p.block_number ASC,
|
|
11187
|
+
p.assets DESC,
|
|
11188
|
+
p.hash ASC;
|
|
10239
11189
|
`);
|
|
10240
11190
|
return {
|
|
10241
11191
|
rows: raw.rows.map((row) => {
|
|
@@ -10351,7 +11301,7 @@ function create$10(db) {
|
|
|
10351
11301
|
const callbacksRows = [];
|
|
10352
11302
|
const offersCallbacksRows = [];
|
|
10353
11303
|
const callbackId = (input) => {
|
|
10354
|
-
const preimage = `0x${input.chainId}${input.contract}${input.user}
|
|
11304
|
+
const preimage = `0x${input.chainId}${input.contract}${input.user}`.toLowerCase();
|
|
10355
11305
|
const id = idCache.get(preimage) ?? keccak256(preimage);
|
|
10356
11306
|
idCache.set(preimage, id);
|
|
10357
11307
|
return id;
|
|
@@ -10364,8 +11314,8 @@ function create$10(db) {
|
|
|
10364
11314
|
chainId: callback.chainId,
|
|
10365
11315
|
contract: callback.contract.toLowerCase(),
|
|
10366
11316
|
user: callback.user.toLowerCase(),
|
|
10367
|
-
|
|
10368
|
-
|
|
11317
|
+
positionTypeId: callback.positionTypeId,
|
|
11318
|
+
type: callback.type
|
|
10369
11319
|
};
|
|
10370
11320
|
const id = callbackId(normalized);
|
|
10371
11321
|
offersCallbacksRows.push({
|
|
@@ -10381,7 +11331,7 @@ function create$10(db) {
|
|
|
10381
11331
|
positionContract: normalized.contract,
|
|
10382
11332
|
positionUser: normalized.user,
|
|
10383
11333
|
positionTypeId: normalized.positionTypeId,
|
|
10384
|
-
|
|
11334
|
+
type: normalized.type
|
|
10385
11335
|
});
|
|
10386
11336
|
}
|
|
10387
11337
|
}
|
|
@@ -10405,10 +11355,10 @@ function create$9(db) {
|
|
|
10405
11355
|
return {
|
|
10406
11356
|
create: async (events) => {
|
|
10407
11357
|
if (events.length === 0) return;
|
|
10408
|
-
const groups$
|
|
11358
|
+
const groups$3 = /* @__PURE__ */ new Map();
|
|
10409
11359
|
for (const event of events) {
|
|
10410
11360
|
const groupId = `${event.chainId}-${event.maker}-${event.group}`.toLowerCase();
|
|
10411
|
-
groups$
|
|
11361
|
+
groups$3.set(groupId, {
|
|
10412
11362
|
chainId: event.chainId,
|
|
10413
11363
|
maker: event.maker,
|
|
10414
11364
|
group: event.group,
|
|
@@ -10416,7 +11366,7 @@ function create$9(db) {
|
|
|
10416
11366
|
});
|
|
10417
11367
|
}
|
|
10418
11368
|
await db.transaction(async (dbTx) => {
|
|
10419
|
-
const groupsRows = Array.from(groups$
|
|
11369
|
+
const groupsRows = Array.from(groups$3.values()).map((group) => ({
|
|
10420
11370
|
chainId: group.chainId,
|
|
10421
11371
|
maker: group.maker.toLowerCase(),
|
|
10422
11372
|
group: group.group.toLowerCase(),
|
|
@@ -10444,23 +11394,42 @@ function create$9(db) {
|
|
|
10444
11394
|
|
|
10445
11395
|
//#endregion
|
|
10446
11396
|
//#region src/database/domains/Groups.ts
|
|
11397
|
+
/** Build composite key for a group. */
|
|
11398
|
+
function compositeKey(g) {
|
|
11399
|
+
return `${g.chainId}-${g.maker.toLowerCase()}-${g.group.toLowerCase()}`;
|
|
11400
|
+
}
|
|
10447
11401
|
/**
|
|
10448
11402
|
* Create a groups domain instance.
|
|
10449
11403
|
* @param db - Database core instance.
|
|
10450
11404
|
* @returns Groups domain. {@link GroupsDomain}
|
|
10451
11405
|
*/
|
|
10452
11406
|
function create$8(db) {
|
|
10453
|
-
return {
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
11407
|
+
return {
|
|
11408
|
+
create: async (groups$1) => {
|
|
11409
|
+
if (groups$1.length === 0) return;
|
|
11410
|
+
const rows = groups$1.map((group) => ({
|
|
11411
|
+
chainId: group.chainId,
|
|
11412
|
+
maker: group.maker.toLowerCase(),
|
|
11413
|
+
group: group.group.toLowerCase(),
|
|
11414
|
+
consumed: (group.consumed ?? 0n).toString(),
|
|
11415
|
+
blockNumber: group.blockNumber
|
|
11416
|
+
}));
|
|
11417
|
+
for (const batch of batch$1(rows, DEFAULT_BATCH_SIZE$1)) await db.insert(groups).values(batch).onConflictDoNothing();
|
|
11418
|
+
},
|
|
11419
|
+
exists: async (groups$2) => {
|
|
11420
|
+
if (groups$2.length === 0) return [];
|
|
11421
|
+
const tuples = groups$2.map((g) => sql`(${sql.raw(g.chainId.toString())}::bigint, ${g.maker.toLowerCase()}, ${g.group.toLowerCase()})`);
|
|
11422
|
+
return (await db.select({
|
|
11423
|
+
chainId: groups.chainId,
|
|
11424
|
+
maker: groups.maker,
|
|
11425
|
+
group: groups.group
|
|
11426
|
+
}).from(groups).where(sql`(${groups.chainId}, ${groups.maker}, ${groups.group}) IN (${sql.join(tuples, sql`, `)})`)).map((row) => ({
|
|
11427
|
+
chainId: row.chainId,
|
|
11428
|
+
maker: row.maker,
|
|
11429
|
+
group: row.group
|
|
11430
|
+
}));
|
|
11431
|
+
}
|
|
11432
|
+
};
|
|
10464
11433
|
}
|
|
10465
11434
|
|
|
10466
11435
|
//#endregion
|
|
@@ -10812,13 +11781,15 @@ const create$3 = (db) => {
|
|
|
10812
11781
|
group_chain_id,
|
|
10813
11782
|
group_maker,
|
|
10814
11783
|
"group_group",
|
|
11784
|
+
obligation_id,
|
|
10815
11785
|
MAX(assets::numeric) AS assets
|
|
10816
11786
|
FROM ${offers}
|
|
10817
|
-
GROUP BY group_chain_id, group_maker, "group_group"
|
|
11787
|
+
GROUP BY group_chain_id, group_maker, "group_group", obligation_id
|
|
10818
11788
|
) offer_agg
|
|
10819
11789
|
ON offer_agg.group_chain_id = g.chain_id
|
|
10820
11790
|
AND LOWER(offer_agg.group_maker) = LOWER(g.maker)
|
|
10821
11791
|
AND offer_agg."group_group" = g."group"
|
|
11792
|
+
AND offer_agg.obligation_id = l.obligation_id
|
|
10822
11793
|
WHERE LOWER(l."user") = LOWER(${user})
|
|
10823
11794
|
GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
|
|
10824
11795
|
),
|
|
@@ -11189,9 +12160,10 @@ function create$1(db) {
|
|
|
11189
12160
|
};
|
|
11190
12161
|
}
|
|
11191
12162
|
const HEX_32 = /^0x[a-fA-F0-9]{64}$/;
|
|
12163
|
+
const HEX_20 = /^0x[a-fA-F0-9]{40}$/;
|
|
11192
12164
|
function parseCursor(cursor) {
|
|
11193
12165
|
const [offerHash, obligationId] = cursor.split(":");
|
|
11194
|
-
if (!offerHash || !obligationId || !HEX_32.test(offerHash) || !
|
|
12166
|
+
if (!offerHash || !obligationId || !HEX_32.test(offerHash) || !HEX_20.test(obligationId)) throw new Error("Invalid cursor format");
|
|
11195
12167
|
return {
|
|
11196
12168
|
offerHash: offerHash.toLowerCase(),
|
|
11197
12169
|
obligationId: obligationId.toLowerCase()
|
|
@@ -11527,6 +12499,9 @@ async function postMigrate(driver) {
|
|
|
11527
12499
|
WHERE o.group_chain_id = t.group_chain_id
|
|
11528
12500
|
AND o.group_maker = t.group_maker
|
|
11529
12501
|
AND o.group_group = t."group"
|
|
12502
|
+
AND g.chain_id = t.group_chain_id
|
|
12503
|
+
AND g.maker = t.group_maker
|
|
12504
|
+
AND g."group" = t."group"
|
|
11530
12505
|
AND o.assets <= g.consumed;
|
|
11531
12506
|
RETURN NULL;
|
|
11532
12507
|
END;
|
|
@@ -11603,62 +12578,10 @@ async function postMigrate(driver) {
|
|
|
11603
12578
|
EXECUTE FUNCTION cleanup_orphan_groups();
|
|
11604
12579
|
`);
|
|
11605
12580
|
await driver.execute(`
|
|
11606
|
-
|
|
11607
|
-
RETURNS TRIGGER AS $$
|
|
11608
|
-
DECLARE
|
|
11609
|
-
orphan_obligation_ids TEXT[];
|
|
11610
|
-
orphan_obligation_keys TEXT[];
|
|
11611
|
-
BEGIN
|
|
11612
|
-
-- 1. Find orphan obligation IDs
|
|
11613
|
-
SELECT ARRAY_AGG(DISTINCT obligation_id) INTO orphan_obligation_ids
|
|
11614
|
-
FROM deleted_rows d
|
|
11615
|
-
WHERE NOT EXISTS (
|
|
11616
|
-
SELECT 1 FROM "${VERSION}"."offers" ov
|
|
11617
|
-
WHERE ov.obligation_id = d.obligation_id
|
|
11618
|
-
)
|
|
11619
|
-
AND NOT EXISTS (
|
|
11620
|
-
SELECT 1 FROM "${VERSION}"."positions" p
|
|
11621
|
-
JOIN "${VERSION}"."position_types" pt ON pt.id = p.position_type_id
|
|
11622
|
-
WHERE p."contract" = d.obligation_id
|
|
11623
|
-
AND pt.type IN ('debtOf', 'collateralOf')
|
|
11624
|
-
);
|
|
11625
|
-
|
|
11626
|
-
-- 2. If no orphan obligation IDs, exit early
|
|
11627
|
-
IF orphan_obligation_ids IS NULL OR array_length(orphan_obligation_ids, 1) IS NULL THEN
|
|
11628
|
-
RETURN NULL;
|
|
11629
|
-
END IF;
|
|
11630
|
-
|
|
11631
|
-
-- 3. Delete orphan obligation id keys
|
|
11632
|
-
DELETE FROM "${VERSION}"."obligation_id_keys" oia
|
|
11633
|
-
WHERE oia.obligation_id = ANY(orphan_obligation_ids);
|
|
11634
|
-
|
|
11635
|
-
-- 4. Find canonical obligation keys with no remaining obligation id keys
|
|
11636
|
-
SELECT ARRAY_AGG(ob.obligation_key) INTO orphan_obligation_keys
|
|
11637
|
-
FROM "${VERSION}"."obligations" ob
|
|
11638
|
-
WHERE NOT EXISTS (
|
|
11639
|
-
SELECT 1 FROM "${VERSION}"."obligation_id_keys" oia
|
|
11640
|
-
WHERE oia.obligation_key = ob.obligation_key
|
|
11641
|
-
);
|
|
11642
|
-
|
|
11643
|
-
-- 5. If no orphan canonical obligations, exit early
|
|
11644
|
-
IF orphan_obligation_keys IS NULL OR array_length(orphan_obligation_keys, 1) IS NULL THEN
|
|
11645
|
-
RETURN NULL;
|
|
11646
|
-
END IF;
|
|
11647
|
-
|
|
11648
|
-
-- 6. Delete orphan canonical obligations (cascades to collaterals)
|
|
11649
|
-
DELETE FROM "${VERSION}"."obligations" ob
|
|
11650
|
-
WHERE ob.obligation_key = ANY(orphan_obligation_keys);
|
|
11651
|
-
|
|
11652
|
-
RETURN NULL;
|
|
11653
|
-
END;
|
|
11654
|
-
$$ LANGUAGE plpgsql;
|
|
12581
|
+
DROP TRIGGER IF EXISTS trg_cleanup_orphan_obligations_and_oracles ON "${VERSION}"."offers";
|
|
11655
12582
|
`);
|
|
11656
12583
|
await driver.execute(`
|
|
11657
|
-
|
|
11658
|
-
AFTER DELETE ON "${VERSION}"."offers"
|
|
11659
|
-
REFERENCING OLD TABLE AS deleted_rows
|
|
11660
|
-
FOR EACH STATEMENT
|
|
11661
|
-
EXECUTE FUNCTION cleanup_orphan_obligations_and_oracles();
|
|
12584
|
+
DROP FUNCTION IF EXISTS cleanup_orphan_obligations_and_oracles();
|
|
11662
12585
|
`);
|
|
11663
12586
|
await driver.execute(`
|
|
11664
12587
|
CREATE OR REPLACE FUNCTION create_offset_on_lot_delete()
|
|
@@ -11736,6 +12659,21 @@ async function postMigrate(driver) {
|
|
|
11736
12659
|
});
|
|
11737
12660
|
}
|
|
11738
12661
|
|
|
12662
|
+
//#endregion
|
|
12663
|
+
//#region src/observability/TraceHeaders.ts
|
|
12664
|
+
/**
|
|
12665
|
+
* Inject active trace context headers into outgoing HTTP headers.
|
|
12666
|
+
* @param headers - Existing headers for the outgoing request.
|
|
12667
|
+
* @returns Headers enriched with active trace context.
|
|
12668
|
+
*/
|
|
12669
|
+
function withActiveTraceHeaders(headers) {
|
|
12670
|
+
const enrichedHeaders = new Headers(headers);
|
|
12671
|
+
const carrier = {};
|
|
12672
|
+
propagation.inject(context.active(), carrier);
|
|
12673
|
+
for (const [key, value] of Object.entries(carrier)) enrichedHeaders.set(key, value);
|
|
12674
|
+
return enrichedHeaders;
|
|
12675
|
+
}
|
|
12676
|
+
|
|
11739
12677
|
//#endregion
|
|
11740
12678
|
//#region src/gatekeeper/Client.ts
|
|
11741
12679
|
var Client_exports = /* @__PURE__ */ __exportAll({ createHttpClient: () => createHttpClient });
|
|
@@ -11756,7 +12694,7 @@ function createHttpClient(config) {
|
|
|
11756
12694
|
try {
|
|
11757
12695
|
return await fetchFn(`${baseUrl}${path}`, {
|
|
11758
12696
|
...init,
|
|
11759
|
-
headers: mergeHeaders(baseHeaders, init.headers),
|
|
12697
|
+
headers: withActiveTraceHeaders(mergeHeaders(baseHeaders, init.headers)),
|
|
11760
12698
|
signal: controller.signal
|
|
11761
12699
|
});
|
|
11762
12700
|
} finally {
|
|
@@ -11953,8 +12891,12 @@ var Rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
11953
12891
|
amountMutualExclusivity: () => amountMutualExclusivity,
|
|
11954
12892
|
callback: () => callback,
|
|
11955
12893
|
collateralToken: () => collateralToken,
|
|
12894
|
+
groupConsistency: () => groupConsistency,
|
|
12895
|
+
groupImmutability: () => groupImmutability,
|
|
11956
12896
|
loanToken: () => loanToken,
|
|
11957
12897
|
maturity: () => maturity,
|
|
12898
|
+
maxCollaterals: () => maxCollaterals,
|
|
12899
|
+
minDuration: () => minDuration,
|
|
11958
12900
|
oracle: () => oracle,
|
|
11959
12901
|
sameMaker: () => sameMaker
|
|
11960
12902
|
});
|
|
@@ -12021,14 +12963,98 @@ const sameMaker = () => batch("mixed_maker", "Validates that all offers in a bat
|
|
|
12021
12963
|
* At most one of (assets, obligationUnits, obligationShares) can be non-zero.
|
|
12022
12964
|
* Matches contract requirement: `atMostOneNonZero(offer.assets, offer.obligationUnits, offer.obligationShares)`.
|
|
12023
12965
|
*/
|
|
12966
|
+
/**
|
|
12967
|
+
* A validation rule that checks if the offer duration (expiry - start) meets a minimum threshold.
|
|
12968
|
+
* @param minSeconds - Minimum required duration in seconds.
|
|
12969
|
+
* @returns The issue that was found. If the offer is valid, this will be undefined.
|
|
12970
|
+
*/
|
|
12971
|
+
const minDuration = ({ minSeconds }) => single("min_duration", `Validates that offer duration (expiry - start) is at least ${minSeconds}s`, (offer) => {
|
|
12972
|
+
const duration = offer.expiry - offer.start;
|
|
12973
|
+
if (duration < minSeconds) return { message: `Duration ${duration}s is below minimum ${minSeconds}s` };
|
|
12974
|
+
});
|
|
12975
|
+
/**
|
|
12976
|
+
* A validation rule that checks if an offer exceeds the maximum number of collaterals.
|
|
12977
|
+
* The contract enforces this limit; this rule rejects early to avoid on-chain reverts.
|
|
12978
|
+
* @param max - Maximum allowed collaterals per offer.
|
|
12979
|
+
* @returns The issue that was found. If the offer is valid, this will be undefined.
|
|
12980
|
+
*/
|
|
12981
|
+
const maxCollaterals = ({ max }) => single("max_collaterals", `Validates that an offer has at most ${max} collaterals`, (offer) => {
|
|
12982
|
+
if (offer.collaterals.length > max) return { message: `Offer has ${offer.collaterals.length} collaterals, exceeding the maximum of ${max}` };
|
|
12983
|
+
});
|
|
12024
12984
|
const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Validates that at most one of (assets, obligationUnits, obligationShares) is non-zero", (offer) => {
|
|
12025
12985
|
if (!atMostOneNonZero(offer.assets, offer.obligationUnits, offer.obligationShares)) return { message: "Inconsistent offer input: at most one of (assets, obligationUnits, obligationShares) must be non-zero" };
|
|
12026
12986
|
});
|
|
12987
|
+
/**
|
|
12988
|
+
* A batch validation rule that ensures all offers within the same group are consistent.
|
|
12989
|
+
* All offers sharing the same group must have the same loan token, assets amount, and side (buy/sell).
|
|
12990
|
+
*/
|
|
12991
|
+
const groupConsistency = () => batch("group_consistency", "Validates that all offers in a group have the same loan token, assets amount, and side", (offers) => {
|
|
12992
|
+
const issues = /* @__PURE__ */ new Map();
|
|
12993
|
+
if (offers.length === 0) return issues;
|
|
12994
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
12995
|
+
for (let i = 0; i < offers.length; i++) {
|
|
12996
|
+
const key = offers[i].group.toLowerCase();
|
|
12997
|
+
const indices = groupMap.get(key);
|
|
12998
|
+
if (indices) indices.push(i);
|
|
12999
|
+
else groupMap.set(key, [i]);
|
|
13000
|
+
}
|
|
13001
|
+
for (const indices of groupMap.values()) {
|
|
13002
|
+
if (indices.length <= 1) continue;
|
|
13003
|
+
const reference = offers[indices[0]];
|
|
13004
|
+
const refLoanToken = reference.loanToken.toLowerCase();
|
|
13005
|
+
const refAssets = reference.assets;
|
|
13006
|
+
const refBuy = reference.buy;
|
|
13007
|
+
for (let j = 1; j < indices.length; j++) {
|
|
13008
|
+
const idx = indices[j];
|
|
13009
|
+
const offer = offers[idx];
|
|
13010
|
+
if (offer.loanToken.toLowerCase() !== refLoanToken) issues.set(idx, { message: `All offers in a group must have the same loan token. Expected ${reference.loanToken}, got ${offer.loanToken}` });
|
|
13011
|
+
else if (offer.assets !== refAssets) issues.set(idx, { message: `All offers in a group must have the same assets amount. Expected ${refAssets}, got ${offer.assets}` });
|
|
13012
|
+
else if (offer.buy !== refBuy) issues.set(idx, { message: `All offers in a group must be on the same side. Expected ${refBuy ? "buy" : "sell"}, got ${offer.buy ? "buy" : "sell"}` });
|
|
13013
|
+
}
|
|
13014
|
+
}
|
|
13015
|
+
return issues;
|
|
13016
|
+
});
|
|
13017
|
+
/**
|
|
13018
|
+
* A batch validation rule that prevents adding offers to groups that already exist in the database.
|
|
13019
|
+
* Groups are immutable after creation — new offers cannot be added to an existing group.
|
|
13020
|
+
*/
|
|
13021
|
+
const groupImmutability = ({ db, chainId }) => batch("group_immutability", "Validates that offers do not target groups that already exist", async (offers) => {
|
|
13022
|
+
const issues = /* @__PURE__ */ new Map();
|
|
13023
|
+
if (offers.length === 0) return issues;
|
|
13024
|
+
const uniqueGroups = /* @__PURE__ */ new Map();
|
|
13025
|
+
for (const offer of offers) {
|
|
13026
|
+
const key = compositeKey({
|
|
13027
|
+
chainId,
|
|
13028
|
+
maker: offer.maker,
|
|
13029
|
+
group: offer.group
|
|
13030
|
+
});
|
|
13031
|
+
if (!uniqueGroups.has(key)) uniqueGroups.set(key, {
|
|
13032
|
+
chainId,
|
|
13033
|
+
maker: offer.maker,
|
|
13034
|
+
group: offer.group
|
|
13035
|
+
});
|
|
13036
|
+
}
|
|
13037
|
+
const existingKeys = await db.groups.exists(Array.from(uniqueGroups.values()));
|
|
13038
|
+
if (existingKeys.length === 0) return issues;
|
|
13039
|
+
const existingSet = new Set(existingKeys.map((k) => compositeKey(k)));
|
|
13040
|
+
for (let i = 0; i < offers.length; i++) {
|
|
13041
|
+
const offer = offers[i];
|
|
13042
|
+
const key = compositeKey({
|
|
13043
|
+
chainId,
|
|
13044
|
+
maker: offer.maker,
|
|
13045
|
+
group: offer.group
|
|
13046
|
+
});
|
|
13047
|
+
if (existingSet.has(key)) issues.set(i, { message: `Cannot add offers to existing group ${offer.group}` });
|
|
13048
|
+
}
|
|
13049
|
+
return issues;
|
|
13050
|
+
});
|
|
12027
13051
|
|
|
12028
13052
|
//#endregion
|
|
12029
13053
|
//#region src/gatekeeper/morphoRules.ts
|
|
12030
13054
|
const morphoRules = (parameters) => {
|
|
12031
|
-
const { chains, chainId } = parameters;
|
|
13055
|
+
const { chains, chainId, db } = parameters;
|
|
13056
|
+
const requestChain = chains.find((c) => c.id === chainId);
|
|
13057
|
+
const config = requestChain ? configs[requestChain.name] : void 0;
|
|
12032
13058
|
const assetsByChainId = {};
|
|
12033
13059
|
const collateralAssetsByChainId = {};
|
|
12034
13060
|
const oraclesByChainId = {};
|
|
@@ -12037,9 +13063,11 @@ const morphoRules = (parameters) => {
|
|
|
12037
13063
|
collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
|
|
12038
13064
|
oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
|
|
12039
13065
|
}
|
|
12040
|
-
|
|
13066
|
+
const rules = [
|
|
12041
13067
|
sameMaker(),
|
|
12042
13068
|
amountMutualExclusivity(),
|
|
13069
|
+
groupConsistency(),
|
|
13070
|
+
...config?.minDuration != null ? [minDuration({ minSeconds: config.minDuration })] : [],
|
|
12043
13071
|
maturity({ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek] }),
|
|
12044
13072
|
callback({
|
|
12045
13073
|
callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
|
|
@@ -12049,6 +13077,7 @@ const morphoRules = (parameters) => {
|
|
|
12049
13077
|
assetsByChainId,
|
|
12050
13078
|
chainId
|
|
12051
13079
|
}),
|
|
13080
|
+
maxCollaterals({ max: 128 }),
|
|
12052
13081
|
collateralToken({
|
|
12053
13082
|
collateralAssetsByChainId,
|
|
12054
13083
|
chainId
|
|
@@ -12058,6 +13087,11 @@ const morphoRules = (parameters) => {
|
|
|
12058
13087
|
chainId
|
|
12059
13088
|
})
|
|
12060
13089
|
];
|
|
13090
|
+
if (db) rules.push(groupImmutability({
|
|
13091
|
+
db,
|
|
13092
|
+
chainId
|
|
13093
|
+
}));
|
|
13094
|
+
return rules;
|
|
12061
13095
|
};
|
|
12062
13096
|
|
|
12063
13097
|
//#endregion
|