@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, Utils, Time, Maturity } from '@morpho-dev/mempool';
2
2
  export * from '@morpho-dev/mempool';
3
- import { parseUnits, maxUint256, formatUnits, parseAbi } from 'viem';
3
+ import { Base64 } from 'js-base64';
4
+ import { parseUnits, maxUint256, formatUnits, erc20Abi } from 'viem';
4
5
  import { z } from 'zod/v4';
5
6
  import { createDocument } from 'zod-openapi';
6
- import { Base64 } from 'js-base64';
7
7
 
8
8
  var __defProp = Object.defineProperty;
9
9
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -13,6 +13,145 @@ var __export = (target, all) => {
13
13
  };
14
14
  var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
15
15
 
16
+ // src/Callback.ts
17
+ var Callback_exports = {};
18
+ __export(Callback_exports, {
19
+ CallbackType: () => CallbackType,
20
+ buildLiquidity: () => buildLiquidity,
21
+ getCallbackIdForOffer: () => getCallbackIdForOffer
22
+ });
23
+ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
24
+ CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
25
+ return CallbackType2;
26
+ })(CallbackType || {});
27
+ function buildLiquidity(parameters) {
28
+ const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
29
+ if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
30
+ throw new Error(`CallbackType not implemented: ${type}`);
31
+ const amountStr = amount.toString();
32
+ const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
33
+ return {
34
+ userPosition: {
35
+ id,
36
+ availableLiquidityQueueId: id,
37
+ user: user.toLowerCase(),
38
+ chainId,
39
+ amount: amountStr,
40
+ updatedAt
41
+ },
42
+ queues: [
43
+ {
44
+ queue: {
45
+ queueId: id,
46
+ availableLiquidityPoolId: id,
47
+ index,
48
+ updatedAt
49
+ },
50
+ pool: {
51
+ id,
52
+ amount: amountStr,
53
+ updatedAt
54
+ }
55
+ }
56
+ ]
57
+ };
58
+ }
59
+ function getCallbackIdForOffer(offer) {
60
+ if (offer.buy && offer.callback.data === "0x") {
61
+ const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
62
+ const user = offer.offering;
63
+ const loanToken = offer.loanToken;
64
+ return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
65
+ }
66
+ return null;
67
+ }
68
+
69
+ // src/Cursor.ts
70
+ var Cursor_exports = {};
71
+ __export(Cursor_exports, {
72
+ decode: () => decode,
73
+ encode: () => encode,
74
+ validate: () => validate
75
+ });
76
+ function validate(cursor) {
77
+ if (!cursor || typeof cursor !== "object") {
78
+ throw new Error("Cursor must be an object");
79
+ }
80
+ const c = cursor;
81
+ if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
82
+ throw new Error(
83
+ `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
84
+ );
85
+ }
86
+ if (!["asc", "desc"].includes(c.dir)) {
87
+ throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
88
+ }
89
+ if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
90
+ throw new Error(
91
+ `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
92
+ );
93
+ }
94
+ const validations = {
95
+ rate: {
96
+ field: "rate",
97
+ type: "string",
98
+ pattern: /^\d+$/,
99
+ error: "numeric string"
100
+ },
101
+ amount: {
102
+ field: "assets",
103
+ type: "string",
104
+ pattern: /^\d+$/,
105
+ error: "numeric string"
106
+ },
107
+ maturity: {
108
+ field: "maturity",
109
+ type: "number",
110
+ validator: (val) => val > 0,
111
+ error: "positive number"
112
+ },
113
+ expiry: {
114
+ field: "expiry",
115
+ type: "number",
116
+ validator: (val) => val > 0,
117
+ error: "positive number"
118
+ }
119
+ };
120
+ const validation = validations[c.sort];
121
+ if (!validation) {
122
+ throw new Error(`Invalid sort field: ${c.sort}`);
123
+ }
124
+ const fieldValue = c[validation.field];
125
+ if (!fieldValue) {
126
+ throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
127
+ }
128
+ if (typeof fieldValue !== validation.type) {
129
+ throw new Error(
130
+ `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
131
+ );
132
+ }
133
+ if (validation.pattern && !validation.pattern.test(fieldValue)) {
134
+ throw new Error(
135
+ `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
136
+ );
137
+ }
138
+ if (validation.validator && !validation.validator(fieldValue)) {
139
+ throw new Error(
140
+ `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
141
+ );
142
+ }
143
+ return true;
144
+ }
145
+ function encode(c) {
146
+ return Base64.encodeURL(JSON.stringify(c));
147
+ }
148
+ function decode(token) {
149
+ if (!token) return null;
150
+ const decoded = JSON.parse(Base64.decode(token));
151
+ validate(decoded);
152
+ return decoded;
153
+ }
154
+
16
155
  // src/core/router/Client.ts
17
156
  var Client_exports = {};
18
157
  __export(Client_exports, {
@@ -106,140 +245,6 @@ var InvalidRouterOfferError = class extends Errors.BaseError {
106
245
  }
107
246
  };
108
247
 
109
- // src/utils/index.ts
110
- var utils_exports = {};
111
- __export(utils_exports, {
112
- batch: () => batch,
113
- decodeCursor: () => decodeCursor,
114
- encodeCursor: () => encodeCursor,
115
- poll: () => poll,
116
- retry: () => retry,
117
- validateCursor: () => validateCursor,
118
- wait: () => wait
119
- });
120
-
121
- // src/utils/batch.ts
122
- function* batch(array, batchSize) {
123
- for (let i = 0; i < array.length; i += batchSize) {
124
- yield array.slice(i, i + batchSize);
125
- }
126
- }
127
- function validateCursor(cursor) {
128
- if (!cursor || typeof cursor !== "object") {
129
- throw new Error("Cursor must be an object");
130
- }
131
- const c = cursor;
132
- if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
133
- throw new Error(
134
- `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
135
- );
136
- }
137
- if (!["asc", "desc"].includes(c.dir)) {
138
- throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
139
- }
140
- if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
141
- throw new Error(
142
- `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
143
- );
144
- }
145
- const validations = {
146
- rate: {
147
- field: "rate",
148
- type: "string",
149
- pattern: /^\d+$/,
150
- error: "numeric string"
151
- },
152
- amount: {
153
- field: "assets",
154
- type: "string",
155
- pattern: /^\d+$/,
156
- error: "numeric string"
157
- },
158
- maturity: {
159
- field: "maturity",
160
- type: "number",
161
- validator: (val) => val > 0,
162
- error: "positive number"
163
- },
164
- expiry: {
165
- field: "expiry",
166
- type: "number",
167
- validator: (val) => val > 0,
168
- error: "positive number"
169
- }
170
- };
171
- const validation = validations[c.sort];
172
- if (!validation) {
173
- throw new Error(`Invalid sort field: ${c.sort}`);
174
- }
175
- const fieldValue = c[validation.field];
176
- if (!fieldValue) {
177
- throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
178
- }
179
- if (typeof fieldValue !== validation.type) {
180
- throw new Error(
181
- `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
182
- );
183
- }
184
- if (validation.pattern && !validation.pattern.test(fieldValue)) {
185
- throw new Error(
186
- `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
187
- );
188
- }
189
- if (validation.validator && !validation.validator(fieldValue)) {
190
- throw new Error(
191
- `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
192
- );
193
- }
194
- return true;
195
- }
196
- function encodeCursor(c) {
197
- return Base64.encodeURL(JSON.stringify(c));
198
- }
199
- function decodeCursor(token) {
200
- if (!token) return null;
201
- const decoded = JSON.parse(Base64.decode(token));
202
- validateCursor(decoded);
203
- return decoded;
204
- }
205
-
206
- // src/utils/wait.ts
207
- async function wait(time) {
208
- return new Promise((res) => setTimeout(res, time));
209
- }
210
-
211
- // src/utils/poll.ts
212
- function poll(fn, { interval }) {
213
- let active = true;
214
- const unwatch = () => active = false;
215
- const watch = async () => {
216
- await wait(interval);
217
- const poll2 = async () => {
218
- if (!active) return;
219
- await fn({ unpoll: unwatch });
220
- await wait(interval);
221
- poll2();
222
- };
223
- poll2();
224
- };
225
- watch();
226
- return unwatch;
227
- }
228
-
229
- // src/utils/retry.ts
230
- var retry = async (fn, attempts = 3, delayMs = 50) => {
231
- let lastErr;
232
- for (let i = 0; i < attempts; i++) {
233
- try {
234
- return await fn();
235
- } catch (err) {
236
- lastErr = err;
237
- if (i < attempts - 1) await new Promise((r) => setTimeout(r, delayMs));
238
- }
239
- }
240
- throw lastErr;
241
- };
242
-
243
248
  // src/core/apiSchema/requests.ts
244
249
  var MAX_LIMIT = 100;
245
250
  var DEFAULT_LIMIT = 20;
@@ -417,7 +422,7 @@ var GetOffersQueryParams = z.object({
417
422
  (val) => {
418
423
  if (!val) return true;
419
424
  try {
420
- const decoded = decodeCursor(val);
425
+ const decoded = decode(val);
421
426
  return decoded !== null;
422
427
  } catch (_error) {
423
428
  return false;
@@ -584,21 +589,12 @@ var MatchOffersQueryParams = z.object({
584
589
  description: "Filter by a specific offer creator address",
585
590
  example: "0x1234567890123456789012345678901234567890"
586
591
  }),
587
- // Status filtering
588
- status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
589
- message: "Status must be comma-separated status values"
590
- }).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
591
- message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
592
- }).optional().meta({
593
- description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
594
- example: "valid,callback_error"
595
- }),
596
592
  // Pagination
597
593
  cursor: z.string().optional().refine(
598
594
  (val) => {
599
595
  if (!val) return true;
600
596
  try {
601
- const decoded = decodeCursor(val);
597
+ const decoded = decode(val);
602
598
  return decoded !== null;
603
599
  } catch (_error) {
604
600
  return false;
@@ -991,6 +987,140 @@ var HttpGetOffersFailedError = class extends Errors.BaseError {
991
987
  }
992
988
  };
993
989
 
990
+ // src/Liquidity.ts
991
+ var Liquidity_exports = {};
992
+ __export(Liquidity_exports, {
993
+ fetch: () => fetch2,
994
+ fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
995
+ serialize: () => serialize
996
+ });
997
+ async function fetchBalancesAndAllowances(parameters) {
998
+ const { client, spender, pairs, options } = parameters;
999
+ if (pairs.length === 0) return /* @__PURE__ */ new Map();
1000
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
1001
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
1002
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
1003
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
1004
+ const out = /* @__PURE__ */ new Map();
1005
+ for (const pairsBatch of Utils.batch(pairs, batchSize)) {
1006
+ const balanceContracts = [];
1007
+ const allowanceContracts = [];
1008
+ for (const { user, token } of pairsBatch) {
1009
+ balanceContracts.push({
1010
+ address: token,
1011
+ abi: erc20Abi,
1012
+ functionName: "balanceOf",
1013
+ args: [user]
1014
+ });
1015
+ allowanceContracts.push({
1016
+ address: token,
1017
+ abi: erc20Abi,
1018
+ functionName: "allowance",
1019
+ args: [user, spender]
1020
+ });
1021
+ }
1022
+ const [balances, allowances] = await Promise.all([
1023
+ Utils.retry(
1024
+ () => client.multicall({
1025
+ allowFailure: false,
1026
+ contracts: balanceContracts,
1027
+ ...blockNumber ? { blockNumber } : {}
1028
+ }),
1029
+ retryAttempts,
1030
+ retryDelayMs
1031
+ ),
1032
+ Utils.retry(
1033
+ () => client.multicall({
1034
+ allowFailure: false,
1035
+ contracts: allowanceContracts,
1036
+ ...blockNumber ? { blockNumber } : {}
1037
+ }),
1038
+ retryAttempts,
1039
+ retryDelayMs
1040
+ )
1041
+ ]);
1042
+ for (let i = 0; i < pairsBatch.length; i++) {
1043
+ const { user, token } = pairsBatch[i];
1044
+ const balance = balances[i];
1045
+ const allowance = allowances[i];
1046
+ let perUser = out.get(user);
1047
+ if (!perUser) {
1048
+ perUser = /* @__PURE__ */ new Map();
1049
+ out.set(user, perUser);
1050
+ }
1051
+ perUser.set(token, { balance, allowance });
1052
+ }
1053
+ }
1054
+ return out;
1055
+ }
1056
+ async function fetch2(parameters) {
1057
+ const { client, chainId, spender, type, pairs, options } = parameters;
1058
+ if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
1059
+ throw new Error(`CallbackType not implemented: ${type}`);
1060
+ const map = await fetchBalancesAndAllowances({
1061
+ client,
1062
+ spender,
1063
+ pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
1064
+ options
1065
+ });
1066
+ const out = [];
1067
+ for (const [user, perContract] of map) {
1068
+ for (const [contract, { balance, allowance }] of perContract) {
1069
+ const amount = balance < allowance ? balance : allowance;
1070
+ out.push(
1071
+ buildLiquidity({
1072
+ type,
1073
+ user,
1074
+ contract,
1075
+ chainId,
1076
+ amount: amount.toString(),
1077
+ index: 0
1078
+ })
1079
+ );
1080
+ }
1081
+ }
1082
+ return out;
1083
+ }
1084
+ function serialize(liquidity) {
1085
+ const normalized = {
1086
+ userPosition: {
1087
+ id: liquidity.userPosition.id,
1088
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1089
+ user: liquidity.userPosition.user,
1090
+ chainId: String(liquidity.userPosition.chainId),
1091
+ amount: String(liquidity.userPosition.amount)
1092
+ },
1093
+ queues: liquidity.queues.map((queueWithPool) => ({
1094
+ queue: {
1095
+ queueId: queueWithPool.queue.queueId,
1096
+ availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
1097
+ index: queueWithPool.queue.index
1098
+ },
1099
+ pool: {
1100
+ id: queueWithPool.pool.id,
1101
+ amount: String(queueWithPool.pool.amount)
1102
+ }
1103
+ })).sort(
1104
+ (left, right) => {
1105
+ const leftQueueId = left.queue.queueId || "";
1106
+ const rightQueueId = right.queue.queueId || "";
1107
+ if (leftQueueId < rightQueueId) return -1;
1108
+ if (leftQueueId > rightQueueId) return 1;
1109
+ const leftPoolId = left.pool.id;
1110
+ const rightPoolId = right.pool.id;
1111
+ if (leftPoolId < rightPoolId) return -1;
1112
+ if (leftPoolId > rightPoolId) return 1;
1113
+ const leftIndex = left.queue.index;
1114
+ const rightIndex = right.queue.index;
1115
+ if (leftIndex < rightIndex) return -1;
1116
+ if (leftIndex > rightIndex) return 1;
1117
+ return 0;
1118
+ }
1119
+ )
1120
+ };
1121
+ return JSON.stringify(normalized);
1122
+ }
1123
+
994
1124
  // src/OfferStore/index.ts
995
1125
  var OfferStore_exports = {};
996
1126
  __export(OfferStore_exports, {
@@ -1002,8 +1132,10 @@ function memory(parameters) {
1002
1132
  const consumedIds = /* @__PURE__ */ new Set();
1003
1133
  const create = async (parameters2) => {
1004
1134
  if (map.has(parameters2.offer.hash.toLowerCase())) return parameters2.offer.hash;
1135
+ const callbackId = getCallbackIdForOffer(parameters2.offer);
1005
1136
  map.set(parameters2.offer.hash.toLowerCase(), {
1006
1137
  ...parameters2.offer,
1138
+ ...callbackId ? { callbackId } : {},
1007
1139
  status: parameters2.status,
1008
1140
  metadata: parameters2.metadata
1009
1141
  });
@@ -1098,7 +1230,7 @@ function memory(parameters) {
1098
1230
  ...o,
1099
1231
  consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1100
1232
  })).filter((o) => o.consumed < o.assets);
1101
- const cursor = decodeCursor(queryCursor);
1233
+ const cursor = decode(queryCursor);
1102
1234
  if (cursor) {
1103
1235
  if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
1104
1236
  throw new Error("Cursor does not match the current sort parameters");
@@ -1196,11 +1328,35 @@ function memory(parameters) {
1196
1328
  default:
1197
1329
  base.expiry = last.expiry;
1198
1330
  }
1199
- nextCursor = encodeCursor(base);
1331
+ nextCursor = encode(base);
1200
1332
  }
1201
1333
  offers = offers.slice(0, limit);
1334
+ const data = offers.map((o) => ({
1335
+ ...Offer.from({
1336
+ offering: o.offering,
1337
+ assets: o.assets,
1338
+ rate: o.rate,
1339
+ maturity: Maturity.from(o.maturity),
1340
+ expiry: o.expiry,
1341
+ start: o.start,
1342
+ nonce: o.nonce,
1343
+ buy: o.buy,
1344
+ chainId: o.chainId,
1345
+ loanToken: o.loanToken,
1346
+ collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
1347
+ callback: {
1348
+ address: o.callback.address,
1349
+ data: o.callback.data,
1350
+ gasLimit: o.callback.gasLimit
1351
+ },
1352
+ ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
1353
+ }),
1354
+ consumed: o.consumed,
1355
+ status: o.status,
1356
+ ...o.metadata ? { metadata: o.metadata } : {}
1357
+ }));
1202
1358
  return {
1203
- offers,
1359
+ offers: data,
1204
1360
  nextCursor
1205
1361
  };
1206
1362
  },
@@ -1215,7 +1371,6 @@ function memory(parameters) {
1215
1371
  maxMaturity,
1216
1372
  loanToken,
1217
1373
  creator,
1218
- status,
1219
1374
  cursor: queryCursor,
1220
1375
  limit = 20
1221
1376
  } = params;
@@ -1226,7 +1381,7 @@ function memory(parameters) {
1226
1381
  ...o,
1227
1382
  consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1228
1383
  })).filter((o) => o.consumed < o.assets);
1229
- const cursor = decodeCursor(queryCursor);
1384
+ const cursor = decode(queryCursor);
1230
1385
  if (cursor) {
1231
1386
  if (cursor.sort !== "rate" || cursor.dir !== sortOrder) {
1232
1387
  throw new Error("Cursor does not match the current sort parameters");
@@ -1261,7 +1416,7 @@ function memory(parameters) {
1261
1416
  maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
1262
1417
  loanToken && (offers = offers.filter((o) => o.loanToken.toLowerCase() === loanToken.toLowerCase()));
1263
1418
  creator && (offers = offers.filter((o) => o.offering.toLowerCase() === creator.toLowerCase()));
1264
- status && (offers = offers.filter((o) => status.includes(o.status)));
1419
+ offers = offers.filter((o) => ["valid"].includes(o.status));
1265
1420
  const byGroup = /* @__PURE__ */ new Map();
1266
1421
  for (const offer of offers) {
1267
1422
  const groupKey = `${offer.chainId}-${offer.offering.toLowerCase()}-${offer.nonce}-${offer.buy}`;
@@ -1296,7 +1451,7 @@ function memory(parameters) {
1296
1451
  let nextCursor = null;
1297
1452
  if (offers.length > limit) {
1298
1453
  const last = offers[limit - 1];
1299
- nextCursor = encodeCursor({
1454
+ nextCursor = encode({
1300
1455
  sort: "rate",
1301
1456
  dir: sortOrder,
1302
1457
  hash: last.hash,
@@ -1304,8 +1459,32 @@ function memory(parameters) {
1304
1459
  });
1305
1460
  }
1306
1461
  offers = offers.slice(0, limit);
1462
+ const data = offers.map((o) => ({
1463
+ ...Offer.from({
1464
+ offering: o.offering,
1465
+ assets: o.assets,
1466
+ rate: o.rate,
1467
+ maturity: Maturity.from(o.maturity),
1468
+ expiry: o.expiry,
1469
+ start: o.start,
1470
+ nonce: o.nonce,
1471
+ buy: o.buy,
1472
+ chainId: o.chainId,
1473
+ loanToken: o.loanToken,
1474
+ collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
1475
+ callback: {
1476
+ address: o.callback.address,
1477
+ data: o.callback.data,
1478
+ gasLimit: o.callback.gasLimit
1479
+ },
1480
+ ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
1481
+ }),
1482
+ consumed: o.consumed,
1483
+ status: o.status,
1484
+ ...o.metadata ? { metadata: o.metadata } : {}
1485
+ }));
1307
1486
  return {
1308
- offers,
1487
+ offers: data,
1309
1488
  nextCursor
1310
1489
  };
1311
1490
  },
@@ -1351,18 +1530,6 @@ function memory(parameters) {
1351
1530
  };
1352
1531
  }
1353
1532
 
1354
- // src/RouterEvent.ts
1355
- var RouterEvent_exports = {};
1356
- __export(RouterEvent_exports, {
1357
- from: () => from2,
1358
- types: () => types
1359
- });
1360
- var types = ["offer_created", "offer_consumed", "offer_validation"];
1361
- function from2(base) {
1362
- const id = base.type === "offer_consumed" ? `${base.type}:${base.offerConsumed.id}` : `${base.type}:${base.offer.hash.toLowerCase()}`;
1363
- return { id, ...base };
1364
- }
1365
-
1366
1533
  // src/Validation.ts
1367
1534
  var Validation_exports = {};
1368
1535
  __export(Validation_exports, {
@@ -1413,35 +1580,29 @@ async function run(parameters) {
1413
1580
  // src/ValidationRule.ts
1414
1581
  var ValidationRule_exports = {};
1415
1582
  __export(ValidationRule_exports, {
1416
- batch: () => batch2,
1583
+ batch: () => batch,
1417
1584
  morpho: () => morpho,
1418
1585
  single: () => single
1419
1586
  });
1420
1587
  function single(name, run2) {
1421
1588
  return { kind: "single", name, run: run2 };
1422
1589
  }
1423
- function batch2(name, run2) {
1590
+ function batch(name, run2) {
1424
1591
  return { kind: "batch", name, run: run2 };
1425
1592
  }
1426
- function morpho(parameters) {
1427
- const { whitelistedChains } = parameters;
1428
- const whitelistedChainIds = new Set(whitelistedChains.map((chain) => chain.id));
1429
- const whitelistedLoanTokensPerChain = new Map(
1430
- whitelistedChains.map((chain) => [
1431
- chain.id,
1432
- new Set(Array.from(chain.whitelistedAssets).map((a) => a.toLowerCase()))
1433
- ])
1434
- );
1435
- const morphoPerChain = new Map(
1436
- whitelistedChains.map((chain) => [chain.id, chain.morpho.toLowerCase()])
1437
- );
1438
- const chainId = single("chain_id", (offer, _) => {
1439
- if (!whitelistedChainIds.has(offer.chainId)) {
1440
- return { message: `Chain ID ${offer.chainId} is not whitelisted` };
1593
+ function morpho() {
1594
+ const chainId = single("chain_id", (offer, { chain }) => {
1595
+ if (chain.id !== offer.chainId) {
1596
+ return {
1597
+ message: `Chain ID ${offer.chainId} is not the same as the chain ID in the context (${chain.id})`
1598
+ };
1441
1599
  }
1442
1600
  });
1443
- const loanToken = single("loan_token", (offer, _) => {
1444
- if (!whitelistedLoanTokensPerChain.get(offer.chainId)?.has(offer.loanToken.toLowerCase())) {
1601
+ const loanToken = single("loan_token", (offer, { chain }) => {
1602
+ const tokens = new Set(
1603
+ Array.from(chain.whitelistedAssets.values()).map((a) => a.toLowerCase())
1604
+ );
1605
+ if (!tokens.has(offer.loanToken.toLowerCase())) {
1445
1606
  return {
1446
1607
  message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
1447
1608
  };
@@ -1452,130 +1613,21 @@ function morpho(parameters) {
1452
1613
  return { message: "Expiry mismatch" };
1453
1614
  }
1454
1615
  });
1455
- const emptyCallback = single("empty_callback", (offer, _) => {
1456
- if (offer.callback.data !== "0x") {
1457
- return { message: "Callback data is not empty. Not supported yet." };
1616
+ const callback = single("empty_callback", (offer, _) => {
1617
+ if (!offer.buy || offer.callback.data !== "0x") {
1618
+ return { message: "Callback not supported yet." };
1458
1619
  }
1459
1620
  });
1460
- const sellOffersEmptyCallback = single(
1461
- "sell_offers_empty_callback",
1462
- (offer, _) => {
1463
- if (!offer.buy && offer.callback.data === "0x") {
1464
- return { message: "Sell offers with empty callback are not supported yet." };
1465
- }
1466
- }
1467
- );
1468
- const buyOffersEmptyCallback = batch2(
1469
- "buy_offers_empty_callback",
1470
- async (offers, { publicClients }) => {
1471
- const issues = /* @__PURE__ */ new Map();
1472
- const hashToIndex = /* @__PURE__ */ new Map();
1473
- for (let i = 0; i < offers.length; i++) {
1474
- const offer = offers[i];
1475
- hashToIndex.set(offer.hash, i);
1476
- }
1477
- const { buyOffers, sellOffers: _sellOffers } = offers.reduce(
1478
- (acc, offer) => {
1479
- offer.buy ? acc.buyOffers.push(offer) : issues.set(hashToIndex.get(offer.hash), {
1480
- message: "Onchain callback for sell offers is not supported yet."
1481
- });
1482
- return acc;
1483
- },
1484
- { buyOffers: [], sellOffers: [] }
1485
- );
1486
- const buyOffersPerLoanAsset = /* @__PURE__ */ new Map();
1487
- for (const offer of buyOffers) {
1488
- const chainName = Chain.getChain(offer.chainId)?.name;
1489
- const loanTokens = buyOffersPerLoanAsset.get(chainName) ?? /* @__PURE__ */ new Map();
1490
- const offers2 = loanTokens.get(offer.loanToken.toLowerCase()) ?? [];
1491
- offers2.push(offer);
1492
- loanTokens.set(offer.loanToken.toLowerCase(), offers2);
1493
- buyOffersPerLoanAsset.set(chainName, loanTokens);
1494
- }
1495
- await Promise.all(
1496
- Array.from(buyOffersPerLoanAsset.entries()).map(async ([name, loanTokens]) => {
1497
- const chainName = name;
1498
- const publicClient = publicClients[chainName];
1499
- const morpho2 = morphoPerChain.get(Chain.chains[chainName].id);
1500
- if (!publicClient) {
1501
- const offers2 = Array.from(loanTokens.values()).flat();
1502
- for (const offer of offers2) {
1503
- issues.set(hashToIndex.get(offer.hash), {
1504
- message: `Public client for chain "${chainName}" is not available`
1505
- });
1506
- }
1507
- return;
1508
- }
1509
- const balances = /* @__PURE__ */ new Map();
1510
- const allowances = /* @__PURE__ */ new Map();
1511
- for (const [loanToken2, offers2] of loanTokens) {
1512
- const data = await Promise.all(
1513
- offers2.flatMap((offer) => [
1514
- publicClient.readContract({
1515
- address: loanToken2,
1516
- abi: parseAbi([
1517
- "function balanceOf(address owner) view returns (uint256 balance)"
1518
- ]),
1519
- functionName: "balanceOf",
1520
- args: [offer.offering]
1521
- }),
1522
- publicClient.readContract({
1523
- address: loanToken2,
1524
- abi: parseAbi([
1525
- "function allowance(address owner, address spender) public view returns (uint256 remaining)"
1526
- ]),
1527
- functionName: "allowance",
1528
- args: [offer.offering, morpho2]
1529
- })
1530
- ])
1531
- );
1532
- for (let i = 0; i < offers2.length; i++) {
1533
- const user = offers2[i].offering.toLowerCase();
1534
- const balance = data[i * 2] || 0n;
1535
- const allowance = data[i * 2 + 1] || 0n;
1536
- const userBalances = balances.get(user) ?? /* @__PURE__ */ new Map();
1537
- userBalances.set(loanToken2.toLowerCase(), balance);
1538
- const userAllowances = allowances.get(user) ?? /* @__PURE__ */ new Map();
1539
- userAllowances.set(loanToken2.toLowerCase(), allowance);
1540
- balances.set(user, userBalances);
1541
- allowances.set(user, userAllowances);
1542
- }
1543
- }
1544
- for (const offer of Array.from(loanTokens.values()).flat()) {
1545
- const user = offer.offering.toLowerCase();
1546
- const userBalances = balances.get(user);
1547
- const balance = userBalances?.get(offer.loanToken.toLowerCase());
1548
- if (balance < offer.assets) {
1549
- issues.set(hashToIndex.get(offer.hash), {
1550
- message: `Insufficient balance for ${offer.loanToken} on chain ${offer.chainId} (${balance.toString()} < ${offer.assets.toString()})`
1551
- });
1552
- continue;
1553
- }
1554
- const userAllowances = allowances.get(user);
1555
- const allowance = userAllowances?.get(offer.loanToken.toLowerCase());
1556
- if (allowance < offer.assets) {
1557
- issues.set(hashToIndex.get(offer.hash), {
1558
- message: `Insufficient allowance for ${offer.loanToken} on chain ${offer.chainId} (${allowance.toString()} < ${offer.assets.toString()})`
1559
- });
1560
- }
1561
- }
1562
- })
1563
- );
1564
- return issues;
1565
- }
1566
- );
1567
1621
  return [
1568
1622
  chainId,
1569
1623
  loanToken,
1570
1624
  expiry,
1571
- // note: callback onchain check should be done last since it does not mean that the offer is forever invalid
1572
- // 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
1573
- emptyCallback,
1574
- sellOffersEmptyCallback,
1575
- buyOffersEmptyCallback
1625
+ // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1626
+ // integrators should be able to choose if they want to keep the offer or not
1627
+ callback
1576
1628
  ];
1577
1629
  }
1578
1630
 
1579
- export { OfferStore_exports as OfferStore, Client_exports as Router, RouterEvent_exports as RouterEvent, RouterOffer_exports as RouterOffer, utils_exports as Utils, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
1631
+ export { Callback_exports as Callback, Cursor_exports as Cursor, Liquidity_exports as Liquidity, OfferStore_exports as OfferStore, Client_exports as Router, RouterOffer_exports as RouterOffer, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
1580
1632
  //# sourceMappingURL=index.browser.mjs.map
1581
1633
  //# sourceMappingURL=index.browser.mjs.map