@morpho-dev/router 0.1.2 → 0.1.4

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,9 +1,9 @@
1
- import { Errors, LLTV, Offer, Format, Time, Maturity, Chain } from '@morpho-dev/mempool';
1
+ import { Errors, LLTV, Offer, Format, Time, Maturity, Utils } from '@morpho-dev/mempool';
2
2
  export * from '@morpho-dev/mempool';
3
+ import { Base64 } from 'js-base64';
3
4
  import { z } from 'zod/v4';
4
5
  import { createDocument } from 'zod-openapi';
5
- import { parseUnits, maxUint256, formatUnits, parseAbi } from 'viem';
6
- import { Base64 } from 'js-base64';
6
+ import { parseUnits, maxUint256, formatUnits, erc20Abi } from 'viem';
7
7
  import { serve as serve$1 } from '@hono/node-server';
8
8
  import { Hono } from 'hono';
9
9
  import { AsyncLocalStorage } from 'async_hooks';
@@ -14,6 +14,145 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
+ // src/Callback.ts
18
+ var Callback_exports = {};
19
+ __export(Callback_exports, {
20
+ CallbackType: () => CallbackType,
21
+ buildLiquidity: () => buildLiquidity,
22
+ getCallbackIdForOffer: () => getCallbackIdForOffer
23
+ });
24
+ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
25
+ CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
26
+ return CallbackType2;
27
+ })(CallbackType || {});
28
+ function buildLiquidity(parameters) {
29
+ const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
30
+ if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
31
+ throw new Error(`CallbackType not implemented: ${type}`);
32
+ const amountStr = amount.toString();
33
+ const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
34
+ return {
35
+ userPosition: {
36
+ id,
37
+ availableLiquidityQueueId: id,
38
+ user: user.toLowerCase(),
39
+ chainId,
40
+ amount: amountStr,
41
+ updatedAt
42
+ },
43
+ queues: [
44
+ {
45
+ queue: {
46
+ queueId: id,
47
+ availableLiquidityPoolId: id,
48
+ index,
49
+ updatedAt
50
+ },
51
+ pool: {
52
+ id,
53
+ amount: amountStr,
54
+ updatedAt
55
+ }
56
+ }
57
+ ]
58
+ };
59
+ }
60
+ function getCallbackIdForOffer(offer) {
61
+ if (offer.buy && offer.callback.data === "0x") {
62
+ const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
63
+ const user = offer.offering;
64
+ const loanToken = offer.loanToken;
65
+ return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
66
+ }
67
+ return null;
68
+ }
69
+
70
+ // src/Cursor.ts
71
+ var Cursor_exports = {};
72
+ __export(Cursor_exports, {
73
+ decode: () => decode,
74
+ encode: () => encode,
75
+ validate: () => validate
76
+ });
77
+ function validate(cursor) {
78
+ if (!cursor || typeof cursor !== "object") {
79
+ throw new Error("Cursor must be an object");
80
+ }
81
+ const c = cursor;
82
+ if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
83
+ throw new Error(
84
+ `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
85
+ );
86
+ }
87
+ if (!["asc", "desc"].includes(c.dir)) {
88
+ throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
89
+ }
90
+ if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
91
+ throw new Error(
92
+ `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
93
+ );
94
+ }
95
+ const validations = {
96
+ rate: {
97
+ field: "rate",
98
+ type: "string",
99
+ pattern: /^\d+$/,
100
+ error: "numeric string"
101
+ },
102
+ amount: {
103
+ field: "assets",
104
+ type: "string",
105
+ pattern: /^\d+$/,
106
+ error: "numeric string"
107
+ },
108
+ maturity: {
109
+ field: "maturity",
110
+ type: "number",
111
+ validator: (val) => val > 0,
112
+ error: "positive number"
113
+ },
114
+ expiry: {
115
+ field: "expiry",
116
+ type: "number",
117
+ validator: (val) => val > 0,
118
+ error: "positive number"
119
+ }
120
+ };
121
+ const validation = validations[c.sort];
122
+ if (!validation) {
123
+ throw new Error(`Invalid sort field: ${c.sort}`);
124
+ }
125
+ const fieldValue = c[validation.field];
126
+ if (!fieldValue) {
127
+ throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
128
+ }
129
+ if (typeof fieldValue !== validation.type) {
130
+ throw new Error(
131
+ `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
132
+ );
133
+ }
134
+ if (validation.pattern && !validation.pattern.test(fieldValue)) {
135
+ throw new Error(
136
+ `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
137
+ );
138
+ }
139
+ if (validation.validator && !validation.validator(fieldValue)) {
140
+ throw new Error(
141
+ `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
142
+ );
143
+ }
144
+ return true;
145
+ }
146
+ function encode(c) {
147
+ return Base64.encodeURL(JSON.stringify(c));
148
+ }
149
+ function decode(token) {
150
+ if (!token) return null;
151
+ const decoded = JSON.parse(Base64.decode(token));
152
+ validate(decoded);
153
+ return decoded;
154
+ }
155
+
17
156
  // src/core/apiSchema/index.ts
18
157
  var apiSchema_exports = {};
19
158
  __export(apiSchema_exports, {
@@ -104,140 +243,6 @@ var InvalidRouterOfferError = class extends Errors.BaseError {
104
243
  }
105
244
  };
106
245
 
107
- // src/utils/index.ts
108
- var utils_exports = {};
109
- __export(utils_exports, {
110
- batch: () => batch,
111
- decodeCursor: () => decodeCursor,
112
- encodeCursor: () => encodeCursor,
113
- poll: () => poll,
114
- retry: () => retry,
115
- validateCursor: () => validateCursor,
116
- wait: () => wait
117
- });
118
-
119
- // src/utils/batch.ts
120
- function* batch(array, batchSize) {
121
- for (let i = 0; i < array.length; i += batchSize) {
122
- yield array.slice(i, i + batchSize);
123
- }
124
- }
125
- function validateCursor(cursor) {
126
- if (!cursor || typeof cursor !== "object") {
127
- throw new Error("Cursor must be an object");
128
- }
129
- const c = cursor;
130
- if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
131
- throw new Error(
132
- `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
133
- );
134
- }
135
- if (!["asc", "desc"].includes(c.dir)) {
136
- throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
137
- }
138
- if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
139
- throw new Error(
140
- `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
141
- );
142
- }
143
- const validations = {
144
- rate: {
145
- field: "rate",
146
- type: "string",
147
- pattern: /^\d+$/,
148
- error: "numeric string"
149
- },
150
- amount: {
151
- field: "assets",
152
- type: "string",
153
- pattern: /^\d+$/,
154
- error: "numeric string"
155
- },
156
- maturity: {
157
- field: "maturity",
158
- type: "number",
159
- validator: (val) => val > 0,
160
- error: "positive number"
161
- },
162
- expiry: {
163
- field: "expiry",
164
- type: "number",
165
- validator: (val) => val > 0,
166
- error: "positive number"
167
- }
168
- };
169
- const validation = validations[c.sort];
170
- if (!validation) {
171
- throw new Error(`Invalid sort field: ${c.sort}`);
172
- }
173
- const fieldValue = c[validation.field];
174
- if (!fieldValue) {
175
- throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
176
- }
177
- if (typeof fieldValue !== validation.type) {
178
- throw new Error(
179
- `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
180
- );
181
- }
182
- if (validation.pattern && !validation.pattern.test(fieldValue)) {
183
- throw new Error(
184
- `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
185
- );
186
- }
187
- if (validation.validator && !validation.validator(fieldValue)) {
188
- throw new Error(
189
- `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
190
- );
191
- }
192
- return true;
193
- }
194
- function encodeCursor(c) {
195
- return Base64.encodeURL(JSON.stringify(c));
196
- }
197
- function decodeCursor(token) {
198
- if (!token) return null;
199
- const decoded = JSON.parse(Base64.decode(token));
200
- validateCursor(decoded);
201
- return decoded;
202
- }
203
-
204
- // src/utils/wait.ts
205
- async function wait(time) {
206
- return new Promise((res) => setTimeout(res, time));
207
- }
208
-
209
- // src/utils/poll.ts
210
- function poll(fn, { interval }) {
211
- let active = true;
212
- const unwatch = () => active = false;
213
- const watch = async () => {
214
- await wait(interval);
215
- const poll2 = async () => {
216
- if (!active) return;
217
- await fn({ unpoll: unwatch });
218
- await wait(interval);
219
- poll2();
220
- };
221
- poll2();
222
- };
223
- watch();
224
- return unwatch;
225
- }
226
-
227
- // src/utils/retry.ts
228
- var retry = async (fn, attempts = 3, delayMs = 50) => {
229
- let lastErr;
230
- for (let i = 0; i < attempts; i++) {
231
- try {
232
- return await fn();
233
- } catch (err) {
234
- lastErr = err;
235
- if (i < attempts - 1) await new Promise((r) => setTimeout(r, delayMs));
236
- }
237
- }
238
- throw lastErr;
239
- };
240
-
241
246
  // src/core/apiSchema/requests.ts
242
247
  var MAX_LIMIT = 100;
243
248
  var DEFAULT_LIMIT = 20;
@@ -415,7 +420,7 @@ var GetOffersQueryParams = z.object({
415
420
  (val) => {
416
421
  if (!val) return true;
417
422
  try {
418
- const decoded = decodeCursor(val);
423
+ const decoded = decode(val);
419
424
  return decoded !== null;
420
425
  } catch (_error) {
421
426
  return false;
@@ -582,21 +587,12 @@ var MatchOffersQueryParams = z.object({
582
587
  description: "Filter by a specific offer creator address",
583
588
  example: "0x1234567890123456789012345678901234567890"
584
589
  }),
585
- // Status filtering
586
- status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
587
- message: "Status must be comma-separated status values"
588
- }).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
589
- message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
590
- }).optional().meta({
591
- description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
592
- example: "valid,callback_error"
593
- }),
594
590
  // Pagination
595
591
  cursor: z.string().optional().refine(
596
592
  (val) => {
597
593
  if (!val) return true;
598
594
  try {
599
- const decoded = decodeCursor(val);
595
+ const decoded = decode(val);
600
596
  return decoded !== null;
601
597
  } catch (_error) {
602
598
  return false;
@@ -1023,8 +1019,10 @@ function memory(parameters) {
1023
1019
  const consumedIds = /* @__PURE__ */ new Set();
1024
1020
  const create = async (parameters2) => {
1025
1021
  if (map.has(parameters2.offer.hash.toLowerCase())) return parameters2.offer.hash;
1022
+ const callbackId = getCallbackIdForOffer(parameters2.offer);
1026
1023
  map.set(parameters2.offer.hash.toLowerCase(), {
1027
1024
  ...parameters2.offer,
1025
+ ...callbackId ? { callbackId } : {},
1028
1026
  status: parameters2.status,
1029
1027
  metadata: parameters2.metadata
1030
1028
  });
@@ -1119,7 +1117,7 @@ function memory(parameters) {
1119
1117
  ...o,
1120
1118
  consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1121
1119
  })).filter((o) => o.consumed < o.assets);
1122
- const cursor = decodeCursor(queryCursor);
1120
+ const cursor = decode(queryCursor);
1123
1121
  if (cursor) {
1124
1122
  if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
1125
1123
  throw new Error("Cursor does not match the current sort parameters");
@@ -1217,11 +1215,35 @@ function memory(parameters) {
1217
1215
  default:
1218
1216
  base.expiry = last.expiry;
1219
1217
  }
1220
- nextCursor = encodeCursor(base);
1218
+ nextCursor = encode(base);
1221
1219
  }
1222
1220
  offers = offers.slice(0, limit);
1221
+ const data = offers.map((o) => ({
1222
+ ...Offer.from({
1223
+ offering: o.offering,
1224
+ assets: o.assets,
1225
+ rate: o.rate,
1226
+ maturity: Maturity.from(o.maturity),
1227
+ expiry: o.expiry,
1228
+ start: o.start,
1229
+ nonce: o.nonce,
1230
+ buy: o.buy,
1231
+ chainId: o.chainId,
1232
+ loanToken: o.loanToken,
1233
+ collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
1234
+ callback: {
1235
+ address: o.callback.address,
1236
+ data: o.callback.data,
1237
+ gasLimit: o.callback.gasLimit
1238
+ },
1239
+ ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
1240
+ }),
1241
+ consumed: o.consumed,
1242
+ status: o.status,
1243
+ ...o.metadata ? { metadata: o.metadata } : {}
1244
+ }));
1223
1245
  return {
1224
- offers,
1246
+ offers: data,
1225
1247
  nextCursor
1226
1248
  };
1227
1249
  },
@@ -1236,7 +1258,6 @@ function memory(parameters) {
1236
1258
  maxMaturity,
1237
1259
  loanToken,
1238
1260
  creator,
1239
- status,
1240
1261
  cursor: queryCursor,
1241
1262
  limit = 20
1242
1263
  } = params;
@@ -1247,7 +1268,7 @@ function memory(parameters) {
1247
1268
  ...o,
1248
1269
  consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1249
1270
  })).filter((o) => o.consumed < o.assets);
1250
- const cursor = decodeCursor(queryCursor);
1271
+ const cursor = decode(queryCursor);
1251
1272
  if (cursor) {
1252
1273
  if (cursor.sort !== "rate" || cursor.dir !== sortOrder) {
1253
1274
  throw new Error("Cursor does not match the current sort parameters");
@@ -1282,7 +1303,7 @@ function memory(parameters) {
1282
1303
  maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
1283
1304
  loanToken && (offers = offers.filter((o) => o.loanToken.toLowerCase() === loanToken.toLowerCase()));
1284
1305
  creator && (offers = offers.filter((o) => o.offering.toLowerCase() === creator.toLowerCase()));
1285
- status && (offers = offers.filter((o) => status.includes(o.status)));
1306
+ offers = offers.filter((o) => ["valid"].includes(o.status));
1286
1307
  const byGroup = /* @__PURE__ */ new Map();
1287
1308
  for (const offer of offers) {
1288
1309
  const groupKey = `${offer.chainId}-${offer.offering.toLowerCase()}-${offer.nonce}-${offer.buy}`;
@@ -1317,7 +1338,7 @@ function memory(parameters) {
1317
1338
  let nextCursor = null;
1318
1339
  if (offers.length > limit) {
1319
1340
  const last = offers[limit - 1];
1320
- nextCursor = encodeCursor({
1341
+ nextCursor = encode({
1321
1342
  sort: "rate",
1322
1343
  dir: sortOrder,
1323
1344
  hash: last.hash,
@@ -1325,8 +1346,32 @@ function memory(parameters) {
1325
1346
  });
1326
1347
  }
1327
1348
  offers = offers.slice(0, limit);
1349
+ const data = offers.map((o) => ({
1350
+ ...Offer.from({
1351
+ offering: o.offering,
1352
+ assets: o.assets,
1353
+ rate: o.rate,
1354
+ maturity: Maturity.from(o.maturity),
1355
+ expiry: o.expiry,
1356
+ start: o.start,
1357
+ nonce: o.nonce,
1358
+ buy: o.buy,
1359
+ chainId: o.chainId,
1360
+ loanToken: o.loanToken,
1361
+ collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
1362
+ callback: {
1363
+ address: o.callback.address,
1364
+ data: o.callback.data,
1365
+ gasLimit: o.callback.gasLimit
1366
+ },
1367
+ ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
1368
+ }),
1369
+ consumed: o.consumed,
1370
+ status: o.status,
1371
+ ...o.metadata ? { metadata: o.metadata } : {}
1372
+ }));
1328
1373
  return {
1329
- offers,
1374
+ offers: data,
1330
1375
  nextCursor
1331
1376
  };
1332
1377
  },
@@ -1433,7 +1478,6 @@ async function serve(parameters) {
1433
1478
  maxMaturity: params.max_maturity,
1434
1479
  loanToken: params.loan_token,
1435
1480
  creator: params.creator,
1436
- status: params.status,
1437
1481
  cursor: params.cursor,
1438
1482
  limit: params.limit
1439
1483
  });
@@ -1549,6 +1593,140 @@ function handleAPIError(error2, c) {
1549
1593
  });
1550
1594
  }
1551
1595
 
1596
+ // src/Liquidity.ts
1597
+ var Liquidity_exports = {};
1598
+ __export(Liquidity_exports, {
1599
+ fetch: () => fetch2,
1600
+ fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
1601
+ serialize: () => serialize
1602
+ });
1603
+ async function fetchBalancesAndAllowances(parameters) {
1604
+ const { client, spender, pairs, options } = parameters;
1605
+ if (pairs.length === 0) return /* @__PURE__ */ new Map();
1606
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
1607
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
1608
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
1609
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
1610
+ const out = /* @__PURE__ */ new Map();
1611
+ for (const pairsBatch of Utils.batch(pairs, batchSize)) {
1612
+ const balanceContracts = [];
1613
+ const allowanceContracts = [];
1614
+ for (const { user, token } of pairsBatch) {
1615
+ balanceContracts.push({
1616
+ address: token,
1617
+ abi: erc20Abi,
1618
+ functionName: "balanceOf",
1619
+ args: [user]
1620
+ });
1621
+ allowanceContracts.push({
1622
+ address: token,
1623
+ abi: erc20Abi,
1624
+ functionName: "allowance",
1625
+ args: [user, spender]
1626
+ });
1627
+ }
1628
+ const [balances, allowances] = await Promise.all([
1629
+ Utils.retry(
1630
+ () => client.multicall({
1631
+ allowFailure: false,
1632
+ contracts: balanceContracts,
1633
+ ...blockNumber ? { blockNumber } : {}
1634
+ }),
1635
+ retryAttempts,
1636
+ retryDelayMs
1637
+ ),
1638
+ Utils.retry(
1639
+ () => client.multicall({
1640
+ allowFailure: false,
1641
+ contracts: allowanceContracts,
1642
+ ...blockNumber ? { blockNumber } : {}
1643
+ }),
1644
+ retryAttempts,
1645
+ retryDelayMs
1646
+ )
1647
+ ]);
1648
+ for (let i = 0; i < pairsBatch.length; i++) {
1649
+ const { user, token } = pairsBatch[i];
1650
+ const balance = balances[i];
1651
+ const allowance = allowances[i];
1652
+ let perUser = out.get(user);
1653
+ if (!perUser) {
1654
+ perUser = /* @__PURE__ */ new Map();
1655
+ out.set(user, perUser);
1656
+ }
1657
+ perUser.set(token, { balance, allowance });
1658
+ }
1659
+ }
1660
+ return out;
1661
+ }
1662
+ async function fetch2(parameters) {
1663
+ const { client, chainId, spender, type, pairs, options } = parameters;
1664
+ if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
1665
+ throw new Error(`CallbackType not implemented: ${type}`);
1666
+ const map = await fetchBalancesAndAllowances({
1667
+ client,
1668
+ spender,
1669
+ pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
1670
+ options
1671
+ });
1672
+ const out = [];
1673
+ for (const [user, perContract] of map) {
1674
+ for (const [contract, { balance, allowance }] of perContract) {
1675
+ const amount = balance < allowance ? balance : allowance;
1676
+ out.push(
1677
+ buildLiquidity({
1678
+ type,
1679
+ user,
1680
+ contract,
1681
+ chainId,
1682
+ amount: amount.toString(),
1683
+ index: 0
1684
+ })
1685
+ );
1686
+ }
1687
+ }
1688
+ return out;
1689
+ }
1690
+ function serialize(liquidity) {
1691
+ const normalized = {
1692
+ userPosition: {
1693
+ id: liquidity.userPosition.id,
1694
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1695
+ user: liquidity.userPosition.user,
1696
+ chainId: String(liquidity.userPosition.chainId),
1697
+ amount: String(liquidity.userPosition.amount)
1698
+ },
1699
+ queues: liquidity.queues.map((queueWithPool) => ({
1700
+ queue: {
1701
+ queueId: queueWithPool.queue.queueId,
1702
+ availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
1703
+ index: queueWithPool.queue.index
1704
+ },
1705
+ pool: {
1706
+ id: queueWithPool.pool.id,
1707
+ amount: String(queueWithPool.pool.amount)
1708
+ }
1709
+ })).sort(
1710
+ (left, right) => {
1711
+ const leftQueueId = left.queue.queueId || "";
1712
+ const rightQueueId = right.queue.queueId || "";
1713
+ if (leftQueueId < rightQueueId) return -1;
1714
+ if (leftQueueId > rightQueueId) return 1;
1715
+ const leftPoolId = left.pool.id;
1716
+ const rightPoolId = right.pool.id;
1717
+ if (leftPoolId < rightPoolId) return -1;
1718
+ if (leftPoolId > rightPoolId) return 1;
1719
+ const leftIndex = left.queue.index;
1720
+ const rightIndex = right.queue.index;
1721
+ if (leftIndex < rightIndex) return -1;
1722
+ if (leftIndex > rightIndex) return 1;
1723
+ return 0;
1724
+ }
1725
+ )
1726
+ };
1727
+ return JSON.stringify(normalized);
1728
+ }
1729
+
1552
1730
  // src/Logger.ts
1553
1731
  var Logger_exports = {};
1554
1732
  __export(Logger_exports, {
@@ -1621,18 +1799,6 @@ function getLogger() {
1621
1799
  return loggerContext.getStore() ?? defaultLogger();
1622
1800
  }
1623
1801
 
1624
- // src/RouterEvent.ts
1625
- var RouterEvent_exports = {};
1626
- __export(RouterEvent_exports, {
1627
- from: () => from2,
1628
- types: () => types
1629
- });
1630
- var types = ["offer_created", "offer_consumed", "offer_validation"];
1631
- function from2(base) {
1632
- const id = base.type === "offer_consumed" ? `${base.type}:${base.offerConsumed.id}` : `${base.type}:${base.offer.hash.toLowerCase()}`;
1633
- return { id, ...base };
1634
- }
1635
-
1636
1802
  // src/Validation.ts
1637
1803
  var Validation_exports = {};
1638
1804
  __export(Validation_exports, {
@@ -1683,35 +1849,29 @@ async function run(parameters) {
1683
1849
  // src/ValidationRule.ts
1684
1850
  var ValidationRule_exports = {};
1685
1851
  __export(ValidationRule_exports, {
1686
- batch: () => batch2,
1852
+ batch: () => batch,
1687
1853
  morpho: () => morpho,
1688
1854
  single: () => single
1689
1855
  });
1690
1856
  function single(name, run2) {
1691
1857
  return { kind: "single", name, run: run2 };
1692
1858
  }
1693
- function batch2(name, run2) {
1859
+ function batch(name, run2) {
1694
1860
  return { kind: "batch", name, run: run2 };
1695
1861
  }
1696
- function morpho(parameters) {
1697
- const { whitelistedChains } = parameters;
1698
- const whitelistedChainIds = new Set(whitelistedChains.map((chain) => chain.id));
1699
- const whitelistedLoanTokensPerChain = new Map(
1700
- whitelistedChains.map((chain) => [
1701
- chain.id,
1702
- new Set(Array.from(chain.whitelistedAssets).map((a) => a.toLowerCase()))
1703
- ])
1704
- );
1705
- const morphoPerChain = new Map(
1706
- whitelistedChains.map((chain) => [chain.id, chain.morpho.toLowerCase()])
1707
- );
1708
- const chainId = single("chain_id", (offer, _) => {
1709
- if (!whitelistedChainIds.has(offer.chainId)) {
1710
- return { message: `Chain ID ${offer.chainId} is not whitelisted` };
1862
+ function morpho() {
1863
+ const chainId = single("chain_id", (offer, { chain }) => {
1864
+ if (chain.id !== offer.chainId) {
1865
+ return {
1866
+ message: `Chain ID ${offer.chainId} is not the same as the chain ID in the context (${chain.id})`
1867
+ };
1711
1868
  }
1712
1869
  });
1713
- const loanToken = single("loan_token", (offer, _) => {
1714
- if (!whitelistedLoanTokensPerChain.get(offer.chainId)?.has(offer.loanToken.toLowerCase())) {
1870
+ const loanToken = single("loan_token", (offer, { chain }) => {
1871
+ const tokens = new Set(
1872
+ Array.from(chain.whitelistedAssets.values()).map((a) => a.toLowerCase())
1873
+ );
1874
+ if (!tokens.has(offer.loanToken.toLowerCase())) {
1715
1875
  return {
1716
1876
  message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
1717
1877
  };
@@ -1722,130 +1882,21 @@ function morpho(parameters) {
1722
1882
  return { message: "Expiry mismatch" };
1723
1883
  }
1724
1884
  });
1725
- const emptyCallback = single("empty_callback", (offer, _) => {
1726
- if (offer.callback.data !== "0x") {
1727
- return { message: "Callback data is not empty. Not supported yet." };
1885
+ const callback = single("empty_callback", (offer, _) => {
1886
+ if (!offer.buy || offer.callback.data !== "0x") {
1887
+ return { message: "Callback not supported yet." };
1728
1888
  }
1729
1889
  });
1730
- const sellOffersEmptyCallback = single(
1731
- "sell_offers_empty_callback",
1732
- (offer, _) => {
1733
- if (!offer.buy && offer.callback.data === "0x") {
1734
- return { message: "Sell offers with empty callback are not supported yet." };
1735
- }
1736
- }
1737
- );
1738
- const buyOffersEmptyCallback = batch2(
1739
- "buy_offers_empty_callback",
1740
- async (offers, { publicClients }) => {
1741
- const issues = /* @__PURE__ */ new Map();
1742
- const hashToIndex = /* @__PURE__ */ new Map();
1743
- for (let i = 0; i < offers.length; i++) {
1744
- const offer = offers[i];
1745
- hashToIndex.set(offer.hash, i);
1746
- }
1747
- const { buyOffers, sellOffers: _sellOffers } = offers.reduce(
1748
- (acc, offer) => {
1749
- offer.buy ? acc.buyOffers.push(offer) : issues.set(hashToIndex.get(offer.hash), {
1750
- message: "Onchain callback for sell offers is not supported yet."
1751
- });
1752
- return acc;
1753
- },
1754
- { buyOffers: [], sellOffers: [] }
1755
- );
1756
- const buyOffersPerLoanAsset = /* @__PURE__ */ new Map();
1757
- for (const offer of buyOffers) {
1758
- const chainName = Chain.getChain(offer.chainId)?.name;
1759
- const loanTokens = buyOffersPerLoanAsset.get(chainName) ?? /* @__PURE__ */ new Map();
1760
- const offers2 = loanTokens.get(offer.loanToken.toLowerCase()) ?? [];
1761
- offers2.push(offer);
1762
- loanTokens.set(offer.loanToken.toLowerCase(), offers2);
1763
- buyOffersPerLoanAsset.set(chainName, loanTokens);
1764
- }
1765
- await Promise.all(
1766
- Array.from(buyOffersPerLoanAsset.entries()).map(async ([name, loanTokens]) => {
1767
- const chainName = name;
1768
- const publicClient = publicClients[chainName];
1769
- const morpho2 = morphoPerChain.get(Chain.chains[chainName].id);
1770
- if (!publicClient) {
1771
- const offers2 = Array.from(loanTokens.values()).flat();
1772
- for (const offer of offers2) {
1773
- issues.set(hashToIndex.get(offer.hash), {
1774
- message: `Public client for chain "${chainName}" is not available`
1775
- });
1776
- }
1777
- return;
1778
- }
1779
- const balances = /* @__PURE__ */ new Map();
1780
- const allowances = /* @__PURE__ */ new Map();
1781
- for (const [loanToken2, offers2] of loanTokens) {
1782
- const data = await Promise.all(
1783
- offers2.flatMap((offer) => [
1784
- publicClient.readContract({
1785
- address: loanToken2,
1786
- abi: parseAbi([
1787
- "function balanceOf(address owner) view returns (uint256 balance)"
1788
- ]),
1789
- functionName: "balanceOf",
1790
- args: [offer.offering]
1791
- }),
1792
- publicClient.readContract({
1793
- address: loanToken2,
1794
- abi: parseAbi([
1795
- "function allowance(address owner, address spender) public view returns (uint256 remaining)"
1796
- ]),
1797
- functionName: "allowance",
1798
- args: [offer.offering, morpho2]
1799
- })
1800
- ])
1801
- );
1802
- for (let i = 0; i < offers2.length; i++) {
1803
- const user = offers2[i].offering.toLowerCase();
1804
- const balance = data[i * 2] || 0n;
1805
- const allowance = data[i * 2 + 1] || 0n;
1806
- const userBalances = balances.get(user) ?? /* @__PURE__ */ new Map();
1807
- userBalances.set(loanToken2.toLowerCase(), balance);
1808
- const userAllowances = allowances.get(user) ?? /* @__PURE__ */ new Map();
1809
- userAllowances.set(loanToken2.toLowerCase(), allowance);
1810
- balances.set(user, userBalances);
1811
- allowances.set(user, userAllowances);
1812
- }
1813
- }
1814
- for (const offer of Array.from(loanTokens.values()).flat()) {
1815
- const user = offer.offering.toLowerCase();
1816
- const userBalances = balances.get(user);
1817
- const balance = userBalances?.get(offer.loanToken.toLowerCase());
1818
- if (balance < offer.assets) {
1819
- issues.set(hashToIndex.get(offer.hash), {
1820
- message: `Insufficient balance for ${offer.loanToken} on chain ${offer.chainId} (${balance.toString()} < ${offer.assets.toString()})`
1821
- });
1822
- continue;
1823
- }
1824
- const userAllowances = allowances.get(user);
1825
- const allowance = userAllowances?.get(offer.loanToken.toLowerCase());
1826
- if (allowance < offer.assets) {
1827
- issues.set(hashToIndex.get(offer.hash), {
1828
- message: `Insufficient allowance for ${offer.loanToken} on chain ${offer.chainId} (${allowance.toString()} < ${offer.assets.toString()})`
1829
- });
1830
- }
1831
- }
1832
- })
1833
- );
1834
- return issues;
1835
- }
1836
- );
1837
1890
  return [
1838
1891
  chainId,
1839
1892
  loanToken,
1840
1893
  expiry,
1841
- // note: callback onchain check should be done last since it does not mean that the offer is forever invalid
1842
- // integrators should be able to keep the offers that have an invalid callback onchain and be sure that is the only check that is not valid
1843
- emptyCallback,
1844
- sellOffersEmptyCallback,
1845
- buyOffersEmptyCallback
1894
+ // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1895
+ // integrators should be able to choose if they want to keep the offer or not
1896
+ callback
1846
1897
  ];
1847
1898
  }
1848
1899
 
1849
- export { apiSchema_exports as ApiSchema, Logger_exports as Logger, OfferStore_exports as OfferStore, router_exports as Router, RouterEvent_exports as RouterEvent, RouterOffer_exports as RouterOffer, utils_exports as Utils, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
1900
+ export { apiSchema_exports as ApiSchema, Callback_exports as Callback, Cursor_exports as Cursor, Liquidity_exports as Liquidity, Logger_exports as Logger, OfferStore_exports as OfferStore, router_exports as Router, RouterOffer_exports as RouterOffer, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
1850
1901
  //# sourceMappingURL=index.node.mjs.map
1851
1902
  //# sourceMappingURL=index.node.mjs.map