@morpho-dev/router 0.1.16 → 0.1.18

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,13 +1,15 @@
1
1
  import { serve as serve$1 } from '@hono/node-server';
2
2
  import { Hono } from 'hono';
3
3
  import { cors } from 'hono/cors';
4
- import { AsyncLocalStorage } from 'async_hooks';
5
- import { maxUint256, isAddress, isHex, decodeAbiParameters, encodeAbiParameters, keccak256, zeroAddress, hashTypedData, publicActions, parseUnits, createWalletClient, http, erc20Abi, stringify, getAddress, parseEventLogs } from 'viem';
4
+ import { z } from 'zod/v4';
5
+ import { createDocument } from 'zod-openapi';
6
+ import { maxUint256, isAddress, isHex, decodeAbiParameters, encodeAbiParameters, getAddress, keccak256, zeroAddress, hashTypedData, publicActions, parseUnits, createWalletClient, http, erc20Abi, stringify, parseEventLogs } from 'viem';
6
7
  import { getBlock, getLogs } from 'viem/actions';
7
8
  import { base, mainnet, anvil } from 'viem/chains';
8
9
  import * as z6 from 'zod';
9
- import { Base64 } from 'js-base64';
10
10
  import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
11
+ import { Base64 } from 'js-base64';
12
+ import { AsyncLocalStorage } from 'async_hooks';
11
13
  import { asc, desc, sql, and, eq, gt, gte, lte, inArray } from 'drizzle-orm';
12
14
  import { pgSchema, integer, varchar, bigint, timestamp, text, boolean, numeric, index, primaryKey, uniqueIndex } from 'drizzle-orm/pg-core';
13
15
  import path from 'path';
@@ -17,8 +19,6 @@ import { migrate } from 'drizzle-orm/node-postgres/migrator';
17
19
  import { drizzle as drizzle$1 } from 'drizzle-orm/pglite';
18
20
  import { migrate as migrate$1 } from 'drizzle-orm/pglite/migrator';
19
21
  import { Pool } from 'pg';
20
- import { z } from 'zod/v4';
21
- import { createDocument } from 'zod-openapi';
22
22
 
23
23
  var __defProp = Object.defineProperty;
24
24
  var __export = (target, all) => {
@@ -26,9 +26,9 @@ var __export = (target, all) => {
26
26
  __defProp(target, name, { get: all[name], enumerable: true });
27
27
  };
28
28
 
29
- // src/api/Api/index.ts
30
- var Api_exports2 = {};
31
- __export(Api_exports2, {
29
+ // src/api/RouterApi.ts
30
+ var RouterApi_exports = {};
31
+ __export(RouterApi_exports, {
32
32
  ChainHealth: () => ChainHealth,
33
33
  ChainsHealthResponse: () => ChainsHealthResponse,
34
34
  CollectorHealth: () => CollectorHealth,
@@ -43,1747 +43,1319 @@ __export(Api_exports2, {
43
43
  safeParse: () => safeParse
44
44
  });
45
45
 
46
- // src/api/Api/Controllers/index.ts
46
+ // src/api/Controllers/index.ts
47
47
  var Controllers_exports = {};
48
48
  __export(Controllers_exports, {
49
+ getDocsHtml: () => getDocsHtml,
49
50
  getHealth: () => getHealth,
50
51
  getHealthChains: () => getHealthChains,
51
52
  getHealthCollectors: () => getHealthCollectors,
52
53
  getObligations: () => getObligations,
53
- getOffers: () => getOffers
54
- });
55
-
56
- // src/services/Health.ts
57
- var Health_exports = {};
58
- __export(Health_exports, {
59
- create: () => create9
60
- });
61
-
62
- // src/collectors/index.ts
63
- var collectors_exports = {};
64
- __export(collectors_exports, {
65
- batch: () => batch2,
66
- create: () => create2,
67
- createBuilder: () => createBuilder,
68
- fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
69
- fetchCollateralAndDebt: () => fetchCollateralAndDebt,
70
- fetchOraclePrices: () => fetchOraclePrices,
71
- fetchUserVaultMarketLiquidity: () => fetchUserVaultMarketLiquidity,
72
- morpho: () => morpho,
73
- names: () => names,
74
- run: () => run,
75
- single: () => single,
76
- start: () => start
54
+ getOffers: () => getOffers,
55
+ getSwaggerJson: () => getSwaggerJson
77
56
  });
78
57
 
79
- // src/services/Logger.ts
80
- var Logger_exports = {};
81
- __export(Logger_exports, {
82
- LogLevelValues: () => LogLevelValues,
83
- defaultLogger: () => defaultLogger,
84
- getLogger: () => getLogger,
85
- runWithLogger: () => runWithLogger,
86
- silentLogger: () => silentLogger
58
+ // src/core/Abi.ts
59
+ var Abi_exports = {};
60
+ __export(Abi_exports, {
61
+ ERC4626: () => ERC4626,
62
+ MetaMorpho: () => MetaMorpho,
63
+ MetaMorphoFactory: () => MetaMorphoFactory,
64
+ Morpho: () => Morpho,
65
+ Oracle: () => Oracle
87
66
  });
88
- var LogLevelValues = [
89
- "trace",
90
- "debug",
91
- "info",
92
- "warn",
93
- "error",
94
- "fatal",
95
- "silent"
96
- ];
97
- function defaultLogger(minLevel, pretty) {
98
- const threshold = minLevel ?? process.env.ROUTER_LOG_LEVEL ?? "info";
99
- const prettyEnabled = typeof pretty === "boolean" ? pretty : String(process.env.ROUTER_LOG_PRETTY ?? "false").toLowerCase() === "true";
100
- const levelIndexByName = LogLevelValues.reduce(
101
- (acc, lvl, idx) => {
102
- acc[lvl] = idx;
103
- return acc;
104
- },
105
- {}
106
- );
107
- const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
108
- const wrap = (consoleMethod, methodLevel) => isEnabled(methodLevel) ? (entry) => {
109
- if (!prettyEnabled) {
110
- console[consoleMethod](stringify({ level: methodLevel, ...entry }));
111
- return;
112
- }
113
- const { msg, ...rest } = entry;
114
- const timestamp2 = (/* @__PURE__ */ new Date()).toISOString();
115
- const level = methodLevel.toUpperCase();
116
- const extras = Object.entries(rest).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ");
117
- const line = extras.length > 0 ? `${timestamp2} [${level}] ${msg} ${extras}` : `${timestamp2} [${level}] ${msg}`;
118
- console[consoleMethod](line);
119
- } : () => {
120
- };
121
- return {
122
- trace: wrap("trace", "trace"),
123
- debug: wrap("debug", "debug"),
124
- info: wrap("info", "info"),
125
- warn: wrap("warn", "warn"),
126
- error: wrap("error", "error"),
127
- fatal: wrap("error", "fatal")
128
- };
129
- }
130
- function silentLogger() {
131
- const noop = () => {
132
- };
133
- return { trace: noop, debug: noop, info: noop, warn: noop, error: noop, fatal: noop };
134
- }
135
- var loggerContext = new AsyncLocalStorage();
136
- function runWithLogger(logger, fn) {
137
- return loggerContext.run(logger, fn);
138
- }
139
- function getLogger() {
140
- return loggerContext.getStore() ?? defaultLogger();
141
- }
142
- function formatValue(value) {
143
- if (value === null || value === void 0 || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
144
- return String(value);
145
- }
146
- if (typeof value === "string") {
147
- if (value.includes(" ")) return JSON.stringify(value);
148
- return value;
149
- }
150
- try {
151
- return stringify(value);
152
- } catch {
153
- try {
154
- return JSON.stringify(value);
155
- } catch {
156
- return String(value);
157
- }
67
+ var Oracle = [
68
+ {
69
+ type: "function",
70
+ name: "price",
71
+ inputs: [],
72
+ outputs: [{ name: "", type: "uint256" }],
73
+ stateMutability: "view"
158
74
  }
159
- }
160
-
161
- // src/utils/index.ts
162
- var utils_exports = {};
163
- __export(utils_exports, {
164
- BaseError: () => BaseError,
165
- Time: () => time_exports,
166
- batch: () => batch,
167
- batchMulticall: () => batchMulticall,
168
- fromSnakeCase: () => fromSnakeCase,
169
- lazy: () => lazy,
170
- max: () => max,
171
- min: () => min,
172
- poll: () => poll,
173
- retry: () => retry,
174
- toSnakeCase: () => toSnakeCase,
175
- wait: () => wait
176
- });
177
-
178
- // src/utils/BigMath.ts
179
- function max(a, b) {
180
- return a > b ? a : b;
181
- }
182
- function min(a, b) {
183
- return a < b ? a : b;
184
- }
185
-
186
- // src/utils/batch.ts
187
- function* batch(array2, batchSize) {
188
- for (let i = 0; i < array2.length; i += batchSize) {
189
- yield array2.slice(i, i + batchSize);
75
+ ];
76
+ var ERC4626 = [
77
+ {
78
+ type: "function",
79
+ name: "asset",
80
+ inputs: [],
81
+ outputs: [{ name: "", type: "address" }],
82
+ stateMutability: "view"
190
83
  }
191
- }
192
-
193
- // src/utils/retry.ts
194
- var retry = async (fn, attempts = 3, delayMs = 50) => {
195
- let lastErr;
196
- for (let i = 0; i < attempts; i++) {
197
- try {
198
- return await fn();
199
- } catch (err) {
200
- lastErr = err;
201
- if (i < attempts - 1) await new Promise((r) => setTimeout(r, delayMs));
202
- }
84
+ ];
85
+ var MetaMorphoFactory = [
86
+ {
87
+ type: "function",
88
+ name: "isMetaMorpho",
89
+ inputs: [{ name: "target", type: "address" }],
90
+ outputs: [{ name: "", type: "bool" }],
91
+ stateMutability: "view"
203
92
  }
204
- throw lastErr;
205
- };
206
-
207
- // src/utils/batchMulticall.ts
208
- async function batchMulticall(parameters) {
209
- const { client, calls, batchSize, retryAttempts, retryDelayMs, blockNumber } = parameters;
210
- const results = [];
211
- for (const callsBatch of batch(calls, batchSize)) {
212
- const batchResults = await retry(
213
- () => client.multicall({
214
- allowFailure: false,
215
- contracts: callsBatch,
216
- ...blockNumber ? { blockNumber } : {}
217
- }),
218
- retryAttempts,
219
- retryDelayMs
220
- );
221
- results.push(...batchResults);
93
+ ];
94
+ var MetaMorpho = [
95
+ {
96
+ type: "function",
97
+ name: "withdrawQueue",
98
+ inputs: [{ name: "index", type: "uint256" }],
99
+ outputs: [{ name: "", type: "bytes32" }],
100
+ stateMutability: "view"
101
+ },
102
+ {
103
+ type: "function",
104
+ name: "withdrawQueueLength",
105
+ inputs: [],
106
+ outputs: [{ name: "", type: "uint256" }],
107
+ stateMutability: "view"
108
+ },
109
+ {
110
+ type: "function",
111
+ name: "maxWithdraw",
112
+ inputs: [{ name: "owner", type: "address" }],
113
+ outputs: [{ name: "", type: "uint256" }],
114
+ stateMutability: "view"
222
115
  }
223
- return results;
224
- }
225
-
226
- // src/utils/Errors.ts
227
- var Errors_exports = {};
228
- __export(Errors_exports, {
229
- BaseError: () => BaseError
230
- });
231
- var BaseError = class _BaseError extends Error {
232
- details;
233
- shortMessage;
234
- cause;
235
- name = "BaseError";
236
- constructor(shortMessage, options = {}) {
237
- const details = (() => {
238
- if (options.cause instanceof _BaseError) {
239
- if (options.cause.details) return options.cause.details;
240
- if (options.cause.shortMessage) return options.cause.shortMessage;
241
- }
242
- if (options.cause && "details" in options.cause && typeof options.cause.details === "string")
243
- return options.cause.details;
244
- if (options.cause?.message) return options.cause.message;
245
- return options.details;
246
- })();
247
- const message = [
248
- shortMessage || "An error occurred.",
249
- ...options.metaMessages ? ["", ...options.metaMessages] : [],
250
- ...details ? ["", details ? `Details: ${details}` : void 0] : []
251
- ].filter((x) => typeof x === "string").join("\n");
252
- super(message, options.cause ? { cause: options.cause } : void 0);
253
- this.cause = options.cause;
254
- this.details = details;
255
- this.shortMessage = shortMessage;
256
- }
257
- walk(fn) {
258
- return walk(this, fn);
116
+ ];
117
+ var Morpho = [
118
+ {
119
+ type: "function",
120
+ name: "collateralOf",
121
+ inputs: [
122
+ {
123
+ name: "",
124
+ type: "address",
125
+ internalType: "address"
126
+ },
127
+ {
128
+ name: "",
129
+ type: "bytes32",
130
+ internalType: "bytes32"
131
+ },
132
+ {
133
+ name: "",
134
+ type: "address",
135
+ internalType: "address"
136
+ }
137
+ ],
138
+ outputs: [
139
+ {
140
+ name: "",
141
+ type: "uint256",
142
+ internalType: "uint256"
143
+ }
144
+ ],
145
+ stateMutability: "view"
146
+ },
147
+ {
148
+ type: "function",
149
+ name: "debtOf",
150
+ inputs: [
151
+ {
152
+ name: "",
153
+ type: "address",
154
+ internalType: "address"
155
+ },
156
+ {
157
+ name: "",
158
+ type: "bytes32",
159
+ internalType: "bytes32"
160
+ }
161
+ ],
162
+ outputs: [
163
+ {
164
+ name: "",
165
+ type: "uint256",
166
+ internalType: "uint256"
167
+ }
168
+ ],
169
+ stateMutability: "view"
170
+ },
171
+ {
172
+ type: "function",
173
+ name: "market",
174
+ inputs: [
175
+ {
176
+ name: "id",
177
+ type: "bytes32",
178
+ internalType: "Id"
179
+ }
180
+ ],
181
+ outputs: [
182
+ {
183
+ name: "totalSupplyAssets",
184
+ type: "uint128",
185
+ internalType: "uint128"
186
+ },
187
+ {
188
+ name: "totalSupplyShares",
189
+ type: "uint128",
190
+ internalType: "uint128"
191
+ },
192
+ {
193
+ name: "totalBorrowAssets",
194
+ type: "uint128",
195
+ internalType: "uint128"
196
+ },
197
+ {
198
+ name: "totalBorrowShares",
199
+ type: "uint128",
200
+ internalType: "uint128"
201
+ },
202
+ {
203
+ name: "lastUpdate",
204
+ type: "uint128",
205
+ internalType: "uint128"
206
+ },
207
+ {
208
+ name: "fee",
209
+ type: "uint128",
210
+ internalType: "uint128"
211
+ }
212
+ ],
213
+ stateMutability: "view"
214
+ },
215
+ {
216
+ type: "function",
217
+ name: "position",
218
+ inputs: [
219
+ {
220
+ name: "id",
221
+ type: "bytes32",
222
+ internalType: "Id"
223
+ },
224
+ {
225
+ name: "user",
226
+ type: "address",
227
+ internalType: "address"
228
+ }
229
+ ],
230
+ outputs: [
231
+ {
232
+ name: "supplyShares",
233
+ type: "uint256",
234
+ internalType: "uint256"
235
+ },
236
+ {
237
+ name: "borrowShares",
238
+ type: "uint128",
239
+ internalType: "uint128"
240
+ },
241
+ {
242
+ name: "collateral",
243
+ type: "uint128",
244
+ internalType: "uint128"
245
+ }
246
+ ],
247
+ stateMutability: "view"
259
248
  }
260
- };
261
- function walk(err, fn) {
262
- if (fn?.(err)) return err;
263
- if (err && typeof err === "object" && "cause" in err && err.cause) return walk(err.cause, fn);
264
- return fn ? null : err;
265
- }
249
+ ];
266
250
 
267
- // src/utils/Format.ts
268
- var Format_exports = {};
269
- __export(Format_exports, {
270
- fromSnakeCase: () => fromSnakeCase,
271
- toSnakeCase: () => toSnakeCase
251
+ // src/core/Callback.ts
252
+ var Callback_exports = {};
253
+ __export(Callback_exports, {
254
+ CallbackType: () => CallbackType,
255
+ WhitelistedCallbackAddresses: () => WhitelistedCallbackAddresses,
256
+ decodeBuyVaultV1Callback: () => decodeBuyVaultV1Callback,
257
+ decodeSellERC20Callback: () => decodeSellERC20Callback,
258
+ encodeBuyVaultV1Callback: () => encodeBuyVaultV1Callback,
259
+ encodeSellERC20Callback: () => encodeSellERC20Callback
272
260
  });
273
- function toSnakeCase(obj) {
274
- return stringifyBigint(
275
- processObject(
276
- obj,
277
- (s2) => s2.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`),
278
- (value) => typeof value === "string" && isAddress(value.toLowerCase()) ? getAddress(value.toLowerCase()) : value
279
- )
280
- );
261
+ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
262
+ CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
263
+ CallbackType2["BuyVaultV1Callback"] = "buy_vault_v1_callback";
264
+ CallbackType2["SellERC20Callback"] = "sell_erc20_callback";
265
+ return CallbackType2;
266
+ })(CallbackType || {});
267
+ var WhitelistedCallbackAddresses = {
268
+ ["buy_with_empty_callback" /* BuyWithEmptyCallback */]: [],
269
+ ["buy_vault_v1_callback" /* BuyVaultV1Callback */]: [
270
+ "0x3333333333333333333333333333333333333333",
271
+ "0x4444444444444444444444444444444444444444"
272
+ // @TODO: update once deployed and add mapping per chain if needed
273
+ ].map((address) => address.toLowerCase()),
274
+ ["sell_erc20_callback" /* SellERC20Callback */]: [
275
+ "0x1111111111111111111111111111111111111111",
276
+ "0x2222222222222222222222222222222222222222"
277
+ // @TODO: update once deployed and add mapping per chain if needed
278
+ ].map((address) => address.toLowerCase())
279
+ };
280
+ function decodeBuyVaultV1Callback(data) {
281
+ if (!data || data === "0x") throw new Error("Empty callback data");
282
+ try {
283
+ const [vaults, amounts] = decodeAbiParameters(
284
+ [{ type: "address[]" }, { type: "uint256[]" }],
285
+ data
286
+ );
287
+ if (vaults.length !== amounts.length) {
288
+ throw new Error("Mismatched array lengths");
289
+ }
290
+ return vaults.map((v, i) => ({ vault: v, amount: amounts[i] }));
291
+ } catch (_) {
292
+ throw new Error("Invalid BuyVaultV1Callback callback data");
293
+ }
281
294
  }
282
- function fromSnakeCase(obj) {
283
- return processObject(
284
- obj,
285
- (s2) => isAddress(s2.toLowerCase()) ? s2 : s2.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
286
- (value) => typeof value === "string" && isAddress(value.toLowerCase()) ? value.toLowerCase() : value
287
- );
295
+ function decodeSellERC20Callback(data) {
296
+ if (!data || data === "0x") throw new Error("Empty callback data");
297
+ try {
298
+ const [collaterals, amounts] = decodeAbiParameters(
299
+ [{ type: "address[]" }, { type: "uint256[]" }],
300
+ data
301
+ );
302
+ if (collaterals.length !== amounts.length) {
303
+ throw new Error("Mismatched array lengths");
304
+ }
305
+ return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
306
+ } catch (_) {
307
+ throw new Error("Invalid SellERC20Callback callback data");
308
+ }
288
309
  }
289
- function processObject(obj, fnKey, fnValue) {
290
- if (typeof obj !== "object" || obj === null) return obj;
291
- if (Array.isArray(obj)) return obj.map((item) => processObject(item, fnKey, fnValue));
292
- return Object.entries(obj).reduce(
293
- (acc, [key, value]) => {
294
- const newKey = fnKey(key);
295
- acc[newKey] = typeof value === "object" && value !== null ? processObject(value, fnKey, fnValue) : fnValue(value);
296
- return acc;
297
- },
298
- {}
310
+ function encodeBuyVaultV1Callback(parameters) {
311
+ return encodeAbiParameters(
312
+ [{ type: "address[]" }, { type: "uint256[]" }],
313
+ [parameters.vaults, parameters.amounts]
299
314
  );
300
315
  }
301
- function stringifyBigint(value) {
302
- if (typeof value === "bigint") return value.toString();
303
- if (Array.isArray(value)) return value.map(stringifyBigint);
304
- if (value && typeof value === "object") {
305
- const out = {};
306
- for (const [k, v] of Object.entries(value)) {
307
- out[k] = stringifyBigint(v);
308
- }
309
- return out;
310
- }
311
- return value;
316
+ function encodeSellERC20Callback(parameters) {
317
+ return encodeAbiParameters(
318
+ [{ type: "address[]" }, { type: "uint256[]" }],
319
+ [parameters.collaterals, parameters.amounts]
320
+ );
312
321
  }
313
322
 
314
- // src/utils/lazy.ts
315
- function lazy(pollFn) {
316
- return () => async function* () {
317
- let active = true;
318
- let resolveNext = null;
319
- const queue = [];
320
- const wait2 = () => new Promise((resolve) => {
321
- resolveNext = resolve;
322
- });
323
- const emit = (item) => {
324
- queue.push(item);
325
- resolveNext?.();
326
- resolveNext = null;
327
- };
328
- let unpoll = null;
329
- const stop = () => {
330
- active = false;
331
- unpoll?.();
332
- resolveNext?.();
333
- resolveNext = null;
334
- };
335
- unpoll = pollFn(emit, { stop });
336
- try {
337
- while (active) {
338
- if (queue.length === 0) await wait2();
339
- while (queue.length > 0 && active) yield queue.shift();
340
- }
341
- } finally {
342
- stop();
343
- }
344
- }();
345
- }
323
+ // src/core/Chain.ts
324
+ var Chain_exports = {};
325
+ __export(Chain_exports, {
326
+ ChainId: () => ChainId,
327
+ InvalidBatchSizeError: () => InvalidBatchSizeError,
328
+ InvalidBlockRangeError: () => InvalidBlockRangeError,
329
+ InvalidBlockWindowError: () => InvalidBlockWindowError,
330
+ MissingBlockNumberError: () => MissingBlockNumberError,
331
+ chainIds: () => chainIds,
332
+ chainNames: () => chainNames,
333
+ chains: () => chains,
334
+ getChain: () => getChain,
335
+ getWhitelistedChains: () => getWhitelistedChains,
336
+ streamLogs: () => streamLogs
337
+ });
346
338
 
347
- // src/utils/wait.ts
348
- async function wait(time) {
349
- return new Promise((res) => setTimeout(res, time));
339
+ // src/utils/BigMath.ts
340
+ function max(a, b) {
341
+ return a > b ? a : b;
342
+ }
343
+ function min(a, b) {
344
+ return a < b ? a : b;
350
345
  }
351
346
 
352
- // src/utils/poll.ts
353
- function poll(fn, { interval }) {
354
- let active = true;
355
- const unwatch = () => active = false;
356
- const watch2 = async () => {
357
- await wait(interval);
358
- const poll2 = async () => {
359
- if (!active) return;
360
- await fn({ unpoll: unwatch });
361
- await wait(interval);
362
- poll2();
363
- };
364
- poll2();
365
- };
366
- watch2();
367
- return unwatch;
347
+ // src/utils/batch.ts
348
+ function* batch(array2, batchSize) {
349
+ for (let i = 0; i < array2.length; i += batchSize) {
350
+ yield array2.slice(i, i + batchSize);
351
+ }
368
352
  }
369
353
 
370
- // src/utils/time.ts
371
- var time_exports = {};
372
- __export(time_exports, {
373
- max: () => max2,
374
- now: () => now
354
+ // src/utils/Errors.ts
355
+ var Errors_exports = {};
356
+ __export(Errors_exports, {
357
+ BaseError: () => BaseError
375
358
  });
376
- function now() {
377
- return Math.floor(Date.now() / 1e3);
378
- }
379
- function max2() {
380
- return 864e16;
359
+ var BaseError = class _BaseError extends Error {
360
+ details;
361
+ shortMessage;
362
+ cause;
363
+ name = "BaseError";
364
+ constructor(shortMessage, options = {}) {
365
+ const details = (() => {
366
+ if (options.cause instanceof _BaseError) {
367
+ if (options.cause.details) return options.cause.details;
368
+ if (options.cause.shortMessage) return options.cause.shortMessage;
369
+ }
370
+ if (options.cause && "details" in options.cause && typeof options.cause.details === "string")
371
+ return options.cause.details;
372
+ if (options.cause?.message) return options.cause.message;
373
+ return options.details;
374
+ })();
375
+ const message = [
376
+ shortMessage || "An error occurred.",
377
+ ...options.metaMessages ? ["", ...options.metaMessages] : [],
378
+ ...details ? ["", details ? `Details: ${details}` : void 0] : []
379
+ ].filter((x) => typeof x === "string").join("\n");
380
+ super(message, options.cause ? { cause: options.cause } : void 0);
381
+ this.cause = options.cause;
382
+ this.details = details;
383
+ this.shortMessage = shortMessage;
384
+ }
385
+ walk(fn) {
386
+ return walk(this, fn);
387
+ }
388
+ };
389
+ function walk(err, fn) {
390
+ if (fn?.(err)) return err;
391
+ if (err && typeof err === "object" && "cause" in err && err.cause) return walk(err.cause, fn);
392
+ return fn ? null : err;
381
393
  }
382
394
 
383
- // src/services/RouterAdmin.ts
384
- function create(parameters) {
385
- const collector = "admin";
395
+ // src/core/Chain.ts
396
+ var chainNames = ["ethereum", "base", "ethereum-virtual-testnet", "anvil"];
397
+ var ChainId = {
398
+ ETHEREUM: BigInt(mainnet.id),
399
+ BASE: BigInt(base.id),
400
+ "ETHEREUM-VIRTUAL-TESTNET": 109111114n,
401
+ ANVIL: 505050505n
402
+ // random id to not clash with other chains
403
+ };
404
+ var chainIds = new Set(Object.values(ChainId));
405
+ var chainNameLookup = new Map(Object.entries(ChainId).map(([key, value]) => [value, key]));
406
+ function getChain(chainId) {
407
+ const chainName = chainNameLookup.get(chainId)?.toLowerCase();
408
+ if (!chainName) {
409
+ return void 0;
410
+ }
411
+ return chains[chainName];
412
+ }
413
+ var getWhitelistedChains = () => {
414
+ return [chains.ethereum, chains.base, chains["ethereum-virtual-testnet"], chains.anvil];
415
+ };
416
+ var chains = {
417
+ ethereum: {
418
+ ...mainnet,
419
+ id: ChainId.ETHEREUM,
420
+ name: "ethereum",
421
+ whitelistedAssets: new Set(
422
+ [
423
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
424
+ // USDC
425
+ "0x6B175474E89094C44Da98b954EedeAC495271d0F",
426
+ // DAI
427
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
428
+ // WETH
429
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
430
+ // WBTC
431
+ ].map((address) => address.toLowerCase())
432
+ ),
433
+ morpho: "0x0000000000000000000000000000000000000000",
434
+ morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
435
+ mempool: {
436
+ address: "0x0000000000000000000000000000000000000000",
437
+ deploymentBlock: 23347674,
438
+ reindexBuffer: 10
439
+ },
440
+ vaultV1Factory: {
441
+ "v1.0": "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
442
+ "v1.1": "0x1897A8997241C1cD4bD0698647e4EB7213535c24"
443
+ }
444
+ },
445
+ base: {
446
+ ...base,
447
+ id: ChainId.BASE,
448
+ name: "base",
449
+ whitelistedAssets: new Set(
450
+ [
451
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
452
+ // USDC
453
+ "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
454
+ // DAI
455
+ "0x4200000000000000000000000000000000000006",
456
+ // WETH
457
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
458
+ // WBTC
459
+ ].map((address) => address.toLowerCase())
460
+ ),
461
+ morpho: "0x0000000000000000000000000000000000000000",
462
+ morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
463
+ mempool: {
464
+ address: "0x0000000000000000000000000000000000000000",
465
+ deploymentBlock: 35449942,
466
+ reindexBuffer: 10
467
+ },
468
+ vaultV1Factory: {
469
+ "v1.0": "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
470
+ "v1.1": "0xFf62A7c278C62eD665133147129245053Bbf5918"
471
+ }
472
+ },
473
+ "ethereum-virtual-testnet": {
474
+ ...mainnet,
475
+ id: ChainId["ETHEREUM-VIRTUAL-TESTNET"],
476
+ name: "ethereum-virtual-testnet",
477
+ whitelistedAssets: new Set(
478
+ [
479
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
480
+ // USDC
481
+ "0x6B175474E89094C44Da98b954EedeAC495271d0F",
482
+ // DAI
483
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
484
+ // WETH
485
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
486
+ // WBTC
487
+ ].map((address) => address.toLowerCase())
488
+ ),
489
+ morpho: "0x11a002d45db720ed47a80d2f3489cba5b833eaf5",
490
+ // @TODO: This is mock Consumed contract, update with Terms once stable
491
+ morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
492
+ mempool: {
493
+ address: "0x5b06224f736a57635b5bcb50b8ef178b189107cb",
494
+ deploymentBlock: 23224302,
495
+ reindexBuffer: 10
496
+ },
497
+ vaultV1Factory: {
498
+ "v1.0": "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
499
+ "v1.1": "0x1897A8997241C1cD4bD0698647e4EB7213535c24"
500
+ }
501
+ },
502
+ anvil: {
503
+ ...anvil,
504
+ id: ChainId.ANVIL,
505
+ name: "anvil",
506
+ whitelistedAssets: new Set(
507
+ [
508
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
509
+ // USDC
510
+ "0x6B175474E89094C44Da98b954EedeAC495271d0F",
511
+ // DAI
512
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
513
+ // WETH
514
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
515
+ // WBTC
516
+ ].map((address) => address.toLowerCase())
517
+ ),
518
+ morpho: "0x23DFBc4B8B80C14CC5e25011B8491f268395BAd6",
519
+ morphoBlue: "0x0000000000000000000000000000000000000000",
520
+ // Set dynamically in tests
521
+ mempool: {
522
+ address: "0xD946246695A9259F3B33a78629026F61B3Ab40aF",
523
+ deploymentBlock: 23223727,
524
+ reindexBuffer: 10
525
+ },
526
+ vaultV1Factory: {
527
+ "v1.0": "0x0000000000000000000000000000000000000000",
528
+ "v1.1": "0x0000000000000000000000000000000000000000"
529
+ }
530
+ }
531
+ };
532
+ var MAX_BATCH_SIZE = 1e4;
533
+ var DEFAULT_BATCH_SIZE = 2500;
534
+ var MAX_BLOCK_WINDOW = 2e3;
535
+ var DEFAULT_BLOCK_WINDOW = 500;
536
+ async function* streamLogs(parameters) {
386
537
  const {
387
538
  client,
388
- chain,
389
- withTransaction,
390
- options: { maxBatchSize = 25, maxBlockNumber } = {}
539
+ contractAddress,
540
+ event,
541
+ blockNumberGte,
542
+ blockNumberLte,
543
+ order = "desc",
544
+ options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = DEFAULT_BLOCK_WINDOW } = {}
391
545
  } = parameters;
392
- const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
393
- let finalizedBlock = null;
394
- let unfinalizedBlocks = [];
395
- const logger = getLogger();
396
- let isMaxBlockNumberReached = false;
397
- let tick = 0;
398
- return {
399
- syncBlock: async () => {
400
- if (isMaxBlockNumberReached) return;
401
- const head = await client.getBlock({
402
- blockTag: "latest",
403
- includeTransactions: false
404
- });
405
- await withTransaction(async ({ chainStore, collectorStore }) => {
406
- const { epoch, blockNumber: latestSavedBlockNumber } = await chainStore.getBlockNumber(
407
- chain.id
408
- );
409
- if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
410
- logger.info({
411
- msg: `Head is greater than max block number`,
412
- collector,
413
- chainId: chain.id,
414
- block_number: head.number,
415
- max_block_number: maxBlockNumber
416
- });
417
- isMaxBlockNumberReached = true;
418
- await chainStore.saveBlockNumber({
419
- chainId: chain.id,
420
- blockNumber: maxBlockNumber,
421
- epoch: epoch + 1n
422
- });
423
- await Promise.all(
424
- names.map(
425
- async (collectorName) => collectorStore.saveBlockNumber({
426
- collectorName,
427
- chainId: chain.id,
428
- blockNumber: maxBlockNumber,
429
- epoch: epoch + 1n
430
- })
431
- )
432
- );
433
- return;
434
- }
435
- finalizedBlock = await fetchFinalizedBlock({
436
- tick,
437
- client,
438
- chain,
439
- logger,
440
- collector,
441
- unfinalizedBlocks,
442
- previousFinalizedBlock: finalizedBlock
443
- });
444
- tick++;
445
- let {
446
- block: returnedBlock,
447
- didReorgHappened,
448
- unfinalizedBlocks: newUnfinalizedBlocks
449
- } = await reconcile({
450
- client,
451
- block: head,
452
- unfinalizedBlocks,
453
- finalizedBlock,
454
- logger,
455
- collector,
456
- chain,
457
- maxBatchSize
458
- });
459
- unfinalizedBlocks = newUnfinalizedBlocks;
460
- const blockNumber = Number(returnedBlock.number);
461
- didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
462
- await chainStore.saveBlockNumber({
463
- chainId: chain.id,
464
- blockNumber,
465
- epoch: didReorgHappened ? epoch + 1n : epoch
466
- });
467
- if (didReorgHappened) {
468
- await Promise.all(
469
- names.map(
470
- async (collectorName) => collectorStore.saveBlockNumber({
471
- collectorName,
472
- chainId: chain.id,
473
- blockNumber,
474
- epoch: epoch + 1n
475
- })
476
- )
477
- );
478
- }
479
- });
480
- }
481
- };
482
- }
483
- var commonAncestor = (block, unfinalizedBlocks) => {
484
- const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
485
- if (parent) return parent;
486
- return null;
487
- };
488
- var fetchFinalizedBlock = async (parameters) => {
489
- let { tick, client, chain, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
490
- let finalizedBlock = previousFinalizedBlock;
491
- if (tick % 20 === 0 || previousFinalizedBlock === null) {
492
- finalizedBlock = await client.getBlock({
493
- blockTag: "finalized",
494
- includeTransactions: false
546
+ if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
547
+ if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
548
+ if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
549
+ const latestBlock = (await getBlock(client, { blockTag: "latest", includeTransactions: false })).number;
550
+ let toBlock = 0n;
551
+ if (order === "asc")
552
+ toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), latestBlock);
553
+ if (order === "desc")
554
+ toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
555
+ let fromBlock = 0n;
556
+ if (order === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
557
+ if (order === "desc")
558
+ fromBlock = max(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
559
+ if (order === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
560
+ if (order === "desc") fromBlock = max(fromBlock, toBlock - BigInt(blockWindow));
561
+ if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
562
+ let streaming = true;
563
+ while (streaming) {
564
+ const logs = await getLogs(client, {
565
+ address: contractAddress,
566
+ event,
567
+ fromBlock,
568
+ toBlock
495
569
  });
496
- if (finalizedBlock === null || finalizedBlock.number === null) {
497
- const msg = "Failed to get finalized block";
498
- logger.fatal({ collector, chainId: chain.id, msg });
499
- throw new Error(msg);
570
+ logs.sort((a, b) => {
571
+ if (a.blockNumber !== b.blockNumber)
572
+ return order === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
573
+ if (a.transactionIndex !== b.transactionIndex)
574
+ return order === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
575
+ return order === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
576
+ });
577
+ for (const logBatch of batch(logs, maxBatchSize)) {
578
+ if (logBatch.length === 0) break;
579
+ yield {
580
+ logs: logBatch,
581
+ blockNumber: logBatch.length === maxBatchSize ? (
582
+ // if the batch is full, return the last block number, block numbers are always sorted
583
+ Number(logBatch[logBatch.length - 1]?.blockNumber)
584
+ ) : (
585
+ // if the batch is not full, return `toBlock` or `fromBlock` to indicate until which block the logs were fetched
586
+ order === "asc" ? Number(toBlock) : Number(fromBlock)
587
+ )
588
+ };
589
+ }
590
+ streaming = order === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
591
+ if (order === "asc") {
592
+ fromBlock = min(BigInt(toBlock) + 1n, latestBlock);
593
+ toBlock = min(fromBlock + BigInt(blockWindow), latestBlock);
594
+ }
595
+ if (order === "desc") {
596
+ const lowerBound = BigInt(blockNumberGte || 0);
597
+ const windowSize = BigInt(blockWindow);
598
+ const nextToBlock = max(fromBlock - 1n, lowerBound);
599
+ const nextFromBlock = max(nextToBlock - windowSize, lowerBound);
600
+ toBlock = nextToBlock;
601
+ fromBlock = nextFromBlock;
500
602
  }
501
- unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
502
603
  }
503
- if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
504
- const msg = "Failed to get finalized block";
505
- logger.fatal({ collector, chainId: chain.id, msg });
506
- throw new Error(msg);
604
+ yield { logs: [], blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock) };
605
+ return;
606
+ }
607
+ var InvalidBlockRangeError = class extends BaseError {
608
+ name = "Chain.InvalidBlockRangeError";
609
+ constructor(fromBlock, toBlock) {
610
+ super(
611
+ `Invalid block range while streaming data from chain. From block ${fromBlock} to block ${toBlock}.`
612
+ );
507
613
  }
508
- return {
509
- hash: finalizedBlock.hash,
510
- number: finalizedBlock.number,
511
- parentHash: finalizedBlock.parentHash
512
- };
513
614
  };
514
- var reconcile = async (parameters) => {
515
- let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, chain, maxBatchSize } = parameters;
516
- if (block.hash === null || block.number === null || block.parentHash === null)
517
- throw new Error("Failed to get block");
518
- const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
519
- if (latestBlock === void 0) {
520
- const newBlock2 = {
521
- hash: block.hash,
522
- number: block.number,
523
- parentHash: block.parentHash
524
- };
525
- unfinalizedBlocks.push(newBlock2);
526
- return { block: newBlock2, didReorgHappened: false, unfinalizedBlocks };
527
- }
528
- if (latestBlock.hash === block.hash)
529
- return { block: latestBlock, didReorgHappened: false, unfinalizedBlocks };
530
- if (latestBlock.number >= block.number) {
531
- const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
532
- logger.info({
533
- msg: `Reorg detected`,
534
- collector,
535
- chain_id: chain.id,
536
- ancestor: ancestor.number,
537
- block_range: [latestBlock.number, block.number]
538
- });
539
- unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
540
- return { block: ancestor, didReorgHappened: true, unfinalizedBlocks };
541
- }
542
- if (latestBlock.number + 1n < block.number) {
543
- logger.debug({
544
- collector,
545
- chain_id: chain.id,
546
- block_range: [latestBlock.number, block.number],
547
- msg: `Missing blocks`
548
- });
549
- const missingBlockNumbers = (() => {
550
- const missingBlockNumbers2 = [];
551
- let start2 = latestBlock.number + 1n;
552
- const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
553
- while (start2 < threshold) {
554
- missingBlockNumbers2.push(start2);
555
- start2 = start2 + 1n;
556
- }
557
- return missingBlockNumbers2;
558
- })();
559
- const missingBlocks = await Promise.all(
560
- missingBlockNumbers.map(
561
- (blockNumber) => retry(async () => await client.getBlock({ blockNumber, includeTransactions: false }))
562
- )
615
+ var InvalidBlockWindowError = class extends BaseError {
616
+ name = "Chain.InvalidBlockWindowError";
617
+ constructor(blockWindow) {
618
+ super(
619
+ `Invalid block window while streaming data from chain. Maximum is ${MAX_BLOCK_WINDOW}. Got ${blockWindow}.`
563
620
  );
564
- for (const missingBlock of missingBlocks) {
565
- const { block: returnedBlock, didReorgHappened } = await reconcile({
566
- client,
567
- block: missingBlock,
568
- unfinalizedBlocks,
569
- finalizedBlock,
570
- logger,
571
- collector,
572
- chain,
573
- maxBatchSize
574
- });
575
- if (returnedBlock.number !== missingBlock.number)
576
- return { block: returnedBlock, didReorgHappened, unfinalizedBlocks };
577
- }
578
- return reconcile({
579
- client,
580
- block,
581
- unfinalizedBlocks,
582
- finalizedBlock,
583
- logger,
584
- collector,
585
- chain,
586
- maxBatchSize
587
- });
588
621
  }
589
- if (block.parentHash !== latestBlock.hash) {
590
- const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
591
- logger.info({
592
- msg: `Reorg detected`,
593
- collector,
594
- chain_id: chain.id,
595
- ancestor: ancestor.number,
596
- block_range: [latestBlock.number, block.number]
597
- });
598
- unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
599
- return { block: ancestor, didReorgHappened: true, unfinalizedBlocks };
622
+ };
623
+ var InvalidBatchSizeError = class extends BaseError {
624
+ name = "Chain.InvalidBatchSizeError";
625
+ constructor(maxBatchSize) {
626
+ super(
627
+ `Invalid batch size while streaming data from chain. Maximum is ${MAX_BATCH_SIZE}. Got ${maxBatchSize}.`
628
+ );
629
+ }
630
+ };
631
+ var MissingBlockNumberError = class extends BaseError {
632
+ name = "Chain.MissingBlockNumberError";
633
+ constructor() {
634
+ super("Missing block number when streaming data from chain in ascending order.");
600
635
  }
601
- const newBlock = {
602
- hash: block.hash,
603
- number: block.number,
604
- parentHash: block.parentHash
605
- };
606
- unfinalizedBlocks.push(newBlock);
607
- return { block: newBlock, didReorgHappened: false, unfinalizedBlocks };
608
636
  };
609
637
 
610
- // src/collectors/Collector.ts
611
- var names = [
612
- "mempool_offers",
613
- "consumed_events",
614
- "buy_with_empty_callback_liquidity",
615
- "buy_vault_v1_callback_liquidity",
616
- "sell_erc20_callback_liquidity"
617
- ];
618
- function create2({
619
- name,
620
- collect,
621
- client,
622
- chain,
623
- withTransaction,
624
- collectorStore,
625
- options
626
- }) {
627
- const admin = create({
628
- client,
629
- chain,
630
- withTransaction,
631
- options
632
- });
633
- return {
634
- name,
635
- chain,
636
- collect: lazy((emit) => {
637
- const collector = name;
638
- const logger = getLogger();
639
- logger.info({
640
- msg: `Collector started`,
641
- collector,
642
- chain_id: chain.id,
643
- interval: options.interval
644
- });
645
- return poll(
646
- async () => {
647
- let { blockNumber: lastBlockNumber, epoch } = await collectorStore.getBlockNumber({
648
- collectorName: name,
649
- chainId: chain.id
650
- });
651
- await admin.syncBlock();
652
- lastBlockNumber = await collect({
653
- chain,
654
- client,
655
- collector: name,
656
- epoch,
657
- lastBlockNumber,
658
- withTransaction
659
- });
660
- emit(lastBlockNumber);
661
- },
662
- { interval: options.interval }
663
- );
664
- })
665
- };
666
- }
667
- function start(collector) {
668
- let stopped = false;
669
- const it = collector.collect();
670
- (async () => {
671
- while (!stopped) await it.next();
672
- await it.return();
673
- })();
674
- return () => {
675
- stopped = true;
676
- };
677
- }
678
-
679
- // src/mempool/index.ts
680
- var mempool_exports = {};
681
- __export(mempool_exports, {
682
- ChainIdMismatchError: () => ChainIdMismatchError,
683
- ViemClientError: () => ViemClientError,
684
- WalletAccountNotSetError: () => WalletAccountNotSetError,
685
- add: () => add,
686
- connect: () => connect,
687
- from: () => from6,
688
- get: () => get,
689
- watch: () => watch
638
+ // src/core/Collateral.ts
639
+ var Collateral_exports = {};
640
+ __export(Collateral_exports, {
641
+ CollateralSchema: () => CollateralSchema,
642
+ CollateralsSchema: () => CollateralsSchema,
643
+ from: () => from2
690
644
  });
645
+ var transformHex = (val, ctx) => {
646
+ if (isHex(val)) return val;
647
+ ctx.addIssue({
648
+ code: "invalid_format",
649
+ input: val,
650
+ format: "hex",
651
+ error: "not a hex"
652
+ });
653
+ return z6.NEVER;
654
+ };
655
+ var transformAddress = (val, ctx) => {
656
+ if (isAddress(val.toLowerCase())) return val.toLowerCase();
657
+ ctx.addIssue({
658
+ code: "invalid_format",
659
+ input: val,
660
+ format: "address",
661
+ error: "not a valid address"
662
+ });
663
+ return z6.NEVER;
664
+ };
691
665
 
692
- // src/core/Abi.ts
693
- var Abi_exports = {};
694
- __export(Abi_exports, {
695
- ERC4626: () => ERC4626,
696
- MetaMorpho: () => MetaMorpho,
697
- MetaMorphoFactory: () => MetaMorphoFactory,
698
- Morpho: () => Morpho,
699
- Oracle: () => Oracle
666
+ // src/core/LLTV.ts
667
+ var LLTV_exports = {};
668
+ __export(LLTV_exports, {
669
+ InvalidLLTVError: () => InvalidLLTVError,
670
+ InvalidOptionError: () => InvalidOptionError,
671
+ LLTVSchema: () => LLTVSchema,
672
+ Options: () => Options,
673
+ from: () => from
700
674
  });
701
- var Oracle = [
702
- {
703
- type: "function",
704
- name: "price",
705
- inputs: [],
706
- outputs: [{ name: "", type: "uint256" }],
707
- stateMutability: "view"
675
+ var Options = [0.385, 0.5, 0.625, 0.77, 0.86, 0.915, 0.945, 0.965, 0.98];
676
+ var LLTV_SCALED = Options.map((lltv) => BigInt(lltv * 10 ** 18));
677
+ function from(lltv) {
678
+ if (typeof lltv === "bigint" && !LLTV_SCALED.includes(lltv)) throw new InvalidLLTVError(lltv);
679
+ if (typeof lltv === "bigint") return lltv;
680
+ if (typeof lltv === "number" && !Options.includes(lltv)) throw new InvalidOptionError(lltv);
681
+ return BigInt(lltv * 10 ** 18);
682
+ }
683
+ var InvalidOptionError = class extends BaseError {
684
+ name = "LLTV.InvalidOptionError";
685
+ constructor(input) {
686
+ super(
687
+ `Invalid LLTV option. Input: "${input}". Accepted values are: ${Options.map(
688
+ (option) => `"${option}"`
689
+ ).join(", ")}.`
690
+ );
708
691
  }
709
- ];
710
- var ERC4626 = [
711
- {
712
- type: "function",
713
- name: "asset",
714
- inputs: [],
715
- outputs: [{ name: "", type: "address" }],
716
- stateMutability: "view"
692
+ };
693
+ var InvalidLLTVError = class extends BaseError {
694
+ name = "LLTV.InvalidLLTVError";
695
+ constructor(input) {
696
+ super(
697
+ `Invalid LLTV. Input: "${input}". Accepted values are: ${LLTV_SCALED.map(
698
+ (option) => `"${option}"`
699
+ ).join(", ")}.`
700
+ );
717
701
  }
718
- ];
719
- var MetaMorphoFactory = [
702
+ };
703
+ var LLTVSchema = z6.bigint({ coerce: true }).refine(
704
+ (lltv) => {
705
+ try {
706
+ from(lltv);
707
+ return true;
708
+ } catch (_) {
709
+ return false;
710
+ }
711
+ },
720
712
  {
721
- type: "function",
722
- name: "isMetaMorpho",
723
- inputs: [{ name: "target", type: "address" }],
724
- outputs: [{ name: "", type: "bool" }],
725
- stateMutability: "view"
713
+ error: () => {
714
+ return "Invalid LLTV: must be one of 0.385, 0.625, 0.77, 0.86, 0.915, 0.945, 0.965 or 0.98 (scaled by 1e18)";
715
+ }
726
716
  }
727
- ];
728
- var MetaMorpho = [
729
- {
730
- type: "function",
731
- name: "withdrawQueue",
732
- inputs: [{ name: "index", type: "uint256" }],
733
- outputs: [{ name: "", type: "bytes32" }],
734
- stateMutability: "view"
717
+ ).transform((lltv) => from(lltv));
718
+
719
+ // src/core/Collateral.ts
720
+ var CollateralSchema = z6.object({
721
+ asset: z6.string().transform(transformAddress),
722
+ oracle: z6.string().transform(transformAddress),
723
+ lltv: LLTVSchema
724
+ });
725
+ var CollateralsSchema = z6.array(CollateralSchema).min(1, { message: "At least one collateral is required" }).refine(
726
+ (collaterals) => {
727
+ for (let i = 1; i < collaterals.length; i++) {
728
+ if (collaterals[i - 1].asset.toLowerCase() > collaterals[i].asset.toLowerCase()) {
729
+ return false;
730
+ }
731
+ }
732
+ return true;
735
733
  },
736
734
  {
737
- type: "function",
738
- name: "withdrawQueueLength",
739
- inputs: [],
740
- outputs: [{ name: "", type: "uint256" }],
741
- stateMutability: "view"
735
+ message: "Collaterals must be sorted alphabetically by address"
736
+ }
737
+ ).refine(
738
+ (collaterals) => {
739
+ const uniqueAssets = /* @__PURE__ */ new Set();
740
+ for (const collateral of collaterals) {
741
+ const assetAddress = collateral.asset.toLowerCase();
742
+ if (uniqueAssets.has(assetAddress)) {
743
+ return false;
744
+ }
745
+ uniqueAssets.add(assetAddress);
746
+ }
747
+ return true;
742
748
  },
743
749
  {
744
- type: "function",
745
- name: "maxWithdraw",
746
- inputs: [{ name: "owner", type: "address" }],
747
- outputs: [{ name: "", type: "uint256" }],
748
- stateMutability: "view"
750
+ message: "Collaterals must not contain duplicate assets"
749
751
  }
750
- ];
751
- var Morpho = [
752
- {
753
- type: "function",
754
- name: "collateralOf",
755
- inputs: [
756
- {
757
- name: "",
758
- type: "address",
759
- internalType: "address"
760
- },
761
- {
762
- name: "",
763
- type: "bytes32",
764
- internalType: "bytes32"
765
- },
766
- {
767
- name: "",
768
- type: "address",
769
- internalType: "address"
770
- }
771
- ],
772
- outputs: [
773
- {
774
- name: "",
775
- type: "uint256",
776
- internalType: "uint256"
777
- }
778
- ],
779
- stateMutability: "view"
780
- },
781
- {
782
- type: "function",
783
- name: "debtOf",
784
- inputs: [
785
- {
786
- name: "",
787
- type: "address",
788
- internalType: "address"
789
- },
790
- {
791
- name: "",
792
- type: "bytes32",
793
- internalType: "bytes32"
794
- }
795
- ],
796
- outputs: [
797
- {
798
- name: "",
799
- type: "uint256",
800
- internalType: "uint256"
801
- }
802
- ],
803
- stateMutability: "view"
804
- },
805
- {
806
- type: "function",
807
- name: "market",
808
- inputs: [
809
- {
810
- name: "id",
811
- type: "bytes32",
812
- internalType: "Id"
813
- }
814
- ],
815
- outputs: [
816
- {
817
- name: "totalSupplyAssets",
818
- type: "uint128",
819
- internalType: "uint128"
820
- },
821
- {
822
- name: "totalSupplyShares",
823
- type: "uint128",
824
- internalType: "uint128"
825
- },
826
- {
827
- name: "totalBorrowAssets",
828
- type: "uint128",
829
- internalType: "uint128"
830
- },
831
- {
832
- name: "totalBorrowShares",
833
- type: "uint128",
834
- internalType: "uint128"
835
- },
836
- {
837
- name: "lastUpdate",
838
- type: "uint128",
839
- internalType: "uint128"
840
- },
841
- {
842
- name: "fee",
843
- type: "uint128",
844
- internalType: "uint128"
845
- }
846
- ],
847
- stateMutability: "view"
848
- },
849
- {
850
- type: "function",
851
- name: "position",
852
- inputs: [
853
- {
854
- name: "id",
855
- type: "bytes32",
856
- internalType: "Id"
857
- },
858
- {
859
- name: "user",
860
- type: "address",
861
- internalType: "address"
862
- }
863
- ],
864
- outputs: [
865
- {
866
- name: "supplyShares",
867
- type: "uint256",
868
- internalType: "uint256"
869
- },
870
- {
871
- name: "borrowShares",
872
- type: "uint128",
873
- internalType: "uint128"
874
- },
875
- {
876
- name: "collateral",
877
- type: "uint128",
878
- internalType: "uint128"
879
- }
880
- ],
881
- stateMutability: "view"
882
- }
883
- ];
752
+ );
753
+ var from2 = (parameters) => {
754
+ return {
755
+ asset: parameters.asset.toLowerCase(),
756
+ lltv: from(parameters.lltv),
757
+ oracle: parameters.oracle.toLowerCase()
758
+ };
759
+ };
884
760
 
885
- // src/core/Callback.ts
886
- var Callback_exports = {};
887
- __export(Callback_exports, {
888
- CallbackType: () => CallbackType,
889
- WhitelistedCallbackAddresses: () => WhitelistedCallbackAddresses,
890
- decodeBuyVaultV1Callback: () => decodeBuyVaultV1Callback,
891
- decodeSellERC20Callback: () => decodeSellERC20Callback,
892
- encodeBuyVaultV1Callback: () => encodeBuyVaultV1Callback,
893
- encodeSellERC20Callback: () => encodeSellERC20Callback
761
+ // src/core/Liquidity.ts
762
+ var Liquidity_exports = {};
763
+ __export(Liquidity_exports, {
764
+ calculateMaxDebt: () => calculateMaxDebt,
765
+ generateAllowancePoolId: () => generateAllowancePoolId,
766
+ generateBalancePoolId: () => generateBalancePoolId,
767
+ generateBuyVaultCallbackPoolId: () => generateBuyVaultCallbackPoolId,
768
+ generateDebtPoolId: () => generateDebtPoolId,
769
+ generateMarketLiquidityPoolId: () => generateMarketLiquidityPoolId,
770
+ generateObligationCollateralPoolId: () => generateObligationCollateralPoolId,
771
+ generateSellERC20CallbackPoolId: () => generateSellERC20CallbackPoolId,
772
+ generateUserVaultPositionPoolId: () => generateUserVaultPositionPoolId,
773
+ generateVaultPositionPoolId: () => generateVaultPositionPoolId
894
774
  });
895
- var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
896
- CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
897
- CallbackType2["BuyVaultV1Callback"] = "buy_vault_v1_callback";
898
- CallbackType2["SellERC20Callback"] = "sell_erc20_callback";
899
- return CallbackType2;
900
- })(CallbackType || {});
901
- var WhitelistedCallbackAddresses = {
902
- ["buy_with_empty_callback" /* BuyWithEmptyCallback */]: [],
903
- ["buy_vault_v1_callback" /* BuyVaultV1Callback */]: [
904
- "0x3333333333333333333333333333333333333333",
905
- "0x4444444444444444444444444444444444444444"
906
- // @TODO: update once deployed and add mapping per chain if needed
907
- ].map((address) => address.toLowerCase()),
908
- ["sell_erc20_callback" /* SellERC20Callback */]: [
909
- "0x1111111111111111111111111111111111111111",
910
- "0x2222222222222222222222222222222222222222"
911
- // @TODO: update once deployed and add mapping per chain if needed
912
- ].map((address) => address.toLowerCase())
913
- };
914
- function decodeBuyVaultV1Callback(data) {
915
- if (!data || data === "0x") throw new Error("Empty callback data");
916
- try {
917
- const [vaults, amounts] = decodeAbiParameters(
918
- [{ type: "address[]" }, { type: "uint256[]" }],
919
- data
920
- );
921
- if (vaults.length !== amounts.length) {
922
- throw new Error("Mismatched array lengths");
923
- }
924
- return vaults.map((v, i) => ({ vault: v, amount: amounts[i] }));
925
- } catch (_) {
926
- throw new Error("Invalid BuyVaultV1Callback callback data");
927
- }
775
+ function calculateMaxDebt(amount, oraclePrice, lltv) {
776
+ const ORACLE_PRICE_SCALE = 10n ** 36n;
777
+ const PRECISION = 10n ** 18n;
778
+ const collateralQuoted = amount * oraclePrice / ORACLE_PRICE_SCALE;
779
+ const maxDebt = collateralQuoted * lltv / PRECISION;
780
+ return maxDebt;
928
781
  }
929
- function decodeSellERC20Callback(data) {
930
- if (!data || data === "0x") throw new Error("Empty callback data");
931
- try {
932
- const [collaterals, amounts] = decodeAbiParameters(
933
- [{ type: "address[]" }, { type: "uint256[]" }],
934
- data
935
- );
936
- if (collaterals.length !== amounts.length) {
937
- throw new Error("Mismatched array lengths");
938
- }
939
- return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
940
- } catch (_) {
941
- throw new Error("Invalid SellERC20Callback callback data");
942
- }
782
+ function generateBalancePoolId(parameters) {
783
+ const { user, chainId, token } = parameters;
784
+ return `${user}-${chainId.toString()}-${token}-balance`.toLowerCase();
943
785
  }
944
- function encodeBuyVaultV1Callback(parameters) {
945
- return encodeAbiParameters(
946
- [{ type: "address[]" }, { type: "uint256[]" }],
947
- [parameters.vaults, parameters.amounts]
948
- );
786
+ function generateAllowancePoolId(parameters) {
787
+ const { user, chainId, token } = parameters;
788
+ return `${user}-${chainId.toString()}-${token}-allowance`.toLowerCase();
949
789
  }
950
- function encodeSellERC20Callback(parameters) {
951
- return encodeAbiParameters(
952
- [{ type: "address[]" }, { type: "uint256[]" }],
953
- [parameters.collaterals, parameters.amounts]
954
- );
790
+ function generateSellERC20CallbackPoolId(parameters) {
791
+ const { user, chainId, obligationId: obligationId2, token, offerHash } = parameters;
792
+ return `${user}-${chainId.toString()}-${obligationId2}-${token}-${offerHash}-sell_erc20_callback`.toLowerCase();
793
+ }
794
+ function generateObligationCollateralPoolId(parameters) {
795
+ const { user, chainId, obligationId: obligationId2, token } = parameters;
796
+ return `${user}-${chainId.toString()}-${obligationId2}-${token}-obligation-collateral`.toLowerCase();
797
+ }
798
+ function generateBuyVaultCallbackPoolId(parameters) {
799
+ const { user, chainId, vault, offerHash } = parameters;
800
+ return `${user}-${chainId.toString()}-${vault}-${offerHash}-${"buy_vault_v1_callback" /* BuyVaultV1Callback */}`.toLowerCase();
801
+ }
802
+ function generateDebtPoolId(parameters) {
803
+ const { user, chainId, obligationId: obligationId2 } = parameters;
804
+ return `${user}-${chainId.toString()}-${obligationId2}-debt`.toLowerCase();
805
+ }
806
+ function generateUserVaultPositionPoolId(parameters) {
807
+ const { user, chainId, vault } = parameters;
808
+ return `${user}-${chainId.toString()}-${vault}-user-vault-position`.toLowerCase();
809
+ }
810
+ function generateVaultPositionPoolId(parameters) {
811
+ const { vault, chainId, marketId } = parameters;
812
+ return `${vault}-${chainId.toString()}-${marketId}-vault-position`.toLowerCase();
813
+ }
814
+ function generateMarketLiquidityPoolId(parameters) {
815
+ const { chainId, marketId } = parameters;
816
+ return `${chainId.toString()}-${marketId}-market-liquidity`.toLowerCase();
955
817
  }
956
818
 
957
- // src/core/Chain.ts
958
- var Chain_exports = {};
959
- __export(Chain_exports, {
960
- ChainId: () => ChainId,
961
- InvalidBatchSizeError: () => InvalidBatchSizeError,
962
- InvalidBlockRangeError: () => InvalidBlockRangeError,
963
- InvalidBlockWindowError: () => InvalidBlockWindowError,
964
- MissingBlockNumberError: () => MissingBlockNumberError,
965
- chainIds: () => chainIds,
966
- chainNames: () => chainNames,
967
- chains: () => chains,
968
- getChain: () => getChain,
969
- getWhitelistedChains: () => getWhitelistedChains,
970
- streamLogs: () => streamLogs
819
+ // src/core/Maturity.ts
820
+ var Maturity_exports = {};
821
+ __export(Maturity_exports, {
822
+ InvalidDateError: () => InvalidDateError,
823
+ InvalidFormatError: () => InvalidFormatError,
824
+ InvalidOptionError: () => InvalidOptionError2,
825
+ MaturitySchema: () => MaturitySchema,
826
+ from: () => from3
971
827
  });
972
- var chainNames = ["ethereum", "base", "ethereum-virtual-testnet", "anvil"];
973
- var ChainId = {
974
- ETHEREUM: BigInt(mainnet.id),
975
- BASE: BigInt(base.id),
976
- "ETHEREUM-VIRTUAL-TESTNET": 109111114n,
977
- ANVIL: 505050505n
978
- // random id to not clash with other chains
979
- };
980
- var chainIds = new Set(Object.values(ChainId));
981
- var chainNameLookup = new Map(Object.entries(ChainId).map(([key, value]) => [value, key]));
982
- function getChain(chainId) {
983
- const chainName = chainNameLookup.get(chainId)?.toLowerCase();
984
- if (!chainName) {
985
- return void 0;
986
- }
987
- return chains[chainName];
988
- }
989
- var getWhitelistedChains = () => {
990
- return [chains.ethereum, chains.base, chains["ethereum-virtual-testnet"], chains.anvil];
991
- };
992
- var chains = {
993
- ethereum: {
994
- ...mainnet,
995
- id: ChainId.ETHEREUM,
996
- name: "ethereum",
997
- whitelistedAssets: new Set(
998
- [
999
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1000
- // USDC
1001
- "0x6B175474E89094C44Da98b954EedeAC495271d0F"
1002
- // DAI
1003
- ].map((address) => address.toLowerCase())
1004
- ),
1005
- morpho: "0x0000000000000000000000000000000000000000",
1006
- morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
1007
- mempool: {
1008
- address: "0x0000000000000000000000000000000000000000",
1009
- deploymentBlock: 23347674,
1010
- reindexBuffer: 10
1011
- },
1012
- vaultV1Factory: {
1013
- "v1.0": "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
1014
- "v1.1": "0x1897A8997241C1cD4bD0698647e4EB7213535c24"
1015
- }
1016
- },
1017
- base: {
1018
- ...base,
1019
- id: ChainId.BASE,
1020
- name: "base",
1021
- whitelistedAssets: new Set(
1022
- [
1023
- "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
1024
- // USDC
1025
- "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb"
1026
- // DAI
1027
- ].map((address) => address.toLowerCase())
1028
- ),
1029
- morpho: "0x0000000000000000000000000000000000000000",
1030
- morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
1031
- mempool: {
1032
- address: "0x0000000000000000000000000000000000000000",
1033
- deploymentBlock: 35449942,
1034
- reindexBuffer: 10
1035
- },
1036
- vaultV1Factory: {
1037
- "v1.0": "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
1038
- "v1.1": "0xFf62A7c278C62eD665133147129245053Bbf5918"
1039
- }
1040
- },
1041
- "ethereum-virtual-testnet": {
1042
- ...mainnet,
1043
- id: ChainId["ETHEREUM-VIRTUAL-TESTNET"],
1044
- name: "ethereum-virtual-testnet",
1045
- whitelistedAssets: new Set(
1046
- [
1047
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1048
- // USDC
1049
- "0x6B175474E89094C44Da98b954EedeAC495271d0F"
1050
- // DAI
1051
- ].map((address) => address.toLowerCase())
1052
- ),
1053
- morpho: "0x11a002d45db720ed47a80d2f3489cba5b833eaf5",
1054
- // @TODO: This is mock Consumed contract, update with Terms once stable
1055
- morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
1056
- mempool: {
1057
- address: "0x5b06224f736a57635b5bcb50b8ef178b189107cb",
1058
- deploymentBlock: 23224302,
1059
- reindexBuffer: 10
1060
- },
1061
- vaultV1Factory: {
1062
- "v1.0": "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
1063
- "v1.1": "0x1897A8997241C1cD4bD0698647e4EB7213535c24"
828
+ var MaturitySchema = z6.number().int().refine(
829
+ (maturity) => {
830
+ try {
831
+ from3(maturity);
832
+ return true;
833
+ } catch (_e) {
834
+ return false;
1064
835
  }
1065
836
  },
1066
- anvil: {
1067
- ...anvil,
1068
- id: ChainId.ANVIL,
1069
- name: "anvil",
1070
- whitelistedAssets: new Set(
1071
- [
1072
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1073
- // USDC
1074
- "0x6B175474E89094C44Da98b954EedeAC495271d0F"
1075
- // DAI
1076
- ].map((address) => address.toLowerCase())
1077
- ),
1078
- morpho: "0x23DFBc4B8B80C14CC5e25011B8491f268395BAd6",
1079
- morphoBlue: "0x0000000000000000000000000000000000000000",
1080
- // Set dynamically in tests
1081
- mempool: {
1082
- address: "0xD946246695A9259F3B33a78629026F61B3Ab40aF",
1083
- deploymentBlock: 23223727,
1084
- reindexBuffer: 10
1085
- },
1086
- vaultV1Factory: {
1087
- "v1.0": "0x0000000000000000000000000000000000000000",
1088
- "v1.1": "0x0000000000000000000000000000000000000000"
837
+ {
838
+ error: (issue) => {
839
+ try {
840
+ const maturityDate = new Date(issue.input * 1e3);
841
+ return `The maturity is set to ${maturityDate}. It must fall on the allowed settlement cycles (Friday 15:00 UTC at the end of week/month/quarter).`;
842
+ } catch (_) {
843
+ return `The maturity is set to ${issue.input}. It must fall on the allowed settlement cycles (Friday 15:00 UTC at the end of week/month/quarter).`;
844
+ }
1089
845
  }
1090
846
  }
847
+ ).transform((maturity) => maturity);
848
+ var MaturityOptions = {
849
+ end_of_week: () => endOfWeek(),
850
+ end_of_next_week: () => endOfNextWeek(),
851
+ end_of_month: () => endOfMonth(),
852
+ end_of_next_month: () => endOfNextMonth(),
853
+ end_of_quarter: () => endOfQuarter(),
854
+ end_of_next_quarter: () => endOfNextQuarter()
1091
855
  };
1092
- var MAX_BATCH_SIZE = 1e4;
1093
- var DEFAULT_BATCH_SIZE = 2500;
1094
- var MAX_BLOCK_WINDOW = 2e3;
1095
- var DEFAULT_BLOCK_WINDOW = 500;
1096
- async function* streamLogs(parameters) {
1097
- const {
1098
- client,
1099
- contractAddress,
1100
- event,
1101
- blockNumberGte,
1102
- blockNumberLte,
1103
- order = "desc",
1104
- options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = DEFAULT_BLOCK_WINDOW } = {}
1105
- } = parameters;
1106
- if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
1107
- if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
1108
- if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
1109
- const latestBlock = (await getBlock(client, { blockTag: "latest", includeTransactions: false })).number;
1110
- let toBlock = 0n;
1111
- if (order === "asc")
1112
- toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), latestBlock);
1113
- if (order === "desc")
1114
- toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
1115
- let fromBlock = 0n;
1116
- if (order === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
1117
- if (order === "desc")
1118
- fromBlock = max(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
1119
- if (order === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
1120
- if (order === "desc") fromBlock = max(fromBlock, toBlock - BigInt(blockWindow));
1121
- if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
1122
- let streaming = true;
1123
- while (streaming) {
1124
- const logs = await getLogs(client, {
1125
- address: contractAddress,
1126
- event,
1127
- fromBlock,
1128
- toBlock
1129
- });
1130
- logs.sort((a, b) => {
1131
- if (a.blockNumber !== b.blockNumber)
1132
- return order === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
1133
- if (a.transactionIndex !== b.transactionIndex)
1134
- return order === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
1135
- return order === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
1136
- });
1137
- for (const logBatch of batch(logs, maxBatchSize)) {
1138
- if (logBatch.length === 0) break;
1139
- yield {
1140
- logs: logBatch,
1141
- blockNumber: logBatch.length === maxBatchSize ? (
1142
- // if the batch is full, return the last block number, block numbers are always sorted
1143
- Number(logBatch[logBatch.length - 1]?.blockNumber)
1144
- ) : (
1145
- // if the batch is not full, return `toBlock` or `fromBlock` to indicate until which block the logs were fetched
1146
- order === "asc" ? Number(toBlock) : Number(fromBlock)
1147
- )
1148
- };
1149
- }
1150
- streaming = order === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
1151
- if (order === "asc") {
1152
- fromBlock = min(BigInt(toBlock) + 1n, latestBlock);
1153
- toBlock = min(fromBlock + BigInt(blockWindow), latestBlock);
1154
- }
1155
- if (order === "desc") {
1156
- const lowerBound = BigInt(blockNumberGte || 0);
1157
- const windowSize = BigInt(blockWindow);
1158
- const nextToBlock = max(fromBlock - 1n, lowerBound);
1159
- const nextFromBlock = max(nextToBlock - windowSize, lowerBound);
1160
- toBlock = nextToBlock;
1161
- fromBlock = nextFromBlock;
1162
- }
856
+ function from3(ts) {
857
+ if (typeof ts === "string") {
858
+ if (ts in MaturityOptions) return MaturityOptions[ts]();
859
+ throw new InvalidOptionError2(ts);
1163
860
  }
1164
- yield { logs: [], blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock) };
1165
- return;
861
+ if (typeof ts === "number" && ts > 1e12) throw new InvalidFormatError();
862
+ if (!Object.values(MaturityOptions).some((option) => option() === ts))
863
+ throw new InvalidDateError(ts);
864
+ return ts;
1166
865
  }
1167
- var InvalidBlockRangeError = class extends BaseError {
1168
- name = "Chain.InvalidBlockRangeError";
1169
- constructor(fromBlock, toBlock) {
1170
- super(
1171
- `Invalid block range while streaming data from chain. From block ${fromBlock} to block ${toBlock}.`
1172
- );
866
+ var endOfWeek = () => fridayOfWeek(0);
867
+ var endOfNextWeek = () => fridayOfWeek(1);
868
+ var endOfMonth = () => lastFridayOfMonth((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth());
869
+ var endOfNextMonth = () => lastFridayOfMonth((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth() + 1);
870
+ var endOfQuarter = () => lastFridayOfQuarter(0);
871
+ var endOfNextQuarter = () => lastFridayOfQuarter(1);
872
+ var fridayOfWeek = (weeksAhead = 0) => {
873
+ const now2 = /* @__PURE__ */ new Date();
874
+ const today15H = new Date(
875
+ Date.UTC(now2.getUTCFullYear(), now2.getUTCMonth(), now2.getUTCDate(), 15)
876
+ );
877
+ let daysUntilFriday = (5 - today15H.getUTCDay() + 7) % 7;
878
+ if (daysUntilFriday === 0 && now2.getTime() >= today15H.getTime()) {
879
+ daysUntilFriday = 7;
1173
880
  }
881
+ const friday = new Date(today15H);
882
+ friday.setUTCDate(friday.getUTCDate() + daysUntilFriday + weeksAhead * 7);
883
+ return friday.getTime() / 1e3;
1174
884
  };
1175
- var InvalidBlockWindowError = class extends BaseError {
1176
- name = "Chain.InvalidBlockWindowError";
1177
- constructor(blockWindow) {
1178
- super(
1179
- `Invalid block window while streaming data from chain. Maximum is ${MAX_BLOCK_WINDOW}. Got ${blockWindow}.`
1180
- );
885
+ var lastFridayOfMonth = (year, month) => {
886
+ const lastDayOfMonth15H = new Date(Date.UTC(year, month + 1, 0, 15));
887
+ while (lastDayOfMonth15H.getUTCDay() !== 5) {
888
+ lastDayOfMonth15H.setUTCDate(lastDayOfMonth15H.getUTCDate() - 1);
1181
889
  }
890
+ const maturity = lastDayOfMonth15H.setUTCDate(lastDayOfMonth15H.getUTCDate()) / 1e3;
891
+ return maturity;
1182
892
  };
1183
- var InvalidBatchSizeError = class extends BaseError {
1184
- name = "Chain.InvalidBatchSizeError";
1185
- constructor(maxBatchSize) {
1186
- super(
1187
- `Invalid batch size while streaming data from chain. Maximum is ${MAX_BATCH_SIZE}. Got ${maxBatchSize}.`
1188
- );
1189
- }
893
+ var lastFridayOfQuarter = (quartersAhead = 0) => {
894
+ const now2 = /* @__PURE__ */ new Date();
895
+ const quarterIndex = Math.floor(now2.getUTCMonth() / 3) + quartersAhead;
896
+ const year = now2.getUTCFullYear() + Math.floor(quarterIndex / 4);
897
+ const quarter = quarterIndex % 4;
898
+ const lastMonth = quarter * 3 + 2;
899
+ return lastFridayOfMonth(year, lastMonth);
1190
900
  };
1191
- var MissingBlockNumberError = class extends BaseError {
1192
- name = "Chain.MissingBlockNumberError";
901
+ var InvalidFormatError = class extends BaseError {
902
+ name = "Maturity.InvalidFormatError";
1193
903
  constructor() {
1194
- super("Missing block number when streaming data from chain in ascending order.");
904
+ super("Invalid maturity format. Maturity should be expressed in seconds.");
1195
905
  }
1196
906
  };
1197
-
1198
- // src/core/Collateral.ts
1199
- var Collateral_exports = {};
1200
- __export(Collateral_exports, {
1201
- CollateralSchema: () => CollateralSchema,
1202
- CollateralsSchema: () => CollateralsSchema,
1203
- from: () => from2
1204
- });
1205
- var transformHex = (val, ctx) => {
1206
- if (isHex(val)) return val;
1207
- ctx.addIssue({
1208
- code: "invalid_format",
1209
- input: val,
1210
- format: "hex",
1211
- error: "not a hex"
1212
- });
1213
- return z6.NEVER;
1214
- };
1215
- var transformAddress = (val, ctx) => {
1216
- if (isAddress(val.toLowerCase())) return val.toLowerCase();
1217
- ctx.addIssue({
1218
- code: "invalid_format",
1219
- input: val,
1220
- format: "address",
1221
- error: "not a valid address"
1222
- });
1223
- return z6.NEVER;
1224
- };
1225
-
1226
- // src/core/LLTV.ts
1227
- var LLTV_exports = {};
1228
- __export(LLTV_exports, {
1229
- InvalidLLTVError: () => InvalidLLTVError,
1230
- InvalidOptionError: () => InvalidOptionError,
1231
- LLTVSchema: () => LLTVSchema,
1232
- Options: () => Options,
1233
- from: () => from
1234
- });
1235
- var Options = [0.385, 0.5, 0.625, 0.77, 0.86, 0.915, 0.945, 0.965, 0.98];
1236
- var LLTV_SCALED = Options.map((lltv) => BigInt(lltv * 10 ** 18));
1237
- function from(lltv) {
1238
- if (typeof lltv === "bigint" && !LLTV_SCALED.includes(lltv)) throw new InvalidLLTVError(lltv);
1239
- if (typeof lltv === "bigint") return lltv;
1240
- if (typeof lltv === "number" && !Options.includes(lltv)) throw new InvalidOptionError(lltv);
1241
- return BigInt(lltv * 10 ** 18);
1242
- }
1243
- var InvalidOptionError = class extends BaseError {
1244
- name = "LLTV.InvalidOptionError";
907
+ var InvalidDateError = class extends BaseError {
908
+ name = "Maturity.InvalidDateError";
1245
909
  constructor(input) {
1246
910
  super(
1247
- `Invalid LLTV option. Input: "${input}". Accepted values are: ${Options.map(
1248
- (option) => `"${option}"`
1249
- ).join(", ")}.`
911
+ `Invalid maturity date. Input: "${input}". Accepted values are: ${Object.values(
912
+ MaturityOptions
913
+ ).map((option) => `"${option()}"`).join(", ")}.`
1250
914
  );
1251
915
  }
1252
916
  };
1253
- var InvalidLLTVError = class extends BaseError {
1254
- name = "LLTV.InvalidLLTVError";
917
+ var InvalidOptionError2 = class extends BaseError {
918
+ name = "Maturity.InvalidOptionError";
1255
919
  constructor(input) {
1256
920
  super(
1257
- `Invalid LLTV. Input: "${input}". Accepted values are: ${LLTV_SCALED.map(
1258
- (option) => `"${option}"`
1259
- ).join(", ")}.`
921
+ `Invalid maturity option. Input: "${input}". Accepted values are: ${Object.keys(
922
+ MaturityOptions
923
+ ).map((option) => `"${option}"`).join(", ")}.`
1260
924
  );
1261
925
  }
1262
926
  };
1263
- var LLTVSchema = z6.bigint({ coerce: true }).refine(
1264
- (lltv) => {
1265
- try {
1266
- from(lltv);
1267
- return true;
1268
- } catch (_) {
1269
- return false;
1270
- }
1271
- },
1272
- {
1273
- error: () => {
1274
- return "Invalid LLTV: must be one of 0.385, 0.625, 0.77, 0.86, 0.915, 0.945, 0.965 or 0.98 (scaled by 1e18)";
1275
- }
1276
- }
1277
- ).transform((lltv) => from(lltv));
1278
927
 
1279
- // src/core/Collateral.ts
1280
- var CollateralSchema = z6.object({
1281
- asset: z6.string().transform(transformAddress),
1282
- oracle: z6.string().transform(transformAddress),
1283
- lltv: LLTVSchema
928
+ // src/core/Obligation.ts
929
+ var Obligation_exports = {};
930
+ __export(Obligation_exports, {
931
+ CollateralsAreNotSortedError: () => CollateralsAreNotSortedError,
932
+ InvalidObligationError: () => InvalidObligationError,
933
+ ObligationSchema: () => ObligationSchema,
934
+ from: () => from4,
935
+ fromSnakeCase: () => fromSnakeCase2,
936
+ id: () => id,
937
+ random: () => random
1284
938
  });
1285
- var CollateralsSchema = z6.array(CollateralSchema).min(1, { message: "At least one collateral is required" }).refine(
1286
- (collaterals) => {
1287
- for (let i = 1; i < collaterals.length; i++) {
1288
- if (collaterals[i - 1].asset.toLowerCase() > collaterals[i].asset.toLowerCase()) {
1289
- return false;
1290
- }
1291
- }
1292
- return true;
1293
- },
1294
- {
1295
- message: "Collaterals must be sorted alphabetically by address"
1296
- }
1297
- ).refine(
1298
- (collaterals) => {
1299
- const uniqueAssets = /* @__PURE__ */ new Set();
1300
- for (const collateral of collaterals) {
1301
- const assetAddress = collateral.asset.toLowerCase();
1302
- if (uniqueAssets.has(assetAddress)) {
1303
- return false;
1304
- }
1305
- uniqueAssets.add(assetAddress);
939
+
940
+ // src/utils/Format.ts
941
+ var Format_exports = {};
942
+ __export(Format_exports, {
943
+ fromSnakeCase: () => fromSnakeCase,
944
+ toSnakeCase: () => toSnakeCase
945
+ });
946
+ function toSnakeCase(obj) {
947
+ return stringifyBigint(
948
+ processObject(
949
+ obj,
950
+ (s2) => s2.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`),
951
+ (value) => typeof value === "string" && isAddress(value.toLowerCase()) ? getAddress(value.toLowerCase()) : value
952
+ )
953
+ );
954
+ }
955
+ function fromSnakeCase(obj) {
956
+ return processObject(
957
+ obj,
958
+ (s2) => isAddress(s2.toLowerCase()) ? s2 : s2.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
959
+ (value) => typeof value === "string" && isAddress(value.toLowerCase()) ? value.toLowerCase() : value
960
+ );
961
+ }
962
+ function processObject(obj, fnKey, fnValue) {
963
+ if (typeof obj !== "object" || obj === null) return obj;
964
+ if (Array.isArray(obj)) return obj.map((item) => processObject(item, fnKey, fnValue));
965
+ return Object.entries(obj).reduce(
966
+ (acc, [key, value]) => {
967
+ const newKey = fnKey(key);
968
+ acc[newKey] = typeof value === "object" && value !== null ? processObject(value, fnKey, fnValue) : fnValue(value);
969
+ return acc;
970
+ },
971
+ {}
972
+ );
973
+ }
974
+ function stringifyBigint(value) {
975
+ if (typeof value === "bigint") return value.toString();
976
+ if (Array.isArray(value)) return value.map(stringifyBigint);
977
+ if (value && typeof value === "object") {
978
+ const out = {};
979
+ for (const [k, v] of Object.entries(value)) {
980
+ out[k] = stringifyBigint(v);
1306
981
  }
1307
- return true;
1308
- },
1309
- {
1310
- message: "Collaterals must not contain duplicate assets"
982
+ return out;
1311
983
  }
1312
- );
1313
- var from2 = (parameters) => {
1314
- return {
1315
- asset: parameters.asset.toLowerCase(),
1316
- lltv: from(parameters.lltv),
1317
- oracle: parameters.oracle.toLowerCase()
1318
- };
1319
- };
984
+ return value;
985
+ }
1320
986
 
1321
- // src/core/Cursor.ts
1322
- var Cursor_exports = {};
1323
- __export(Cursor_exports, {
1324
- decode: () => decode,
1325
- encode: () => encode,
1326
- validate: () => validate
987
+ // src/core/Obligation.ts
988
+ var ObligationSchema = z6.object({
989
+ chainId: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
990
+ loanToken: z6.string().transform(transformAddress),
991
+ collaterals: CollateralsSchema,
992
+ maturity: MaturitySchema
1327
993
  });
1328
- function validate(cursor) {
1329
- if (!cursor || typeof cursor !== "object") {
1330
- throw new Error("Cursor must be an object");
1331
- }
1332
- const c = cursor;
1333
- if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
1334
- throw new Error(
1335
- `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
1336
- );
994
+ function from4(parameters) {
995
+ try {
996
+ const parsedObligation = ObligationSchema.parse({
997
+ ...parameters,
998
+ maturity: from3(parameters.maturity)
999
+ });
1000
+ return {
1001
+ chainId: parsedObligation.chainId,
1002
+ loanToken: parsedObligation.loanToken.toLowerCase(),
1003
+ collaterals: parsedObligation.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)),
1004
+ maturity: parsedObligation.maturity
1005
+ };
1006
+ } catch (error2) {
1007
+ throw new InvalidObligationError(error2);
1337
1008
  }
1338
- if (!["asc", "desc"].includes(c.dir)) {
1339
- throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
1009
+ }
1010
+ function fromSnakeCase2(input) {
1011
+ return from4(fromSnakeCase(input));
1012
+ }
1013
+ function id(obligation) {
1014
+ let lastAsset = "";
1015
+ for (const collateral of obligation.collaterals) {
1016
+ const newAsset = collateral.asset.toLowerCase();
1017
+ if (newAsset.localeCompare(lastAsset) < 0) throw new CollateralsAreNotSortedError();
1018
+ lastAsset = newAsset;
1340
1019
  }
1341
- if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
1342
- throw new Error(
1343
- `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
1344
- );
1345
- }
1346
- const validations = {
1347
- rate: {
1348
- field: "rate",
1349
- type: "string",
1350
- pattern: /^\d+$/,
1351
- error: "numeric string"
1352
- },
1353
- amount: {
1354
- field: "assets",
1355
- type: "string",
1356
- pattern: /^\d+$/,
1357
- error: "numeric string"
1358
- },
1359
- maturity: {
1360
- field: "maturity",
1361
- type: "number",
1362
- validator: (val) => val > 0,
1363
- error: "positive number"
1364
- },
1365
- expiry: {
1366
- field: "expiry",
1367
- type: "number",
1368
- validator: (val) => val > 0,
1369
- error: "positive number"
1370
- }
1371
- };
1372
- const validation = validations[c.sort];
1373
- if (!validation) {
1374
- throw new Error(`Invalid sort field: ${c.sort}`);
1375
- }
1376
- const fieldValue = c[validation.field];
1377
- if (!fieldValue) {
1378
- throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
1020
+ return keccak256(
1021
+ encodeAbiParameters(
1022
+ [
1023
+ { type: "uint256" },
1024
+ { type: "address" },
1025
+ {
1026
+ type: "tuple[]",
1027
+ components: [
1028
+ { type: "address", name: "token" },
1029
+ { type: "uint256", name: "lltv" },
1030
+ { type: "address", name: "oracle" }
1031
+ ]
1032
+ },
1033
+ { type: "uint256" }
1034
+ ],
1035
+ [
1036
+ obligation.chainId,
1037
+ obligation.loanToken.toLowerCase(),
1038
+ obligation.collaterals.map((c) => ({
1039
+ token: c.asset.toLowerCase(),
1040
+ lltv: c.lltv,
1041
+ oracle: c.oracle.toLowerCase()
1042
+ })),
1043
+ BigInt(obligation.maturity)
1044
+ ]
1045
+ )
1046
+ );
1047
+ }
1048
+ function random() {
1049
+ return from4({
1050
+ chainId: 1n,
1051
+ loanToken: privateKeyToAccount(generatePrivateKey()).address,
1052
+ collaterals: [
1053
+ from2({
1054
+ asset: privateKeyToAccount(generatePrivateKey()).address,
1055
+ oracle: privateKeyToAccount(generatePrivateKey()).address,
1056
+ lltv: 0.965
1057
+ })
1058
+ ],
1059
+ maturity: from3("end_of_next_quarter")
1060
+ });
1061
+ }
1062
+ var InvalidObligationError = class extends BaseError {
1063
+ name = "Obligation.InvalidObligationError";
1064
+ constructor(error2) {
1065
+ super("Invalid obligation.", { cause: error2 });
1379
1066
  }
1380
- if (typeof fieldValue !== validation.type) {
1381
- throw new Error(
1382
- `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
1383
- );
1067
+ };
1068
+ var CollateralsAreNotSortedError = class extends BaseError {
1069
+ name = "Obligation.CollateralsAreNotSortedError";
1070
+ constructor() {
1071
+ super("Collaterals are not sorted alphabetically by address.");
1384
1072
  }
1385
- if (validation.pattern && !validation.pattern.test(fieldValue)) {
1386
- throw new Error(
1387
- `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
1388
- );
1073
+ };
1074
+
1075
+ // src/core/Offer.ts
1076
+ var Offer_exports = {};
1077
+ __export(Offer_exports, {
1078
+ AccountNotSetError: () => AccountNotSetError,
1079
+ InvalidOfferError: () => InvalidOfferError,
1080
+ OfferHashSchema: () => OfferHashSchema,
1081
+ OfferSchema: () => OfferSchema,
1082
+ consumedEvent: () => consumedEvent,
1083
+ decode: () => decode,
1084
+ domain: () => domain,
1085
+ encode: () => encode,
1086
+ from: () => from5,
1087
+ fromConsumedLog: () => fromConsumedLog,
1088
+ fromSnakeCase: () => fromSnakeCase3,
1089
+ hash: () => hash,
1090
+ obligationId: () => obligationId,
1091
+ random: () => random2,
1092
+ sign: () => sign,
1093
+ toSnakeCase: () => toSnakeCase2,
1094
+ types: () => types
1095
+ });
1096
+
1097
+ // src/utils/index.ts
1098
+ var utils_exports = {};
1099
+ __export(utils_exports, {
1100
+ BaseError: () => BaseError,
1101
+ Time: () => time_exports,
1102
+ batch: () => batch,
1103
+ batchMulticall: () => batchMulticall,
1104
+ fromSnakeCase: () => fromSnakeCase,
1105
+ lazy: () => lazy,
1106
+ max: () => max,
1107
+ min: () => min,
1108
+ poll: () => poll,
1109
+ retry: () => retry,
1110
+ toSnakeCase: () => toSnakeCase,
1111
+ wait: () => wait
1112
+ });
1113
+
1114
+ // src/utils/retry.ts
1115
+ var retry = async (fn, attempts = 3, delayMs = 50) => {
1116
+ let lastErr;
1117
+ for (let i = 0; i < attempts; i++) {
1118
+ try {
1119
+ return await fn();
1120
+ } catch (err) {
1121
+ lastErr = err;
1122
+ if (i < attempts - 1) await new Promise((r) => setTimeout(r, delayMs));
1123
+ }
1389
1124
  }
1390
- if (validation.validator && !validation.validator(fieldValue)) {
1391
- throw new Error(
1392
- `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
1125
+ throw lastErr;
1126
+ };
1127
+
1128
+ // src/utils/batchMulticall.ts
1129
+ async function batchMulticall(parameters) {
1130
+ const { client, calls, batchSize, retryAttempts, retryDelayMs, blockNumber } = parameters;
1131
+ const results = [];
1132
+ for (const callsBatch of batch(calls, batchSize)) {
1133
+ const batchResults = await retry(
1134
+ () => client.multicall({
1135
+ allowFailure: false,
1136
+ contracts: callsBatch,
1137
+ ...blockNumber ? { blockNumber } : {}
1138
+ }),
1139
+ retryAttempts,
1140
+ retryDelayMs
1393
1141
  );
1142
+ results.push(...batchResults);
1394
1143
  }
1395
- if (c.page !== void 0) {
1396
- if (typeof c.page !== "number" || !Number.isInteger(c.page) || c.page < 1) {
1397
- throw new Error("Invalid page: must be a positive integer");
1144
+ return results;
1145
+ }
1146
+
1147
+ // src/utils/lazy.ts
1148
+ function lazy(pollFn) {
1149
+ return () => async function* () {
1150
+ let active = true;
1151
+ let resolveNext = null;
1152
+ const queue = [];
1153
+ const wait2 = () => new Promise((resolve) => {
1154
+ resolveNext = resolve;
1155
+ });
1156
+ const emit = (item) => {
1157
+ queue.push(item);
1158
+ resolveNext?.();
1159
+ resolveNext = null;
1160
+ };
1161
+ let unpoll = null;
1162
+ const stop = () => {
1163
+ active = false;
1164
+ unpoll?.();
1165
+ resolveNext?.();
1166
+ resolveNext = null;
1167
+ };
1168
+ unpoll = pollFn(emit, { stop });
1169
+ try {
1170
+ while (active) {
1171
+ if (queue.length === 0) await wait2();
1172
+ while (queue.length > 0 && active) yield queue.shift();
1173
+ }
1174
+ } finally {
1175
+ stop();
1398
1176
  }
1399
- }
1400
- return true;
1177
+ }();
1401
1178
  }
1402
- function encode(c) {
1403
- return Base64.encodeURL(JSON.stringify(c));
1179
+
1180
+ // src/utils/wait.ts
1181
+ async function wait(time) {
1182
+ return new Promise((res) => setTimeout(res, time));
1404
1183
  }
1405
- function decode(token) {
1406
- if (!token) return null;
1407
- const decoded = JSON.parse(Base64.decode(token));
1408
- validate(decoded);
1409
- return decoded;
1184
+
1185
+ // src/utils/poll.ts
1186
+ function poll(fn, { interval }) {
1187
+ let active = true;
1188
+ const unwatch = () => active = false;
1189
+ const watch2 = async () => {
1190
+ await wait(interval);
1191
+ const poll2 = async () => {
1192
+ if (!active) return;
1193
+ await fn({ unpoll: unwatch });
1194
+ await wait(interval);
1195
+ poll2();
1196
+ };
1197
+ poll2();
1198
+ };
1199
+ watch2();
1200
+ return unwatch;
1410
1201
  }
1411
1202
 
1412
- // src/core/Liquidity.ts
1413
- var Liquidity_exports = {};
1414
- __export(Liquidity_exports, {
1415
- calculateMaxDebt: () => calculateMaxDebt,
1416
- generateAllowancePoolId: () => generateAllowancePoolId,
1417
- generateBalancePoolId: () => generateBalancePoolId,
1418
- generateBuyVaultCallbackPoolId: () => generateBuyVaultCallbackPoolId,
1419
- generateDebtPoolId: () => generateDebtPoolId,
1420
- generateMarketLiquidityPoolId: () => generateMarketLiquidityPoolId,
1421
- generateObligationCollateralPoolId: () => generateObligationCollateralPoolId,
1422
- generateSellERC20CallbackPoolId: () => generateSellERC20CallbackPoolId,
1423
- generateUserVaultPositionPoolId: () => generateUserVaultPositionPoolId,
1424
- generateVaultPositionPoolId: () => generateVaultPositionPoolId
1203
+ // src/utils/time.ts
1204
+ var time_exports = {};
1205
+ __export(time_exports, {
1206
+ max: () => max2,
1207
+ now: () => now
1425
1208
  });
1426
- function calculateMaxDebt(amount, oraclePrice, lltv) {
1427
- const ORACLE_PRICE_SCALE = 10n ** 36n;
1428
- const PRECISION = 10n ** 18n;
1429
- const collateralQuoted = amount * oraclePrice / ORACLE_PRICE_SCALE;
1430
- const maxDebt = collateralQuoted * lltv / PRECISION;
1431
- return maxDebt;
1209
+ function now() {
1210
+ return Math.floor(Date.now() / 1e3);
1432
1211
  }
1433
- function generateBalancePoolId(parameters) {
1434
- const { user, chainId, token } = parameters;
1435
- return `${user}-${chainId.toString()}-${token}-balance`.toLowerCase();
1436
- }
1437
- function generateAllowancePoolId(parameters) {
1438
- const { user, chainId, token } = parameters;
1439
- return `${user}-${chainId.toString()}-${token}-allowance`.toLowerCase();
1440
- }
1441
- function generateSellERC20CallbackPoolId(parameters) {
1442
- const { user, chainId, obligationId: obligationId2, token, offerHash } = parameters;
1443
- return `${user}-${chainId.toString()}-${obligationId2}-${token}-${offerHash}-sell_erc20_callback`.toLowerCase();
1444
- }
1445
- function generateObligationCollateralPoolId(parameters) {
1446
- const { user, chainId, obligationId: obligationId2, token } = parameters;
1447
- return `${user}-${chainId.toString()}-${obligationId2}-${token}-obligation-collateral`.toLowerCase();
1448
- }
1449
- function generateBuyVaultCallbackPoolId(parameters) {
1450
- const { user, chainId, vault, offerHash } = parameters;
1451
- return `${user}-${chainId.toString()}-${vault}-${offerHash}-${"buy_vault_v1_callback" /* BuyVaultV1Callback */}`.toLowerCase();
1452
- }
1453
- function generateDebtPoolId(parameters) {
1454
- const { user, chainId, obligationId: obligationId2 } = parameters;
1455
- return `${user}-${chainId.toString()}-${obligationId2}-debt`.toLowerCase();
1456
- }
1457
- function generateUserVaultPositionPoolId(parameters) {
1458
- const { user, chainId, vault } = parameters;
1459
- return `${user}-${chainId.toString()}-${vault}-user-vault-position`.toLowerCase();
1460
- }
1461
- function generateVaultPositionPoolId(parameters) {
1462
- const { vault, chainId, marketId } = parameters;
1463
- return `${vault}-${chainId.toString()}-${marketId}-vault-position`.toLowerCase();
1464
- }
1465
- function generateMarketLiquidityPoolId(parameters) {
1466
- const { chainId, marketId } = parameters;
1467
- return `${chainId.toString()}-${marketId}-market-liquidity`.toLowerCase();
1212
+ function max2() {
1213
+ return 864e16;
1468
1214
  }
1469
1215
 
1470
- // src/core/Maturity.ts
1471
- var Maturity_exports = {};
1472
- __export(Maturity_exports, {
1473
- InvalidDateError: () => InvalidDateError,
1474
- InvalidFormatError: () => InvalidFormatError,
1475
- InvalidOptionError: () => InvalidOptionError2,
1476
- MaturitySchema: () => MaturitySchema,
1477
- from: () => from3
1478
- });
1479
- var MaturitySchema = z6.number().int().refine(
1480
- (maturity) => {
1481
- try {
1482
- from3(maturity);
1483
- return true;
1484
- } catch (_e) {
1485
- return false;
1486
- }
1487
- },
1488
- {
1489
- error: (issue) => {
1490
- try {
1491
- const maturityDate = new Date(issue.input * 1e3);
1492
- return `The maturity is set to ${maturityDate}. It must fall on the allowed settlement cycles (Friday 15:00 UTC at the end of week/month/quarter).`;
1493
- } catch (_) {
1494
- return `The maturity is set to ${issue.input}. It must fall on the allowed settlement cycles (Friday 15:00 UTC at the end of week/month/quarter).`;
1495
- }
1496
- }
1497
- }
1498
- ).transform((maturity) => maturity);
1499
- var MaturityOptions = {
1500
- end_of_week: () => endOfWeek(),
1501
- end_of_next_week: () => endOfNextWeek(),
1502
- end_of_month: () => endOfMonth(),
1503
- end_of_next_month: () => endOfNextMonth(),
1504
- end_of_quarter: () => endOfQuarter(),
1505
- end_of_next_quarter: () => endOfNextQuarter()
1506
- };
1507
- function from3(ts) {
1508
- if (typeof ts === "string") {
1509
- if (ts in MaturityOptions) return MaturityOptions[ts]();
1510
- throw new InvalidOptionError2(ts);
1511
- }
1512
- if (typeof ts === "number" && ts > 1e12) throw new InvalidFormatError();
1513
- if (!Object.values(MaturityOptions).some((option) => option() === ts))
1514
- throw new InvalidDateError(ts);
1515
- return ts;
1516
- }
1517
- var endOfWeek = () => fridayOfWeek(0);
1518
- var endOfNextWeek = () => fridayOfWeek(1);
1519
- var endOfMonth = () => lastFridayOfMonth((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth());
1520
- var endOfNextMonth = () => lastFridayOfMonth((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth() + 1);
1521
- var endOfQuarter = () => lastFridayOfQuarter(0);
1522
- var endOfNextQuarter = () => lastFridayOfQuarter(1);
1523
- var fridayOfWeek = (weeksAhead = 0) => {
1524
- const now2 = /* @__PURE__ */ new Date();
1525
- const today15H = new Date(
1526
- Date.UTC(now2.getUTCFullYear(), now2.getUTCMonth(), now2.getUTCDate(), 15)
1527
- );
1528
- let daysUntilFriday = (5 - today15H.getUTCDay() + 7) % 7;
1529
- if (daysUntilFriday === 0 && now2.getTime() >= today15H.getTime()) {
1530
- daysUntilFriday = 7;
1531
- }
1532
- const friday = new Date(today15H);
1533
- friday.setUTCDate(friday.getUTCDate() + daysUntilFriday + weeksAhead * 7);
1534
- return friday.getTime() / 1e3;
1535
- };
1536
- var lastFridayOfMonth = (year, month) => {
1537
- const lastDayOfMonth15H = new Date(Date.UTC(year, month + 1, 0, 15));
1538
- while (lastDayOfMonth15H.getUTCDay() !== 5) {
1539
- lastDayOfMonth15H.setUTCDate(lastDayOfMonth15H.getUTCDate() - 1);
1540
- }
1541
- const maturity = lastDayOfMonth15H.setUTCDate(lastDayOfMonth15H.getUTCDate()) / 1e3;
1542
- return maturity;
1543
- };
1544
- var lastFridayOfQuarter = (quartersAhead = 0) => {
1545
- const now2 = /* @__PURE__ */ new Date();
1546
- const quarterIndex = Math.floor(now2.getUTCMonth() / 3) + quartersAhead;
1547
- const year = now2.getUTCFullYear() + Math.floor(quarterIndex / 4);
1548
- const quarter = quarterIndex % 4;
1549
- const lastMonth = quarter * 3 + 2;
1550
- return lastFridayOfMonth(year, lastMonth);
1551
- };
1552
- var InvalidFormatError = class extends BaseError {
1553
- name = "Maturity.InvalidFormatError";
1554
- constructor() {
1555
- super("Invalid maturity format. Maturity should be expressed in seconds.");
1556
- }
1557
- };
1558
- var InvalidDateError = class extends BaseError {
1559
- name = "Maturity.InvalidDateError";
1560
- constructor(input) {
1561
- super(
1562
- `Invalid maturity date. Input: "${input}". Accepted values are: ${Object.values(
1563
- MaturityOptions
1564
- ).map((option) => `"${option()}"`).join(", ")}.`
1565
- );
1566
- }
1567
- };
1568
- var InvalidOptionError2 = class extends BaseError {
1569
- name = "Maturity.InvalidOptionError";
1570
- constructor(input) {
1571
- super(
1572
- `Invalid maturity option. Input: "${input}". Accepted values are: ${Object.keys(
1573
- MaturityOptions
1574
- ).map((option) => `"${option}"`).join(", ")}.`
1575
- );
1576
- }
1216
+ // src/core/Offer.ts
1217
+ var OfferHashSchema = z6.string().regex(/^0x[0-9a-fA-F]{64}$/, {
1218
+ message: "Hash must be a valid 32-byte hex string"
1219
+ }).transform(transformHex);
1220
+ var OfferSchema = (parameters) => {
1221
+ const { omitHash = false } = parameters || {};
1222
+ const now2 = time_exports.now();
1223
+ let base = z6.object({
1224
+ offering: z6.string().transform(transformAddress),
1225
+ assets: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1226
+ rate: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1227
+ maturity: MaturitySchema,
1228
+ expiry: z6.number().int().min(now2, { message: "Expiry must be set to a future date" }).max(Number.MAX_SAFE_INTEGER),
1229
+ start: z6.number().int().max(Number.MAX_SAFE_INTEGER),
1230
+ nonce: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1231
+ buy: z6.boolean(),
1232
+ chainId: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1233
+ loanToken: z6.string().transform(transformAddress),
1234
+ collaterals: CollateralsSchema,
1235
+ callback: z6.object({
1236
+ address: z6.string().transform(transformAddress),
1237
+ data: z6.string().transform(transformHex),
1238
+ gasLimit: z6.bigint({ coerce: true }).min(0n).max(maxUint256)
1239
+ }),
1240
+ consumed: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1241
+ blockNumber: z6.number().int().max(Number.MAX_SAFE_INTEGER),
1242
+ signature: z6.string().regex(/^0x[0-9a-fA-F]{130}$/, {
1243
+ message: "Signature must be a valid 65-byte hex string"
1244
+ }).transform(transformHex).optional()
1245
+ });
1246
+ if (!omitHash) base = base.extend({ hash: OfferHashSchema });
1247
+ return base.refine((data) => data.start < data.expiry, {
1248
+ message: "Start must be before expiry",
1249
+ path: ["start"]
1250
+ }).refine((data) => data.expiry <= data.maturity, {
1251
+ message: "Expiry cannot be after maturity",
1252
+ path: ["expiry"]
1253
+ });
1577
1254
  };
1578
-
1579
- // src/core/Obligation.ts
1580
- var Obligation_exports = {};
1581
- __export(Obligation_exports, {
1582
- CollateralsAreNotSortedError: () => CollateralsAreNotSortedError,
1583
- InvalidObligationError: () => InvalidObligationError,
1584
- ObligationSchema: () => ObligationSchema,
1585
- from: () => from4,
1586
- fromSnakeCase: () => fromSnakeCase2,
1587
- id: () => id,
1588
- random: () => random
1589
- });
1590
- var ObligationSchema = z6.object({
1591
- chainId: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1592
- loanToken: z6.string().transform(transformAddress),
1593
- collaterals: CollateralsSchema,
1594
- maturity: MaturitySchema
1595
- });
1596
- function from4(parameters) {
1255
+ function from5(input) {
1597
1256
  try {
1598
- const parsedObligation = ObligationSchema.parse({
1599
- ...parameters,
1600
- maturity: from3(parameters.maturity)
1601
- });
1257
+ const parsedOffer = OfferSchema({ omitHash: true }).parse(input);
1258
+ const parsedHash = OfferHashSchema.parse(hash(parsedOffer));
1602
1259
  return {
1603
- chainId: parsedObligation.chainId,
1604
- loanToken: parsedObligation.loanToken.toLowerCase(),
1605
- collaterals: parsedObligation.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)),
1606
- maturity: parsedObligation.maturity
1260
+ ...parsedOffer,
1261
+ hash: parsedHash
1607
1262
  };
1608
1263
  } catch (error2) {
1609
- throw new InvalidObligationError(error2);
1264
+ throw new InvalidOfferError(error2);
1610
1265
  }
1611
1266
  }
1612
- function fromSnakeCase2(input) {
1613
- return from4(fromSnakeCase(input));
1267
+ function fromSnakeCase3(input) {
1268
+ return from5(fromSnakeCase(input));
1614
1269
  }
1615
- function id(obligation) {
1616
- let lastAsset = "";
1617
- for (const collateral of obligation.collaterals) {
1618
- const newAsset = collateral.asset.toLowerCase();
1619
- if (newAsset.localeCompare(lastAsset) < 0) throw new CollateralsAreNotSortedError();
1620
- lastAsset = newAsset;
1621
- }
1622
- return keccak256(
1623
- encodeAbiParameters(
1624
- [
1625
- { type: "uint256" },
1626
- { type: "address" },
1627
- {
1628
- type: "tuple[]",
1629
- components: [
1630
- { type: "address", name: "token" },
1631
- { type: "uint256", name: "lltv" },
1632
- { type: "address", name: "oracle" }
1633
- ]
1634
- },
1635
- { type: "uint256" }
1636
- ],
1637
- [
1638
- obligation.chainId,
1639
- obligation.loanToken.toLowerCase(),
1640
- obligation.collaterals.map((c) => ({
1641
- token: c.asset.toLowerCase(),
1642
- lltv: c.lltv,
1643
- oracle: c.oracle.toLowerCase()
1644
- })),
1645
- BigInt(obligation.maturity)
1646
- ]
1647
- )
1648
- );
1270
+ function toSnakeCase2(offer) {
1271
+ return toSnakeCase(offer);
1649
1272
  }
1650
- function random() {
1651
- return from4({
1652
- chainId: 1n,
1653
- loanToken: privateKeyToAccount(generatePrivateKey()).address,
1654
- collaterals: [
1655
- from2({
1656
- asset: privateKeyToAccount(generatePrivateKey()).address,
1657
- oracle: privateKeyToAccount(generatePrivateKey()).address,
1658
- lltv: 0.965
1659
- })
1660
- ],
1661
- maturity: from3("end_of_next_quarter")
1662
- });
1663
- }
1664
- var InvalidObligationError = class extends BaseError {
1665
- name = "Obligation.InvalidObligationError";
1666
- constructor(error2) {
1667
- super("Invalid obligation.", { cause: error2 });
1668
- }
1669
- };
1670
- var CollateralsAreNotSortedError = class extends BaseError {
1671
- name = "Obligation.CollateralsAreNotSortedError";
1672
- constructor() {
1673
- super("Collaterals are not sorted alphabetically by address.");
1674
- }
1675
- };
1676
-
1677
- // src/core/Offer.ts
1678
- var Offer_exports = {};
1679
- __export(Offer_exports, {
1680
- AccountNotSetError: () => AccountNotSetError,
1681
- InvalidOfferError: () => InvalidOfferError,
1682
- OfferHashSchema: () => OfferHashSchema,
1683
- OfferSchema: () => OfferSchema,
1684
- consumedEvent: () => consumedEvent,
1685
- decode: () => decode2,
1686
- domain: () => domain,
1687
- encode: () => encode2,
1688
- from: () => from5,
1689
- fromConsumedLog: () => fromConsumedLog,
1690
- fromSnakeCase: () => fromSnakeCase3,
1691
- hash: () => hash,
1692
- obligationId: () => obligationId,
1693
- random: () => random2,
1694
- sign: () => sign,
1695
- toSnakeCase: () => toSnakeCase2,
1696
- types: () => types
1697
- });
1698
- var OfferHashSchema = z6.string().regex(/^0x[0-9a-fA-F]{64}$/, {
1699
- message: "Hash must be a valid 32-byte hex string"
1700
- }).transform(transformHex);
1701
- var OfferSchema = (parameters) => {
1702
- const { omitHash = false } = parameters || {};
1703
- const now2 = time_exports.now();
1704
- let base = z6.object({
1705
- offering: z6.string().transform(transformAddress),
1706
- assets: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1707
- rate: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1708
- maturity: MaturitySchema,
1709
- expiry: z6.number().int().min(now2, { message: "Expiry must be set to a future date" }).max(Number.MAX_SAFE_INTEGER),
1710
- start: z6.number().int().max(Number.MAX_SAFE_INTEGER),
1711
- nonce: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1712
- buy: z6.boolean(),
1713
- chainId: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1714
- loanToken: z6.string().transform(transformAddress),
1715
- collaterals: CollateralsSchema,
1716
- callback: z6.object({
1717
- address: z6.string().transform(transformAddress),
1718
- data: z6.string().transform(transformHex),
1719
- gasLimit: z6.bigint({ coerce: true }).min(0n).max(maxUint256)
1720
- }),
1721
- consumed: z6.bigint({ coerce: true }).min(0n).max(maxUint256),
1722
- blockNumber: z6.number().int().max(Number.MAX_SAFE_INTEGER),
1723
- signature: z6.string().regex(/^0x[0-9a-fA-F]{130}$/, {
1724
- message: "Signature must be a valid 65-byte hex string"
1725
- }).transform(transformHex).optional()
1726
- });
1727
- if (!omitHash) base = base.extend({ hash: OfferHashSchema });
1728
- return base.refine((data) => data.start < data.expiry, {
1729
- message: "Start must be before expiry",
1730
- path: ["start"]
1731
- }).refine((data) => data.expiry <= data.maturity, {
1732
- message: "Expiry cannot be after maturity",
1733
- path: ["expiry"]
1734
- });
1735
- };
1736
- function from5(input) {
1737
- try {
1738
- const parsedOffer = OfferSchema({ omitHash: true }).parse(input);
1739
- const parsedHash = OfferHashSchema.parse(hash(parsedOffer));
1740
- return {
1741
- ...parsedOffer,
1742
- hash: parsedHash
1743
- };
1744
- } catch (error2) {
1745
- throw new InvalidOfferError(error2);
1746
- }
1747
- }
1748
- function fromSnakeCase3(input) {
1749
- return from5(fromSnakeCase(input));
1750
- }
1751
- function toSnakeCase2(offer) {
1752
- return toSnakeCase(offer);
1753
- }
1754
- function random2() {
1755
- const loanToken = privateKeyToAccount(generatePrivateKey()).address;
1756
- const maturity = from3("end_of_month");
1757
- const expiry = from3("end_of_week") - 1;
1758
- const lltv = from(0.965);
1273
+ function random2(config) {
1274
+ const chain = config?.chains ? config.chains[Math.floor(Math.random() * config.chains.length)] : chains.ethereum;
1275
+ const loanToken = config?.loanTokens ? config.loanTokens[Math.floor(Math.random() * config.loanTokens.length)] : privateKeyToAccount(generatePrivateKey()).address;
1276
+ const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [privateKeyToAccount(generatePrivateKey()).address];
1277
+ const collateralAsset = collateralCandidates[Math.floor(Math.random() * collateralCandidates.length)];
1278
+ const maturityOption = weightedChoice([
1279
+ ["end_of_month", 1],
1280
+ ["end_of_next_month", 1]
1281
+ ]);
1282
+ const maturity = config?.maturity ?? from3(maturityOption);
1283
+ const lltv = from(
1284
+ weightedChoice([
1285
+ [0.385, 1],
1286
+ [0.5, 1],
1287
+ [0.625, 2],
1288
+ [0.77, 8],
1289
+ [0.86, 10],
1290
+ [0.915, 8],
1291
+ [0.945, 6],
1292
+ [0.965, 4],
1293
+ [0.98, 2]
1294
+ ])
1295
+ );
1296
+ const buy = config?.buy !== void 0 ? config.buy : Math.random() > 0.5;
1297
+ const ONE = 1000000000000000000n;
1298
+ const qMin = buy ? 16 : 4;
1299
+ const qMax = buy ? 32 : 16;
1300
+ const len = qMax - qMin + 1;
1301
+ const ratePairs = Array.from(
1302
+ { length: len },
1303
+ (_, idx) => {
1304
+ const q = qMin + idx;
1305
+ const scaledRate = BigInt(q) * (ONE / 4n);
1306
+ const weight = buy ? 1 + idx : 1 + (len - 1 - idx);
1307
+ return [scaledRate, weight];
1308
+ }
1309
+ );
1310
+ const rate = config?.rate ?? weightedChoice(ratePairs);
1311
+ const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
1312
+ const unit = BigInt(10) ** BigInt(loanTokenDecimals);
1313
+ const amountBase = BigInt(100 + Math.floor(Math.random() * (1e6 - 100 + 1)));
1314
+ const assetsScaled = config?.assets ?? amountBase * unit;
1315
+ const consumed2 = config?.consumed !== void 0 ? config.consumed : Math.random() < 0.8 ? 0n : assetsScaled * BigInt(1 + Math.floor(Math.random() * 900)) / 1000n;
1316
+ const callbackBySide = (() => {
1317
+ if (buy) return { address: zeroAddress, data: "0x", gasLimit: 0n };
1318
+ const sellCallbackAddress = WhitelistedCallbackAddresses["sell_erc20_callback" /* SellERC20Callback */][0].toLowerCase();
1319
+ const amount = assetsScaled * 1000000000000000000000n;
1320
+ const data = encodeSellERC20Callback({
1321
+ collaterals: [collateralAsset],
1322
+ amounts: [amount]
1323
+ });
1324
+ return { address: sellCallbackAddress, data, gasLimit: 500000n };
1325
+ })();
1759
1326
  const offer = from5({
1760
- offering: privateKeyToAccount(generatePrivateKey()).address,
1761
- assets: BigInt(Math.floor(Math.random() * 1e6)),
1762
- rate: BigInt(Math.floor(Math.random() * 1e6)),
1327
+ offering: config?.offering ?? privateKeyToAccount(generatePrivateKey()).address,
1328
+ assets: assetsScaled,
1329
+ rate,
1763
1330
  maturity,
1764
- expiry,
1765
- start: expiry - 10,
1331
+ expiry: config?.expiry ?? maturity - 1,
1332
+ start: config?.start ?? maturity - 10,
1766
1333
  nonce: BigInt(Math.floor(Math.random() * 1e6)),
1767
- buy: Math.random() > 0.5,
1768
- chainId: 1n,
1334
+ buy,
1335
+ chainId: chain.id,
1769
1336
  loanToken,
1770
- collaterals: [
1337
+ collaterals: config?.collaterals ?? [
1771
1338
  from2({
1772
- asset: zeroAddress,
1339
+ asset: collateralAsset,
1773
1340
  oracle: zeroAddress,
1774
1341
  lltv
1775
1342
  })
1776
1343
  ],
1777
- callback: {
1778
- address: zeroAddress,
1779
- data: "0x",
1780
- gasLimit: 0n
1781
- },
1782
- consumed: 0n,
1344
+ callback: config?.callback ?? callbackBySide,
1345
+ consumed: consumed2,
1783
1346
  blockNumber: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
1784
1347
  });
1785
1348
  return offer;
1786
1349
  }
1350
+ var weightedChoice = (pairs) => {
1351
+ const total = pairs.reduce((sum, [, weight]) => sum + weight, 0);
1352
+ let roll = Math.random() * total;
1353
+ for (const [value, weight] of pairs) {
1354
+ roll -= weight;
1355
+ if (roll < 0) return value;
1356
+ }
1357
+ return pairs[0][0];
1358
+ };
1787
1359
  var domain = (chainId) => ({
1788
1360
  chainId,
1789
1361
  verifyingContract: zeroAddress
@@ -1839,166 +1411,1084 @@ function sign(offer, wallet) {
1839
1411
  gasLimit: offer.callback.gasLimit
1840
1412
  }
1841
1413
  }
1842
- });
1414
+ });
1415
+ }
1416
+ function hash(offer) {
1417
+ return hashTypedData({
1418
+ domain: domain(offer.chainId),
1419
+ message: {
1420
+ offering: offer.offering.toLowerCase(),
1421
+ assets: offer.assets,
1422
+ rate: offer.rate,
1423
+ maturity: BigInt(offer.maturity),
1424
+ expiry: BigInt(offer.expiry),
1425
+ nonce: offer.nonce,
1426
+ buy: offer.buy,
1427
+ loanToken: offer.loanToken.toLowerCase(),
1428
+ collaterals: offer.collaterals,
1429
+ callback: {
1430
+ address: offer.callback.address.toLowerCase(),
1431
+ data: offer.callback.data,
1432
+ gasLimit: offer.callback.gasLimit
1433
+ }
1434
+ },
1435
+ primaryType: "Offer",
1436
+ types
1437
+ });
1438
+ }
1439
+ function obligationId(offer) {
1440
+ return id(
1441
+ from4({
1442
+ chainId: offer.chainId,
1443
+ loanToken: offer.loanToken,
1444
+ collaterals: offer.collaterals,
1445
+ maturity: offer.maturity
1446
+ })
1447
+ );
1448
+ }
1449
+ var OfferAbi = [
1450
+ { name: "offering", type: "address" },
1451
+ { name: "assets", type: "uint256" },
1452
+ { name: "rate", type: "uint256" },
1453
+ { name: "maturity", type: "uint256" },
1454
+ { name: "expiry", type: "uint256" },
1455
+ { name: "nonce", type: "uint256" },
1456
+ { name: "buy", type: "bool" },
1457
+ { name: "chainId", type: "uint256" },
1458
+ { name: "loanToken", type: "address" },
1459
+ { name: "start", type: "uint256" },
1460
+ {
1461
+ name: "collaterals",
1462
+ type: "tuple[]",
1463
+ components: [
1464
+ { name: "asset", type: "address" },
1465
+ { name: "oracle", type: "address" },
1466
+ { name: "lltv", type: "uint256" }
1467
+ ]
1468
+ },
1469
+ {
1470
+ name: "callback",
1471
+ type: "tuple",
1472
+ components: [
1473
+ { name: "address", type: "address" },
1474
+ { name: "data", type: "bytes" },
1475
+ { name: "gasLimit", type: "uint256" }
1476
+ ]
1477
+ },
1478
+ { name: "signature", type: "bytes" }
1479
+ ];
1480
+ function encode(offer) {
1481
+ return encodeAbiParameters(OfferAbi, [
1482
+ offer.offering,
1483
+ offer.assets,
1484
+ offer.rate,
1485
+ BigInt(offer.maturity),
1486
+ BigInt(offer.expiry),
1487
+ offer.nonce,
1488
+ offer.buy,
1489
+ offer.chainId,
1490
+ offer.loanToken,
1491
+ BigInt(offer.start),
1492
+ offer.collaterals,
1493
+ offer.callback,
1494
+ offer.signature ?? "0x"
1495
+ ]);
1496
+ }
1497
+ function decode(data, blockNumber) {
1498
+ let decoded;
1499
+ try {
1500
+ decoded = decodeAbiParameters(OfferAbi, data);
1501
+ } catch (error2) {
1502
+ throw new InvalidOfferError(error2);
1503
+ }
1504
+ const offer = from5({
1505
+ offering: decoded[0],
1506
+ assets: decoded[1],
1507
+ rate: decoded[2],
1508
+ maturity: from3(Number(decoded[3])),
1509
+ expiry: Number(decoded[4]),
1510
+ nonce: decoded[5],
1511
+ buy: decoded[6],
1512
+ chainId: decoded[7],
1513
+ loanToken: decoded[8],
1514
+ start: Number(decoded[9]),
1515
+ collaterals: decoded[10].map((c) => {
1516
+ return from2({
1517
+ asset: c.asset,
1518
+ oracle: c.oracle,
1519
+ lltv: c.lltv
1520
+ });
1521
+ }),
1522
+ callback: {
1523
+ address: decoded[11].address,
1524
+ data: decoded[11].data,
1525
+ gasLimit: decoded[11].gasLimit
1526
+ },
1527
+ consumed: 0n,
1528
+ blockNumber: Number(blockNumber),
1529
+ ...decoded[12] === "0x" ? {} : { signature: decoded[12] }
1530
+ });
1531
+ return offer;
1532
+ }
1533
+ var consumedEvent = {
1534
+ type: "event",
1535
+ name: "Consumed",
1536
+ inputs: [
1537
+ { name: "user", type: "address", indexed: true, internalType: "address" },
1538
+ { name: "nonce", type: "uint256", indexed: true, internalType: "uint256" },
1539
+ { name: "amount", type: "uint256", indexed: false, internalType: "uint256" }
1540
+ ],
1541
+ anonymous: false
1542
+ };
1543
+ function fromConsumedLog(parameters) {
1544
+ const { blockNumber, logIndex, chainId, transactionHash, user, nonce, amount } = parameters;
1545
+ return {
1546
+ id: `${blockNumber.toString()}-${logIndex.toString()}-${chainId}-${transactionHash}`,
1547
+ chainId: BigInt(chainId),
1548
+ offering: user,
1549
+ nonce,
1550
+ amount,
1551
+ blockNumber: Number(blockNumber)
1552
+ };
1553
+ }
1554
+ var InvalidOfferError = class extends BaseError {
1555
+ name = "Offer.InvalidOfferError";
1556
+ constructor(error2) {
1557
+ super("Invalid offer.", { cause: error2 });
1558
+ }
1559
+ };
1560
+ var AccountNotSetError = class extends BaseError {
1561
+ name = "Offer.AccountNotSetError";
1562
+ constructor() {
1563
+ super("Account not set.");
1564
+ }
1565
+ };
1566
+
1567
+ // src/core/Quote.ts
1568
+ var Quote_exports = {};
1569
+ __export(Quote_exports, {
1570
+ InvalidQuoteError: () => InvalidQuoteError,
1571
+ QuoteSchema: () => QuoteSchema,
1572
+ from: () => from6,
1573
+ fromSnakeCase: () => fromSnakeCase4,
1574
+ random: () => random3
1575
+ });
1576
+ var QuoteSchema = z6.object({
1577
+ obligationId: z6.string().transform(transformHex),
1578
+ ask: z6.object({
1579
+ rate: z6.bigint({ coerce: true }).min(0n).max(maxUint256)
1580
+ }),
1581
+ bid: z6.object({
1582
+ rate: z6.bigint({ coerce: true }).min(0n).max(maxUint256)
1583
+ })
1584
+ });
1585
+ function from6(parameters) {
1586
+ try {
1587
+ const parsedQuote = QuoteSchema.parse(parameters);
1588
+ return {
1589
+ obligationId: parsedQuote.obligationId,
1590
+ ask: parsedQuote.ask,
1591
+ bid: parsedQuote.bid
1592
+ };
1593
+ } catch (error2) {
1594
+ throw new InvalidQuoteError(error2);
1595
+ }
1596
+ }
1597
+ function fromSnakeCase4(snake) {
1598
+ return from6(fromSnakeCase(snake));
1599
+ }
1600
+ function random3() {
1601
+ return from6({
1602
+ obligationId: Obligation_exports.id(Obligation_exports.random()),
1603
+ ask: {
1604
+ rate: BigInt(Math.floor(Math.random() * 1e6))
1605
+ },
1606
+ bid: {
1607
+ rate: BigInt(Math.floor(Math.random() * 1e6))
1608
+ }
1609
+ });
1610
+ }
1611
+ var InvalidQuoteError = class extends BaseError {
1612
+ name = "Quote.InvalidQuoteError";
1613
+ constructor(error2) {
1614
+ super("Invalid quote.", { cause: error2 });
1615
+ }
1616
+ };
1617
+
1618
+ // src/core/types.ts
1619
+ var BrandTypeId = Symbol.for("mempool/Brand");
1620
+ var CollectorHealth = z.object({
1621
+ name: z.string(),
1622
+ chain_id: z.number(),
1623
+ block_number: z.number().nullable(),
1624
+ updated_at: z.string().nullable(),
1625
+ lag: z.number().nullable(),
1626
+ status: z.enum(["live", "lagging", "unknown"])
1627
+ });
1628
+ var CollectorsHealthResponse = z.array(CollectorHealth);
1629
+ var ChainHealth = z.object({
1630
+ chain_id: z.number(),
1631
+ block_number: z.number(),
1632
+ updated_at: z.string()
1633
+ });
1634
+ var ChainsHealthResponse = z.array(ChainHealth);
1635
+ var RouterStatusResponse = z.object({
1636
+ status: z.enum(["live", "syncing"])
1637
+ });
1638
+
1639
+ // src/stores/utils/Cursor.ts
1640
+ var Cursor_exports = {};
1641
+ __export(Cursor_exports, {
1642
+ decode: () => decode2,
1643
+ encode: () => encode2,
1644
+ validate: () => validate
1645
+ });
1646
+ function validate(cursor) {
1647
+ if (!cursor || typeof cursor !== "object") {
1648
+ throw new Error("Cursor must be an object");
1649
+ }
1650
+ const c = cursor;
1651
+ if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
1652
+ throw new Error(
1653
+ `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
1654
+ );
1655
+ }
1656
+ if (!["asc", "desc"].includes(c.dir)) {
1657
+ throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
1658
+ }
1659
+ if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
1660
+ throw new Error(
1661
+ `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
1662
+ );
1663
+ }
1664
+ const validations = {
1665
+ rate: {
1666
+ field: "rate",
1667
+ type: "string",
1668
+ pattern: /^\d+$/,
1669
+ error: "numeric string"
1670
+ },
1671
+ amount: {
1672
+ field: "assets",
1673
+ type: "string",
1674
+ pattern: /^\d+$/,
1675
+ error: "numeric string"
1676
+ },
1677
+ maturity: {
1678
+ field: "maturity",
1679
+ type: "number",
1680
+ validator: (val) => val > 0,
1681
+ error: "positive number"
1682
+ },
1683
+ expiry: {
1684
+ field: "expiry",
1685
+ type: "number",
1686
+ validator: (val) => val > 0,
1687
+ error: "positive number"
1688
+ }
1689
+ };
1690
+ const validation = validations[c.sort];
1691
+ if (!validation) {
1692
+ throw new Error(`Invalid sort field: ${c.sort}`);
1693
+ }
1694
+ const fieldValue = c[validation.field];
1695
+ if (!fieldValue) {
1696
+ throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
1697
+ }
1698
+ if (typeof fieldValue !== validation.type) {
1699
+ throw new Error(
1700
+ `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
1701
+ );
1702
+ }
1703
+ if (validation.pattern && !validation.pattern.test(fieldValue)) {
1704
+ throw new Error(
1705
+ `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
1706
+ );
1707
+ }
1708
+ if (validation.validator && !validation.validator(fieldValue)) {
1709
+ throw new Error(
1710
+ `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
1711
+ );
1712
+ }
1713
+ if (c.page !== void 0) {
1714
+ if (typeof c.page !== "number" || !Number.isInteger(c.page) || c.page < 1) {
1715
+ throw new Error("Invalid page: must be a positive integer");
1716
+ }
1717
+ }
1718
+ return true;
1719
+ }
1720
+ function encode2(c) {
1721
+ return Base64.encodeURL(JSON.stringify(c));
1722
+ }
1723
+ function decode2(token) {
1724
+ if (!token) return null;
1725
+ const decoded = JSON.parse(Base64.decode(token));
1726
+ validate(decoded);
1727
+ return decoded;
1728
+ }
1729
+
1730
+ // src/api/Schema/requests.ts
1731
+ var MAX_LIMIT = 100;
1732
+ var DEFAULT_LIMIT = 20;
1733
+ var PaginationQueryParams = z6.object({
1734
+ cursor: z6.string().optional().refine(
1735
+ (val) => {
1736
+ if (!val) return true;
1737
+ try {
1738
+ const decoded = Cursor_exports.decode(val);
1739
+ return decoded !== null;
1740
+ } catch (_error) {
1741
+ return false;
1742
+ }
1743
+ },
1744
+ {
1745
+ message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
1746
+ }
1747
+ ).meta({
1748
+ description: "Pagination cursor in base64url-encoded format",
1749
+ example: "eyJzb3J0IjoicHJpY2UiLCJkaXIiOiJkZXNjIiwicHJpY2UiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwiaGFzaCI6IjB4ZGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIifQ"
1750
+ }),
1751
+ limit: z6.string().regex(/^[1-9]\d*$/, {
1752
+ message: "Limit must be a positive integer"
1753
+ }).transform((val) => Number.parseInt(val, 10)).pipe(
1754
+ z6.number().max(MAX_LIMIT, {
1755
+ message: `Limit cannot exceed ${MAX_LIMIT}`
1756
+ })
1757
+ ).optional().default(DEFAULT_LIMIT).meta({
1758
+ description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT}`,
1759
+ example: 10
1760
+ })
1761
+ });
1762
+ var GetOffersQueryParams = z6.object({
1763
+ ...PaginationQueryParams.shape,
1764
+ side: z6.enum(["buy", "sell"]).meta({
1765
+ description: "Side of the offer.",
1766
+ example: "buy"
1767
+ }),
1768
+ obligation_id: z6.string({ error: "Obligation id is required and must be a valid 32-byte hex string" }).regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).meta({
1769
+ description: "Offers obligation id",
1770
+ example: "0x1234567890123456789012345678901234567890123456789012345678901234"
1771
+ })
1772
+ });
1773
+ var GetObligationsQueryParams = z6.object({
1774
+ ...PaginationQueryParams.shape,
1775
+ cursor: z6.string().optional().meta({
1776
+ description: "Obligation id cursor",
1777
+ example: "0x1234567890123456789012345678901234567890123456789012345678901234"
1778
+ })
1779
+ });
1780
+ var schemas = {
1781
+ get_offers: GetOffersQueryParams,
1782
+ get_obligations: GetObligationsQueryParams
1783
+ };
1784
+ function parse(action, query) {
1785
+ return schemas[action].parse(query);
1786
+ }
1787
+ function safeParse(action, query, error2) {
1788
+ return schemas[action].safeParse(query, {
1789
+ error: error2
1790
+ });
1791
+ }
1792
+
1793
+ // src/api/Schema/openapi.ts
1794
+ var timestampExample = "2024-01-01T12:00:00.000Z";
1795
+ var cursorExample = "eyJvZmZzZXQiOjEwMH0";
1796
+ function makeSuccessResponse(parameters) {
1797
+ const { dataSchema, dataDescription, dataExample, cursor } = parameters;
1798
+ const withDataMeta = dataDescription ? dataSchema.meta({ description: dataDescription }) : dataSchema;
1799
+ return z.object({
1800
+ status: z.literal("success"),
1801
+ cursor: z.string().nullable(),
1802
+ data: z.any(),
1803
+ meta: z.object({
1804
+ timestamp: z.string()
1805
+ })
1806
+ }).extend({
1807
+ data: withDataMeta
1808
+ }).meta({
1809
+ example: {
1810
+ status: "success",
1811
+ cursor,
1812
+ data: dataExample,
1813
+ meta: { timestamp: timestampExample }
1814
+ }
1815
+ });
1816
+ }
1817
+ var OffersSuccessResponseSchema = makeSuccessResponse({
1818
+ dataSchema: z.array(z.any()),
1819
+ dataDescription: "Offers matching the provided filters.",
1820
+ dataExample: [toSnakeCase(Offer_exports.random())],
1821
+ cursor: cursorExample
1822
+ });
1823
+ var ObligationsSuccessResponseSchema = makeSuccessResponse({
1824
+ dataSchema: z.array(z.any()),
1825
+ dataDescription: "Obligations known to the router.",
1826
+ dataExample: [toSnakeCase(Obligation_exports.random())],
1827
+ cursor: cursorExample
1828
+ });
1829
+ var RouterStatusSuccessResponseSchema = makeSuccessResponse({
1830
+ dataSchema: RouterStatusResponse,
1831
+ dataDescription: "Aggregated router status.",
1832
+ dataExample: { status: "live" },
1833
+ cursor: null
1834
+ });
1835
+ var CollectorsHealthSuccessResponseSchema = makeSuccessResponse({
1836
+ dataSchema: CollectorsHealthResponse,
1837
+ dataDescription: "Collectors health details and sync status.",
1838
+ dataExample: [
1839
+ {
1840
+ name: "mempool_offers",
1841
+ chain_id: "1",
1842
+ block_number: 21345678,
1843
+ updated_at: "2024-01-01T12:00:00.000Z",
1844
+ lag: 0,
1845
+ status: "live"
1846
+ }
1847
+ ],
1848
+ cursor: null
1849
+ });
1850
+ var ChainsHealthSuccessResponseSchema = makeSuccessResponse({
1851
+ dataSchema: ChainsHealthResponse,
1852
+ dataDescription: "Latest processed block per chain.",
1853
+ dataExample: [
1854
+ {
1855
+ chain_id: "1",
1856
+ block_number: 21345678,
1857
+ updated_at: "2024-01-01T12:00:00.000Z"
1858
+ }
1859
+ ],
1860
+ cursor: null
1861
+ });
1862
+ var errorResponseSchema = z.object({
1863
+ status: z.literal("error"),
1864
+ error: z.object({
1865
+ code: z.string(),
1866
+ message: z.string(),
1867
+ details: z.any().optional()
1868
+ }),
1869
+ meta: z.object({
1870
+ timestamp: z.string()
1871
+ })
1872
+ }).meta({
1873
+ description: "Error response wrapper.",
1874
+ example: {
1875
+ status: "error",
1876
+ error: {
1877
+ code: "VALIDATION_ERROR",
1878
+ message: "Invalid cursor format. Must be a valid base64url-encoded cursor object",
1879
+ details: [
1880
+ {
1881
+ field: "cursor",
1882
+ issue: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
1883
+ }
1884
+ ]
1885
+ },
1886
+ meta: {
1887
+ timestamp: timestampExample
1888
+ }
1889
+ }
1890
+ });
1891
+ var paths = {
1892
+ "/v1/offers": {
1893
+ get: {
1894
+ summary: "Offers",
1895
+ description: "Find offers that match specific criteria",
1896
+ tags: ["Offers"],
1897
+ requestParams: {
1898
+ query: GetOffersQueryParams
1899
+ },
1900
+ responses: {
1901
+ 200: {
1902
+ description: "Success",
1903
+ content: {
1904
+ "application/json": {
1905
+ schema: OffersSuccessResponseSchema
1906
+ }
1907
+ }
1908
+ },
1909
+ 400: {
1910
+ description: "Bad Request",
1911
+ content: {
1912
+ "application/json": {
1913
+ schema: errorResponseSchema
1914
+ }
1915
+ }
1916
+ }
1917
+ }
1918
+ }
1919
+ },
1920
+ "/v1/obligations": {
1921
+ get: {
1922
+ summary: "Obligations",
1923
+ description: "List obligations with pagination support",
1924
+ tags: ["Obligations"],
1925
+ requestParams: {
1926
+ query: GetObligationsQueryParams
1927
+ },
1928
+ responses: {
1929
+ 200: {
1930
+ description: "Success",
1931
+ content: {
1932
+ "application/json": {
1933
+ schema: ObligationsSuccessResponseSchema
1934
+ }
1935
+ }
1936
+ },
1937
+ 400: {
1938
+ description: "Bad Request",
1939
+ content: {
1940
+ "application/json": {
1941
+ schema: errorResponseSchema
1942
+ }
1943
+ }
1944
+ }
1945
+ }
1946
+ }
1947
+ },
1948
+ "/v1/health": {
1949
+ get: {
1950
+ summary: "Router status",
1951
+ description: "Retrieve the aggregated status of the router.",
1952
+ tags: ["Health"],
1953
+ responses: {
1954
+ 200: {
1955
+ description: "Success",
1956
+ content: {
1957
+ "application/json": {
1958
+ schema: RouterStatusSuccessResponseSchema
1959
+ }
1960
+ }
1961
+ }
1962
+ }
1963
+ }
1964
+ },
1965
+ "/v1/health/collectors": {
1966
+ get: {
1967
+ summary: "Collectors health",
1968
+ description: "Retrieve the block numbers processed by collectors and their sync status.",
1969
+ tags: ["Health"],
1970
+ responses: {
1971
+ 200: {
1972
+ description: "Success",
1973
+ content: {
1974
+ "application/json": {
1975
+ schema: CollectorsHealthSuccessResponseSchema
1976
+ }
1977
+ }
1978
+ }
1979
+ }
1980
+ }
1981
+ },
1982
+ "/v1/health/chains": {
1983
+ get: {
1984
+ summary: "Chains health",
1985
+ description: "Retrieve the latest block processed for each chain.",
1986
+ tags: ["Health"],
1987
+ responses: {
1988
+ 200: {
1989
+ description: "Success",
1990
+ content: {
1991
+ "application/json": {
1992
+ schema: ChainsHealthSuccessResponseSchema
1993
+ }
1994
+ }
1995
+ }
1996
+ }
1997
+ }
1998
+ }
1999
+ };
2000
+ var OpenApi = createDocument({
2001
+ openapi: "3.1.0",
2002
+ info: {
2003
+ title: "Router API",
2004
+ version: "1.0.0",
2005
+ description: "API for the Morpho Router"
2006
+ },
2007
+ tags: [
2008
+ {
2009
+ name: "Offers"
2010
+ },
2011
+ {
2012
+ name: "Obligations"
2013
+ },
2014
+ {
2015
+ name: "Health"
2016
+ }
2017
+ ],
2018
+ servers: [
2019
+ {
2020
+ url: "https://router.morpho.dev",
2021
+ description: "Production server"
2022
+ },
2023
+ {
2024
+ url: "http://localhost:7891",
2025
+ description: "Local development server"
2026
+ }
2027
+ ],
2028
+ paths
2029
+ });
2030
+
2031
+ // src/api/Controllers/getDocs.ts
2032
+ function getSwaggerJson() {
2033
+ return OpenApi;
2034
+ }
2035
+ function getDocsHtml() {
2036
+ const html = `<!DOCTYPE html>
2037
+ <html>
2038
+ <head>
2039
+ <meta charset="UTF-8">
2040
+ <title>Router API Docs (Scalar)</title>
2041
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
2042
+ <style>
2043
+ html, body { margin: 0; height: 100%; }
2044
+ api-reference { height: 100%; width: 100%; }
2045
+ </style>
2046
+ </head>
2047
+ <body>
2048
+ <div id="api-container" style="height:100%;width:100%;"></div>
2049
+ <script>
2050
+ window.addEventListener('load', function () {
2051
+ const spec = ${JSON.stringify(OpenApi)};
2052
+ Scalar.createApiReference('#api-container', { spec: { content: spec } });
2053
+ });
2054
+ </script>
2055
+ </body>
2056
+ </html>`;
2057
+ return html;
2058
+ }
2059
+
2060
+ // src/services/Health.ts
2061
+ var Health_exports = {};
2062
+ __export(Health_exports, {
2063
+ create: () => create9
2064
+ });
2065
+
2066
+ // src/collectors/index.ts
2067
+ var collectors_exports = {};
2068
+ __export(collectors_exports, {
2069
+ batch: () => batch2,
2070
+ create: () => create2,
2071
+ createBuilder: () => createBuilder,
2072
+ fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
2073
+ fetchCollateralAndDebt: () => fetchCollateralAndDebt,
2074
+ fetchOraclePrices: () => fetchOraclePrices,
2075
+ fetchUserVaultMarketLiquidity: () => fetchUserVaultMarketLiquidity,
2076
+ morpho: () => morpho,
2077
+ names: () => names,
2078
+ run: () => run,
2079
+ single: () => single,
2080
+ start: () => start
2081
+ });
2082
+
2083
+ // src/services/Logger.ts
2084
+ var Logger_exports = {};
2085
+ __export(Logger_exports, {
2086
+ LogLevelValues: () => LogLevelValues,
2087
+ defaultLogger: () => defaultLogger,
2088
+ getLogger: () => getLogger,
2089
+ runWithLogger: () => runWithLogger,
2090
+ silentLogger: () => silentLogger
2091
+ });
2092
+ var LogLevelValues = [
2093
+ "trace",
2094
+ "debug",
2095
+ "info",
2096
+ "warn",
2097
+ "error",
2098
+ "fatal",
2099
+ "silent"
2100
+ ];
2101
+ function defaultLogger(minLevel, pretty) {
2102
+ const threshold = minLevel ?? process.env.ROUTER_LOG_LEVEL ?? "info";
2103
+ const prettyEnabled = typeof pretty === "boolean" ? pretty : String(process.env.ROUTER_LOG_PRETTY ?? "false").toLowerCase() === "true";
2104
+ const levelIndexByName = LogLevelValues.reduce(
2105
+ (acc, lvl, idx) => {
2106
+ acc[lvl] = idx;
2107
+ return acc;
2108
+ },
2109
+ {}
2110
+ );
2111
+ const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
2112
+ const wrap = (consoleMethod, methodLevel) => isEnabled(methodLevel) ? (entry) => {
2113
+ if (!prettyEnabled) {
2114
+ console[consoleMethod](stringify({ level: methodLevel, ...entry }));
2115
+ return;
2116
+ }
2117
+ const { msg, ...rest } = entry;
2118
+ const stack = typeof rest.stack === "string" ? rest.stack : void 0;
2119
+ if (stack) delete rest.stack;
2120
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString();
2121
+ const level = methodLevel.toUpperCase();
2122
+ const extras = Object.entries(rest).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ");
2123
+ const line = extras.length > 0 ? `${timestamp2} [${level}] ${msg} ${extras}` : `${timestamp2} [${level}] ${msg}`;
2124
+ console[consoleMethod](line);
2125
+ if (stack) {
2126
+ console[consoleMethod](stack);
2127
+ }
2128
+ } : () => {
2129
+ };
2130
+ return {
2131
+ trace: wrap("trace", "trace"),
2132
+ debug: wrap("debug", "debug"),
2133
+ info: wrap("info", "info"),
2134
+ warn: wrap("warn", "warn"),
2135
+ error: wrap("error", "error"),
2136
+ fatal: wrap("error", "fatal")
2137
+ };
1843
2138
  }
1844
- function hash(offer) {
1845
- return hashTypedData({
1846
- domain: domain(offer.chainId),
1847
- message: {
1848
- offering: offer.offering.toLowerCase(),
1849
- assets: offer.assets,
1850
- rate: offer.rate,
1851
- maturity: BigInt(offer.maturity),
1852
- expiry: BigInt(offer.expiry),
1853
- nonce: offer.nonce,
1854
- buy: offer.buy,
1855
- loanToken: offer.loanToken.toLowerCase(),
1856
- collaterals: offer.collaterals,
1857
- callback: {
1858
- address: offer.callback.address.toLowerCase(),
1859
- data: offer.callback.data,
1860
- gasLimit: offer.callback.gasLimit
1861
- }
1862
- },
1863
- primaryType: "Offer",
1864
- types
1865
- });
2139
+ function silentLogger() {
2140
+ const noop = () => {
2141
+ };
2142
+ return { trace: noop, debug: noop, info: noop, warn: noop, error: noop, fatal: noop };
1866
2143
  }
1867
- function obligationId(offer) {
1868
- return id(
1869
- from4({
1870
- chainId: offer.chainId,
1871
- loanToken: offer.loanToken,
1872
- collaterals: offer.collaterals,
1873
- maturity: offer.maturity
1874
- })
1875
- );
2144
+ var loggerContext = new AsyncLocalStorage();
2145
+ function runWithLogger(logger, fn) {
2146
+ return loggerContext.run(logger, fn);
1876
2147
  }
1877
- var OfferAbi = [
1878
- { name: "offering", type: "address" },
1879
- { name: "assets", type: "uint256" },
1880
- { name: "rate", type: "uint256" },
1881
- { name: "maturity", type: "uint256" },
1882
- { name: "expiry", type: "uint256" },
1883
- { name: "nonce", type: "uint256" },
1884
- { name: "buy", type: "bool" },
1885
- { name: "chainId", type: "uint256" },
1886
- { name: "loanToken", type: "address" },
1887
- { name: "start", type: "uint256" },
1888
- {
1889
- name: "collaterals",
1890
- type: "tuple[]",
1891
- components: [
1892
- { name: "asset", type: "address" },
1893
- { name: "oracle", type: "address" },
1894
- { name: "lltv", type: "uint256" }
1895
- ]
1896
- },
1897
- {
1898
- name: "callback",
1899
- type: "tuple",
1900
- components: [
1901
- { name: "address", type: "address" },
1902
- { name: "data", type: "bytes" },
1903
- { name: "gasLimit", type: "uint256" }
1904
- ]
1905
- },
1906
- { name: "signature", type: "bytes" }
1907
- ];
1908
- function encode2(offer) {
1909
- return encodeAbiParameters(OfferAbi, [
1910
- offer.offering,
1911
- offer.assets,
1912
- offer.rate,
1913
- BigInt(offer.maturity),
1914
- BigInt(offer.expiry),
1915
- offer.nonce,
1916
- offer.buy,
1917
- offer.chainId,
1918
- offer.loanToken,
1919
- BigInt(offer.start),
1920
- offer.collaterals,
1921
- offer.callback,
1922
- offer.signature ?? "0x"
1923
- ]);
2148
+ function getLogger() {
2149
+ return loggerContext.getStore() ?? defaultLogger();
1924
2150
  }
1925
- function decode2(data, blockNumber) {
1926
- let decoded;
2151
+ function formatValue(value) {
2152
+ if (value === null || value === void 0 || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
2153
+ return String(value);
2154
+ }
2155
+ if (typeof value === "string") {
2156
+ if (value.includes(" ")) return JSON.stringify(value);
2157
+ return value;
2158
+ }
1927
2159
  try {
1928
- decoded = decodeAbiParameters(OfferAbi, data);
1929
- } catch (error2) {
1930
- throw new InvalidOfferError(error2);
2160
+ return stringify(value);
2161
+ } catch {
2162
+ try {
2163
+ return JSON.stringify(value);
2164
+ } catch {
2165
+ return String(value);
2166
+ }
1931
2167
  }
1932
- const offer = from5({
1933
- offering: decoded[0],
1934
- assets: decoded[1],
1935
- rate: decoded[2],
1936
- maturity: from3(Number(decoded[3])),
1937
- expiry: Number(decoded[4]),
1938
- nonce: decoded[5],
1939
- buy: decoded[6],
1940
- chainId: decoded[7],
1941
- loanToken: decoded[8],
1942
- start: Number(decoded[9]),
1943
- collaterals: decoded[10].map((c) => {
1944
- return from2({
1945
- asset: c.asset,
1946
- oracle: c.oracle,
1947
- lltv: c.lltv
2168
+ }
2169
+
2170
+ // src/services/RouterAdmin.ts
2171
+ function create(parameters) {
2172
+ const collector = "admin";
2173
+ const {
2174
+ client,
2175
+ chain,
2176
+ withTransaction,
2177
+ options: { maxBatchSize = 25, maxBlockNumber } = {}
2178
+ } = parameters;
2179
+ const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
2180
+ let finalizedBlock = null;
2181
+ let unfinalizedBlocks = [];
2182
+ const logger = getLogger();
2183
+ let isMaxBlockNumberReached = false;
2184
+ let tick = 0;
2185
+ return {
2186
+ syncBlock: async () => {
2187
+ if (isMaxBlockNumberReached) return;
2188
+ const head = await client.getBlock({
2189
+ blockTag: "latest",
2190
+ includeTransactions: false
2191
+ });
2192
+ await withTransaction(async ({ chainStore, collectorStore }) => {
2193
+ const { epoch, blockNumber: latestSavedBlockNumber } = await chainStore.getBlockNumber(
2194
+ chain.id
2195
+ );
2196
+ if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
2197
+ logger.info({
2198
+ msg: `Head is greater than max block number`,
2199
+ collector,
2200
+ chainId: chain.id,
2201
+ block_number: head.number,
2202
+ max_block_number: maxBlockNumber
2203
+ });
2204
+ isMaxBlockNumberReached = true;
2205
+ await chainStore.saveBlockNumber({
2206
+ chainId: chain.id,
2207
+ blockNumber: maxBlockNumber,
2208
+ epoch: epoch + 1n
2209
+ });
2210
+ await Promise.all(
2211
+ names.map(
2212
+ async (collectorName) => collectorStore.saveBlockNumber({
2213
+ collectorName,
2214
+ chainId: chain.id,
2215
+ blockNumber: maxBlockNumber,
2216
+ epoch: epoch + 1n
2217
+ })
2218
+ )
2219
+ );
2220
+ return;
2221
+ }
2222
+ finalizedBlock = await fetchFinalizedBlock({
2223
+ tick,
2224
+ client,
2225
+ chain,
2226
+ logger,
2227
+ collector,
2228
+ unfinalizedBlocks,
2229
+ previousFinalizedBlock: finalizedBlock
2230
+ });
2231
+ tick++;
2232
+ let {
2233
+ block: returnedBlock,
2234
+ didReorgHappened,
2235
+ unfinalizedBlocks: newUnfinalizedBlocks
2236
+ } = await reconcile({
2237
+ client,
2238
+ block: head,
2239
+ unfinalizedBlocks,
2240
+ finalizedBlock,
2241
+ logger,
2242
+ collector,
2243
+ chain,
2244
+ maxBatchSize
2245
+ });
2246
+ unfinalizedBlocks = newUnfinalizedBlocks;
2247
+ const blockNumber = Number(returnedBlock.number);
2248
+ didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
2249
+ await chainStore.saveBlockNumber({
2250
+ chainId: chain.id,
2251
+ blockNumber,
2252
+ epoch: didReorgHappened ? epoch + 1n : epoch
2253
+ });
2254
+ if (didReorgHappened) {
2255
+ await Promise.all(
2256
+ names.map(
2257
+ async (collectorName) => collectorStore.saveBlockNumber({
2258
+ collectorName,
2259
+ chainId: chain.id,
2260
+ blockNumber,
2261
+ epoch: epoch + 1n
2262
+ })
2263
+ )
2264
+ );
2265
+ }
1948
2266
  });
1949
- }),
1950
- callback: {
1951
- address: decoded[11].address,
1952
- data: decoded[11].data,
1953
- gasLimit: decoded[11].gasLimit
1954
- },
1955
- consumed: 0n,
1956
- blockNumber: Number(blockNumber),
1957
- ...decoded[12] === "0x" ? {} : { signature: decoded[12] }
1958
- });
1959
- return offer;
2267
+ }
2268
+ };
1960
2269
  }
1961
- var consumedEvent = {
1962
- type: "event",
1963
- name: "Consumed",
1964
- inputs: [
1965
- { name: "user", type: "address", indexed: true, internalType: "address" },
1966
- { name: "nonce", type: "uint256", indexed: true, internalType: "uint256" },
1967
- { name: "amount", type: "uint256", indexed: false, internalType: "uint256" }
1968
- ],
1969
- anonymous: false
2270
+ var commonAncestor = (block, unfinalizedBlocks) => {
2271
+ const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
2272
+ if (parent) return parent;
2273
+ return null;
1970
2274
  };
1971
- function fromConsumedLog(parameters) {
1972
- const { blockNumber, logIndex, chainId, transactionHash, user, nonce, amount } = parameters;
2275
+ var fetchFinalizedBlock = async (parameters) => {
2276
+ let { tick, client, chain, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
2277
+ let finalizedBlock = previousFinalizedBlock;
2278
+ if (tick % 20 === 0 || previousFinalizedBlock === null) {
2279
+ finalizedBlock = await client.getBlock({
2280
+ blockTag: "finalized",
2281
+ includeTransactions: false
2282
+ });
2283
+ if (finalizedBlock === null || finalizedBlock.number === null) {
2284
+ const msg = "Failed to get finalized block";
2285
+ logger.fatal({ collector, chainId: chain.id, msg });
2286
+ throw new Error(msg);
2287
+ }
2288
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
2289
+ }
2290
+ if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
2291
+ const msg = "Failed to get finalized block";
2292
+ logger.fatal({ collector, chainId: chain.id, msg });
2293
+ throw new Error(msg);
2294
+ }
1973
2295
  return {
1974
- id: `${blockNumber.toString()}-${logIndex.toString()}-${chainId}-${transactionHash}`,
1975
- chainId: BigInt(chainId),
1976
- offering: user,
1977
- nonce,
1978
- amount,
1979
- blockNumber: Number(blockNumber)
2296
+ hash: finalizedBlock.hash,
2297
+ number: finalizedBlock.number,
2298
+ parentHash: finalizedBlock.parentHash
1980
2299
  };
1981
- }
1982
- var InvalidOfferError = class extends BaseError {
1983
- name = "Offer.InvalidOfferError";
1984
- constructor(error2) {
1985
- super("Invalid offer.", { cause: error2 });
1986
- }
1987
2300
  };
1988
- var AccountNotSetError = class extends BaseError {
1989
- name = "Offer.AccountNotSetError";
1990
- constructor() {
1991
- super("Account not set.");
2301
+ var reconcile = async (parameters) => {
2302
+ let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, chain, maxBatchSize } = parameters;
2303
+ if (block.hash === null || block.number === null || block.parentHash === null)
2304
+ throw new Error("Failed to get block");
2305
+ const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
2306
+ if (latestBlock === void 0) {
2307
+ const newBlock2 = {
2308
+ hash: block.hash,
2309
+ number: block.number,
2310
+ parentHash: block.parentHash
2311
+ };
2312
+ unfinalizedBlocks.push(newBlock2);
2313
+ return { block: newBlock2, didReorgHappened: false, unfinalizedBlocks };
2314
+ }
2315
+ if (latestBlock.hash === block.hash)
2316
+ return { block: latestBlock, didReorgHappened: false, unfinalizedBlocks };
2317
+ if (latestBlock.number >= block.number) {
2318
+ const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
2319
+ logger.info({
2320
+ msg: `Reorg detected`,
2321
+ collector,
2322
+ chain_id: chain.id,
2323
+ ancestor: ancestor.number,
2324
+ block_range: [latestBlock.number, block.number]
2325
+ });
2326
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
2327
+ return { block: ancestor, didReorgHappened: true, unfinalizedBlocks };
2328
+ }
2329
+ if (latestBlock.number + 1n < block.number) {
2330
+ logger.debug({
2331
+ collector,
2332
+ chain_id: chain.id,
2333
+ block_range: [latestBlock.number, block.number],
2334
+ msg: `Missing blocks`
2335
+ });
2336
+ const missingBlockNumbers = (() => {
2337
+ const missingBlockNumbers2 = [];
2338
+ let start2 = latestBlock.number + 1n;
2339
+ const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
2340
+ while (start2 < threshold) {
2341
+ missingBlockNumbers2.push(start2);
2342
+ start2 = start2 + 1n;
2343
+ }
2344
+ return missingBlockNumbers2;
2345
+ })();
2346
+ const missingBlocks = await Promise.all(
2347
+ missingBlockNumbers.map(
2348
+ (blockNumber) => retry(async () => await client.getBlock({ blockNumber, includeTransactions: false }))
2349
+ )
2350
+ );
2351
+ for (const missingBlock of missingBlocks) {
2352
+ const { block: returnedBlock, didReorgHappened } = await reconcile({
2353
+ client,
2354
+ block: missingBlock,
2355
+ unfinalizedBlocks,
2356
+ finalizedBlock,
2357
+ logger,
2358
+ collector,
2359
+ chain,
2360
+ maxBatchSize
2361
+ });
2362
+ if (returnedBlock.number !== missingBlock.number)
2363
+ return { block: returnedBlock, didReorgHappened, unfinalizedBlocks };
2364
+ }
2365
+ return reconcile({
2366
+ client,
2367
+ block,
2368
+ unfinalizedBlocks,
2369
+ finalizedBlock,
2370
+ logger,
2371
+ collector,
2372
+ chain,
2373
+ maxBatchSize
2374
+ });
2375
+ }
2376
+ if (block.parentHash !== latestBlock.hash) {
2377
+ const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
2378
+ logger.info({
2379
+ msg: `Reorg detected`,
2380
+ collector,
2381
+ chain_id: chain.id,
2382
+ ancestor: ancestor.number,
2383
+ block_range: [latestBlock.number, block.number]
2384
+ });
2385
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
2386
+ return { block: ancestor, didReorgHappened: true, unfinalizedBlocks };
1992
2387
  }
2388
+ const newBlock = {
2389
+ hash: block.hash,
2390
+ number: block.number,
2391
+ parentHash: block.parentHash
2392
+ };
2393
+ unfinalizedBlocks.push(newBlock);
2394
+ return { block: newBlock, didReorgHappened: false, unfinalizedBlocks };
1993
2395
  };
1994
2396
 
1995
- // src/core/types.ts
1996
- var BrandTypeId = Symbol.for("mempool/Brand");
2397
+ // src/collectors/Collector.ts
2398
+ var names = [
2399
+ "mempool_offers",
2400
+ "consumed_events",
2401
+ "buy_with_empty_callback_liquidity",
2402
+ "buy_vault_v1_callback_liquidity",
2403
+ "sell_erc20_callback_liquidity"
2404
+ ];
2405
+ function create2({
2406
+ name,
2407
+ collect,
2408
+ client,
2409
+ chain,
2410
+ withTransaction,
2411
+ collectorStore,
2412
+ options
2413
+ }) {
2414
+ const admin = create({
2415
+ client,
2416
+ chain,
2417
+ withTransaction,
2418
+ options
2419
+ });
2420
+ return {
2421
+ name,
2422
+ chain,
2423
+ collect: lazy((emit) => {
2424
+ const collector = name;
2425
+ const logger = getLogger();
2426
+ logger.info({
2427
+ msg: `Collector started`,
2428
+ collector,
2429
+ chain_id: chain.id,
2430
+ interval: options.interval
2431
+ });
2432
+ return poll(
2433
+ async () => {
2434
+ try {
2435
+ let { blockNumber: lastBlockNumber, epoch } = await collectorStore.getBlockNumber({
2436
+ collectorName: name,
2437
+ chainId: chain.id
2438
+ });
2439
+ await admin.syncBlock();
2440
+ lastBlockNumber = await collect({
2441
+ chain,
2442
+ client,
2443
+ collector: name,
2444
+ epoch,
2445
+ lastBlockNumber,
2446
+ withTransaction
2447
+ });
2448
+ emit(lastBlockNumber);
2449
+ } catch (err) {
2450
+ const isError = err instanceof Error;
2451
+ logger.error({
2452
+ msg: "Collector error",
2453
+ collector,
2454
+ chain_id: chain.id,
2455
+ error: isError ? err.message : String(err),
2456
+ stack: isError ? err.stack : void 0
2457
+ });
2458
+ }
2459
+ },
2460
+ { interval: options.interval }
2461
+ );
2462
+ })
2463
+ };
2464
+ }
2465
+ function start(collector) {
2466
+ let stopped = false;
2467
+ const it = collector.collect();
2468
+ (async () => {
2469
+ while (!stopped) await it.next();
2470
+ await it.return();
2471
+ })();
2472
+ return () => {
2473
+ stopped = true;
2474
+ };
2475
+ }
1997
2476
 
1998
- // src/mempool/MempoolEVMClient.ts
2477
+ // src/mempool/index.ts
2478
+ var mempool_exports = {};
2479
+ __export(mempool_exports, {
2480
+ ChainIdMismatchError: () => ChainIdMismatchError,
2481
+ ViemClientError: () => ViemClientError,
2482
+ WalletAccountNotSetError: () => WalletAccountNotSetError,
2483
+ add: () => add,
2484
+ connect: () => connect,
2485
+ from: () => from7,
2486
+ get: () => get,
2487
+ watch: () => watch
2488
+ });
1999
2489
  var DEFAULT_BATCH_SIZE2 = 100;
2000
2490
  var DEFAULT_BLOCK_WINDOW2 = 100;
2001
- function from6(parameters) {
2491
+ function from7(parameters) {
2002
2492
  const config = {
2003
2493
  client: parameters.client,
2004
2494
  mempoolAddress: parameters.mempoolAddress,
@@ -2140,7 +2630,7 @@ var ChainIdMismatchError = class extends BaseError {
2140
2630
 
2141
2631
  // src/mempool/MempoolClient.ts
2142
2632
  function connect(parameters) {
2143
- return from6(parameters);
2633
+ return from7(parameters);
2144
2634
  }
2145
2635
 
2146
2636
  // src/services/Services.ts
@@ -2148,7 +2638,7 @@ var Services_exports = {};
2148
2638
  __export(Services_exports, {
2149
2639
  createStores: () => createStores,
2150
2640
  createWithTransaction: () => createWithTransaction,
2151
- from: () => from7
2641
+ from: () => from8
2152
2642
  });
2153
2643
 
2154
2644
  // src/indexer/RouterIndexer.ts
@@ -3055,9 +3545,67 @@ function create7(config) {
3055
3545
  })
3056
3546
  );
3057
3547
  }
3058
- const returnedItems = Array.from(items.values());
3059
- const nextCursor = returnedItems.length === limit && returnedItems.length > 0 ? result[result.length - 1].obligationId : null;
3060
- return { obligations: returnedItems, nextCursor };
3548
+ const returnedItems = Array.from(items.values());
3549
+ const nextCursor = returnedItems.length === limit && returnedItems.length > 0 ? result[result.length - 1].obligationId : null;
3550
+ return { obligations: returnedItems, nextCursor };
3551
+ },
3552
+ getQuotes: async (parameters) => {
3553
+ const { obligationIds } = parameters;
3554
+ if (obligationIds.length === 0) return [];
3555
+ const now2 = time_exports.now();
3556
+ const sumConsumed = db.select({
3557
+ consumed: sql`COALESCE(SUM(${consumed.consumed}), 0)`.as("consumed")
3558
+ }).from(consumed).where(
3559
+ and(
3560
+ eq(consumed.offering, offers.offering),
3561
+ eq(consumed.nonce, offers.nonce),
3562
+ eq(consumed.chainId, offers.chainId)
3563
+ )
3564
+ ).as("sum_consumed");
3565
+ const remaining = sql`(${offers.assets} - COALESCE(${sumConsumed.consumed}, 0))`;
3566
+ const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
3567
+ obligationId: offers.obligationId,
3568
+ rate: offers.rate
3569
+ }).from(offers).leftJoinLateral(sumConsumed, sql`true`).where(
3570
+ and(
3571
+ inArray(offers.obligationId, obligationIds),
3572
+ gte(offers.expiry, now2),
3573
+ gte(offers.maturity, now2),
3574
+ eq(offers.buy, side === "buy"),
3575
+ sql`${remaining} > 0`
3576
+ )
3577
+ ).orderBy(
3578
+ offers.obligationId,
3579
+ // lower rates first for buy, higher rates first for sell
3580
+ side === "buy" ? asc(offers.rate) : desc(offers.rate)
3581
+ );
3582
+ const [bestBuys, bestSells] = await Promise.all([
3583
+ query({ side: "buy" }),
3584
+ query({ side: "sell" })
3585
+ ]);
3586
+ const quotes = /* @__PURE__ */ new Map();
3587
+ for (const row of bestSells) {
3588
+ quotes.set(row.obligationId, {
3589
+ ask: { rate: row.rate },
3590
+ bid: { rate: 0n }
3591
+ });
3592
+ }
3593
+ for (const row of bestBuys) {
3594
+ const quote = quotes.get(row.obligationId);
3595
+ if (!quote) {
3596
+ quotes.set(row.obligationId, {
3597
+ ask: { rate: 0n },
3598
+ bid: { rate: row.rate }
3599
+ });
3600
+ continue;
3601
+ }
3602
+ quote.bid = { rate: row.rate };
3603
+ }
3604
+ return Array.from(quotes.entries()).map(([id2, quote]) => {
3605
+ return Quote_exports.from({ obligationId: id2, ask: quote.ask, bid: quote.bid });
3606
+ }).sort((a, b) => {
3607
+ return a.obligationId.localeCompare(b.obligationId);
3608
+ });
3061
3609
  }
3062
3610
  };
3063
3611
  }
@@ -3785,7 +4333,7 @@ async function clean(pg) {
3785
4333
  }
3786
4334
 
3787
4335
  // src/services/Services.ts
3788
- function from7(config) {
4336
+ function from8(config) {
3789
4337
  const {
3790
4338
  chain,
3791
4339
  rpcUrl,
@@ -5437,7 +5985,7 @@ function handleZodError(error2) {
5437
5985
  return new ValidationError("Validation failed", formattedErrors);
5438
5986
  }
5439
5987
 
5440
- // src/api/Api/Controllers/getHealth.ts
5988
+ // src/api/Controllers/getHealth.ts
5441
5989
  async function getHealth(healthService) {
5442
5990
  const logger = Logger_exports.getLogger();
5443
5991
  try {
@@ -5502,269 +6050,31 @@ async function getHealthCollectors(healthService) {
5502
6050
  return error(err);
5503
6051
  }
5504
6052
  }
5505
- var CollectorHealth = z.object({
5506
- name: z.string(),
5507
- chain_id: z.number(),
5508
- block_number: z.number().nullable(),
5509
- updated_at: z.string().nullable(),
5510
- lag: z.number().nullable(),
5511
- status: z.enum(["live", "lagging", "unknown"])
5512
- });
5513
- var CollectorsHealthResponse = z.object({
5514
- collectors: z.array(CollectorHealth)
5515
- });
5516
- var ChainHealth = z.object({
5517
- chain_id: z.number(),
5518
- block_number: z.number(),
5519
- updated_at: z.string()
5520
- });
5521
- var ChainsHealthResponse = z.object({
5522
- chains: z.array(ChainHealth)
5523
- });
5524
- var RouterStatusResponse = z.object({
5525
- status: z.enum(["live", "syncing"])
5526
- });
5527
6053
 
5528
- // src/api/Api/Schema/ObligationResponse.ts
6054
+ // src/api/Schema/ObligationResponse.ts
5529
6055
  var ObligationResponse_exports = {};
5530
6056
  __export(ObligationResponse_exports, {
5531
- from: () => from8
6057
+ from: () => from9
5532
6058
  });
5533
- function from8(obligation) {
5534
- return toSnakeCase({ id: Obligation_exports.id(obligation), ...obligation });
6059
+ function from9(obligation, quote) {
6060
+ return toSnakeCase({
6061
+ id: quote.obligationId,
6062
+ ...obligation,
6063
+ ask: quote.ask,
6064
+ bid: quote.bid
6065
+ });
5535
6066
  }
5536
6067
 
5537
- // src/api/Api/Schema/OfferResponse.ts
6068
+ // src/api/Schema/OfferResponse.ts
5538
6069
  var OfferResponse_exports = {};
5539
6070
  __export(OfferResponse_exports, {
5540
- from: () => from9
6071
+ from: () => from10
5541
6072
  });
5542
- function from9(offer) {
6073
+ function from10(offer) {
5543
6074
  return toSnakeCase(offer);
5544
6075
  }
5545
- var MAX_LIMIT = 100;
5546
- var DEFAULT_LIMIT = 20;
5547
- var PaginationQueryParams = z6.object({
5548
- cursor: z6.string().optional().refine(
5549
- (val) => {
5550
- if (!val) return true;
5551
- try {
5552
- const decoded = Cursor_exports.decode(val);
5553
- return decoded !== null;
5554
- } catch (_error) {
5555
- return false;
5556
- }
5557
- },
5558
- {
5559
- message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
5560
- }
5561
- ).meta({
5562
- description: "Pagination cursor in base64url-encoded format",
5563
- example: "eyJzb3J0IjoicHJpY2UiLCJkaXIiOiJkZXNjIiwicHJpY2UiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwiaGFzaCI6IjB4ZGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIifQ"
5564
- }),
5565
- limit: z6.string().regex(/^[1-9]\d*$/, {
5566
- message: "Limit must be a positive integer"
5567
- }).transform((val) => Number.parseInt(val, 10)).pipe(
5568
- z6.number().max(MAX_LIMIT, {
5569
- message: `Limit cannot exceed ${MAX_LIMIT}`
5570
- })
5571
- ).optional().default(DEFAULT_LIMIT).meta({
5572
- description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT}`,
5573
- example: 10
5574
- })
5575
- });
5576
- var GetOffersQueryParams = z6.object({
5577
- ...PaginationQueryParams.shape,
5578
- side: z6.enum(["buy", "sell"]).meta({
5579
- description: "Side of the offer.",
5580
- example: "buy"
5581
- }),
5582
- obligation_id: z6.string({ error: "Obligation id is required and must be a valid 32-byte hex string" }).regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).meta({
5583
- description: "Offers obligation id",
5584
- example: "0x1234567890123456789012345678901234567890123456789012345678901234"
5585
- })
5586
- });
5587
- var GetObligationsQueryParams = z6.object({
5588
- ...PaginationQueryParams.shape,
5589
- cursor: z6.string().optional().meta({
5590
- description: "Obligation id cursor",
5591
- example: "0x1234567890123456789012345678901234567890123456789012345678901234"
5592
- })
5593
- });
5594
- var schemas = {
5595
- get_offers: GetOffersQueryParams,
5596
- get_obligations: GetObligationsQueryParams
5597
- };
5598
- function parse(action, query) {
5599
- return schemas[action].parse(query);
5600
- }
5601
- function safeParse(action, query, error2) {
5602
- return schemas[action].safeParse(query, {
5603
- error: error2
5604
- });
5605
- }
5606
-
5607
- // src/api/Api/Schema/openapi.ts
5608
- var successResponseSchema = z.object({
5609
- status: z.literal("success"),
5610
- cursor: z.string().nullable(),
5611
- data: z.array(z.any()),
5612
- meta: z.object({
5613
- timestamp: z.string()
5614
- })
5615
- });
5616
- var errorResponseSchema = z.object({
5617
- status: z.literal("error"),
5618
- error: z.object({
5619
- code: z.string(),
5620
- message: z.string(),
5621
- details: z.any().optional()
5622
- }),
5623
- meta: z.object({
5624
- timestamp: z.string()
5625
- })
5626
- });
5627
- var paths = {
5628
- "/v1/offers": {
5629
- get: {
5630
- summary: "Offers",
5631
- description: "Find offers that match specific criteria",
5632
- tags: ["Offers"],
5633
- requestParams: {
5634
- query: GetOffersQueryParams
5635
- },
5636
- responses: {
5637
- 200: {
5638
- description: "Success",
5639
- content: {
5640
- "application/json": {
5641
- schema: successResponseSchema
5642
- }
5643
- }
5644
- },
5645
- 400: {
5646
- description: "Bad Request",
5647
- content: {
5648
- "application/json": {
5649
- schema: errorResponseSchema
5650
- }
5651
- }
5652
- }
5653
- }
5654
- }
5655
- },
5656
- "/v1/obligations": {
5657
- get: {
5658
- summary: "Obligations",
5659
- description: "List obligations with pagination support",
5660
- tags: ["Obligations"],
5661
- requestParams: {
5662
- query: GetObligationsQueryParams
5663
- },
5664
- responses: {
5665
- 200: {
5666
- description: "Success",
5667
- content: {
5668
- "application/json": {
5669
- schema: successResponseSchema
5670
- }
5671
- }
5672
- },
5673
- 400: {
5674
- description: "Bad Request",
5675
- content: {
5676
- "application/json": {
5677
- schema: errorResponseSchema
5678
- }
5679
- }
5680
- }
5681
- }
5682
- }
5683
- },
5684
- "/v1/health": {
5685
- get: {
5686
- summary: "Router status",
5687
- description: "Retrieve the aggregated status of the router.",
5688
- tags: ["Health"],
5689
- responses: {
5690
- 200: {
5691
- description: "Success",
5692
- content: {
5693
- "application/json": {
5694
- schema: RouterStatusResponse
5695
- }
5696
- }
5697
- }
5698
- }
5699
- }
5700
- },
5701
- "/v1/health/collectors": {
5702
- get: {
5703
- summary: "Collectors health",
5704
- description: "Retrieve the block numbers processed by collectors and their sync status.",
5705
- tags: ["Health"],
5706
- responses: {
5707
- 200: {
5708
- description: "Success",
5709
- content: {
5710
- "application/json": {
5711
- schema: CollectorsHealthResponse
5712
- }
5713
- }
5714
- }
5715
- }
5716
- }
5717
- },
5718
- "/v1/health/chains": {
5719
- get: {
5720
- summary: "Chains health",
5721
- description: "Retrieve the latest block processed for each chain.",
5722
- tags: ["Health"],
5723
- responses: {
5724
- 200: {
5725
- description: "Success",
5726
- content: {
5727
- "application/json": {
5728
- schema: ChainsHealthResponse
5729
- }
5730
- }
5731
- }
5732
- }
5733
- }
5734
- }
5735
- };
5736
- var OpenApi = createDocument({
5737
- openapi: "3.1.0",
5738
- info: {
5739
- title: "Router API",
5740
- version: "1.0.0",
5741
- description: "API for the Morpho Router"
5742
- },
5743
- tags: [
5744
- {
5745
- name: "Offers"
5746
- },
5747
- {
5748
- name: "Obligations"
5749
- },
5750
- {
5751
- name: "Health"
5752
- }
5753
- ],
5754
- servers: [
5755
- {
5756
- url: "https://router.morpho.dev",
5757
- description: "Production server"
5758
- },
5759
- {
5760
- url: "http://localhost:8081",
5761
- description: "Local development server"
5762
- }
5763
- ],
5764
- paths
5765
- });
5766
6076
 
5767
- // src/api/Api/Controllers/getObligations.ts
6077
+ // src/api/Controllers/getObligations.ts
5768
6078
  async function getObligations(queryParameters, store) {
5769
6079
  const logger = Logger_exports.getLogger();
5770
6080
  const result = safeParse("get_obligations", queryParameters, (issue) => issue.message);
@@ -5775,8 +6085,20 @@ async function getObligations(queryParameters, store) {
5775
6085
  cursor: query.cursor,
5776
6086
  limit: query.limit
5777
6087
  });
6088
+ const quotes = await store.getQuotes({
6089
+ obligationIds: obligations2.map((o) => Obligation_exports.id(o))
6090
+ });
5778
6091
  return success({
5779
- data: obligations2.map((o) => ObligationResponse_exports.from(o)),
6092
+ data: obligations2.map(
6093
+ (o) => ObligationResponse_exports.from(
6094
+ o,
6095
+ quotes.find((q) => q.obligationId === Obligation_exports.id(o)) ?? {
6096
+ obligationId: Obligation_exports.id(o),
6097
+ ask: { rate: 0n },
6098
+ bid: { rate: 0n }
6099
+ }
6100
+ )
6101
+ ),
5780
6102
  cursor: nextCursor ?? null
5781
6103
  });
5782
6104
  } catch (err) {
@@ -5790,7 +6112,7 @@ async function getObligations(queryParameters, store) {
5790
6112
  }
5791
6113
  }
5792
6114
 
5793
- // src/api/Api/Controllers/getOffers.ts
6115
+ // src/api/Controllers/getOffers.ts
5794
6116
  async function getOffers(queryParameters, store) {
5795
6117
  const logger = Logger_exports.getLogger();
5796
6118
  const result = safeParse("get_offers", queryParameters, (issue) => issue.message);
@@ -5818,7 +6140,7 @@ async function getOffers(queryParameters, store) {
5818
6140
  }
5819
6141
  }
5820
6142
 
5821
- // src/api/Api/Api.ts
6143
+ // src/api/Api.ts
5822
6144
  function create8(config) {
5823
6145
  return {
5824
6146
  serve: ({ port }) => {
@@ -5850,13 +6172,20 @@ function serve(parameters) {
5850
6172
  const { statusCode, body } = await getHealthChains(healthService);
5851
6173
  return c.json(body, statusCode);
5852
6174
  });
6175
+ app.get(
6176
+ "/docs/swagger.json",
6177
+ (c) => c.text(JSON.stringify(getSwaggerJson()), 200, {
6178
+ "Content-Type": "application/json; charset=utf-8"
6179
+ })
6180
+ );
6181
+ app.get("/docs", (c) => c.html(getDocsHtml(), 200));
5853
6182
  serve$1({
5854
6183
  fetch: app.fetch,
5855
6184
  port: parameters.port
5856
6185
  });
5857
6186
  }
5858
6187
 
5859
- // src/api/Client.ts
6188
+ // src/client/Client.ts
5860
6189
  var Client_exports = {};
5861
6190
  __export(Client_exports, {
5862
6191
  HttpForbiddenError: () => HttpForbiddenError,
@@ -5911,8 +6240,16 @@ async function getObligations2(config, parameters) {
5911
6240
  if (parameters?.limit !== void 0) {
5912
6241
  url.searchParams.set("limit", parameters.limit.toString());
5913
6242
  }
5914
- const { cursor: returnedCursor, data: obligationsSnake } = await getApi(config, url);
5915
- const obligations2 = obligationsSnake.map(Obligation_exports.fromSnakeCase);
6243
+ const { cursor: returnedCursor, data: items } = await getApi(config, url);
6244
+ const obligations2 = items.map((item) => {
6245
+ const obligation = Obligation_exports.fromSnakeCase(item);
6246
+ const { obligationId: _, ...returned } = {
6247
+ id: () => Obligation_exports.id(obligation),
6248
+ ...obligation,
6249
+ ...Quote_exports.fromSnakeCase({ obligation_id: item.id, ask: item.ask, bid: item.bid })
6250
+ };
6251
+ return returned;
6252
+ });
5916
6253
  return {
5917
6254
  cursor: returnedCursor,
5918
6255
  obligations: obligations2
@@ -5999,6 +6336,6 @@ var HttpGetApiFailedError = class extends BaseError {
5999
6336
  }
6000
6337
  };
6001
6338
 
6002
- export { Abi_exports as Abi, BrandTypeId, Callback_exports as Callback, Chain_exports as Chain, ChainHealth, ChainStore_exports as ChainStore, ChainsHealthResponse, Collateral_exports as Collateral, collectors_exports as Collector, CollectorHealth, CollectorStore_exports as CollectorStore, CollectorsHealthResponse, Cursor_exports as Cursor, Errors_exports as Errors, Format_exports as Format, Health_exports as Health, LLTV_exports as LLTV, Liquidity_exports as Liquidity, LiquidityStore_exports as LiquidityStore, Logger_exports as Logger, Maturity_exports as Maturity, mempool_exports as Mempool, Obligation_exports as Obligation, ObligationResponse_exports as ObligationResponse, Offer_exports as Offer, OfferResponse_exports as OfferResponse, OfferStore_exports as OfferStore, schema_exports as OffersSchema, OpenApi, PG_exports as PG, Api_exports2 as RouterApi, Client_exports as RouterClient, RouterIndexer_exports as RouterIndexer, RouterStatusResponse, Services_exports as Services, time_exports as Time, utils_exports as Utils, Validation_exports as Validation, ValidationRule_exports as ValidationRule, parse, safeParse };
6339
+ export { Abi_exports as Abi, BrandTypeId, Callback_exports as Callback, Chain_exports as Chain, ChainHealth, ChainStore_exports as ChainStore, ChainsHealthResponse, Collateral_exports as Collateral, collectors_exports as Collector, CollectorHealth, CollectorStore_exports as CollectorStore, CollectorsHealthResponse, Cursor_exports as Cursor, Errors_exports as Errors, Format_exports as Format, Health_exports as Health, LLTV_exports as LLTV, Liquidity_exports as Liquidity, LiquidityStore_exports as LiquidityStore, Logger_exports as Logger, Maturity_exports as Maturity, mempool_exports as Mempool, Obligation_exports as Obligation, ObligationResponse_exports as ObligationResponse, Offer_exports as Offer, OfferResponse_exports as OfferResponse, OfferStore_exports as OfferStore, schema_exports as OffersSchema, OpenApi, PG_exports as PG, Quote_exports as Quote, RouterApi_exports as RouterApi, Client_exports as RouterClient, RouterIndexer_exports as RouterIndexer, RouterStatusResponse, Services_exports as Services, time_exports as Time, utils_exports as Utils, Validation_exports as Validation, ValidationRule_exports as ValidationRule, parse, safeParse };
6003
6340
  //# sourceMappingURL=index.node.mjs.map
6004
6341
  //# sourceMappingURL=index.node.mjs.map