@morpho-dev/router 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,22 @@
1
- import { Errors, LLTV, Offer, Format, Time, Maturity, Utils } from '@morpho-dev/mempool';
1
+ import { Errors, LLTV, Utils, Offer, Format, Chain, Maturity, Time, Mempool } from '@morpho-dev/mempool';
2
2
  export * from '@morpho-dev/mempool';
3
- import { Base64 } from 'js-base64';
3
+ import { parseUnits, publicActions, maxUint256, parseEventLogs, formatUnits, createWalletClient, http, erc20Abi } from 'viem';
4
+ import { getBlockNumber } from 'viem/actions';
5
+ import { AsyncLocalStorage } from 'async_hooks';
4
6
  import { z } from 'zod/v4';
7
+ import { desc, and, eq, sql, gte, lte, asc, inArray } from 'drizzle-orm';
8
+ import { pgSchema, timestamp, varchar, bigint, text, boolean, integer, numeric, index, serial, jsonb, uniqueIndex, primaryKey } from 'drizzle-orm/pg-core';
9
+ import { Base64 } from 'js-base64';
5
10
  import { createDocument } from 'zod-openapi';
6
- import { parseUnits, maxUint256, formatUnits, erc20Abi } from 'viem';
7
11
  import { serve as serve$1 } from '@hono/node-server';
8
12
  import { Hono } from 'hono';
9
- import { AsyncLocalStorage } from 'async_hooks';
13
+ import path from 'path';
14
+ import { PGlite } from '@electric-sql/pglite';
15
+ import { drizzle } from 'drizzle-orm/node-postgres';
16
+ import { migrate } from 'drizzle-orm/node-postgres/migrator';
17
+ import { drizzle as drizzle$1 } from 'drizzle-orm/pglite';
18
+ import { migrate as migrate$1 } from 'drizzle-orm/pglite/migrator';
19
+ import { Pool } from 'pg';
10
20
 
11
21
  var __defProp = Object.defineProperty;
12
22
  var __export = (target, all) => {
@@ -26,7 +36,7 @@ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
26
36
  return CallbackType2;
27
37
  })(CallbackType || {});
28
38
  function buildLiquidity(parameters) {
29
- const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
39
+ const { type, user, contract, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
30
40
  if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
31
41
  throw new Error(`CallbackType not implemented: ${type}`);
32
42
  const amountStr = amount.toString();
@@ -45,7 +55,7 @@ function buildLiquidity(parameters) {
45
55
  queue: {
46
56
  queueId: id,
47
57
  availableLiquidityPoolId: id,
48
- index,
58
+ index: index2,
49
59
  updatedAt
50
60
  },
51
61
  pool: {
@@ -67,101 +77,436 @@ function getCallbackIdForOffer(offer) {
67
77
  return null;
68
78
  }
69
79
 
70
- // src/Cursor.ts
71
- var Cursor_exports = {};
72
- __export(Cursor_exports, {
73
- decode: () => decode,
74
- encode: () => encode,
75
- validate: () => validate
80
+ // src/Collector/index.ts
81
+ var Collector_exports = {};
82
+ __export(Collector_exports, {
83
+ createBuyWithEmptyCallbackLiquidityCollector: () => createBuyWithEmptyCallbackLiquidityCollector,
84
+ createChainReorgsCollector: () => createChainReorgsCollector,
85
+ createConsumedEventsCollector: () => createConsumedEventsCollector,
86
+ createMempoolCollector: () => createMempoolCollector,
87
+ start: () => start
76
88
  });
77
- function validate(cursor) {
78
- if (!cursor || typeof cursor !== "object") {
79
- throw new Error("Cursor must be an object");
80
- }
81
- const c = cursor;
82
- if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
83
- throw new Error(
84
- `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
85
- );
86
- }
87
- if (!["asc", "desc"].includes(c.dir)) {
88
- throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
89
+
90
+ // src/Liquidity.ts
91
+ var Liquidity_exports = {};
92
+ __export(Liquidity_exports, {
93
+ fetch: () => fetch2,
94
+ fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
95
+ serialize: () => serialize
96
+ });
97
+ async function fetchBalancesAndAllowances(parameters) {
98
+ const { client, spender, pairs, options } = parameters;
99
+ if (pairs.length === 0) return /* @__PURE__ */ new Map();
100
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
101
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
102
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
103
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
104
+ const out = /* @__PURE__ */ new Map();
105
+ for (const pairsBatch of Utils.batch(pairs, batchSize)) {
106
+ const balanceContracts = [];
107
+ const allowanceContracts = [];
108
+ for (const { user, token } of pairsBatch) {
109
+ balanceContracts.push({
110
+ address: token,
111
+ abi: erc20Abi,
112
+ functionName: "balanceOf",
113
+ args: [user]
114
+ });
115
+ allowanceContracts.push({
116
+ address: token,
117
+ abi: erc20Abi,
118
+ functionName: "allowance",
119
+ args: [user, spender]
120
+ });
121
+ }
122
+ const [balances, allowances] = await Promise.all([
123
+ Utils.retry(
124
+ () => client.multicall({
125
+ allowFailure: false,
126
+ contracts: balanceContracts,
127
+ ...blockNumber ? { blockNumber } : {}
128
+ }),
129
+ retryAttempts,
130
+ retryDelayMs
131
+ ),
132
+ Utils.retry(
133
+ () => client.multicall({
134
+ allowFailure: false,
135
+ contracts: allowanceContracts,
136
+ ...blockNumber ? { blockNumber } : {}
137
+ }),
138
+ retryAttempts,
139
+ retryDelayMs
140
+ )
141
+ ]);
142
+ for (let i = 0; i < pairsBatch.length; i++) {
143
+ const { user, token } = pairsBatch[i];
144
+ const balance = balances[i];
145
+ const allowance = allowances[i];
146
+ let perUser = out.get(user);
147
+ if (!perUser) {
148
+ perUser = /* @__PURE__ */ new Map();
149
+ out.set(user, perUser);
150
+ }
151
+ perUser.set(token, { balance, allowance });
152
+ }
89
153
  }
90
- if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
91
- throw new Error(
92
- `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
93
- );
154
+ return out;
155
+ }
156
+ async function fetch2(parameters) {
157
+ const { client, chainId, spender, type, pairs, options } = parameters;
158
+ if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
159
+ throw new Error(`CallbackType not implemented: ${type}`);
160
+ const map = await fetchBalancesAndAllowances({
161
+ client,
162
+ spender,
163
+ pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
164
+ options
165
+ });
166
+ const out = [];
167
+ for (const [user, perContract] of map) {
168
+ for (const [contract, { balance, allowance }] of perContract) {
169
+ const amount = balance < allowance ? balance : allowance;
170
+ out.push(
171
+ buildLiquidity({
172
+ type,
173
+ user,
174
+ contract,
175
+ chainId,
176
+ amount: amount.toString(),
177
+ index: 0
178
+ })
179
+ );
180
+ }
94
181
  }
95
- const validations = {
96
- rate: {
97
- field: "rate",
98
- type: "string",
99
- pattern: /^\d+$/,
100
- error: "numeric string"
101
- },
102
- amount: {
103
- field: "assets",
104
- type: "string",
105
- pattern: /^\d+$/,
106
- error: "numeric string"
107
- },
108
- maturity: {
109
- field: "maturity",
110
- type: "number",
111
- validator: (val) => val > 0,
112
- error: "positive number"
182
+ return out;
183
+ }
184
+ function serialize(liquidity) {
185
+ const normalized = {
186
+ userPosition: {
187
+ id: liquidity.userPosition.id,
188
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
189
+ user: liquidity.userPosition.user,
190
+ chainId: String(liquidity.userPosition.chainId),
191
+ amount: String(liquidity.userPosition.amount)
113
192
  },
114
- expiry: {
115
- field: "expiry",
116
- type: "number",
117
- validator: (val) => val > 0,
118
- error: "positive number"
119
- }
193
+ queues: liquidity.queues.map((queueWithPool) => ({
194
+ queue: {
195
+ queueId: queueWithPool.queue.queueId,
196
+ availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
197
+ index: queueWithPool.queue.index
198
+ },
199
+ pool: {
200
+ id: queueWithPool.pool.id,
201
+ amount: String(queueWithPool.pool.amount)
202
+ }
203
+ })).sort(
204
+ (left, right) => {
205
+ const leftQueueId = left.queue.queueId || "";
206
+ const rightQueueId = right.queue.queueId || "";
207
+ if (leftQueueId < rightQueueId) return -1;
208
+ if (leftQueueId > rightQueueId) return 1;
209
+ const leftPoolId = left.pool.id;
210
+ const rightPoolId = right.pool.id;
211
+ if (leftPoolId < rightPoolId) return -1;
212
+ if (leftPoolId > rightPoolId) return 1;
213
+ const leftIndex = left.queue.index;
214
+ const rightIndex = right.queue.index;
215
+ if (leftIndex < rightIndex) return -1;
216
+ if (leftIndex > rightIndex) return 1;
217
+ return 0;
218
+ }
219
+ )
120
220
  };
121
- const validation = validations[c.sort];
122
- if (!validation) {
123
- throw new Error(`Invalid sort field: ${c.sort}`);
124
- }
125
- const fieldValue = c[validation.field];
126
- if (!fieldValue) {
127
- throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
128
- }
129
- if (typeof fieldValue !== validation.type) {
130
- throw new Error(
131
- `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
132
- );
133
- }
134
- if (validation.pattern && !validation.pattern.test(fieldValue)) {
135
- throw new Error(
136
- `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
137
- );
138
- }
139
- if (validation.validator && !validation.validator(fieldValue)) {
140
- throw new Error(
141
- `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
221
+ return JSON.stringify(normalized);
222
+ }
223
+
224
+ // src/Collector/BuyWithEmptyCallbackLiquidityCollector.ts
225
+ function createBuyWithEmptyCallbackLiquidityCollector(params) {
226
+ const {
227
+ client,
228
+ collectorBlockStore,
229
+ liquidityStore,
230
+ offerStore,
231
+ chain,
232
+ options: { maxBatchSize, interval } = {}
233
+ } = params;
234
+ const collectorName = "buy_with_empty_callback_liquidity";
235
+ async function getUniqueUserTokenPairs() {
236
+ const unique = /* @__PURE__ */ new Set();
237
+ const pairs = [];
238
+ let cursor;
239
+ do {
240
+ const { offers: offers2, nextCursor } = await offerStore.getAll({
241
+ query: {
242
+ side: "buy",
243
+ chains: [Number(chain.id)],
244
+ cursor,
245
+ limit: 1e3
246
+ }
247
+ });
248
+ cursor = nextCursor ?? void 0;
249
+ for (const offer of offers2) {
250
+ const creator = offer.offering;
251
+ const loanToken = offer.loanToken;
252
+ const key = `${offer.offering}-${offer.loanToken}`.toLowerCase();
253
+ if (unique.has(key)) continue;
254
+ unique.add(key);
255
+ pairs.push({ user: creator, token: loanToken });
256
+ }
257
+ } while (cursor);
258
+ return pairs;
259
+ }
260
+ const collect = Utils.lazy((emit) => {
261
+ return Utils.poll(
262
+ async () => {
263
+ const publicClient = client.extend(publicActions);
264
+ const pairs = await getUniqueUserTokenPairs();
265
+ const latestBlockNumber = await getBlockNumber(publicClient);
266
+ const liquidityEntries = await fetch2({
267
+ client: publicClient,
268
+ chainId: chain.id,
269
+ spender: chain.morpho,
270
+ type: "buy_with_empty_callback" /* BuyWithEmptyCallback */,
271
+ pairs: pairs.map(({ user, token }) => ({ user, contract: token })),
272
+ options: {
273
+ blockNumber: Number(latestBlockNumber),
274
+ batchSize: maxBatchSize
275
+ }
276
+ });
277
+ const existingLiquidityEntries = await liquidityStore.getAll();
278
+ const existingById = /* @__PURE__ */ new Map();
279
+ for (const liquidityEntry of existingLiquidityEntries) {
280
+ const liquidityId = liquidityEntry.userPosition.id;
281
+ const serializedEntry = serialize(liquidityEntry);
282
+ existingById.set(liquidityId, serializedEntry);
283
+ }
284
+ for (const liquidity of liquidityEntries) {
285
+ const serialized = serialize(liquidity);
286
+ const prevSerialized = existingById.get(liquidity.userPosition.id);
287
+ if (prevSerialized === serialized) continue;
288
+ await liquidityStore.save({ liquidity });
289
+ }
290
+ await collectorBlockStore.saveBlockNumber({
291
+ collectorName,
292
+ chainId: chain.id,
293
+ blockNumber: Number(latestBlockNumber)
294
+ });
295
+ emit(Number(latestBlockNumber));
296
+ },
297
+ { interval: interval ?? 3e4 }
142
298
  );
143
- }
144
- return true;
299
+ });
300
+ const onReorg = (_lastFinalizedBlockNumber) => {
301
+ };
302
+ return {
303
+ name: collectorName,
304
+ lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId: chain.id }),
305
+ collect,
306
+ onReorg
307
+ };
145
308
  }
146
- function encode(c) {
147
- return Base64.encodeURL(JSON.stringify(c));
309
+ function createChainReorgsCollector(parameters) {
310
+ const collectorName = "chain_reorgs";
311
+ const {
312
+ client,
313
+ subscribers,
314
+ collectorStore,
315
+ chain,
316
+ options: { maxBatchSize = 25, interval } = {}
317
+ } = parameters;
318
+ let finalizedBlock = null;
319
+ let unfinalizedBlocks = [];
320
+ const commonAncestor = (block) => {
321
+ const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
322
+ if (parent) return parent;
323
+ if (finalizedBlock == null) throw new Error("Failed to get common ancestor");
324
+ return finalizedBlock;
325
+ };
326
+ const reconcile = async (block) => {
327
+ if (block.hash === null || block.number === null || block.parentHash === null)
328
+ throw new Error("Failed to get block");
329
+ const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
330
+ if (latestBlock === void 0) {
331
+ unfinalizedBlocks.push({
332
+ hash: block.hash,
333
+ number: block.number,
334
+ parentHash: block.parentHash
335
+ });
336
+ return;
337
+ }
338
+ if (latestBlock.hash === block.hash) return;
339
+ if (latestBlock.number >= block.number) {
340
+ const ancestor = commonAncestor(block);
341
+ console.log(
342
+ `reorg detected, latestBlock.number: ${latestBlock.number} > block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
343
+ );
344
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
345
+ unfinalizedBlocks = [];
346
+ return;
347
+ }
348
+ if (latestBlock.number + 1n < block.number) {
349
+ console.log(
350
+ `missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
351
+ );
352
+ const missingBlockNumbers = (() => {
353
+ const missingBlockNumbers2 = [];
354
+ let start2 = latestBlock.number + 1n;
355
+ const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
356
+ while (start2 < threshold) {
357
+ missingBlockNumbers2.push(start2);
358
+ start2 = start2 + 1n;
359
+ }
360
+ return missingBlockNumbers2;
361
+ })();
362
+ const missingBlocks = await Promise.all(
363
+ missingBlockNumbers.map(
364
+ (blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
365
+ )
366
+ );
367
+ for (const missingBlock of missingBlocks) await reconcile(missingBlock);
368
+ await reconcile(block);
369
+ return;
370
+ }
371
+ if (block.parentHash !== latestBlock.hash) {
372
+ const ancestor = commonAncestor(block);
373
+ console.log(
374
+ `reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
375
+ );
376
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
377
+ unfinalizedBlocks = [];
378
+ return;
379
+ }
380
+ unfinalizedBlocks.push({
381
+ hash: block.hash,
382
+ number: block.number,
383
+ parentHash: block.parentHash
384
+ });
385
+ };
386
+ const collect = Utils.lazy((emit) => {
387
+ let tick = 0;
388
+ const fetchFinalizedBlock = async () => {
389
+ if (tick % 20 === 0 || finalizedBlock === null) {
390
+ finalizedBlock = await client.getBlock({
391
+ blockTag: "finalized",
392
+ includeTransactions: false
393
+ });
394
+ if (finalizedBlock === null) throw new Error("Failed to get finalized block");
395
+ }
396
+ tick++;
397
+ };
398
+ return Utils.poll(
399
+ async () => {
400
+ await fetchFinalizedBlock();
401
+ const latestBlock = await client.getBlock({
402
+ blockTag: "latest",
403
+ includeTransactions: false
404
+ });
405
+ await reconcile(latestBlock);
406
+ const lastBlockNumber = Number(latestBlock.number);
407
+ await collectorStore.saveBlockNumber({
408
+ collectorName: "chain_reorgs",
409
+ chainId: chain.id,
410
+ blockNumber: lastBlockNumber
411
+ });
412
+ emit(lastBlockNumber);
413
+ },
414
+ { interval: interval ?? 3e4 }
415
+ );
416
+ });
417
+ return {
418
+ name: collectorName,
419
+ lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName, chainId: chain.id }),
420
+ collect,
421
+ onReorg: (_) => {
422
+ }
423
+ };
148
424
  }
149
- function decode(token) {
150
- if (!token) return null;
151
- const decoded = JSON.parse(Base64.decode(token));
152
- validate(decoded);
153
- return decoded;
425
+
426
+ // src/Collector/Collector.ts
427
+ function start(collector) {
428
+ let stopped = false;
429
+ const it = collector.collect();
430
+ (async () => {
431
+ while (!stopped) await it.next();
432
+ await it.return();
433
+ })();
434
+ return () => {
435
+ stopped = true;
436
+ };
154
437
  }
155
438
 
156
- // src/core/apiSchema/index.ts
157
- var apiSchema_exports = {};
158
- __export(apiSchema_exports, {
159
- OpenApi: () => OpenApi,
160
- fromResponse: () => fromResponse,
161
- parse: () => parse,
162
- safeParse: () => safeParse,
163
- toResponse: () => toResponse
439
+ // src/Logger.ts
440
+ var Logger_exports = {};
441
+ __export(Logger_exports, {
442
+ LogLevelValues: () => LogLevelValues,
443
+ defaultLogger: () => defaultLogger,
444
+ getLogger: () => getLogger,
445
+ runWithLogger: () => runWithLogger,
446
+ silentLogger: () => silentLogger
164
447
  });
448
+ var LogLevelValues = [
449
+ "silent",
450
+ "trace",
451
+ "debug",
452
+ "info",
453
+ "warn",
454
+ "error",
455
+ "fatal"
456
+ ];
457
+ function defaultLogger(minLevel) {
458
+ const threshold = minLevel ?? "trace";
459
+ const levelIndexByName = LogLevelValues.reduce(
460
+ (acc, lvl, idx) => {
461
+ acc[lvl] = idx;
462
+ return acc;
463
+ },
464
+ {}
465
+ );
466
+ const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
467
+ return {
468
+ // biome-ignore lint/suspicious/noConsole: console is used for logging
469
+ trace: isEnabled("trace") ? console.trace.bind(console) : () => {
470
+ },
471
+ // biome-ignore lint/suspicious/noConsole: console is used for logging
472
+ debug: isEnabled("debug") ? console.debug.bind(console) : () => {
473
+ },
474
+ // biome-ignore lint/suspicious/noConsole: console is used for logging
475
+ info: isEnabled("info") ? console.info.bind(console) : () => {
476
+ },
477
+ // biome-ignore lint/suspicious/noConsole: console is used for logging
478
+ warn: isEnabled("warn") ? console.warn.bind(console) : () => {
479
+ },
480
+ // biome-ignore lint/suspicious/noConsole: console is used for logging
481
+ error: isEnabled("error") ? console.error.bind(console) : () => {
482
+ },
483
+ fatal: isEnabled("fatal") ? (...args) => console.error("[fatal]", ...args) : () => {
484
+ }
485
+ };
486
+ }
487
+ function silentLogger() {
488
+ return {
489
+ trace: () => {
490
+ },
491
+ debug: () => {
492
+ },
493
+ info: () => {
494
+ },
495
+ warn: () => {
496
+ },
497
+ error: () => {
498
+ },
499
+ fatal: () => {
500
+ }
501
+ };
502
+ }
503
+ var loggerContext = new AsyncLocalStorage();
504
+ function runWithLogger(logger, fn) {
505
+ return loggerContext.run(logger, fn);
506
+ }
507
+ function getLogger() {
508
+ return loggerContext.getStore() ?? defaultLogger();
509
+ }
165
510
 
166
511
  // src/RouterOffer.ts
167
512
  var RouterOffer_exports = {};
@@ -243,158 +588,715 @@ var InvalidRouterOfferError = class extends Errors.BaseError {
243
588
  }
244
589
  };
245
590
 
246
- // src/core/apiSchema/requests.ts
247
- var MAX_LIMIT = 100;
248
- var DEFAULT_LIMIT = 20;
249
- var MAX_LLTV = 100;
250
- var MIN_LLTV = 0;
251
- var GetOffersQueryParams = z.object({
252
- // Core filtering parameters
253
- creators: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
254
- message: "Creators must be comma-separated Ethereum addresses"
255
- }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
256
- description: "Filter by multiple creator addresses (comma-separated)",
257
- example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
258
- }),
259
- side: z.enum(["buy", "sell"]).optional().meta({
260
- description: "Filter by offer type: buy offers or sell offers",
261
- example: "buy"
262
- }),
263
- chains: z.string().regex(/^\d+(,\d+)*$/, {
264
- message: "Chains must be comma-separated chain IDs"
265
- }).transform((val) => val.split(",").map(Number)).optional().meta({
266
- description: "Filter by multiple blockchain networks (comma-separated chain IDs)",
267
- example: "1,137,10"
268
- }),
269
- loan_tokens: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
270
- message: "Loan assets must be comma-separated Ethereum addresses"
271
- }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
272
- description: "Filter by multiple loan assets (comma-separated)",
273
- example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
274
- }),
275
- status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
276
- message: "Status must be comma-separated status values"
277
- }).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
278
- message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
279
- }).optional().meta({
280
- description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
281
- example: "valid,callback_error"
282
- }),
283
- callback_addresses: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
284
- message: "Callback addresses must be comma-separated Ethereum addresses"
285
- }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
286
- description: "Filter by multiple callback addresses (comma-separated)",
287
- example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
288
- }),
289
- // Asset range
290
- min_amount: z.bigint({ coerce: true }).positive({
291
- message: "Min amount must be a positive number"
292
- }).optional().meta({
293
- description: "Minimum amount of assets in the offer",
294
- example: "1000"
295
- }),
296
- max_amount: z.bigint({ coerce: true }).positive({
297
- message: "Max amount must be a positive number"
298
- }).optional().meta({
299
- description: "Maximum amount of assets in the offer",
300
- example: "10000"
301
- }),
302
- // Rate range
303
- min_rate: z.bigint({ coerce: true }).positive({
304
- message: "Min rate must be a positive number"
305
- }).optional().meta({
306
- description: "Minimum rate per asset (in wei)",
307
- example: "500000000000000000"
308
- }),
309
- max_rate: z.bigint({ coerce: true }).positive({
310
- message: "Max rate must be a positive number"
311
- }).optional().meta({
312
- description: "Maximum rate per asset (in wei)",
313
- example: "1500000000000000000"
314
- }),
315
- // Time range
316
- min_maturity: z.coerce.number().int().min(0).optional().meta({
317
- description: "Minimum maturity timestamp (Unix timestamp in seconds)",
318
- example: "1700000000"
319
- }),
320
- max_maturity: z.coerce.number().int().min(0).optional().meta({
321
- description: "Maximum maturity timestamp (Unix timestamp in seconds)",
322
- example: "1800000000"
323
- }),
324
- min_expiry: z.coerce.number().int().optional().meta({
325
- description: "Minimum expiry timestamp (Unix timestamp in seconds)",
326
- example: "1700000000"
327
- }),
328
- max_expiry: z.coerce.number().int().optional().meta({
329
- description: "Maximum expiry timestamp (Unix timestamp in seconds)",
330
- example: "1800000000"
331
- }),
332
- // Collateral filtering
333
- collateral_assets: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
334
- message: "Collateral assets must be comma-separated Ethereum addresses"
335
- }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
336
- description: "Filter by multiple collateral assets (comma-separated)",
337
- example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
338
- }),
339
- collateral_oracles: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
340
- message: "Collateral oracles must be comma-separated Ethereum addresses"
341
- }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
342
- description: "Filter by multiple rate oracles (comma-separated)",
343
- example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
344
- }),
345
- collateral_tuple: z.string().transform((val, ctx) => {
346
- const pattern = /^(0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)(#0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)*$/;
347
- if (!pattern.test(val)) {
348
- ctx.addIssue({
349
- code: "custom",
350
- message: "collateral_tuple has an invalid format",
351
- input: val
352
- });
353
- }
354
- return val.split("#").map((tuple) => {
355
- const parts = tuple.split(":");
356
- if (parts.length === 0 || !parts[0]) {
357
- ctx.addIssue({
358
- code: "custom",
359
- message: "Asset address is required for each collateral tuple",
360
- path: ["asset"],
361
- input: val
591
+ // src/Collector/ConsumedEventsCollector.ts
592
+ function createConsumedEventsCollector(parameters) {
593
+ const {
594
+ client,
595
+ contractAddress,
596
+ offerStore,
597
+ collectorBlockStore,
598
+ options: { maxBatchSize, interval } = {},
599
+ chainId
600
+ } = parameters;
601
+ const logger = getLogger();
602
+ const collectorName = "consumed_events";
603
+ const collect = Utils.lazy((emit) => {
604
+ return Utils.poll(
605
+ async () => {
606
+ const lastSyncedBlock = await collectorBlockStore.getBlockNumber({
607
+ collectorName,
608
+ chainId
362
609
  });
363
- return z.NEVER;
364
- }
365
- const asset = parts[0]?.toLowerCase();
366
- const oracle = parts[1]?.toLowerCase();
367
- const lltv = parts[2] ? parseFloat(parts[2]) : void 0;
368
- if (lltv !== void 0 && (lltv < MIN_LLTV || lltv > MAX_LLTV)) {
369
- ctx.addIssue({
370
- code: "custom",
371
- message: `LLTV must be between ${MIN_LLTV} and ${MAX_LLTV} (0-100%)`,
372
- path: ["lltv"],
373
- input: val
610
+ const stream = Chain.streamLogs({
611
+ client,
612
+ contractAddress,
613
+ event: consumedEvent,
614
+ blockNumberGte: lastSyncedBlock,
615
+ order: "asc",
616
+ options: { maxBatchSize }
374
617
  });
375
- return z.NEVER;
376
- }
377
- let lltvValue;
378
- if (lltv !== void 0) {
379
- try {
380
- lltvValue = LLTV.from(parseUnits(lltv.toString(), 16));
381
- } catch (e) {
382
- ctx.issues.push({
383
- code: "custom",
384
- message: e instanceof LLTV.InvalidLLTVError || e instanceof LLTV.InvalidOptionError ? e.message : "Invalid LLTV.",
385
- input: lltv,
386
- path: ["lltv"]
387
- });
388
- return z.NEVER;
618
+ for await (const logs of stream) {
619
+ if (logs.length === 0) break;
620
+ const parsedLogs = parseEventLogs({ abi: [consumedEvent], logs });
621
+ const offersConsumed = [];
622
+ for (const log of parsedLogs) {
623
+ offersConsumed.push(
624
+ fromConsumedLog({
625
+ blockNumber: log.blockNumber,
626
+ logIndex: log.logIndex,
627
+ chainId: Number(chainId),
628
+ transactionHash: log.transactionHash,
629
+ user: log.args.user,
630
+ nonce: log.args.nonce,
631
+ amount: log.args.amount
632
+ })
633
+ );
634
+ }
635
+ try {
636
+ for (const offerConsumed of offersConsumed) {
637
+ logger.debug?.("updating consumed amount", { offerConsumed });
638
+ await offerStore.updateConsumedAmount({
639
+ id: offerConsumed.id,
640
+ chainId: offerConsumed.chainId,
641
+ offering: offerConsumed.offering,
642
+ nonce: offerConsumed.nonce,
643
+ consumed: offerConsumed.amount
644
+ });
645
+ }
646
+ const lastBlockNumber = Number(logs[logs.length - 1]?.blockNumber);
647
+ await collectorBlockStore.saveBlockNumber({
648
+ collectorName: "consumed_events",
649
+ chainId,
650
+ blockNumber: lastBlockNumber
651
+ });
652
+ emit(lastBlockNumber);
653
+ } catch (err) {
654
+ logger.error({ err }, "Failed to process offer_consumed events");
655
+ }
656
+ }
657
+ },
658
+ { interval: interval ?? 3e4 }
659
+ );
660
+ });
661
+ const onReorg = (_lastFinalizedBlockNumber) => {
662
+ };
663
+ return {
664
+ name: collectorName,
665
+ lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId }),
666
+ onReorg,
667
+ collect
668
+ };
669
+ }
670
+
671
+ // src/Validation.ts
672
+ var Validation_exports = {};
673
+ __export(Validation_exports, {
674
+ run: () => run
675
+ });
676
+ async function run(parameters) {
677
+ const { items, rules, ctx = {}, chunkSize } = parameters;
678
+ const issues = [];
679
+ let validItems = items.slice();
680
+ for (const rule of rules) {
681
+ if (validItems.length === 0) return { valid: [], issues };
682
+ const indicesToRemove = /* @__PURE__ */ new Set();
683
+ if (rule.kind === "single") {
684
+ for (let i = 0; i < validItems.length; i++) {
685
+ const item = validItems[i];
686
+ const issue = await rule.run(item, ctx);
687
+ if (issue) {
688
+ issues.push({ ...issue, ruleName: rule.name, item });
689
+ indicesToRemove.add(i);
389
690
  }
390
691
  }
391
- return {
392
- asset,
393
- oracle,
394
- lltv: lltvValue
395
- };
396
- });
397
- }).optional().meta({
692
+ } else if (rule.kind === "batch") {
693
+ const exec = async (slice, offset) => {
694
+ const map = await rule.run(slice, ctx);
695
+ for (let i = 0; i < slice.length; i++) {
696
+ const issue = map.get(i);
697
+ if (issue !== void 0) {
698
+ issues.push({ ...issue, ruleName: rule.name, item: slice[i] });
699
+ indicesToRemove.add(offset + i);
700
+ }
701
+ }
702
+ };
703
+ if (!chunkSize) await exec(validItems, 0);
704
+ else {
705
+ for (let i = 0; i < validItems.length; i += chunkSize) {
706
+ await exec(validItems.slice(i, i + chunkSize), i);
707
+ }
708
+ }
709
+ }
710
+ validItems = validItems.filter((_, i) => !indicesToRemove.has(i));
711
+ }
712
+ return {
713
+ valid: validItems,
714
+ issues
715
+ };
716
+ }
717
+
718
+ // src/ValidationRule.ts
719
+ var ValidationRule_exports = {};
720
+ __export(ValidationRule_exports, {
721
+ batch: () => batch,
722
+ morpho: () => morpho,
723
+ single: () => single
724
+ });
725
+ function single(name, run2) {
726
+ return { kind: "single", name, run: run2 };
727
+ }
728
+ function batch(name, run2) {
729
+ return { kind: "batch", name, run: run2 };
730
+ }
731
+ function morpho() {
732
+ const chainId = single("chain_id", (offer, { chain }) => {
733
+ if (chain.id !== offer.chainId) {
734
+ return {
735
+ message: `Chain ID ${offer.chainId} is not the same as the chain ID in the context (${chain.id})`
736
+ };
737
+ }
738
+ });
739
+ const loanToken = single("loan_token", (offer, { chain }) => {
740
+ const tokens = new Set(
741
+ Array.from(chain.whitelistedAssets.values()).map((a) => a.toLowerCase())
742
+ );
743
+ if (!tokens.has(offer.loanToken.toLowerCase())) {
744
+ return {
745
+ message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
746
+ };
747
+ }
748
+ });
749
+ const expiry = single("expiry", (offer, _) => {
750
+ if (offer.expiry < Math.floor(Date.now() / 1e3)) {
751
+ return { message: "Expiry mismatch" };
752
+ }
753
+ });
754
+ const callback = single("empty_callback", (offer, _) => {
755
+ if (!offer.buy || offer.callback.data !== "0x") {
756
+ return { message: "Callback not supported yet." };
757
+ }
758
+ });
759
+ return [
760
+ chainId,
761
+ loanToken,
762
+ expiry,
763
+ // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
764
+ // integrators should be able to choose if they want to keep the offer or not
765
+ callback
766
+ ];
767
+ }
768
+
769
+ // src/Collector/MempoolCollector.ts
770
+ function createMempoolCollector(parameters) {
771
+ const logger = getLogger();
772
+ const {
773
+ mempool,
774
+ offerStore,
775
+ collectorBlockStore,
776
+ chain,
777
+ options: { maxBatchSize, interval } = {}
778
+ } = parameters;
779
+ const collectorName = "mempool_offers";
780
+ const collect = Utils.lazy((emit) => {
781
+ return mempool.watch({
782
+ lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId: chain.id }),
783
+ polling: { maxBatchSize, interval },
784
+ onOffers: async (offers2, blockNumber) => {
785
+ try {
786
+ const { valid: validOffers, issues } = await run({
787
+ items: offers2,
788
+ rules: morpho(),
789
+ ctx: { chain }
790
+ });
791
+ const invalidOffersToSave = [];
792
+ const issueToStatus = {
793
+ empty_callback: "callback_not_supported",
794
+ sell_offers_empty_callback: "callback_not_supported",
795
+ buy_offers_empty_callback: "callback_error"
796
+ };
797
+ for (const issue of issues) {
798
+ const status = issueToStatus[issue.ruleName];
799
+ if (status) {
800
+ invalidOffersToSave.push({
801
+ offer: issue.item,
802
+ status,
803
+ metadata: { issue: issue.ruleName }
804
+ });
805
+ }
806
+ }
807
+ await offerStore.createMany([
808
+ ...validOffers.map((offer) => ({
809
+ offer,
810
+ status: "valid"
811
+ })),
812
+ ...invalidOffersToSave
813
+ ]);
814
+ } catch (err) {
815
+ logger.error(
816
+ { err },
817
+ "Failed to process offer_created events, falling back to unverified status"
818
+ );
819
+ await offerStore.createMany(
820
+ offers2.map((offer) => ({
821
+ offer,
822
+ status: "unverified",
823
+ metadata: { issue: "Offer processing failed" }
824
+ }))
825
+ );
826
+ }
827
+ await collectorBlockStore.saveBlockNumber({
828
+ collectorName,
829
+ chainId: chain.id,
830
+ blockNumber
831
+ });
832
+ emit(blockNumber);
833
+ }
834
+ });
835
+ });
836
+ const onReorg = (_lastFinalizedBlockNumber) => {
837
+ };
838
+ return {
839
+ name: collectorName,
840
+ lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId: chain.id }),
841
+ collect,
842
+ onReorg
843
+ };
844
+ }
845
+
846
+ // src/CollectorBlockStore.ts
847
+ var CollectorBlockStore_exports = {};
848
+ __export(CollectorBlockStore_exports, {
849
+ create: () => create,
850
+ memory: () => memory
851
+ });
852
+
853
+ // src/OfferStore/drizzle/schema.ts
854
+ var schema_exports = {};
855
+ __export(schema_exports, {
856
+ VERSION: () => VERSION,
857
+ availableLiquidityPools: () => availableLiquidityPools,
858
+ availableLiquidityQueues: () => availableLiquidityQueues,
859
+ collectorBlockNumbers: () => collectorBlockNumbers,
860
+ consumed: () => consumed,
861
+ offerCollaterals: () => offerCollaterals,
862
+ offerStatus: () => offerStatus,
863
+ offers: () => offers,
864
+ userPositions: () => userPositions
865
+ });
866
+ var VERSION = "offers_v1.1";
867
+ var s = pgSchema(VERSION);
868
+ var offers = s.table(
869
+ "offers",
870
+ {
871
+ hash: varchar("hash", { length: 66 }).primaryKey(),
872
+ offering: varchar("offering", { length: 42 }).notNull(),
873
+ assets: numeric("assets", { precision: 78, scale: 0 }).notNull(),
874
+ rate: bigint("rate", { mode: "bigint" }).notNull(),
875
+ maturity: integer("maturity").notNull(),
876
+ expiry: integer("expiry").notNull(),
877
+ start: integer("start").notNull(),
878
+ nonce: bigint("nonce", { mode: "bigint" }).notNull(),
879
+ buy: boolean("buy").notNull(),
880
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
881
+ loanToken: varchar("loan_token", { length: 42 }).notNull(),
882
+ callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
883
+ callbackData: text("callback_data").notNull(),
884
+ callbackGasLimit: bigint("callback_gas_limit", { mode: "bigint" }).notNull(),
885
+ signature: varchar("signature", { length: 132 }),
886
+ callbackId: varchar("callback_id", { length: 256 }),
887
+ createdAt: timestamp("created_at").defaultNow().notNull()
888
+ },
889
+ (table) => [
890
+ index("offers_offering_idx").on(table.offering),
891
+ index("offers_buy_idx").on(table.buy),
892
+ index("offers_chain_id_idx").on(table.chainId),
893
+ index("offers_loan_token_idx").on(table.loanToken),
894
+ index("offers_maturity_idx").on(table.maturity),
895
+ index("offers_expiry_idx").on(table.expiry),
896
+ index("offers_rate_idx").on(table.rate),
897
+ index("offers_assets_idx").on(table.assets),
898
+ // Compound indices for cursor pagination with hash
899
+ index("offers_rate_hash_idx").on(table.rate, table.hash),
900
+ index("offers_maturity_hash_idx").on(table.maturity, table.hash),
901
+ index("offers_expiry_hash_idx").on(table.expiry, table.hash),
902
+ index("offers_assets_hash_idx").on(table.assets, table.hash)
903
+ ]
904
+ );
905
+ var offerCollaterals = s.table(
906
+ "offer_collaterals",
907
+ {
908
+ id: serial("id").primaryKey(),
909
+ offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
910
+ asset: varchar("asset", { length: 42 }).notNull(),
911
+ oracle: varchar("oracle", { length: 42 }).notNull(),
912
+ lltv: bigint("lltv", { mode: "bigint" }).notNull()
913
+ },
914
+ (table) => [
915
+ index("offer_collaterals_offer_hash_idx").on(table.offerHash),
916
+ index("offer_collaterals_asset_idx").on(table.asset),
917
+ index("offer_collaterals_oracle_idx").on(table.oracle),
918
+ // Composite index
919
+ index("offer_collaterals_tuple_idx").on(table.asset, table.oracle, table.lltv)
920
+ ]
921
+ );
922
+ var offerStatus = s.table(
923
+ "offer_status",
924
+ {
925
+ id: serial("id").primaryKey(),
926
+ offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
927
+ status: text("status").$type().notNull(),
928
+ metadata: jsonb("metadata"),
929
+ createdAt: timestamp("created_at").defaultNow().notNull()
930
+ },
931
+ (table) => [
932
+ index("offer_status_offer_hash_created_at_idx").on(table.offerHash, desc(table.createdAt)),
933
+ index("offer_status_status_idx").on(table.status)
934
+ ]
935
+ );
936
+ var consumed = s.table(
937
+ "consumed_per_user_and_nonce",
938
+ {
939
+ id: varchar("id", { length: 255 }).primaryKey(),
940
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
941
+ offering: varchar("offering", { length: 42 }).notNull(),
942
+ nonce: bigint("nonce", { mode: "bigint" }).notNull(),
943
+ consumed: numeric("consumed", { precision: 78, scale: 0 }).notNull(),
944
+ createdAt: timestamp("created_at").defaultNow().notNull()
945
+ },
946
+ (table) => [
947
+ index("consumed_per_user_and_nonce_chain_id_offering_nonce_created_at_idx").on(
948
+ table.chainId,
949
+ table.offering,
950
+ table.nonce,
951
+ desc(table.createdAt)
952
+ )
953
+ ]
954
+ );
955
+ var collectorBlockNumbers = s.table(
956
+ "collector_block_numbers",
957
+ {
958
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
959
+ name: text("name").notNull(),
960
+ blockNumber: bigint("block_number", { mode: "number" }).notNull(),
961
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
962
+ },
963
+ (table) => [uniqueIndex("collector_block_numbers_chain_name_idx").on(table.chainId, table.name)]
964
+ );
965
+ var availableLiquidityPools = s.table("available_liquidity_pools", {
966
+ id: varchar("id", { length: 255 }).primaryKey(),
967
+ amount: numeric("amount", { precision: 78, scale: 0 }).notNull(),
968
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
969
+ });
970
+ var availableLiquidityQueues = s.table(
971
+ "available_liquidity_queues",
972
+ {
973
+ queueId: varchar("queue_id", { length: 255 }).notNull(),
974
+ availableLiquidityPoolId: varchar("available_liquidity_pool_id", { length: 255 }).notNull().references(() => availableLiquidityPools.id, { onDelete: "cascade" }),
975
+ index: integer("index").notNull(),
976
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
977
+ },
978
+ (table) => [
979
+ primaryKey({
980
+ columns: [table.queueId, table.availableLiquidityPoolId],
981
+ name: "available_liquidity_queues_pk"
982
+ }),
983
+ index("available_liquidity_queues_queue_index_idx").on(table.queueId, table.index)
984
+ ]
985
+ );
986
+ var userPositions = s.table(
987
+ "user_positions",
988
+ {
989
+ id: varchar("id", { length: 255 }).primaryKey(),
990
+ availableLiquidityQueueId: varchar("available_liquidity_queue_id", { length: 255 }).notNull(),
991
+ user: varchar("user", { length: 255 }).notNull(),
992
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
993
+ amount: numeric("amount", { precision: 78, scale: 0 }).notNull(),
994
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
995
+ },
996
+ (table) => [
997
+ index("user_positions_available_liquidity_queue_id_idx").on(table.availableLiquidityQueueId)
998
+ ]
999
+ );
1000
+
1001
+ // src/CollectorBlockStore.ts
1002
+ var create = (config) => {
1003
+ const db = config.db;
1004
+ return {
1005
+ getBlockNumber: async (parameters) => {
1006
+ const name = parameters.collectorName.toLowerCase();
1007
+ const result = await db.select({ blockNumber: collectorBlockNumbers.blockNumber }).from(collectorBlockNumbers).where(
1008
+ and(
1009
+ eq(collectorBlockNumbers.name, name),
1010
+ eq(collectorBlockNumbers.chainId, parameters.chainId)
1011
+ )
1012
+ ).limit(1);
1013
+ if (result.length === 0)
1014
+ return Chain.getChain(parameters.chainId)?.mempool.deploymentBlock || 0;
1015
+ return Number(result[0].blockNumber);
1016
+ },
1017
+ saveBlockNumber: async (parameters) => {
1018
+ const name = parameters.collectorName.toLowerCase();
1019
+ await db.insert(collectorBlockNumbers).values({
1020
+ chainId: parameters.chainId,
1021
+ name,
1022
+ blockNumber: parameters.blockNumber,
1023
+ updatedAt: /* @__PURE__ */ new Date()
1024
+ }).onConflictDoUpdate({
1025
+ target: [collectorBlockNumbers.chainId, collectorBlockNumbers.name],
1026
+ set: {
1027
+ blockNumber: parameters.blockNumber,
1028
+ updatedAt: /* @__PURE__ */ new Date()
1029
+ }
1030
+ });
1031
+ }
1032
+ };
1033
+ };
1034
+ function memory() {
1035
+ const blockNumbers = /* @__PURE__ */ new Map();
1036
+ return {
1037
+ getBlockNumber: async (parameters) => {
1038
+ const name = parameters.collectorName.toLowerCase();
1039
+ return blockNumbers.get(name)?.get(parameters.chainId) || Chain.getChain(parameters.chainId)?.mempool.deploymentBlock || 0;
1040
+ },
1041
+ saveBlockNumber: async (parameters) => {
1042
+ const name = parameters.collectorName.toLowerCase();
1043
+ if (!blockNumbers.has(name)) {
1044
+ blockNumbers.set(name, /* @__PURE__ */ new Map());
1045
+ }
1046
+ if (!blockNumbers.get(name)?.has(parameters.chainId)) {
1047
+ blockNumbers.get(name).set(parameters.chainId, parameters.blockNumber);
1048
+ }
1049
+ blockNumbers.get(name).set(parameters.chainId, parameters.blockNumber);
1050
+ }
1051
+ };
1052
+ }
1053
+
1054
+ // src/Cursor.ts
1055
+ var Cursor_exports = {};
1056
+ __export(Cursor_exports, {
1057
+ decode: () => decode,
1058
+ encode: () => encode,
1059
+ validate: () => validate
1060
+ });
1061
+ function validate(cursor) {
1062
+ if (!cursor || typeof cursor !== "object") {
1063
+ throw new Error("Cursor must be an object");
1064
+ }
1065
+ const c = cursor;
1066
+ if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
1067
+ throw new Error(
1068
+ `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
1069
+ );
1070
+ }
1071
+ if (!["asc", "desc"].includes(c.dir)) {
1072
+ throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
1073
+ }
1074
+ if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
1075
+ throw new Error(
1076
+ `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
1077
+ );
1078
+ }
1079
+ const validations = {
1080
+ rate: {
1081
+ field: "rate",
1082
+ type: "string",
1083
+ pattern: /^\d+$/,
1084
+ error: "numeric string"
1085
+ },
1086
+ amount: {
1087
+ field: "assets",
1088
+ type: "string",
1089
+ pattern: /^\d+$/,
1090
+ error: "numeric string"
1091
+ },
1092
+ maturity: {
1093
+ field: "maturity",
1094
+ type: "number",
1095
+ validator: (val) => val > 0,
1096
+ error: "positive number"
1097
+ },
1098
+ expiry: {
1099
+ field: "expiry",
1100
+ type: "number",
1101
+ validator: (val) => val > 0,
1102
+ error: "positive number"
1103
+ }
1104
+ };
1105
+ const validation = validations[c.sort];
1106
+ if (!validation) {
1107
+ throw new Error(`Invalid sort field: ${c.sort}`);
1108
+ }
1109
+ const fieldValue = c[validation.field];
1110
+ if (!fieldValue) {
1111
+ throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
1112
+ }
1113
+ if (typeof fieldValue !== validation.type) {
1114
+ throw new Error(
1115
+ `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
1116
+ );
1117
+ }
1118
+ if (validation.pattern && !validation.pattern.test(fieldValue)) {
1119
+ throw new Error(
1120
+ `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
1121
+ );
1122
+ }
1123
+ if (validation.validator && !validation.validator(fieldValue)) {
1124
+ throw new Error(
1125
+ `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
1126
+ );
1127
+ }
1128
+ return true;
1129
+ }
1130
+ function encode(c) {
1131
+ return Base64.encodeURL(JSON.stringify(c));
1132
+ }
1133
+ function decode(token) {
1134
+ if (!token) return null;
1135
+ const decoded = JSON.parse(Base64.decode(token));
1136
+ validate(decoded);
1137
+ return decoded;
1138
+ }
1139
+
1140
+ // src/core/apiSchema/index.ts
1141
+ var apiSchema_exports = {};
1142
+ __export(apiSchema_exports, {
1143
+ OpenApi: () => OpenApi,
1144
+ fromResponse: () => fromResponse,
1145
+ parse: () => parse,
1146
+ safeParse: () => safeParse,
1147
+ toResponse: () => toResponse
1148
+ });
1149
+ var MAX_LIMIT = 100;
1150
+ var DEFAULT_LIMIT = 20;
1151
+ var MAX_LLTV = 100;
1152
+ var MIN_LLTV = 0;
1153
+ var GetOffersQueryParams = z.object({
1154
+ // Core filtering parameters
1155
+ creators: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
1156
+ message: "Creators must be comma-separated Ethereum addresses"
1157
+ }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
1158
+ description: "Filter by multiple creator addresses (comma-separated)",
1159
+ example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
1160
+ }),
1161
+ side: z.enum(["buy", "sell"]).optional().meta({
1162
+ description: "Filter by offer type: buy offers or sell offers",
1163
+ example: "buy"
1164
+ }),
1165
+ chains: z.string().regex(/^\d+(,\d+)*$/, {
1166
+ message: "Chains must be comma-separated chain IDs"
1167
+ }).transform((val) => val.split(",").map(Number)).optional().meta({
1168
+ description: "Filter by multiple blockchain networks (comma-separated chain IDs)",
1169
+ example: "1,137,10"
1170
+ }),
1171
+ loan_tokens: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
1172
+ message: "Loan assets must be comma-separated Ethereum addresses"
1173
+ }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
1174
+ description: "Filter by multiple loan assets (comma-separated)",
1175
+ example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
1176
+ }),
1177
+ status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
1178
+ message: "Status must be comma-separated status values"
1179
+ }).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
1180
+ message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
1181
+ }).optional().meta({
1182
+ description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
1183
+ example: "valid,callback_error"
1184
+ }),
1185
+ callback_addresses: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
1186
+ message: "Callback addresses must be comma-separated Ethereum addresses"
1187
+ }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
1188
+ description: "Filter by multiple callback addresses (comma-separated)",
1189
+ example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
1190
+ }),
1191
+ // Asset range
1192
+ min_amount: z.bigint({ coerce: true }).positive({
1193
+ message: "Min amount must be a positive number"
1194
+ }).optional().meta({
1195
+ description: "Minimum amount of assets in the offer",
1196
+ example: "1000"
1197
+ }),
1198
+ max_amount: z.bigint({ coerce: true }).positive({
1199
+ message: "Max amount must be a positive number"
1200
+ }).optional().meta({
1201
+ description: "Maximum amount of assets in the offer",
1202
+ example: "10000"
1203
+ }),
1204
+ // Rate range
1205
+ min_rate: z.bigint({ coerce: true }).positive({
1206
+ message: "Min rate must be a positive number"
1207
+ }).optional().meta({
1208
+ description: "Minimum rate per asset (in wei)",
1209
+ example: "500000000000000000"
1210
+ }),
1211
+ max_rate: z.bigint({ coerce: true }).positive({
1212
+ message: "Max rate must be a positive number"
1213
+ }).optional().meta({
1214
+ description: "Maximum rate per asset (in wei)",
1215
+ example: "1500000000000000000"
1216
+ }),
1217
+ // Time range
1218
+ min_maturity: z.coerce.number().int().min(0).optional().meta({
1219
+ description: "Minimum maturity timestamp (Unix timestamp in seconds)",
1220
+ example: "1700000000"
1221
+ }),
1222
+ max_maturity: z.coerce.number().int().min(0).optional().meta({
1223
+ description: "Maximum maturity timestamp (Unix timestamp in seconds)",
1224
+ example: "1800000000"
1225
+ }),
1226
+ min_expiry: z.coerce.number().int().optional().meta({
1227
+ description: "Minimum expiry timestamp (Unix timestamp in seconds)",
1228
+ example: "1700000000"
1229
+ }),
1230
+ max_expiry: z.coerce.number().int().optional().meta({
1231
+ description: "Maximum expiry timestamp (Unix timestamp in seconds)",
1232
+ example: "1800000000"
1233
+ }),
1234
+ // Collateral filtering
1235
+ collateral_assets: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
1236
+ message: "Collateral assets must be comma-separated Ethereum addresses"
1237
+ }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
1238
+ description: "Filter by multiple collateral assets (comma-separated)",
1239
+ example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
1240
+ }),
1241
+ collateral_oracles: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
1242
+ message: "Collateral oracles must be comma-separated Ethereum addresses"
1243
+ }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
1244
+ description: "Filter by multiple rate oracles (comma-separated)",
1245
+ example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
1246
+ }),
1247
+ collateral_tuple: z.string().transform((val, ctx) => {
1248
+ const pattern = /^(0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)(#0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)*$/;
1249
+ if (!pattern.test(val)) {
1250
+ ctx.addIssue({
1251
+ code: "custom",
1252
+ message: "collateral_tuple has an invalid format",
1253
+ input: val
1254
+ });
1255
+ }
1256
+ return val.split("#").map((tuple) => {
1257
+ const parts = tuple.split(":");
1258
+ if (parts.length === 0 || !parts[0]) {
1259
+ ctx.addIssue({
1260
+ code: "custom",
1261
+ message: "Asset address is required for each collateral tuple",
1262
+ path: ["asset"],
1263
+ input: val
1264
+ });
1265
+ return z.NEVER;
1266
+ }
1267
+ const asset = parts[0]?.toLowerCase();
1268
+ const oracle = parts[1]?.toLowerCase();
1269
+ const lltv = parts[2] ? parseFloat(parts[2]) : void 0;
1270
+ if (lltv !== void 0 && (lltv < MIN_LLTV || lltv > MAX_LLTV)) {
1271
+ ctx.addIssue({
1272
+ code: "custom",
1273
+ message: `LLTV must be between ${MIN_LLTV} and ${MAX_LLTV} (0-100%)`,
1274
+ path: ["lltv"],
1275
+ input: val
1276
+ });
1277
+ return z.NEVER;
1278
+ }
1279
+ let lltvValue;
1280
+ if (lltv !== void 0) {
1281
+ try {
1282
+ lltvValue = LLTV.from(parseUnits(lltv.toString(), 16));
1283
+ } catch (e) {
1284
+ ctx.issues.push({
1285
+ code: "custom",
1286
+ message: e instanceof LLTV.InvalidLLTVError || e instanceof LLTV.InvalidOptionError ? e.message : "Invalid LLTV.",
1287
+ input: lltv,
1288
+ path: ["lltv"]
1289
+ });
1290
+ return z.NEVER;
1291
+ }
1292
+ }
1293
+ return {
1294
+ asset,
1295
+ oracle,
1296
+ lltv: lltvValue
1297
+ };
1298
+ });
1299
+ }).optional().meta({
398
1300
  description: "Filter by collateral combinations in format: asset:oracle:lltv#asset2:oracle2:lltv2. Oracle and lltv are optional. Use # to separate multiple combinations.",
399
1301
  example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:86#0x9876543210987654321098765432109876543210:94.5"
400
1302
  }),
@@ -740,19 +1642,19 @@ var OpenApi = createDocument({
740
1642
 
741
1643
  // src/core/apiSchema/utils.ts
742
1644
  function toResponse(routerOffer) {
743
- const { consumed, status, metadata, ...offer } = routerOffer;
1645
+ const { consumed: consumed2, status, metadata, ...offer } = routerOffer;
744
1646
  return {
745
1647
  offer,
746
- consumed,
1648
+ consumed: consumed2,
747
1649
  status,
748
1650
  metadata
749
1651
  };
750
1652
  }
751
1653
  function fromResponse(offerResponse) {
752
- const { offer, consumed, status, metadata } = offerResponse;
1654
+ const { offer, consumed: consumed2, status, metadata } = offerResponse;
753
1655
  return {
754
1656
  ...offer,
755
- consumed,
1657
+ consumed: consumed2,
756
1658
  status,
757
1659
  metadata
758
1660
  };
@@ -878,8 +1780,8 @@ async function get(config, parameters) {
878
1780
  if (parameters.limit !== void 0) {
879
1781
  url.searchParams.set("limit", parameters.limit.toString());
880
1782
  }
881
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
882
- const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
1783
+ const { cursor: returnedCursor, data: offers2 } = await getApi(config, url);
1784
+ const routerOffers = offers2.map(Format.fromSnakeCase).map(fromResponse);
883
1785
  return {
884
1786
  cursor: returnedCursor,
885
1787
  offers: routerOffers.map(from).map(toResponse)
@@ -920,8 +1822,8 @@ async function match(config, parameters) {
920
1822
  if (parameters.limit !== void 0) {
921
1823
  url.searchParams.set("limit", parameters.limit.toString());
922
1824
  }
923
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
924
- const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
1825
+ const { cursor: returnedCursor, data: offers2 } = await getApi(config, url);
1826
+ const routerOffers = offers2.map(Format.fromSnakeCase).map(fromResponse);
925
1827
  return {
926
1828
  cursor: returnedCursor,
927
1829
  offers: routerOffers.map(from).map(toResponse)
@@ -933,501 +1835,87 @@ async function getApi(config, url) {
933
1835
  switch (true) {
934
1836
  case pathname.includes("/v1/offers/match"):
935
1837
  action = "match_offers";
936
- break;
937
- case pathname.includes("/v1/offers"):
938
- action = "get_offers";
939
- break;
940
- default:
941
- throw new HttpGetOffersFailedError("Unknown endpoint", {
942
- details: `Unsupported path: ${pathname}`
943
- });
944
- }
945
- const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
946
- if (!schemaParseResult.success) {
947
- throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
948
- details: schemaParseResult.error.issues[0]?.message
949
- });
950
- }
951
- const response = await fetch(url.toString(), {
952
- method: "GET",
953
- headers: config.headers
954
- });
955
- if (!response.ok) {
956
- switch (response.status) {
957
- case 401:
958
- throw new HttpUnauthorizedError();
959
- case 403:
960
- throw new HttpForbiddenError();
961
- case 429:
962
- throw new HttpRateLimitError();
963
- }
964
- throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
965
- details: await response.text()
966
- });
967
- }
968
- return response.json();
969
- }
970
- var InvalidUrlError = class extends Errors.BaseError {
971
- name = "Router.InvalidUrlError";
972
- constructor(url) {
973
- super(`URL "${url}" is not http/https.`);
974
- }
975
- };
976
- var HttpUnauthorizedError = class extends Errors.BaseError {
977
- name = "Router.HttpUnauthorizedError";
978
- constructor() {
979
- super("Unauthorized.", {
980
- metaMessages: ["Ensure that an API key is provided."]
981
- });
982
- }
983
- };
984
- var HttpForbiddenError = class extends Errors.BaseError {
985
- name = "Router.HttpForbiddenError";
986
- constructor() {
987
- super("Forbidden.", {
988
- metaMessages: ["Ensure that the API key is valid."]
989
- });
990
- }
991
- };
992
- var HttpRateLimitError = class extends Errors.BaseError {
993
- name = "Router.HttpRateLimitError";
994
- constructor() {
995
- super("Rate limit exceeded.", {
996
- metaMessages: [
997
- "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
998
- ]
999
- });
1000
- }
1001
- };
1002
- var HttpGetOffersFailedError = class extends Errors.BaseError {
1003
- name = "Router.HttpGetOffersFailedError";
1004
- constructor(message, { details } = {}) {
1005
- super(message, {
1006
- metaMessages: [details]
1007
- });
1008
- }
1009
- };
1010
-
1011
- // src/OfferStore/index.ts
1012
- var OfferStore_exports = {};
1013
- __export(OfferStore_exports, {
1014
- memory: () => memory
1015
- });
1016
- function memory(parameters) {
1017
- const map = parameters.offers;
1018
- const filled = parameters.filled;
1019
- const consumedIds = /* @__PURE__ */ new Set();
1020
- const create = async (parameters2) => {
1021
- if (map.has(parameters2.offer.hash.toLowerCase())) return parameters2.offer.hash;
1022
- const callbackId = getCallbackIdForOffer(parameters2.offer);
1023
- map.set(parameters2.offer.hash.toLowerCase(), {
1024
- ...parameters2.offer,
1025
- ...callbackId ? { callbackId } : {},
1026
- status: parameters2.status,
1027
- metadata: parameters2.metadata
1028
- });
1029
- const chainId = parameters2.offer.chainId;
1030
- const address = parameters2.offer.offering.toLowerCase();
1031
- const nonce = parameters2.offer.nonce;
1032
- const filledForChain = filled.get(chainId) || /* @__PURE__ */ new Map();
1033
- const filledForOffering = filledForChain.get(address) || /* @__PURE__ */ new Map();
1034
- if (!filledForOffering.has(nonce)) filledForOffering.set(nonce, 0n);
1035
- filledForChain.set(address, filledForOffering);
1036
- filled.set(chainId, filledForChain);
1037
- return Promise.resolve(parameters2.offer.hash);
1038
- };
1039
- const sort = (sortBy, sortOrder, a, b) => {
1040
- sortBy = sortBy || "expiry";
1041
- sortOrder = sortOrder || "desc";
1042
- const sortKey = sortBy === "amount" ? "assets" : sortBy;
1043
- if (a[sortKey] === b[sortKey]) {
1044
- if (a.hash === b.hash) return 0;
1045
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : b.hash > a.hash ? 1 : -1;
1046
- }
1047
- switch (sortBy) {
1048
- case "rate":
1049
- if (a.rate === b.rate) {
1050
- if (a.hash === b.hash) return 0;
1051
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1052
- }
1053
- return sortOrder === "asc" ? a.rate > b.rate ? 1 : -1 : b.rate > a.rate ? 1 : -1;
1054
- case "maturity":
1055
- if (a.maturity === b.maturity) {
1056
- if (a.hash === b.hash) return 0;
1057
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1058
- }
1059
- return sortOrder === "asc" ? a.maturity > b.maturity ? 1 : -1 : b.maturity > a.maturity ? 1 : -1;
1060
- case "expiry":
1061
- if (a.expiry === b.expiry) {
1062
- if (a.hash === b.hash) return 0;
1063
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1064
- }
1065
- return sortOrder === "asc" ? a.expiry > b.expiry ? 1 : -1 : b.expiry > a.expiry ? 1 : -1;
1066
- case "amount":
1067
- if (a.assets === b.assets) {
1068
- if (a.hash === b.hash) return 0;
1069
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1070
- }
1071
- return sortOrder === "asc" ? a.assets > b.assets ? 1 : -1 : b.assets > a.assets ? 1 : -1;
1072
- default:
1073
- if (a.expiry === b.expiry) {
1074
- if (a.hash === b.hash) return 0;
1075
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1076
- }
1077
- return sortOrder === "asc" ? a.expiry > b.expiry ? 1 : -1 : b.expiry > a.expiry ? 1 : -1;
1078
- }
1079
- };
1080
- return {
1081
- create,
1082
- createMany: async (parameters2) => {
1083
- return Promise.all(
1084
- parameters2.map((p) => create({ offer: p.offer, status: p.status, metadata: p.metadata }))
1085
- );
1086
- },
1087
- getAll: async (params) => {
1088
- const { query } = params || {};
1089
- let {
1090
- creators,
1091
- side,
1092
- chains,
1093
- loanTokens,
1094
- status = ["valid"],
1095
- callbackAddresses,
1096
- minAmount,
1097
- maxAmount,
1098
- minRate,
1099
- maxRate,
1100
- minMaturity,
1101
- maxMaturity,
1102
- minExpiry,
1103
- maxExpiry,
1104
- collateralAssets,
1105
- collateralOracles,
1106
- collateralTuple,
1107
- minLltv,
1108
- maxLltv,
1109
- sortBy = "expiry",
1110
- sortOrder = "desc",
1111
- cursor: queryCursor,
1112
- limit = 20
1113
- } = query || {};
1114
- const now = Time.now();
1115
- const buy = side === "buy";
1116
- let offers = Array.from(map.values()).map((o) => ({
1117
- ...o,
1118
- consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1119
- })).filter((o) => o.consumed < o.assets);
1120
- const cursor = decode(queryCursor);
1121
- if (cursor) {
1122
- if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
1123
- throw new Error("Cursor does not match the current sort parameters");
1124
- }
1125
- switch (cursor.sort) {
1126
- case "rate":
1127
- offers = offers.filter(
1128
- (o) => (sortOrder === "asc" ? o.rate >= BigInt(cursor.rate) : o.rate <= BigInt(cursor.rate)) && (o.rate !== BigInt(cursor.rate) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1129
- );
1130
- break;
1131
- case "maturity":
1132
- offers = offers.filter(
1133
- (o) => (sortOrder === "asc" ? o.maturity >= BigInt(cursor.maturity) : o.maturity <= BigInt(cursor.maturity)) && (o.maturity !== Maturity.from(cursor.maturity) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1134
- );
1135
- break;
1136
- case "expiry":
1137
- offers = offers.filter(
1138
- (o) => (sortOrder === "asc" ? o.expiry >= cursor.expiry : o.expiry <= cursor.expiry) && (o.expiry !== cursor.expiry || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1139
- );
1140
- break;
1141
- case "amount":
1142
- offers = offers.filter(
1143
- (o) => (sortOrder === "asc" ? o.assets >= BigInt(cursor.assets) : o.assets <= BigInt(cursor.assets)) && (o.assets !== BigInt(cursor.assets) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1144
- );
1145
- break;
1146
- default:
1147
- throw new Error("Invalid sort parameter");
1148
- }
1149
- offers = offers.filter((o) => o.hash !== cursor.hash);
1150
- }
1151
- creators && (creators = creators.map((c) => c.toLowerCase()));
1152
- loanTokens && (loanTokens = loanTokens.map((lt) => lt.toLowerCase()));
1153
- callbackAddresses && (callbackAddresses = callbackAddresses.map((ca) => ca.toLowerCase()));
1154
- collateralAssets && (collateralAssets = collateralAssets.map((ca) => ca.toLowerCase()));
1155
- collateralOracles && (collateralOracles = collateralOracles.map((co) => co.toLowerCase()));
1156
- collateralTuple && (collateralTuple = collateralTuple.map((ct) => ({
1157
- asset: ct.asset.toLowerCase(),
1158
- oracle: ct.oracle?.toLowerCase()
1159
- })));
1160
- offers = offers.filter((o) => o.expiry >= now);
1161
- creators && (offers = offers.filter((o) => creators.includes(o.offering.toLowerCase())));
1162
- side && (offers = offers.filter((o) => o.buy === buy));
1163
- chains && (offers = offers.filter((o) => chains.includes(Number(o.chainId))));
1164
- loanTokens && (offers = offers.filter((o) => loanTokens.includes(o.loanToken.toLowerCase())));
1165
- status && (offers = offers.filter((o) => status.includes(o.status)));
1166
- callbackAddresses && (offers = offers.filter(
1167
- (o) => callbackAddresses.includes(o.callback.address.toLowerCase())
1168
- ));
1169
- minAmount && (offers = offers.filter((o) => o.assets >= minAmount));
1170
- maxAmount && (offers = offers.filter((o) => o.assets <= maxAmount));
1171
- minRate && (offers = offers.filter((o) => o.rate >= minRate));
1172
- maxRate && (offers = offers.filter((o) => o.rate <= maxRate));
1173
- minMaturity && (offers = offers.filter((o) => o.maturity >= minMaturity));
1174
- maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
1175
- minExpiry && (offers = offers.filter((o) => o.expiry >= minExpiry));
1176
- maxExpiry && (offers = offers.filter((o) => o.expiry <= maxExpiry));
1177
- collateralAssets && (offers = offers.filter(
1178
- (o) => o.collaterals.some((c) => collateralAssets.includes(c.asset.toLowerCase()))
1179
- ));
1180
- collateralOracles && (offers = offers.filter(
1181
- (o) => o.collaterals.some((c) => collateralOracles.includes(c.oracle.toLowerCase()))
1182
- ));
1183
- collateralTuple && (offers = offers.filter(
1184
- (o) => o.collaterals.some(
1185
- (c) => collateralTuple.some(
1186
- (ct) => c.asset.toLowerCase() === ct.asset.toLowerCase() && (ct.oracle ? c.oracle.toLowerCase() === ct.oracle.toLowerCase() : true) && (ct.lltv ? c.lltv === LLTV.from(BigInt(ct.lltv)) : true)
1187
- )
1188
- )
1189
- ));
1190
- minLltv && (offers = offers.filter(
1191
- (o) => o.collaterals.every((c) => c.lltv >= parseUnits(minLltv.toString(), 16))
1192
- ));
1193
- maxLltv && (offers = offers.filter(
1194
- (o) => o.collaterals.every((c) => c.lltv <= parseUnits(maxLltv.toString(), 16))
1195
- ));
1196
- offers = offers.sort((a, b) => sort(sortBy, sortOrder, a, b));
1197
- let nextCursor = null;
1198
- if (offers.length > limit) {
1199
- const last = offers[limit - 1];
1200
- const base = {
1201
- sort: sortBy,
1202
- dir: sortOrder,
1203
- hash: last.hash
1204
- };
1205
- switch (sortBy) {
1206
- case "rate":
1207
- base.rate = last.rate.toString();
1208
- break;
1209
- case "amount":
1210
- base.assets = last.assets.toString();
1211
- break;
1212
- case "maturity":
1213
- base.maturity = last.maturity;
1214
- break;
1215
- default:
1216
- base.expiry = last.expiry;
1217
- }
1218
- nextCursor = encode(base);
1219
- }
1220
- offers = offers.slice(0, limit);
1221
- const data = offers.map((o) => ({
1222
- ...Offer.from({
1223
- offering: o.offering,
1224
- assets: o.assets,
1225
- rate: o.rate,
1226
- maturity: Maturity.from(o.maturity),
1227
- expiry: o.expiry,
1228
- start: o.start,
1229
- nonce: o.nonce,
1230
- buy: o.buy,
1231
- chainId: o.chainId,
1232
- loanToken: o.loanToken,
1233
- collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
1234
- callback: {
1235
- address: o.callback.address,
1236
- data: o.callback.data,
1237
- gasLimit: o.callback.gasLimit
1238
- },
1239
- ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
1240
- }),
1241
- consumed: o.consumed,
1242
- status: o.status,
1243
- ...o.metadata ? { metadata: o.metadata } : {}
1244
- }));
1245
- return {
1246
- offers: data,
1247
- nextCursor
1248
- };
1249
- },
1250
- findMatchingOffers: async (params) => {
1251
- const {
1252
- side,
1253
- chainId,
1254
- rate,
1255
- collaterals = [],
1256
- maturity,
1257
- minMaturity,
1258
- maxMaturity,
1259
- loanToken,
1260
- creator,
1261
- cursor: queryCursor,
1262
- limit = 20
1263
- } = params;
1264
- const now = Time.now();
1265
- const isBuying = side === "buy";
1266
- const sortOrder = isBuying ? "desc" : "asc";
1267
- let offers = Array.from(map.values()).map((o) => ({
1268
- ...o,
1269
- consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1270
- })).filter((o) => o.consumed < o.assets);
1271
- const cursor = decode(queryCursor);
1272
- if (cursor) {
1273
- if (cursor.sort !== "rate" || cursor.dir !== sortOrder) {
1274
- throw new Error("Cursor does not match the current sort parameters");
1275
- }
1276
- offers = offers.filter(
1277
- (o) => sortOrder === "asc" ? o.rate >= BigInt(cursor.rate) : o.rate <= BigInt(cursor.rate)
1278
- );
1279
- }
1280
- offers = offers.filter((o) => o.buy === !isBuying);
1281
- offers = offers.filter((o) => o.chainId === BigInt(chainId));
1282
- offers = offers.filter((o) => o.expiry >= now);
1283
- rate && (offers = offers.filter((o) => isBuying ? o.rate >= rate : o.rate <= rate));
1284
- collaterals.length > 0 && (offers = offers.filter(
1285
- (o) => isBuying ? (
1286
- // when wanting to buy, sell offer collaterals ⊆ user buy collaterals
1287
- o.collaterals.every((oc) => {
1288
- return collaterals.some(
1289
- (c) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
1290
- );
1291
- })
1292
- ) : (
1293
- // when wanting to sell, user sell collaterals ⊆ buy offer collaterals
1294
- collaterals.every((c) => {
1295
- return o.collaterals.some(
1296
- (oc) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
1297
- );
1298
- })
1299
- )
1300
- ));
1301
- maturity && (offers = offers.filter((o) => o.maturity === maturity));
1302
- minMaturity && (offers = offers.filter((o) => o.maturity >= minMaturity));
1303
- maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
1304
- loanToken && (offers = offers.filter((o) => o.loanToken.toLowerCase() === loanToken.toLowerCase()));
1305
- creator && (offers = offers.filter((o) => o.offering.toLowerCase() === creator.toLowerCase()));
1306
- offers = offers.filter((o) => ["valid"].includes(o.status));
1307
- const byGroup = /* @__PURE__ */ new Map();
1308
- for (const offer of offers) {
1309
- const groupKey = `${offer.chainId}-${offer.offering.toLowerCase()}-${offer.nonce}-${offer.buy}`;
1310
- const current = byGroup.get(groupKey);
1311
- if (!current) {
1312
- byGroup.set(groupKey, offer);
1313
- continue;
1314
- }
1315
- const remainingCandidate = offer.assets - offer.consumed;
1316
- const remainingCurrent = current.assets - current.consumed;
1317
- let candidateIsBetter = false;
1318
- if (offer.buy) {
1319
- if (offer.rate !== current.rate) candidateIsBetter = offer.rate < current.rate;
1320
- else if (remainingCandidate !== remainingCurrent)
1321
- candidateIsBetter = remainingCandidate > remainingCurrent;
1322
- else if (offer.maturity !== current.maturity)
1323
- candidateIsBetter = offer.maturity > current.maturity;
1324
- else candidateIsBetter = offer.hash < current.hash;
1325
- } else {
1326
- if (offer.rate !== current.rate) candidateIsBetter = offer.rate > current.rate;
1327
- else if (remainingCandidate !== remainingCurrent)
1328
- candidateIsBetter = remainingCandidate > remainingCurrent;
1329
- else if (offer.maturity !== current.maturity)
1330
- candidateIsBetter = offer.maturity > current.maturity;
1331
- else candidateIsBetter = offer.hash < current.hash;
1332
- }
1333
- if (candidateIsBetter) byGroup.set(groupKey, offer);
1334
- }
1335
- offers = Array.from(byGroup.values());
1336
- offers = offers.sort((a, b) => sort("rate", sortOrder, a, b));
1337
- cursor && (offers = offers.filter((o) => o.hash !== cursor.hash));
1338
- let nextCursor = null;
1339
- if (offers.length > limit) {
1340
- const last = offers[limit - 1];
1341
- nextCursor = encode({
1342
- sort: "rate",
1343
- dir: sortOrder,
1344
- hash: last.hash,
1345
- rate: last.rate.toString()
1346
- });
1347
- }
1348
- offers = offers.slice(0, limit);
1349
- const data = offers.map((o) => ({
1350
- ...Offer.from({
1351
- offering: o.offering,
1352
- assets: o.assets,
1353
- rate: o.rate,
1354
- maturity: Maturity.from(o.maturity),
1355
- expiry: o.expiry,
1356
- start: o.start,
1357
- nonce: o.nonce,
1358
- buy: o.buy,
1359
- chainId: o.chainId,
1360
- loanToken: o.loanToken,
1361
- collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
1362
- callback: {
1363
- address: o.callback.address,
1364
- data: o.callback.data,
1365
- gasLimit: o.callback.gasLimit
1366
- },
1367
- ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
1368
- }),
1369
- consumed: o.consumed,
1370
- status: o.status,
1371
- ...o.metadata ? { metadata: o.metadata } : {}
1372
- }));
1373
- return {
1374
- offers: data,
1375
- nextCursor
1376
- };
1377
- },
1378
- delete: async (hash) => {
1379
- if (!map.has(hash.toLowerCase())) return false;
1380
- map.delete(hash.toLowerCase());
1381
- return true;
1382
- },
1383
- deleteMany: async (hashes) => {
1384
- let deleted = 0;
1385
- for (const hash of hashes) {
1386
- if (map.has(hash.toLowerCase())) {
1387
- map.delete(hash.toLowerCase());
1388
- deleted++;
1389
- }
1390
- }
1391
- return deleted;
1392
- },
1393
- updateStatus: async (parameters2) => {
1394
- const key = parameters2.offerHash.toLowerCase();
1395
- const existing = map.get(key);
1396
- if (!existing) return;
1397
- if (existing.status === parameters2.status) return;
1398
- map.set(key, {
1399
- ...existing,
1400
- status: parameters2.status,
1401
- metadata: parameters2.metadata
1838
+ break;
1839
+ case pathname.includes("/v1/offers"):
1840
+ action = "get_offers";
1841
+ break;
1842
+ default:
1843
+ throw new HttpGetOffersFailedError("Unknown endpoint", {
1844
+ details: `Unsupported path: ${pathname}`
1402
1845
  });
1403
- },
1404
- updateConsumedAmount: async (parameters2) => {
1405
- if (consumedIds.has(parameters2.id)) return;
1406
- consumedIds.add(parameters2.id);
1407
- const chainId = parameters2.chainId;
1408
- const address = parameters2.offering.toLowerCase();
1409
- const nonce = parameters2.nonce;
1410
- const filledForChain = filled.get(chainId) || /* @__PURE__ */ new Map();
1411
- const filledForOffering = filledForChain.get(address) || /* @__PURE__ */ new Map();
1412
- const current = filledForOffering.get(nonce) || 0n;
1413
- filledForOffering.set(nonce, current + parameters2.consumed);
1414
- filledForChain.set(address, filledForOffering);
1415
- filled.set(chainId, filledForChain);
1846
+ }
1847
+ const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
1848
+ if (!schemaParseResult.success) {
1849
+ throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
1850
+ details: schemaParseResult.error.issues[0]?.message
1851
+ });
1852
+ }
1853
+ const response = await fetch(url.toString(), {
1854
+ method: "GET",
1855
+ headers: config.headers
1856
+ });
1857
+ if (!response.ok) {
1858
+ switch (response.status) {
1859
+ case 401:
1860
+ throw new HttpUnauthorizedError();
1861
+ case 403:
1862
+ throw new HttpForbiddenError();
1863
+ case 429:
1864
+ throw new HttpRateLimitError();
1416
1865
  }
1417
- };
1866
+ throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
1867
+ details: await response.text()
1868
+ });
1869
+ }
1870
+ return response.json();
1418
1871
  }
1419
-
1420
- // src/core/router/Server.ts
1872
+ var InvalidUrlError = class extends Errors.BaseError {
1873
+ name = "Router.InvalidUrlError";
1874
+ constructor(url) {
1875
+ super(`URL "${url}" is not http/https.`);
1876
+ }
1877
+ };
1878
+ var HttpUnauthorizedError = class extends Errors.BaseError {
1879
+ name = "Router.HttpUnauthorizedError";
1880
+ constructor() {
1881
+ super("Unauthorized.", {
1882
+ metaMessages: ["Ensure that an API key is provided."]
1883
+ });
1884
+ }
1885
+ };
1886
+ var HttpForbiddenError = class extends Errors.BaseError {
1887
+ name = "Router.HttpForbiddenError";
1888
+ constructor() {
1889
+ super("Forbidden.", {
1890
+ metaMessages: ["Ensure that the API key is valid."]
1891
+ });
1892
+ }
1893
+ };
1894
+ var HttpRateLimitError = class extends Errors.BaseError {
1895
+ name = "Router.HttpRateLimitError";
1896
+ constructor() {
1897
+ super("Rate limit exceeded.", {
1898
+ metaMessages: [
1899
+ "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
1900
+ ]
1901
+ });
1902
+ }
1903
+ };
1904
+ var HttpGetOffersFailedError = class extends Errors.BaseError {
1905
+ name = "Router.HttpGetOffersFailedError";
1906
+ constructor(message, { details } = {}) {
1907
+ super(message, {
1908
+ metaMessages: [details]
1909
+ });
1910
+ }
1911
+ };
1421
1912
  async function serve(parameters) {
1422
1913
  const app = new Hono();
1423
- const store = parameters.store ? parameters.store : memory({
1424
- offers: /* @__PURE__ */ new Map(),
1425
- filled: /* @__PURE__ */ new Map()
1426
- });
1914
+ const store = parameters.store;
1427
1915
  app.get("/v1/offers", async (c) => {
1428
1916
  try {
1429
1917
  const params = parse("get_offers", c.req.query());
1430
- const offers = await store.getAll({
1918
+ const offers2 = await store.getAll({
1431
1919
  query: {
1432
1920
  creators: params.creators,
1433
1921
  side: params.side,
@@ -1455,10 +1943,10 @@ async function serve(parameters) {
1455
1943
  }
1456
1944
  });
1457
1945
  return success(c, {
1458
- data: offers.offers.map(
1946
+ data: offers2.offers.map(
1459
1947
  (offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
1460
1948
  ),
1461
- cursor: offers.nextCursor ?? null
1949
+ cursor: offers2.nextCursor ?? null
1462
1950
  });
1463
1951
  } catch (err) {
1464
1952
  console.error(err);
@@ -1468,7 +1956,7 @@ async function serve(parameters) {
1468
1956
  app.get("/v1/offers/match", async (c) => {
1469
1957
  try {
1470
1958
  const params = parse("match_offers", c.req.query());
1471
- const offers = await store.findMatchingOffers({
1959
+ const offers2 = await store.findMatchingOffers({
1472
1960
  side: params.side,
1473
1961
  chainId: params.chain_id,
1474
1962
  rate: params.rate,
@@ -1482,10 +1970,10 @@ async function serve(parameters) {
1482
1970
  limit: params.limit
1483
1971
  });
1484
1972
  return success(c, {
1485
- data: offers.offers.map(
1973
+ data: offers2.offers.map(
1486
1974
  (offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
1487
1975
  ),
1488
- cursor: offers.nextCursor ?? null
1976
+ cursor: offers2.nextCursor ?? null
1489
1977
  });
1490
1978
  } catch (err) {
1491
1979
  console.error(err);
@@ -1593,310 +2081,1018 @@ function handleAPIError(error2, c) {
1593
2081
  });
1594
2082
  }
1595
2083
 
1596
- // src/Liquidity.ts
1597
- var Liquidity_exports = {};
1598
- __export(Liquidity_exports, {
1599
- fetch: () => fetch2,
1600
- fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
1601
- serialize: () => serialize
1602
- });
1603
- async function fetchBalancesAndAllowances(parameters) {
1604
- const { client, spender, pairs, options } = parameters;
1605
- if (pairs.length === 0) return /* @__PURE__ */ new Map();
1606
- const batchSize = Math.max(1, options?.batchSize ?? 5e3);
1607
- const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
1608
- const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
1609
- const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
1610
- const out = /* @__PURE__ */ new Map();
1611
- for (const pairsBatch of Utils.batch(pairs, batchSize)) {
1612
- const balanceContracts = [];
1613
- const allowanceContracts = [];
1614
- for (const { user, token } of pairsBatch) {
1615
- balanceContracts.push({
1616
- address: token,
1617
- abi: erc20Abi,
1618
- functionName: "balanceOf",
1619
- args: [user]
1620
- });
1621
- allowanceContracts.push({
1622
- address: token,
1623
- abi: erc20Abi,
1624
- functionName: "allowance",
1625
- args: [user, spender]
1626
- });
1627
- }
1628
- const [balances, allowances] = await Promise.all([
1629
- Utils.retry(
1630
- () => client.multicall({
1631
- allowFailure: false,
1632
- contracts: balanceContracts,
1633
- ...blockNumber ? { blockNumber } : {}
1634
- }),
1635
- retryAttempts,
1636
- retryDelayMs
1637
- ),
1638
- Utils.retry(
1639
- () => client.multicall({
1640
- allowFailure: false,
1641
- contracts: allowanceContracts,
1642
- ...blockNumber ? { blockNumber } : {}
1643
- }),
1644
- retryAttempts,
1645
- retryDelayMs
1646
- )
1647
- ]);
1648
- for (let i = 0; i < pairsBatch.length; i++) {
1649
- const { user, token } = pairsBatch[i];
1650
- const balance = balances[i];
1651
- const allowance = allowances[i];
1652
- let perUser = out.get(user);
1653
- if (!perUser) {
1654
- perUser = /* @__PURE__ */ new Map();
1655
- out.set(user, perUser);
1656
- }
1657
- perUser.set(token, { balance, allowance });
1658
- }
1659
- }
1660
- return out;
1661
- }
1662
- async function fetch2(parameters) {
1663
- const { client, chainId, spender, type, pairs, options } = parameters;
1664
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
1665
- throw new Error(`CallbackType not implemented: ${type}`);
1666
- const map = await fetchBalancesAndAllowances({
1667
- client,
1668
- spender,
1669
- pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
1670
- options
1671
- });
1672
- const out = [];
1673
- for (const [user, perContract] of map) {
1674
- for (const [contract, { balance, allowance }] of perContract) {
1675
- const amount = balance < allowance ? balance : allowance;
1676
- out.push(
1677
- buildLiquidity({
1678
- type,
1679
- user,
1680
- contract,
1681
- chainId,
1682
- amount: amount.toString(),
1683
- index: 0
1684
- })
1685
- );
1686
- }
1687
- }
1688
- return out;
1689
- }
1690
- function serialize(liquidity) {
1691
- const normalized = {
1692
- userPosition: {
1693
- id: liquidity.userPosition.id,
1694
- availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1695
- user: liquidity.userPosition.user,
1696
- chainId: String(liquidity.userPosition.chainId),
1697
- amount: String(liquidity.userPosition.amount)
1698
- },
1699
- queues: liquidity.queues.map((queueWithPool) => ({
1700
- queue: {
1701
- queueId: queueWithPool.queue.queueId,
1702
- availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
1703
- index: queueWithPool.queue.index
1704
- },
1705
- pool: {
1706
- id: queueWithPool.pool.id,
1707
- amount: String(queueWithPool.pool.amount)
1708
- }
1709
- })).sort(
1710
- (left, right) => {
1711
- const leftQueueId = left.queue.queueId || "";
1712
- const rightQueueId = right.queue.queueId || "";
1713
- if (leftQueueId < rightQueueId) return -1;
1714
- if (leftQueueId > rightQueueId) return 1;
1715
- const leftPoolId = left.pool.id;
1716
- const rightPoolId = right.pool.id;
1717
- if (leftPoolId < rightPoolId) return -1;
1718
- if (leftPoolId > rightPoolId) return 1;
1719
- const leftIndex = left.queue.index;
1720
- const rightIndex = right.queue.index;
1721
- if (leftIndex < rightIndex) return -1;
1722
- if (leftIndex > rightIndex) return 1;
1723
- return 0;
1724
- }
1725
- )
1726
- };
1727
- return JSON.stringify(normalized);
1728
- }
1729
-
1730
- // src/Logger.ts
1731
- var Logger_exports = {};
1732
- __export(Logger_exports, {
1733
- LogLevelValues: () => LogLevelValues,
1734
- defaultLogger: () => defaultLogger,
1735
- getLogger: () => getLogger,
1736
- runWithLogger: () => runWithLogger,
1737
- silentLogger: () => silentLogger
2084
+ // src/LiquidityStore.ts
2085
+ var LiquidityStore_exports = {};
2086
+ __export(LiquidityStore_exports, {
2087
+ create: () => create2,
2088
+ memory: () => memory2
1738
2089
  });
1739
- var LogLevelValues = [
1740
- "silent",
1741
- "trace",
1742
- "debug",
1743
- "info",
1744
- "warn",
1745
- "error",
1746
- "fatal"
1747
- ];
1748
- function defaultLogger(minLevel) {
1749
- const threshold = minLevel ?? "trace";
1750
- const levelIndexByName = LogLevelValues.reduce(
1751
- (acc, lvl, idx) => {
1752
- acc[lvl] = idx;
1753
- return acc;
1754
- },
1755
- {}
1756
- );
1757
- const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
2090
+ var create2 = (config) => {
2091
+ const db = config.db;
1758
2092
  return {
1759
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1760
- trace: isEnabled("trace") ? console.trace.bind(console) : () => {
1761
- },
1762
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1763
- debug: isEnabled("debug") ? console.debug.bind(console) : () => {
1764
- },
1765
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1766
- info: isEnabled("info") ? console.info.bind(console) : () => {
1767
- },
1768
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1769
- warn: isEnabled("warn") ? console.warn.bind(console) : () => {
2093
+ getByUserPositionId: async (parameters) => {
2094
+ const up = await db.select().from(userPositions).where(eq(userPositions.id, parameters.userPositionId)).limit(1);
2095
+ if (up.length === 0) return null;
2096
+ const userPositionRow = up[0];
2097
+ const rows = await db.select({ queue: availableLiquidityQueues, pool: availableLiquidityPools }).from(availableLiquidityQueues).innerJoin(
2098
+ availableLiquidityPools,
2099
+ eq(availableLiquidityPools.id, availableLiquidityQueues.availableLiquidityPoolId)
2100
+ ).where(eq(availableLiquidityQueues.queueId, userPositionRow.availableLiquidityQueueId));
2101
+ const queues = rows.map((row) => ({
2102
+ queue: row.queue,
2103
+ pool: row.pool
2104
+ }));
2105
+ return { userPosition: userPositionRow, queues };
1770
2106
  },
1771
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1772
- error: isEnabled("error") ? console.error.bind(console) : () => {
2107
+ getAll: async () => {
2108
+ const rows = await db.select({
2109
+ userPosition: userPositions,
2110
+ queue: availableLiquidityQueues,
2111
+ pool: availableLiquidityPools
2112
+ }).from(userPositions).innerJoin(
2113
+ availableLiquidityQueues,
2114
+ eq(availableLiquidityQueues.queueId, userPositions.availableLiquidityQueueId)
2115
+ ).innerJoin(
2116
+ availableLiquidityPools,
2117
+ eq(availableLiquidityPools.id, availableLiquidityQueues.availableLiquidityPoolId)
2118
+ );
2119
+ const byUserPosition = /* @__PURE__ */ new Map();
2120
+ for (const row of rows) {
2121
+ const id = row.userPosition.id;
2122
+ if (!byUserPosition.has(id)) {
2123
+ byUserPosition.set(id, { userPosition: row.userPosition, queues: [] });
2124
+ }
2125
+ byUserPosition.get(id).queues.push({ queue: row.queue, pool: row.pool });
2126
+ }
2127
+ return Array.from(byUserPosition.values());
1773
2128
  },
1774
- fatal: isEnabled("fatal") ? (...args) => console.error("[fatal]", ...args) : () => {
2129
+ save: async (parameters) => {
2130
+ const { liquidity } = parameters;
2131
+ await db.transaction(async (tx) => {
2132
+ for (const qp of liquidity.queues) {
2133
+ await tx.insert(availableLiquidityPools).values({
2134
+ id: qp.pool.id,
2135
+ amount: qp.pool.amount,
2136
+ updatedAt: /* @__PURE__ */ new Date()
2137
+ }).onConflictDoUpdate({
2138
+ target: availableLiquidityPools.id,
2139
+ set: {
2140
+ amount: qp.pool.amount,
2141
+ updatedAt: /* @__PURE__ */ new Date()
2142
+ }
2143
+ });
2144
+ }
2145
+ for (const qp of liquidity.queues) {
2146
+ await tx.insert(availableLiquidityQueues).values({
2147
+ queueId: qp.queue.queueId,
2148
+ availableLiquidityPoolId: qp.pool.id,
2149
+ index: qp.queue.index,
2150
+ updatedAt: /* @__PURE__ */ new Date()
2151
+ }).onConflictDoUpdate({
2152
+ target: [
2153
+ availableLiquidityQueues.queueId,
2154
+ availableLiquidityQueues.availableLiquidityPoolId
2155
+ ],
2156
+ set: {
2157
+ index: qp.queue.index,
2158
+ updatedAt: /* @__PURE__ */ new Date()
2159
+ }
2160
+ });
2161
+ }
2162
+ await tx.insert(userPositions).values({
2163
+ id: liquidity.userPosition.id,
2164
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
2165
+ user: liquidity.userPosition.user,
2166
+ chainId: liquidity.userPosition.chainId,
2167
+ amount: liquidity.userPosition.amount,
2168
+ updatedAt: /* @__PURE__ */ new Date()
2169
+ }).onConflictDoUpdate({
2170
+ target: userPositions.id,
2171
+ set: {
2172
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
2173
+ user: liquidity.userPosition.user,
2174
+ chainId: liquidity.userPosition.chainId,
2175
+ amount: liquidity.userPosition.amount,
2176
+ updatedAt: /* @__PURE__ */ new Date()
2177
+ }
2178
+ });
2179
+ });
1775
2180
  }
1776
2181
  };
1777
- }
1778
- function silentLogger() {
2182
+ };
2183
+ function memory2() {
2184
+ const poolsById = /* @__PURE__ */ new Map();
2185
+ const queuesByComposite = /* @__PURE__ */ new Map();
2186
+ const queueIndexByQueueId = /* @__PURE__ */ new Map();
2187
+ const userPositionsById = /* @__PURE__ */ new Map();
1779
2188
  return {
1780
- trace: () => {
1781
- },
1782
- debug: () => {
1783
- },
1784
- info: () => {
1785
- },
1786
- warn: () => {
2189
+ getByUserPositionId: async (parameters) => {
2190
+ const up = userPositionsById.get(parameters.userPositionId);
2191
+ if (!up) return null;
2192
+ const compositeKeys = queueIndexByQueueId.get(up.availableLiquidityQueueId) || /* @__PURE__ */ new Set();
2193
+ const queues = [];
2194
+ for (const key of compositeKeys) {
2195
+ const q = queuesByComposite.get(key);
2196
+ if (!q) continue;
2197
+ const p = poolsById.get(q.availableLiquidityPoolId);
2198
+ if (!p) continue;
2199
+ queues.push({ queue: q, pool: p });
2200
+ }
2201
+ return { userPosition: up, queues };
1787
2202
  },
1788
- error: () => {
2203
+ getAll: async () => {
2204
+ const results = [];
2205
+ for (const up of userPositionsById.values()) {
2206
+ const compositeKeys = queueIndexByQueueId.get(up.availableLiquidityQueueId) || /* @__PURE__ */ new Set();
2207
+ const queues = [];
2208
+ for (const key of compositeKeys) {
2209
+ const q = queuesByComposite.get(key);
2210
+ if (!q) continue;
2211
+ const p = poolsById.get(q.availableLiquidityPoolId);
2212
+ if (!p) continue;
2213
+ queues.push({ queue: q, pool: p });
2214
+ }
2215
+ results.push({ userPosition: up, queues });
2216
+ }
2217
+ return results;
1789
2218
  },
1790
- fatal: () => {
2219
+ save: async (parameters) => {
2220
+ const { liquidity } = parameters;
2221
+ for (const qp of liquidity.queues) {
2222
+ poolsById.set(qp.pool.id, {
2223
+ id: qp.pool.id,
2224
+ amount: qp.pool.amount,
2225
+ updatedAt: /* @__PURE__ */ new Date()
2226
+ });
2227
+ }
2228
+ for (const qp of liquidity.queues) {
2229
+ const qid = qp.queue.queueId;
2230
+ if (!qid) continue;
2231
+ const composite = `${qid}::${qp.pool.id}`;
2232
+ queuesByComposite.set(composite, {
2233
+ queueId: qid,
2234
+ availableLiquidityPoolId: qp.pool.id,
2235
+ index: qp.queue.index,
2236
+ updatedAt: /* @__PURE__ */ new Date()
2237
+ });
2238
+ if (!queueIndexByQueueId.has(qid)) queueIndexByQueueId.set(qid, /* @__PURE__ */ new Set());
2239
+ queueIndexByQueueId.get(qid).add(composite);
2240
+ }
2241
+ userPositionsById.set(liquidity.userPosition.id, {
2242
+ id: liquidity.userPosition.id,
2243
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
2244
+ user: liquidity.userPosition.user,
2245
+ chainId: liquidity.userPosition.chainId,
2246
+ amount: liquidity.userPosition.amount,
2247
+ updatedAt: /* @__PURE__ */ new Date()
2248
+ });
1791
2249
  }
1792
2250
  };
1793
2251
  }
1794
- var loggerContext = new AsyncLocalStorage();
1795
- function runWithLogger(logger, fn) {
1796
- return loggerContext.run(logger, fn);
1797
- }
1798
- function getLogger() {
1799
- return loggerContext.getStore() ?? defaultLogger();
1800
- }
1801
2252
 
1802
- // src/Validation.ts
1803
- var Validation_exports = {};
1804
- __export(Validation_exports, {
1805
- run: () => run
2253
+ // src/OfferStore/index.ts
2254
+ var OfferStore_exports = {};
2255
+ __export(OfferStore_exports, {
2256
+ create: () => create3
1806
2257
  });
1807
- async function run(parameters) {
1808
- const { items, rules, ctx = {}, chunkSize } = parameters;
1809
- const issues = [];
1810
- let validItems = items.slice();
1811
- for (const rule of rules) {
1812
- if (validItems.length === 0) return { valid: [], issues };
1813
- const indicesToRemove = /* @__PURE__ */ new Set();
1814
- if (rule.kind === "single") {
1815
- for (let i = 0; i < validItems.length; i++) {
1816
- const item = validItems[i];
1817
- const issue = await rule.run(item, ctx);
1818
- if (issue) {
1819
- issues.push({ ...issue, ruleName: rule.name, item });
1820
- indicesToRemove.add(i);
2258
+ function create3(config) {
2259
+ const db = config.db;
2260
+ return {
2261
+ create: async (parameters) => {
2262
+ const { offer, status, metadata } = parameters;
2263
+ return await db.transaction(async (tx) => {
2264
+ const callbackId = getCallbackIdForOffer(offer);
2265
+ const result = await tx.insert(offers).values({
2266
+ hash: offer.hash.toLowerCase(),
2267
+ offering: offer.offering.toLowerCase(),
2268
+ assets: offer.assets.toString(),
2269
+ rate: offer.rate,
2270
+ maturity: offer.maturity,
2271
+ expiry: offer.expiry,
2272
+ start: offer.start,
2273
+ nonce: offer.nonce,
2274
+ buy: offer.buy,
2275
+ chainId: offer.chainId,
2276
+ loanToken: offer.loanToken.toLowerCase(),
2277
+ callbackAddress: offer.callback.address.toLowerCase(),
2278
+ callbackData: offer.callback.data,
2279
+ callbackGasLimit: offer.callback.gasLimit,
2280
+ signature: offer.signature,
2281
+ callbackId: callbackId ?? null
2282
+ }).onConflictDoNothing().returning();
2283
+ if (result.length === 0) {
2284
+ return offer.hash;
2285
+ }
2286
+ for (const collateral of offer.collaterals) {
2287
+ await tx.insert(offerCollaterals).values({
2288
+ offerHash: offer.hash.toLowerCase(),
2289
+ asset: collateral.asset.toLowerCase(),
2290
+ oracle: collateral.oracle.toLowerCase(),
2291
+ lltv: collateral.lltv
2292
+ });
2293
+ }
2294
+ await tx.insert(offerStatus).values({
2295
+ offerHash: offer.hash.toLowerCase(),
2296
+ status,
2297
+ metadata
2298
+ });
2299
+ return offer.hash;
2300
+ });
2301
+ },
2302
+ createMany: async (parameters) => {
2303
+ return await db.transaction(async (tx) => {
2304
+ const hashes = [];
2305
+ for (const { offer, status, metadata } of parameters) {
2306
+ const callbackId = getCallbackIdForOffer(offer);
2307
+ const result = await tx.insert(offers).values({
2308
+ hash: offer.hash.toLowerCase(),
2309
+ offering: offer.offering.toLowerCase(),
2310
+ assets: offer.assets.toString(),
2311
+ rate: offer.rate,
2312
+ maturity: offer.maturity,
2313
+ expiry: offer.expiry,
2314
+ start: offer.start,
2315
+ nonce: offer.nonce,
2316
+ buy: offer.buy,
2317
+ chainId: offer.chainId,
2318
+ loanToken: offer.loanToken.toLowerCase(),
2319
+ callbackAddress: offer.callback.address.toLowerCase(),
2320
+ callbackData: offer.callback.data,
2321
+ callbackGasLimit: offer.callback.gasLimit,
2322
+ signature: offer.signature,
2323
+ callbackId: callbackId ?? null
2324
+ }).onConflictDoNothing().returning();
2325
+ if (result.length === 0) continue;
2326
+ for (const collateral of offer.collaterals) {
2327
+ await tx.insert(offerCollaterals).values({
2328
+ offerHash: offer.hash.toLowerCase(),
2329
+ asset: collateral.asset.toLowerCase(),
2330
+ oracle: collateral.oracle.toLowerCase(),
2331
+ lltv: collateral.lltv
2332
+ });
2333
+ }
2334
+ await tx.insert(offerStatus).values({
2335
+ offerHash: offer.hash.toLowerCase(),
2336
+ status,
2337
+ metadata
2338
+ });
2339
+ hashes.push(offer.hash);
1821
2340
  }
2341
+ return hashes;
2342
+ });
2343
+ },
2344
+ getAll: async (params) => {
2345
+ const { query } = params ?? {};
2346
+ const conditions = [];
2347
+ const now = Time.now();
2348
+ if (query?.creators && query.creators.length > 0) {
2349
+ conditions.push(
2350
+ inArray(
2351
+ offers.offering,
2352
+ query.creators.map((c) => c.toLowerCase())
2353
+ )
2354
+ );
1822
2355
  }
1823
- } else if (rule.kind === "batch") {
1824
- const exec = async (slice, offset) => {
1825
- const map = await rule.run(slice, ctx);
1826
- for (let i = 0; i < slice.length; i++) {
1827
- const issue = map.get(i);
1828
- if (issue !== void 0) {
1829
- issues.push({ ...issue, ruleName: rule.name, item: slice[i] });
1830
- indicesToRemove.add(offset + i);
2356
+ if (query?.side) {
2357
+ conditions.push(eq(offers.buy, query.side === "buy"));
2358
+ }
2359
+ if (query?.chains && query.chains.length > 0) {
2360
+ conditions.push(
2361
+ inArray(
2362
+ offers.chainId,
2363
+ query.chains.map((chain) => BigInt(chain))
2364
+ )
2365
+ );
2366
+ }
2367
+ if (query?.loanTokens && query.loanTokens.length > 0) {
2368
+ conditions.push(
2369
+ inArray(
2370
+ offers.loanToken,
2371
+ query.loanTokens.map((t) => t.toLowerCase())
2372
+ )
2373
+ );
2374
+ }
2375
+ if (query?.callbackAddresses && query.callbackAddresses.length > 0) {
2376
+ conditions.push(
2377
+ inArray(
2378
+ offers.callbackAddress,
2379
+ query.callbackAddresses.map((c) => c.toLowerCase())
2380
+ )
2381
+ );
2382
+ }
2383
+ conditions.push(gte(offers.expiry, now));
2384
+ if (query?.minAmount !== void 0) {
2385
+ conditions.push(gte(offers.assets, query.minAmount.toString()));
2386
+ }
2387
+ if (query?.maxAmount !== void 0) {
2388
+ conditions.push(lte(offers.assets, query.maxAmount.toString()));
2389
+ }
2390
+ if (query?.minRate !== void 0) {
2391
+ conditions.push(gte(offers.rate, query.minRate));
2392
+ }
2393
+ if (query?.maxRate !== void 0) {
2394
+ conditions.push(lte(offers.rate, query.maxRate));
2395
+ }
2396
+ if (query?.minMaturity !== void 0) {
2397
+ conditions.push(gte(offers.maturity, query.minMaturity));
2398
+ }
2399
+ if (query?.maxMaturity !== void 0) {
2400
+ conditions.push(lte(offers.maturity, query.maxMaturity));
2401
+ }
2402
+ if (query?.minExpiry !== void 0) {
2403
+ conditions.push(gte(offers.expiry, query.minExpiry));
2404
+ }
2405
+ if (query?.maxExpiry !== void 0) {
2406
+ conditions.push(lte(offers.expiry, query.maxExpiry));
2407
+ }
2408
+ if (query?.collateralAssets && query.collateralAssets.length > 0) {
2409
+ conditions.push(
2410
+ inArray(
2411
+ offerCollaterals.asset,
2412
+ query.collateralAssets.map((a) => a.toLowerCase())
2413
+ )
2414
+ );
2415
+ }
2416
+ if (query?.collateralOracles && query.collateralOracles.length > 0) {
2417
+ conditions.push(
2418
+ inArray(
2419
+ offerCollaterals.oracle,
2420
+ query.collateralOracles.map((o) => o.toLowerCase())
2421
+ )
2422
+ );
2423
+ }
2424
+ if (query?.collateralTuple && query.collateralTuple.length > 0) {
2425
+ const tupleClauses = query.collateralTuple.map((tuple) => {
2426
+ const parts = [
2427
+ sql`${offerCollaterals.asset} = ${tuple.asset.toLowerCase()}`
2428
+ ];
2429
+ if (tuple.oracle) {
2430
+ parts.push(sql`${offerCollaterals.oracle} = ${tuple.oracle.toLowerCase()}`);
2431
+ }
2432
+ if (tuple.lltv !== void 0) {
2433
+ parts.push(sql`${offerCollaterals.lltv} = ${tuple.lltv}`);
2434
+ }
2435
+ let clause = parts[0];
2436
+ for (let i = 1; i < parts.length; i++) {
2437
+ clause = sql`${clause} AND ${parts[i]}`;
1831
2438
  }
2439
+ return sql`(${clause})`;
2440
+ }).filter((c) => c !== void 0);
2441
+ if (tupleClauses.length > 0) {
2442
+ let combined = tupleClauses[0];
2443
+ for (let i = 1; i < tupleClauses.length; i++) {
2444
+ combined = sql`${combined} OR ${tupleClauses[i]}`;
2445
+ }
2446
+ conditions.push(sql`(${combined})`);
1832
2447
  }
1833
- };
1834
- if (!chunkSize) await exec(validItems, 0);
1835
- else {
1836
- for (let i = 0; i < validItems.length; i += chunkSize) {
1837
- await exec(validItems.slice(i, i + chunkSize), i);
2448
+ }
2449
+ if (query?.minLltv !== void 0) {
2450
+ conditions.push(gte(offerCollaterals.lltv, parseUnits(query.minLltv.toString(), 16)));
2451
+ }
2452
+ if (query?.maxLltv !== void 0) {
2453
+ conditions.push(lte(offerCollaterals.lltv, parseUnits(query.maxLltv.toString(), 16)));
2454
+ }
2455
+ const sortBy = query?.sortBy ?? "expiry";
2456
+ const sortOrder = query?.sortOrder ?? "desc";
2457
+ const sortColumn = (() => {
2458
+ switch (sortBy) {
2459
+ case "rate":
2460
+ return offers.rate;
2461
+ case "maturity":
2462
+ return offers.maturity;
2463
+ case "expiry":
2464
+ return offers.expiry;
2465
+ case "amount":
2466
+ return offers.assets;
2467
+ default:
2468
+ return offers.expiry;
2469
+ }
2470
+ })();
2471
+ const cursor = decode(query?.cursor);
2472
+ if (cursor) {
2473
+ if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
2474
+ throw new Error("Cursor does not match the current sort parameters");
2475
+ }
2476
+ const op = sortOrder === "asc" ? sql`>` : sql`<`;
2477
+ const cursorVal = (() => {
2478
+ switch (sortBy) {
2479
+ case "rate":
2480
+ return BigInt(cursor.rate);
2481
+ case "amount":
2482
+ return BigInt(cursor.assets);
2483
+ case "maturity":
2484
+ return cursor.maturity;
2485
+ case "expiry":
2486
+ return cursor.expiry;
2487
+ default:
2488
+ return cursor.expiry;
2489
+ }
2490
+ })();
2491
+ conditions.push(sql`(${sortColumn}, ${offers.hash}) ${op} (${cursorVal}, ${cursor.hash})`);
2492
+ }
2493
+ const limit = query?.limit ?? 20;
2494
+ const latestStatus = db.select({
2495
+ status: offerStatus.status,
2496
+ metadata: offerStatus.metadata
2497
+ }).from(offerStatus).where(eq(offerStatus.offerHash, offers.hash)).orderBy(desc(offerStatus.createdAt)).limit(1).as("latest_status");
2498
+ const sumConsumed = db.select({
2499
+ consumed: sql`COALESCE(SUM(${consumed.consumed}), 0)`.as("consumed")
2500
+ }).from(consumed).where(
2501
+ and(
2502
+ eq(consumed.offering, offers.offering),
2503
+ eq(consumed.nonce, offers.nonce),
2504
+ eq(consumed.chainId, offers.chainId)
2505
+ )
2506
+ ).as("sum_consumed");
2507
+ const results = await db.select({
2508
+ hash: offers.hash,
2509
+ offering: offers.offering,
2510
+ assets: offers.assets,
2511
+ consumed: sumConsumed.consumed,
2512
+ rate: offers.rate,
2513
+ maturity: offers.maturity,
2514
+ expiry: offers.expiry,
2515
+ start: offers.start,
2516
+ nonce: offers.nonce,
2517
+ buy: offers.buy,
2518
+ chainId: offers.chainId,
2519
+ loanToken: offers.loanToken,
2520
+ callbackAddress: offers.callbackAddress,
2521
+ callbackData: offers.callbackData,
2522
+ callbackGasLimit: offers.callbackGasLimit,
2523
+ signature: offers.signature,
2524
+ createdAt: offers.createdAt,
2525
+ collateralAsset: offerCollaterals.asset,
2526
+ collateralOracle: offerCollaterals.oracle,
2527
+ collateralLltv: offerCollaterals.lltv,
2528
+ status: latestStatus.status,
2529
+ metadata: latestStatus.metadata
2530
+ }).from(offers).leftJoin(offerCollaterals, eq(offers.hash, offerCollaterals.offerHash)).leftJoinLateral(latestStatus, sql`true`).leftJoinLateral(sumConsumed, sql`true`).where(
2531
+ and(
2532
+ conditions.length > 0 ? and(...conditions) : sql`true`,
2533
+ query?.status && query.status.length > 0 ? inArray(latestStatus.status, query.status) : eq(latestStatus.status, "valid"),
2534
+ sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) > 0`
2535
+ )
2536
+ ).orderBy(
2537
+ ...sortOrder === "asc" ? [asc(sortColumn), asc(offers.hash)] : [desc(sortColumn), desc(offers.hash)]
2538
+ ).limit(limit);
2539
+ const offerMap = /* @__PURE__ */ new Map();
2540
+ for (const row of results) {
2541
+ const offer = offerMap.get(row.hash) || {
2542
+ hash: row.hash,
2543
+ offering: row.offering,
2544
+ assets: BigInt(row.assets),
2545
+ consumed: BigInt(row.consumed),
2546
+ rate: row.rate,
2547
+ maturity: row.maturity,
2548
+ expiry: row.expiry,
2549
+ start: row.start,
2550
+ nonce: row.nonce,
2551
+ buy: row.buy,
2552
+ chainId: row.chainId,
2553
+ loanToken: row.loanToken,
2554
+ callbackAddress: row.callbackAddress,
2555
+ callbackData: row.callbackData,
2556
+ callbackGasLimit: row.callbackGasLimit,
2557
+ signature: row.signature,
2558
+ createdAt: row.createdAt,
2559
+ collaterals: [],
2560
+ status: row.status,
2561
+ metadata: row.metadata
2562
+ };
2563
+ if (row.collateralAsset && row.collateralOracle && row.collateralLltv) {
2564
+ offer.collaterals.push({
2565
+ asset: row.collateralAsset,
2566
+ oracle: row.collateralOracle,
2567
+ lltv: LLTV.from(row.collateralLltv)
2568
+ });
2569
+ }
2570
+ offerMap.set(row.hash, offer);
2571
+ }
2572
+ let nextCursor = null;
2573
+ if (results.length === limit && results.length > 0) {
2574
+ const lastRow = results[results.length - 1];
2575
+ const base = {
2576
+ sort: sortBy,
2577
+ dir: sortOrder,
2578
+ hash: lastRow.hash
2579
+ };
2580
+ switch (sortBy) {
2581
+ case "rate":
2582
+ base.rate = lastRow.rate.toString();
2583
+ break;
2584
+ case "amount":
2585
+ base.assets = lastRow.assets.toString();
2586
+ break;
2587
+ case "maturity":
2588
+ base.maturity = lastRow.maturity;
2589
+ break;
2590
+ default:
2591
+ base.expiry = lastRow.expiry;
2592
+ }
2593
+ nextCursor = encode(base);
2594
+ }
2595
+ const transformedResults = [];
2596
+ for (const offerData of offerMap.values()) {
2597
+ const offer = Offer.from({
2598
+ offering: offerData.offering,
2599
+ assets: offerData.assets,
2600
+ rate: offerData.rate,
2601
+ maturity: Maturity.from(offerData.maturity),
2602
+ expiry: offerData.expiry,
2603
+ start: offerData.start,
2604
+ nonce: offerData.nonce,
2605
+ buy: offerData.buy,
2606
+ chainId: offerData.chainId,
2607
+ loanToken: offerData.loanToken,
2608
+ collaterals: offerData.collaterals.map((c) => ({
2609
+ asset: c.asset,
2610
+ oracle: c.oracle,
2611
+ lltv: LLTV.from(c.lltv)
2612
+ })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
2613
+ callback: {
2614
+ address: offerData.callbackAddress,
2615
+ data: offerData.callbackData,
2616
+ gasLimit: offerData.callbackGasLimit
2617
+ },
2618
+ ...offerData.signature !== null ? { signature: offerData.signature } : void 0
2619
+ });
2620
+ transformedResults.push({
2621
+ ...offer,
2622
+ consumed: offerData.consumed,
2623
+ status: offerData.status,
2624
+ ...offerData.metadata !== null ? { metadata: offerData.metadata } : void 0
2625
+ });
2626
+ }
2627
+ return { offers: transformedResults, nextCursor };
2628
+ },
2629
+ /**
2630
+ * Returns offers that match specified offer based on the provided parameters.
2631
+ *
2632
+ * Rules for filtering:
2633
+ * - If offer expiry is in the past compared to now, it should not be included
2634
+ * - If offer maturity is in the past compared to now, it should not be included
2635
+ * - It should return if buyOffer.rate >= sellOffer.rate
2636
+ * - It should return if buyOffer.userAddress != sellOffer.userAddress
2637
+ * - It should return if maturity matches
2638
+ * - It should return if loanToken matches
2639
+ * - If the incoming intent is a buy / lend offer, only consider existing sell / borrow offers.
2640
+ * - If the incoming intent is a sell / borrow offer, only consider existing buy / lend offers.
2641
+ * - When the intent is lend: require offer_collaterals ⊆ intent_collaterals
2642
+ * - When the intent is borrow: require intent_collaterals ⊆ offer_collaterals
2643
+ * - It should sort by increasing rate if the input offer is a buy/lend offer
2644
+ * - It should sort by decreasing rate if the input offer is a sell/borrow offer
2645
+ */
2646
+ findMatchingOffers: async (params) => {
2647
+ const {
2648
+ side,
2649
+ chainId,
2650
+ rate,
2651
+ collaterals = [],
2652
+ maturity,
2653
+ minMaturity,
2654
+ maxMaturity,
2655
+ loanToken,
2656
+ creator,
2657
+ cursor,
2658
+ limit = 20
2659
+ } = params;
2660
+ const isIncomingBuy = side === "buy";
2661
+ const nowEpochSeconds = Math.floor(Date.now() / 1e3);
2662
+ const rateSortDirection = isIncomingBuy ? "desc" : "asc";
2663
+ const baseWhereClauses = [];
2664
+ baseWhereClauses.push(eq(offers.buy, !isIncomingBuy));
2665
+ baseWhereClauses.push(eq(offers.chainId, BigInt(chainId)));
2666
+ baseWhereClauses.push(gte(offers.expiry, nowEpochSeconds));
2667
+ baseWhereClauses.push(gte(offers.maturity, nowEpochSeconds));
2668
+ if (maturity) baseWhereClauses.push(eq(offers.maturity, maturity));
2669
+ if (minMaturity) baseWhereClauses.push(gte(offers.maturity, minMaturity));
2670
+ if (maxMaturity) baseWhereClauses.push(lte(offers.maturity, maxMaturity));
2671
+ if (loanToken) baseWhereClauses.push(eq(offers.loanToken, loanToken.toLowerCase()));
2672
+ if (creator) baseWhereClauses.push(eq(offers.offering, creator.toLowerCase()));
2673
+ if (rate)
2674
+ baseWhereClauses.push(isIncomingBuy ? gte(offers.rate, rate) : lte(offers.rate, rate));
2675
+ const parsedCursor = decode(cursor);
2676
+ if (parsedCursor) {
2677
+ if (parsedCursor.sort !== "rate" || parsedCursor.dir !== rateSortDirection) {
2678
+ throw new Error("Cursor does not match the current sort parameters");
2679
+ }
2680
+ }
2681
+ if (collaterals.length > 0) {
2682
+ const collateralValues = collaterals.map(
2683
+ (c) => `('${c.asset.toLowerCase()}', '${c.oracle.toLowerCase()}', ${c.lltv.toString()})`
2684
+ ).join(", ");
2685
+ const userCollaterals = sql`(VALUES ${sql.raw(collateralValues)}) AS user_collaterals(asset, oracle, lltv)`;
2686
+ const sellPredicate = sql`
2687
+ NOT EXISTS (
2688
+ SELECT 1 FROM ${userCollaterals}
2689
+ WHERE NOT EXISTS (
2690
+ SELECT 1 FROM ${offerCollaterals} offer_collaterals
2691
+ WHERE offer_collaterals.offer_hash = ${offers.hash}
2692
+ AND offer_collaterals.asset = user_collaterals.asset
2693
+ AND offer_collaterals.oracle = user_collaterals.oracle
2694
+ AND offer_collaterals.lltv = user_collaterals.lltv
2695
+ )
2696
+ )
2697
+ `;
2698
+ const buyPredicate = sql`
2699
+ NOT EXISTS (
2700
+ SELECT 1 FROM ${offerCollaterals} offer_collaterals
2701
+ WHERE offer_collaterals.offer_hash = ${offers.hash}
2702
+ AND NOT EXISTS (
2703
+ SELECT 1 FROM ${userCollaterals}
2704
+ WHERE user_collaterals.asset = offer_collaterals.asset
2705
+ AND user_collaterals.oracle = offer_collaterals.oracle
2706
+ AND user_collaterals.lltv = offer_collaterals.lltv
2707
+ )
2708
+ )
2709
+ `;
2710
+ baseWhereClauses.push(isIncomingBuy ? buyPredicate : sellPredicate);
2711
+ }
2712
+ const latestStatus = db.select({
2713
+ status: offerStatus.status,
2714
+ metadata: offerStatus.metadata
2715
+ }).from(offerStatus).where(eq(offerStatus.offerHash, offers.hash)).orderBy(desc(offerStatus.createdAt)).limit(1).as("latest_status");
2716
+ const sumConsumed = db.select({
2717
+ consumed: sql`COALESCE(SUM(${consumed.consumed}), 0)`.as("consumed")
2718
+ }).from(consumed).where(
2719
+ and(
2720
+ eq(consumed.offering, offers.offering),
2721
+ eq(consumed.nonce, offers.nonce),
2722
+ eq(consumed.chainId, offers.chainId)
2723
+ )
2724
+ ).as("sum_consumed");
2725
+ const statusCondition = eq(latestStatus.status, "valid");
2726
+ const bestOffers = db.selectDistinctOn(
2727
+ // group key
2728
+ [offers.offering, offers.nonce, offers.buy],
2729
+ {
2730
+ hash: offers.hash,
2731
+ offering: offers.offering,
2732
+ assets: offers.assets,
2733
+ consumed: sumConsumed.consumed,
2734
+ remaining: sql`${offers.assets} - COALESCE(${sumConsumed.consumed}, 0)`.as("remaining"),
2735
+ rate: offers.rate,
2736
+ maturity: offers.maturity,
2737
+ expiry: offers.expiry,
2738
+ start: offers.start,
2739
+ nonce: offers.nonce,
2740
+ buy: offers.buy,
2741
+ chainId: offers.chainId,
2742
+ loanToken: offers.loanToken,
2743
+ callbackAddress: offers.callbackAddress,
2744
+ callbackData: offers.callbackData,
2745
+ callbackGasLimit: offers.callbackGasLimit,
2746
+ signature: offers.signature,
2747
+ callbackId: offers.callbackId,
2748
+ status: latestStatus.status,
2749
+ metadata: latestStatus.metadata
2750
+ }
2751
+ ).from(offers).leftJoinLateral(latestStatus, sql`true`).leftJoinLateral(sumConsumed, sql`true`).where(
2752
+ and(
2753
+ and(...baseWhereClauses),
2754
+ statusCondition,
2755
+ sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) > 0`
2756
+ )
2757
+ ).orderBy(
2758
+ offers.offering,
2759
+ offers.nonce,
2760
+ offers.buy,
2761
+ // 1 price (direction depends on side)
2762
+ sql`CASE WHEN ${offers.buy} THEN ${offers.rate} ELSE -${offers.rate} END`,
2763
+ // 2 size (remaining)
2764
+ sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) DESC`,
2765
+ // 3 term (longer maturity)
2766
+ desc(offers.maturity)
2767
+ ).as("best_offers");
2768
+ const queueLiquidity = db.select({
2769
+ callbackId: userPositions.id,
2770
+ userAmount: userPositions.amount,
2771
+ // user's per-callback cap
2772
+ queueLiquidity: sql`COALESCE(SUM(${availableLiquidityPools.amount}), 0)`.as(
2773
+ "queue_liquidity"
2774
+ )
2775
+ }).from(userPositions).leftJoin(
2776
+ availableLiquidityQueues,
2777
+ eq(userPositions.availableLiquidityQueueId, availableLiquidityQueues.queueId)
2778
+ ).leftJoin(
2779
+ availableLiquidityPools,
2780
+ eq(availableLiquidityQueues.availableLiquidityPoolId, availableLiquidityPools.id)
2781
+ ).groupBy(userPositions.id).as("queue_liquidity");
2782
+ const sortExpr = sql`CASE WHEN ${bestOffers.buy} THEN ${bestOffers.rate} ELSE -${bestOffers.rate} END`;
2783
+ const offersWithEligibility = db.select({
2784
+ hash: bestOffers.hash,
2785
+ offering: bestOffers.offering,
2786
+ assets: bestOffers.assets,
2787
+ consumed: bestOffers.consumed,
2788
+ remaining: bestOffers.remaining,
2789
+ rate: bestOffers.rate,
2790
+ maturity: bestOffers.maturity,
2791
+ expiry: bestOffers.expiry,
2792
+ start: bestOffers.start,
2793
+ nonce: bestOffers.nonce,
2794
+ buy: bestOffers.buy,
2795
+ chainId: bestOffers.chainId,
2796
+ loanToken: bestOffers.loanToken,
2797
+ callbackAddress: bestOffers.callbackAddress,
2798
+ callbackData: bestOffers.callbackData,
2799
+ callbackGasLimit: bestOffers.callbackGasLimit,
2800
+ signature: bestOffers.signature,
2801
+ callbackId: bestOffers.callbackId,
2802
+ status: bestOffers.status,
2803
+ metadata: bestOffers.metadata,
2804
+ // liquidity caps
2805
+ userAmount: sql`COALESCE(${queueLiquidity.userAmount}, 0)`.as("user_amount"),
2806
+ queueLiquidity: sql`COALESCE(${queueLiquidity.queueLiquidity}, 0)`.as(
2807
+ "queue_liquidity"
2808
+ ),
2809
+ // running total of remaining per callback, ordered by the *same* priority as selection
2810
+ cumulativeRemaining: sql`
2811
+ SUM(${bestOffers.remaining}) OVER (
2812
+ PARTITION BY ${bestOffers.callbackId}
2813
+ ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
2814
+ ROWS UNBOUNDED PRECEDING
2815
+ )
2816
+ `.as("cumulative_remaining"),
2817
+ eligible: sql`
2818
+ CASE
2819
+ WHEN ${bestOffers.buy} = false THEN true
2820
+ WHEN ${bestOffers.remaining} <= 0 THEN false
2821
+ ELSE ( SUM(${bestOffers.remaining}) OVER (
2822
+ PARTITION BY ${bestOffers.callbackId}
2823
+ ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
2824
+ ROWS UNBOUNDED PRECEDING
2825
+ )
2826
+ <= LEAST(
2827
+ COALESCE(${queueLiquidity.userAmount}, 0),
2828
+ COALESCE(${queueLiquidity.queueLiquidity}, 0)
2829
+ )
2830
+ )
2831
+ END
2832
+ `.as("eligible")
2833
+ }).from(bestOffers).leftJoin(queueLiquidity, eq(bestOffers.callbackId, queueLiquidity.callbackId)).as("offers_with_eligibility");
2834
+ const hardCap = limit * 5 + 1 + (parsedCursor ? 1 : 0);
2835
+ const validOffers = db.select({
2836
+ // pass-through all fields + a global row_number for hard cap
2837
+ hash: offersWithEligibility.hash,
2838
+ offering: offersWithEligibility.offering,
2839
+ assets: offersWithEligibility.assets,
2840
+ consumed: offersWithEligibility.consumed,
2841
+ remaining: offersWithEligibility.remaining,
2842
+ rate: offersWithEligibility.rate,
2843
+ maturity: offersWithEligibility.maturity,
2844
+ expiry: offersWithEligibility.expiry,
2845
+ start: offersWithEligibility.start,
2846
+ nonce: offersWithEligibility.nonce,
2847
+ buy: offersWithEligibility.buy,
2848
+ chainId: offersWithEligibility.chainId,
2849
+ loanToken: offersWithEligibility.loanToken,
2850
+ callbackAddress: offersWithEligibility.callbackAddress,
2851
+ callbackData: offersWithEligibility.callbackData,
2852
+ callbackGasLimit: offersWithEligibility.callbackGasLimit,
2853
+ signature: offersWithEligibility.signature,
2854
+ callbackId: offersWithEligibility.callbackId,
2855
+ status: offersWithEligibility.status,
2856
+ metadata: offersWithEligibility.metadata,
2857
+ userAmount: offersWithEligibility.userAmount,
2858
+ queueLiquidity: offersWithEligibility.queueLiquidity,
2859
+ cumulativeRemaining: offersWithEligibility.cumulativeRemaining,
2860
+ eligible: offersWithEligibility.eligible,
2861
+ // sort expression is the same again as in offersWithEligibility
2862
+ rowNumber: sql`
2863
+ ROW_NUMBER() OVER (
2864
+ ORDER BY
2865
+ CASE WHEN ${offersWithEligibility.buy} THEN ${offersWithEligibility.rate} ELSE -${offersWithEligibility.rate} END,
2866
+ ${asc(offersWithEligibility.hash)}
2867
+ )
2868
+ `.as("row_number")
2869
+ }).from(offersWithEligibility).where(sql`${offersWithEligibility.eligible} = true`).limit(hardCap).as("valid_offers");
2870
+ const cursorTuple = parsedCursor ? {
2871
+ rate: BigInt(parsedCursor.rate),
2872
+ hash: parsedCursor.hash
2873
+ } : null;
2874
+ const withCollats = await db.select({
2875
+ // base fields
2876
+ hash: sql`${validOffers.hash}`,
2877
+ offering: sql`${validOffers.offering}`,
2878
+ assets: validOffers.assets,
2879
+ consumed: validOffers.consumed,
2880
+ rate: validOffers.rate,
2881
+ maturity: validOffers.maturity,
2882
+ expiry: validOffers.expiry,
2883
+ start: validOffers.start,
2884
+ nonce: validOffers.nonce,
2885
+ buy: validOffers.buy,
2886
+ chainId: validOffers.chainId,
2887
+ loanToken: sql`${validOffers.loanToken}`,
2888
+ callbackAddress: sql`${validOffers.callbackAddress}`,
2889
+ callbackData: sql`${validOffers.callbackData}`,
2890
+ callbackGasLimit: validOffers.callbackGasLimit,
2891
+ signature: sql`${validOffers.signature}`,
2892
+ callbackId: validOffers.callbackId,
2893
+ status: sql`${validOffers.status}`,
2894
+ metadata: validOffers.metadata,
2895
+ // collateral fields
2896
+ collateralAsset: sql`${offerCollaterals.asset}`,
2897
+ collateralOracle: sql`${offerCollaterals.oracle}`,
2898
+ collateralLltv: offerCollaterals.lltv
2899
+ }).from(validOffers).leftJoin(offerCollaterals, eq(validOffers.hash, offerCollaterals.offerHash)).where(
2900
+ and(
2901
+ ...cursorTuple ? [
2902
+ sql`(${validOffers.rate}, ${validOffers.hash}) ${rateSortDirection === "asc" ? sql`>` : sql`<`} (${cursorTuple.rate}, ${cursorTuple.hash})`
2903
+ ] : []
2904
+ )
2905
+ ).orderBy(
2906
+ rateSortDirection === "asc" ? asc(validOffers.rate) : desc(validOffers.rate),
2907
+ asc(validOffers.hash)
2908
+ );
2909
+ const buildOffersMap = (rows, skipHash) => {
2910
+ const map = /* @__PURE__ */ new Map();
2911
+ for (const row of rows) {
2912
+ const entry = map.get(row.hash) ?? { base: row, collaterals: [] };
2913
+ if (row.collateralAsset && row.collateralOracle && row.collateralLltv) {
2914
+ entry.collaterals.push({
2915
+ asset: row.collateralAsset,
2916
+ oracle: row.collateralOracle,
2917
+ lltv: LLTV.from(row.collateralLltv)
2918
+ });
2919
+ }
2920
+ map.set(row.hash, entry);
1838
2921
  }
2922
+ return map;
2923
+ };
2924
+ const allEntries = Array.from(buildOffersMap(withCollats).values());
2925
+ const pageEntries = allEntries.slice(0, limit);
2926
+ let nextCursor = null;
2927
+ if (allEntries.length > limit) {
2928
+ const last = pageEntries[pageEntries.length - 1].base;
2929
+ nextCursor = encode({
2930
+ sort: "rate",
2931
+ dir: rateSortDirection,
2932
+ hash: last.hash,
2933
+ rate: last.rate.toString()
2934
+ });
2935
+ }
2936
+ const data = pageEntries.map(({ base, collaterals: cols }) => ({
2937
+ ...Offer.from({
2938
+ offering: base.offering,
2939
+ assets: BigInt(base.assets),
2940
+ rate: base.rate,
2941
+ maturity: Maturity.from(base.maturity),
2942
+ expiry: base.expiry,
2943
+ start: base.start,
2944
+ nonce: base.nonce,
2945
+ buy: base.buy,
2946
+ chainId: base.chainId,
2947
+ loanToken: base.loanToken,
2948
+ collaterals: cols.sort(
2949
+ (a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())
2950
+ ),
2951
+ callback: {
2952
+ address: base.callbackAddress,
2953
+ data: base.callbackData,
2954
+ gasLimit: base.callbackGasLimit
2955
+ },
2956
+ ...base.signature !== null ? { signature: base.signature } : void 0
2957
+ }),
2958
+ consumed: BigInt(base.consumed),
2959
+ status: base.status,
2960
+ ...base.metadata ? { metadata: base.metadata } : {}
2961
+ }));
2962
+ return { offers: data, nextCursor };
2963
+ },
2964
+ delete: async (hash) => {
2965
+ const result = await db.delete(offers).where(eq(offers.hash, hash.toLowerCase()));
2966
+ return result.affectedRows > 0;
2967
+ },
2968
+ deleteMany: async (hashes) => {
2969
+ if (hashes.length === 0) {
2970
+ return 0;
1839
2971
  }
2972
+ return await db.transaction(async (tx) => {
2973
+ const normalizedHashes = hashes.map((hash) => hash.toLowerCase());
2974
+ const result = await tx.delete(offers).where(
2975
+ sql`${offers.hash} = ANY(${sql.raw(`ARRAY[${normalizedHashes.map((h) => `'${h}'`).join(", ")}]`)})`
2976
+ );
2977
+ return result.affectedRows;
2978
+ });
2979
+ },
2980
+ updateStatus: async (parameters) => {
2981
+ const latest = await db.select({ status: offerStatus.status }).from(offerStatus).where(eq(offerStatus.offerHash, parameters.offerHash.toLowerCase())).orderBy(desc(offerStatus.createdAt)).limit(1);
2982
+ if (latest.length > 0 && latest[0].status === parameters.status) return;
2983
+ await db.insert(offerStatus).values({
2984
+ offerHash: parameters.offerHash.toLowerCase(),
2985
+ status: parameters.status,
2986
+ metadata: parameters.metadata
2987
+ });
2988
+ },
2989
+ updateConsumedAmount: async (parameters) => {
2990
+ await db.insert(consumed).values({
2991
+ id: parameters.id,
2992
+ chainId: parameters.chainId,
2993
+ offering: parameters.offering.toLowerCase(),
2994
+ nonce: parameters.nonce,
2995
+ consumed: parameters.consumed.toString()
2996
+ }).onConflictDoNothing();
1840
2997
  }
1841
- validItems = validItems.filter((_, i) => !indicesToRemove.has(i));
1842
- }
1843
- return {
1844
- valid: validItems,
1845
- issues
1846
2998
  };
1847
2999
  }
1848
3000
 
1849
- // src/ValidationRule.ts
1850
- var ValidationRule_exports = {};
1851
- __export(ValidationRule_exports, {
1852
- batch: () => batch,
1853
- morpho: () => morpho,
1854
- single: () => single
3001
+ // src/OfferStore/PG.ts
3002
+ var PG_exports = {};
3003
+ __export(PG_exports, {
3004
+ applyMigrations: () => applyMigrations,
3005
+ clean: () => clean,
3006
+ connect: () => connect2
1855
3007
  });
1856
- function single(name, run2) {
1857
- return { kind: "single", name, run: run2 };
3008
+ function connect2(parameters) {
3009
+ if (parameters.type === "pg") {
3010
+ const pool2 = new Pool({ connectionString: parameters.endpoint });
3011
+ const client2 = drizzle(pool2, { schema: schema_exports });
3012
+ return Object.assign(client2, { name: "pg", pool: pool2 });
3013
+ }
3014
+ const pool = new PGlite();
3015
+ const client = drizzle$1(pool, { schema: schema_exports });
3016
+ return Object.assign(client, { name: "pglite", pool });
1858
3017
  }
1859
- function batch(name, run2) {
1860
- return { kind: "batch", name, run: run2 };
3018
+ async function applyMigrations(pg) {
3019
+ const migrationsFolder = process.env.AWS_LAMBDA_FUNCTION_NAME ? path.join(process.cwd(), "drizzle", VERSION) : path.join(__dirname, "drizzle", VERSION);
3020
+ await pg.execute(`create schema if not exists "${VERSION}"`);
3021
+ if (pg.name === "pg") {
3022
+ await migrate(pg, { migrationsFolder });
3023
+ return;
3024
+ }
3025
+ await migrate$1(pg, { migrationsFolder });
1861
3026
  }
1862
- function morpho() {
1863
- const chainId = single("chain_id", (offer, { chain }) => {
1864
- if (chain.id !== offer.chainId) {
1865
- return {
1866
- message: `Chain ID ${offer.chainId} is not the same as the chain ID in the context (${chain.id})`
1867
- };
1868
- }
3027
+ async function clean(pg) {
3028
+ await pg.execute(`drop schema if exists "${VERSION}" cascade`);
3029
+ await pg.execute(`create schema "${VERSION}"`);
3030
+ await pg.execute("drop schema if exists drizzle cascade");
3031
+ }
3032
+
3033
+ // src/Services.ts
3034
+ var Services_exports = {};
3035
+ __export(Services_exports, {
3036
+ from: () => from2
3037
+ });
3038
+ var from2 = async (config) => {
3039
+ const { chain, rpcUrl, dbConfig } = config;
3040
+ const walletClient = createWalletClient({
3041
+ chain,
3042
+ transport: http(rpcUrl)
3043
+ }).extend(publicActions);
3044
+ if (dbConfig.type === "pg" && !dbConfig.endpoint) {
3045
+ throw new Error("dbOffer.endpoint is required when dbOffer.type is pg");
3046
+ }
3047
+ const DB = dbConfig.type === "pg" ? connect2({ type: "pg", endpoint: dbConfig.endpoint }) : connect2({ type: "pglite" });
3048
+ const offerStore = create3({ db: DB });
3049
+ const collectorBlockStore = create({
3050
+ db: DB
1869
3051
  });
1870
- const loanToken = single("loan_token", (offer, { chain }) => {
1871
- const tokens = new Set(
1872
- Array.from(chain.whitelistedAssets.values()).map((a) => a.toLowerCase())
1873
- );
1874
- if (!tokens.has(offer.loanToken.toLowerCase())) {
1875
- return {
1876
- message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
1877
- };
3052
+ const liquidityStore = create2({ db: DB });
3053
+ const mempoolOffersCollector = createMempoolCollector({
3054
+ mempool: Mempool.connect({
3055
+ client: walletClient,
3056
+ mempoolAddress: chain.mempool.address
3057
+ }),
3058
+ offerStore,
3059
+ collectorBlockStore,
3060
+ chain,
3061
+ options: {
3062
+ interval: 1e4
1878
3063
  }
1879
3064
  });
1880
- const expiry = single("expiry", (offer, _) => {
1881
- if (offer.expiry < Math.floor(Date.now() / 1e3)) {
1882
- return { message: "Expiry mismatch" };
3065
+ const consumedEventsCollector = createConsumedEventsCollector({
3066
+ client: walletClient,
3067
+ contractAddress: chain.morpho,
3068
+ offerStore,
3069
+ collectorBlockStore,
3070
+ chainId: chain.id,
3071
+ options: {
3072
+ interval: 1e4
1883
3073
  }
1884
3074
  });
1885
- const callback = single("empty_callback", (offer, _) => {
1886
- if (!offer.buy || offer.callback.data !== "0x") {
1887
- return { message: "Callback not supported yet." };
3075
+ const buyWithEmptyCallbackLiquidityCollector = createBuyWithEmptyCallbackLiquidityCollector({
3076
+ client: walletClient,
3077
+ offerStore,
3078
+ collectorBlockStore,
3079
+ liquidityStore,
3080
+ chain,
3081
+ options: {
3082
+ interval: 1e4
1888
3083
  }
1889
3084
  });
1890
- return [
1891
- chainId,
1892
- loanToken,
1893
- expiry,
1894
- // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1895
- // integrators should be able to choose if they want to keep the offer or not
1896
- callback
1897
- ];
1898
- }
3085
+ return {
3086
+ offerStore,
3087
+ collectorBlockStore,
3088
+ liquidityStore,
3089
+ mempoolOffersCollector,
3090
+ consumedEventsCollector,
3091
+ buyWithEmptyCallbackLiquidityCollector,
3092
+ DB
3093
+ };
3094
+ };
1899
3095
 
1900
- export { apiSchema_exports as ApiSchema, Callback_exports as Callback, Cursor_exports as Cursor, Liquidity_exports as Liquidity, Logger_exports as Logger, OfferStore_exports as OfferStore, router_exports as Router, RouterOffer_exports as RouterOffer, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
3096
+ export { apiSchema_exports as ApiSchema, Callback_exports as Callback, Collector_exports as Collector, CollectorBlockStore_exports as CollectorBlockStore, Cursor_exports as Cursor, Liquidity_exports as Liquidity, LiquidityStore_exports as LiquidityStore, Logger_exports as Logger, OfferStore_exports as OfferStore, schema_exports as OffersSchema, PG_exports as PG, router_exports as Router, RouterOffer_exports as RouterOffer, Services_exports as Services, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
1901
3097
  //# sourceMappingURL=index.node.mjs.map
1902
3098
  //# sourceMappingURL=index.node.mjs.map