@morpho-dev/router 0.1.10 → 0.1.12

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.
Files changed (28) hide show
  1. package/README.md +7 -7
  2. package/dist/drizzle/{offers_v1.1 → router_v1.1}/0000_init.sql +47 -39
  3. package/dist/drizzle/{offers_v1.1/meta/0005_snapshot.json → router_v1.1/meta/0000_snapshot.json} +76 -21
  4. package/dist/drizzle/router_v1.1/meta/_journal.json +13 -0
  5. package/dist/index.browser.d.cts +185 -143
  6. package/dist/index.browser.d.ts +185 -143
  7. package/dist/index.browser.js +808 -443
  8. package/dist/index.browser.js.map +1 -1
  9. package/dist/index.browser.mjs +810 -445
  10. package/dist/index.browser.mjs.map +1 -1
  11. package/dist/index.node.d.cts +1861 -1792
  12. package/dist/index.node.d.ts +1861 -1792
  13. package/dist/index.node.js +2921 -2384
  14. package/dist/index.node.js.map +1 -1
  15. package/dist/index.node.mjs +2923 -2385
  16. package/dist/index.node.mjs.map +1 -1
  17. package/package.json +4 -4
  18. package/dist/drizzle/offers_v1.1/0001_new_table_for_collectors_block_numbers.sql +0 -5
  19. package/dist/drizzle/offers_v1.1/0002_update-liquidity-tables.sql +0 -8
  20. package/dist/drizzle/offers_v1.1/0003_add-not-null-for-queue-id.sql +0 -1
  21. package/dist/drizzle/offers_v1.1/0004_add-callback-id-to-offer.sql +0 -1
  22. package/dist/drizzle/offers_v1.1/0005_add-missing-indices-to-liquidity-tables.sql +0 -2
  23. package/dist/drizzle/offers_v1.1/meta/0000_snapshot.json +0 -827
  24. package/dist/drizzle/offers_v1.1/meta/0001_snapshot.json +0 -827
  25. package/dist/drizzle/offers_v1.1/meta/0002_snapshot.json +0 -833
  26. package/dist/drizzle/offers_v1.1/meta/0003_snapshot.json +0 -833
  27. package/dist/drizzle/offers_v1.1/meta/0004_snapshot.json +0 -839
  28. package/dist/drizzle/offers_v1.1/meta/_journal.json +0 -48
@@ -2,9 +2,9 @@
2
2
 
3
3
  var mempool = require('@morpho-dev/mempool');
4
4
  var viem = require('viem');
5
- var jsBase64 = require('js-base64');
6
5
  var v4 = require('zod/v4');
7
6
  var zodOpenapi = require('zod-openapi');
7
+ var jsBase64 = require('js-base64');
8
8
 
9
9
  var __defProp = Object.defineProperty;
10
10
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -14,108 +14,104 @@ var __export = (target, all) => {
14
14
  };
15
15
  var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
16
16
 
17
- // src/core/Callback.ts
18
- var Callback_exports = {};
19
- __export(Callback_exports, {
20
- CallbackType: () => CallbackType,
21
- WhitelistedCallbackAddresses: () => WhitelistedCallbackAddresses,
22
- buildLiquidity: () => buildLiquidity,
23
- decode: () => decode,
24
- encode: () => encode,
25
- getCallbackIdForOffer: () => getCallbackIdForOffer
17
+ // src/api/Client.ts
18
+ var Client_exports = {};
19
+ __export(Client_exports, {
20
+ HttpForbiddenError: () => HttpForbiddenError,
21
+ HttpGetOffersFailedError: () => HttpGetOffersFailedError,
22
+ HttpRateLimitError: () => HttpRateLimitError,
23
+ HttpUnauthorizedError: () => HttpUnauthorizedError,
24
+ InvalidUrlError: () => InvalidUrlError,
25
+ connect: () => connect,
26
+ get: () => get,
27
+ match: () => match
26
28
  });
27
- var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
28
- CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
29
- CallbackType2["SellWithdrawFromWallet"] = "sell_withdraw_from_wallet";
30
- return CallbackType2;
31
- })(CallbackType || {});
32
- var WhitelistedCallbackAddresses = {
33
- ["buy_with_empty_callback" /* BuyWithEmptyCallback */]: [],
34
- ["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */]: [
35
- "0x1111111111111111111111111111111111111111",
36
- "0x2222222222222222222222222222222222222222"
37
- // @TODO: update once deployed and add mapping per chain if needed
38
- ]
29
+
30
+ // src/core/RouterOffer.ts
31
+ var RouterOffer_exports = {};
32
+ __export(RouterOffer_exports, {
33
+ InvalidRouterOfferError: () => InvalidRouterOfferError,
34
+ OfferStatusValues: () => OfferStatusValues,
35
+ RouterOfferSchema: () => RouterOfferSchema,
36
+ consumedEvent: () => consumedEvent,
37
+ from: () => from,
38
+ fromConsumedLog: () => fromConsumedLog,
39
+ fromSnakeCase: () => fromSnakeCase,
40
+ random: () => random,
41
+ toSnakeCase: () => toSnakeCase
42
+ });
43
+ var OfferStatusValues = [
44
+ "valid",
45
+ "callback_not_supported",
46
+ "callback_error",
47
+ "unverified"
48
+ ];
49
+ var RouterOfferSchema = (parameters) => mempool.Offer.OfferSchema(parameters).extend({
50
+ consumed: v4.z.bigint({ coerce: true }).min(0n).max(viem.maxUint256),
51
+ status: v4.z.enum(OfferStatusValues),
52
+ metadata: v4.z.object({
53
+ issue: v4.z.string()
54
+ }).optional()
55
+ });
56
+ var consumedEvent = {
57
+ type: "event",
58
+ name: "Consumed",
59
+ inputs: [
60
+ { name: "user", type: "address", indexed: true, internalType: "address" },
61
+ { name: "nonce", type: "uint256", indexed: true, internalType: "uint256" },
62
+ { name: "amount", type: "uint256", indexed: false, internalType: "uint256" }
63
+ ],
64
+ anonymous: false
39
65
  };
40
- function buildLiquidity(parameters) {
41
- const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
42
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
43
- throw new Error(`CallbackType not implemented: ${type}`);
44
- const amountStr = amount.toString();
45
- const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
46
- return {
47
- userPosition: {
48
- id,
49
- availableLiquidityQueueId: id,
50
- user: user.toLowerCase(),
51
- chainId,
52
- amount: amountStr,
53
- updatedAt
54
- },
55
- queues: [
56
- {
57
- queue: {
58
- queueId: id,
59
- availableLiquidityPoolId: id,
60
- index,
61
- updatedAt
62
- },
63
- pool: {
64
- id,
65
- amount: amountStr,
66
- updatedAt
67
- }
68
- }
69
- ]
70
- };
71
- }
72
- function getCallbackIdForOffer(offer) {
73
- if (offer.buy && offer.callback.data === "0x") {
74
- const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
75
- const user = offer.offering;
76
- const loanToken = offer.loanToken;
77
- return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
78
- }
79
- return null;
80
- }
81
- function decodeSellWithdrawFromWalletData(data) {
82
- if (!data || data === "0x") throw new Error("Empty callback data");
66
+ function from(input) {
83
67
  try {
84
- const [collaterals, amounts] = viem.decodeAbiParameters(
85
- [{ type: "address[]" }, { type: "uint256[]" }],
86
- data
87
- );
88
- if (collaterals.length !== amounts.length) {
89
- throw new Error("Mismatched array lengths");
90
- }
91
- return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
92
- } catch (_) {
93
- throw new Error("Invalid SellWithdrawFromWallet callback data");
68
+ const parsedOffer = RouterOfferSchema({ omitHash: true }).parse(input);
69
+ const parsedHash = mempool.Offer.OfferHashSchema.parse(mempool.Offer.hash(parsedOffer));
70
+ return {
71
+ ...parsedOffer,
72
+ hash: parsedHash
73
+ };
74
+ } catch (error) {
75
+ throw new InvalidRouterOfferError(error);
94
76
  }
95
77
  }
96
- function decode(parameters) {
97
- const { type, data } = parameters;
98
- if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
99
- return decodeSellWithdrawFromWalletData(data);
100
- }
101
- throw new Error(`CallbackType not implemented: ${type}`);
78
+ function fromSnakeCase(input) {
79
+ return from(mempool.Format.fromSnakeCase(input));
102
80
  }
103
- function encode(parameters) {
104
- const { type, data } = parameters;
105
- if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
106
- return viem.encodeAbiParameters(
107
- [{ type: "address[]" }, { type: "uint256[]" }],
108
- [data.collaterals, data.amounts]
109
- );
110
- }
111
- throw new Error(`CallbackType not implemented: ${type}`);
81
+ function toSnakeCase(offer) {
82
+ return mempool.Format.toSnakeCase(offer);
83
+ }
84
+ function random() {
85
+ const baseOffer = mempool.Offer.random();
86
+ return from({
87
+ ...baseOffer,
88
+ status: "valid",
89
+ metadata: void 0,
90
+ consumed: 0n
91
+ });
112
92
  }
93
+ function fromConsumedLog(parameters) {
94
+ const { blockNumber, logIndex, chainId, transactionHash, user, nonce, amount } = parameters;
95
+ return {
96
+ id: `${blockNumber.toString()}-${logIndex.toString()}-${chainId}-${transactionHash}`,
97
+ chainId: BigInt(chainId),
98
+ offering: user,
99
+ nonce,
100
+ amount
101
+ };
102
+ }
103
+ var InvalidRouterOfferError = class extends mempool.Errors.BaseError {
104
+ constructor(error) {
105
+ super("Invalid router offer.", { cause: error });
106
+ __publicField(this, "name", "RouterOffer.InvalidRouterOfferError");
107
+ }
108
+ };
113
109
 
114
110
  // src/core/Cursor.ts
115
111
  var Cursor_exports = {};
116
112
  __export(Cursor_exports, {
117
- decode: () => decode2,
118
- encode: () => encode2,
113
+ decode: () => decode,
114
+ encode: () => encode,
119
115
  validate: () => validate
120
116
  });
121
117
  function validate(cursor) {
@@ -187,242 +183,17 @@ function validate(cursor) {
187
183
  }
188
184
  return true;
189
185
  }
190
- function encode2(c) {
186
+ function encode(c) {
191
187
  return jsBase64.Base64.encodeURL(JSON.stringify(c));
192
188
  }
193
- function decode2(token) {
189
+ function decode(token) {
194
190
  if (!token) return null;
195
191
  const decoded = JSON.parse(jsBase64.Base64.decode(token));
196
192
  validate(decoded);
197
193
  return decoded;
198
194
  }
199
195
 
200
- // src/core/Liquidity.ts
201
- var Liquidity_exports = {};
202
- __export(Liquidity_exports, {
203
- fetch: () => fetch2,
204
- fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
205
- serialize: () => serialize
206
- });
207
- async function fetchBalancesAndAllowances(parameters) {
208
- const { client, spender, pairs, options } = parameters;
209
- if (pairs.length === 0) return /* @__PURE__ */ new Map();
210
- const batchSize = Math.max(1, options?.batchSize ?? 5e3);
211
- const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
212
- const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
213
- const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
214
- const out = /* @__PURE__ */ new Map();
215
- for (const pairsBatch of mempool.Utils.batch(pairs, batchSize)) {
216
- const balanceContracts = [];
217
- const allowanceContracts = [];
218
- for (const { user, token } of pairsBatch) {
219
- balanceContracts.push({
220
- address: token,
221
- abi: viem.erc20Abi,
222
- functionName: "balanceOf",
223
- args: [user]
224
- });
225
- allowanceContracts.push({
226
- address: token,
227
- abi: viem.erc20Abi,
228
- functionName: "allowance",
229
- args: [user, spender]
230
- });
231
- }
232
- const [balances, allowances] = await Promise.all([
233
- mempool.Utils.retry(
234
- () => client.multicall({
235
- allowFailure: false,
236
- contracts: balanceContracts,
237
- ...blockNumber ? { blockNumber } : {}
238
- }),
239
- retryAttempts,
240
- retryDelayMs
241
- ),
242
- mempool.Utils.retry(
243
- () => client.multicall({
244
- allowFailure: false,
245
- contracts: allowanceContracts,
246
- ...blockNumber ? { blockNumber } : {}
247
- }),
248
- retryAttempts,
249
- retryDelayMs
250
- )
251
- ]);
252
- for (let i = 0; i < pairsBatch.length; i++) {
253
- const { user, token } = pairsBatch[i];
254
- const balance = balances[i];
255
- const allowance = allowances[i];
256
- let perUser = out.get(user);
257
- if (!perUser) {
258
- perUser = /* @__PURE__ */ new Map();
259
- out.set(user, perUser);
260
- }
261
- perUser.set(token, { balance, allowance });
262
- }
263
- }
264
- return out;
265
- }
266
- async function fetch2(parameters) {
267
- const { client, chainId, spender, type, pairs, options } = parameters;
268
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
269
- throw new Error(`CallbackType not implemented: ${type}`);
270
- const map = await fetchBalancesAndAllowances({
271
- client,
272
- spender,
273
- pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
274
- options
275
- });
276
- const out = [];
277
- for (const [user, perContract] of map) {
278
- for (const [contract, { balance, allowance }] of perContract) {
279
- const amount = balance < allowance ? balance : allowance;
280
- out.push(
281
- buildLiquidity({
282
- type,
283
- user,
284
- contract,
285
- chainId,
286
- amount: amount.toString(),
287
- index: 0
288
- })
289
- );
290
- }
291
- }
292
- return out;
293
- }
294
- function serialize(liquidity) {
295
- const normalized = {
296
- userPosition: {
297
- id: liquidity.userPosition.id,
298
- availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
299
- user: liquidity.userPosition.user,
300
- chainId: String(liquidity.userPosition.chainId),
301
- amount: String(liquidity.userPosition.amount)
302
- },
303
- queues: liquidity.queues.map((queueWithPool) => ({
304
- queue: {
305
- queueId: queueWithPool.queue.queueId,
306
- availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
307
- index: queueWithPool.queue.index
308
- },
309
- pool: {
310
- id: queueWithPool.pool.id,
311
- amount: String(queueWithPool.pool.amount)
312
- }
313
- })).sort(
314
- (left, right) => {
315
- const leftQueueId = left.queue.queueId || "";
316
- const rightQueueId = right.queue.queueId || "";
317
- if (leftQueueId < rightQueueId) return -1;
318
- if (leftQueueId > rightQueueId) return 1;
319
- const leftPoolId = left.pool.id;
320
- const rightPoolId = right.pool.id;
321
- if (leftPoolId < rightPoolId) return -1;
322
- if (leftPoolId > rightPoolId) return 1;
323
- const leftIndex = left.queue.index;
324
- const rightIndex = right.queue.index;
325
- if (leftIndex < rightIndex) return -1;
326
- if (leftIndex > rightIndex) return 1;
327
- return 0;
328
- }
329
- )
330
- };
331
- return JSON.stringify(normalized);
332
- }
333
-
334
- // src/core/RouterOffer.ts
335
- var RouterOffer_exports = {};
336
- __export(RouterOffer_exports, {
337
- InvalidRouterOfferError: () => InvalidRouterOfferError,
338
- OfferStatusValues: () => OfferStatusValues,
339
- RouterOfferSchema: () => RouterOfferSchema,
340
- consumedEvent: () => consumedEvent,
341
- from: () => from,
342
- fromConsumedLog: () => fromConsumedLog,
343
- fromSnakeCase: () => fromSnakeCase,
344
- random: () => random,
345
- toSnakeCase: () => toSnakeCase
346
- });
347
- var OfferStatusValues = [
348
- "valid",
349
- "callback_not_supported",
350
- "callback_error",
351
- "unverified"
352
- ];
353
- var RouterOfferSchema = (parameters) => mempool.Offer.OfferSchema(parameters).extend({
354
- consumed: v4.z.bigint({ coerce: true }).min(0n).max(viem.maxUint256),
355
- status: v4.z.enum(OfferStatusValues),
356
- metadata: v4.z.object({
357
- issue: v4.z.string()
358
- }).optional()
359
- });
360
- var consumedEvent = {
361
- type: "event",
362
- name: "Consumed",
363
- inputs: [
364
- { name: "user", type: "address", indexed: true, internalType: "address" },
365
- { name: "nonce", type: "uint256", indexed: true, internalType: "uint256" },
366
- { name: "amount", type: "uint256", indexed: false, internalType: "uint256" }
367
- ],
368
- anonymous: false
369
- };
370
- function from(input) {
371
- try {
372
- const parsedOffer = RouterOfferSchema({ omitHash: true }).parse(input);
373
- const parsedHash = mempool.Offer.OfferHashSchema.parse(mempool.Offer.hash(parsedOffer));
374
- return {
375
- ...parsedOffer,
376
- hash: parsedHash
377
- };
378
- } catch (error) {
379
- throw new InvalidRouterOfferError(error);
380
- }
381
- }
382
- function fromSnakeCase(input) {
383
- return from(mempool.Format.fromSnakeCase(input));
384
- }
385
- function toSnakeCase(offer) {
386
- return mempool.Format.toSnakeCase(offer);
387
- }
388
- function random() {
389
- const baseOffer = mempool.Offer.random();
390
- return from({
391
- ...baseOffer,
392
- status: "valid",
393
- metadata: void 0,
394
- consumed: 0n
395
- });
396
- }
397
- function fromConsumedLog(parameters) {
398
- const { blockNumber, logIndex, chainId, transactionHash, user, nonce, amount } = parameters;
399
- return {
400
- id: `${blockNumber.toString()}-${logIndex.toString()}-${chainId}-${transactionHash}`,
401
- chainId: BigInt(chainId),
402
- offering: user,
403
- nonce,
404
- amount
405
- };
406
- }
407
- var InvalidRouterOfferError = class extends mempool.Errors.BaseError {
408
- constructor(error) {
409
- super("Invalid router offer.", { cause: error });
410
- __publicField(this, "name", "RouterOffer.InvalidRouterOfferError");
411
- }
412
- };
413
-
414
- // src/core/router/Client.ts
415
- var Client_exports = {};
416
- __export(Client_exports, {
417
- HttpForbiddenError: () => HttpForbiddenError,
418
- HttpGetOffersFailedError: () => HttpGetOffersFailedError,
419
- HttpRateLimitError: () => HttpRateLimitError,
420
- HttpUnauthorizedError: () => HttpUnauthorizedError,
421
- InvalidUrlError: () => InvalidUrlError,
422
- connect: () => connect,
423
- get: () => get,
424
- match: () => match
425
- });
196
+ // src/api/Schema/requests.ts
426
197
  var MAX_LIMIT = 100;
427
198
  var DEFAULT_LIMIT = 20;
428
199
  var MAX_LLTV = 100;
@@ -599,7 +370,7 @@ var GetOffersQueryParams = v4.z.object({
599
370
  (val) => {
600
371
  if (!val) return true;
601
372
  try {
602
- const decoded = decode2(val);
373
+ const decoded = decode(val);
603
374
  return decoded !== null;
604
375
  } catch (_error) {
605
376
  return false;
@@ -771,7 +542,7 @@ var MatchOffersQueryParams = v4.z.object({
771
542
  (val) => {
772
543
  if (!val) return true;
773
544
  try {
774
- const decoded = decode2(val);
545
+ const decoded = decode(val);
775
546
  return decoded !== null;
776
547
  } catch (_error) {
777
548
  return false;
@@ -811,7 +582,7 @@ function safeParse(action, query, error) {
811
582
  });
812
583
  }
813
584
 
814
- // src/core/apiSchema/openapi.ts
585
+ // src/api/Schema/openapi.ts
815
586
  var successResponseSchema = v4.z.object({
816
587
  status: v4.z.literal("success"),
817
588
  cursor: v4.z.string().nullable(),
@@ -914,7 +685,7 @@ zodOpenapi.createDocument({
914
685
  paths
915
686
  });
916
687
 
917
- // src/core/apiSchema/utils.ts
688
+ // src/api/Schema/utils.ts
918
689
  function toResponse(routerOffer) {
919
690
  const { consumed, status, metadata, ...offer } = routerOffer;
920
691
  return {
@@ -934,7 +705,7 @@ function fromResponse(offerResponse) {
934
705
  };
935
706
  }
936
707
 
937
- // src/core/router/Client.ts
708
+ // src/api/Client.ts
938
709
  function connect(opts) {
939
710
  const u = new URL(opts?.url || "https://router.morpho.dev");
940
711
  if (u.protocol !== "http:" && u.protocol !== "https:") {
@@ -1034,135 +805,720 @@ async function get(config, parameters) {
1034
805
  if (parameters.limit !== void 0) {
1035
806
  url.searchParams.set("limit", parameters.limit.toString());
1036
807
  }
1037
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
1038
- const routerOffers = offers.map(mempool.Format.fromSnakeCase).map(fromResponse);
1039
- return {
1040
- cursor: returnedCursor,
1041
- offers: routerOffers.map(from).map(toResponse)
1042
- };
808
+ const { cursor: returnedCursor, data: offers } = await getApi(config, url);
809
+ const routerOffers = offers.map(mempool.Format.fromSnakeCase).map(fromResponse);
810
+ return {
811
+ cursor: returnedCursor,
812
+ offers: routerOffers.map(from).map(toResponse)
813
+ };
814
+ }
815
+ async function match(config, parameters) {
816
+ const url = new URL(`${config.url.toString()}v1/offers/match`);
817
+ url.searchParams.set("side", parameters.side);
818
+ url.searchParams.set("chain_id", parameters.chainId.toString());
819
+ if (parameters.rate !== void 0) {
820
+ url.searchParams.set("rate", parameters.rate.toString());
821
+ }
822
+ if (parameters.collaterals?.length) {
823
+ const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${viem.formatUnits(lltv, 16)}`).join("#");
824
+ url.searchParams.set("collaterals", collateralsStr);
825
+ }
826
+ if (parameters.maturity !== void 0) {
827
+ url.searchParams.set("maturity", parameters.maturity.toString());
828
+ }
829
+ if (parameters.minMaturity !== void 0) {
830
+ url.searchParams.set("min_maturity", parameters.minMaturity.toString());
831
+ }
832
+ if (parameters.maxMaturity !== void 0) {
833
+ url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
834
+ }
835
+ if (parameters.loanToken) {
836
+ url.searchParams.set("loan_token", parameters.loanToken);
837
+ }
838
+ if (parameters.creator) {
839
+ url.searchParams.set("creator", parameters.creator);
840
+ }
841
+ if (parameters.status?.length) {
842
+ url.searchParams.set("status", parameters.status.join(","));
843
+ }
844
+ if (parameters.cursor) {
845
+ url.searchParams.set("cursor", parameters.cursor);
846
+ }
847
+ if (parameters.limit !== void 0) {
848
+ url.searchParams.set("limit", parameters.limit.toString());
849
+ }
850
+ const { cursor: returnedCursor, data: offers } = await getApi(config, url);
851
+ const routerOffers = offers.map(mempool.Format.fromSnakeCase).map(fromResponse);
852
+ return {
853
+ cursor: returnedCursor,
854
+ offers: routerOffers.map(from).map(toResponse)
855
+ };
856
+ }
857
+ async function getApi(config, url) {
858
+ const pathname = url.pathname;
859
+ let action;
860
+ switch (true) {
861
+ case pathname.includes("/v1/offers/match"):
862
+ action = "match_offers";
863
+ break;
864
+ case pathname.includes("/v1/offers"):
865
+ action = "get_offers";
866
+ break;
867
+ default:
868
+ throw new HttpGetOffersFailedError("Unknown endpoint", {
869
+ details: `Unsupported path: ${pathname}`
870
+ });
871
+ }
872
+ const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
873
+ if (!schemaParseResult.success) {
874
+ throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
875
+ details: schemaParseResult.error.issues[0]?.message
876
+ });
877
+ }
878
+ const response = await fetch(url.toString(), {
879
+ method: "GET",
880
+ headers: config.headers
881
+ });
882
+ if (!response.ok) {
883
+ switch (response.status) {
884
+ case 401:
885
+ throw new HttpUnauthorizedError();
886
+ case 403:
887
+ throw new HttpForbiddenError();
888
+ case 429:
889
+ throw new HttpRateLimitError();
890
+ }
891
+ throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
892
+ details: await response.text()
893
+ });
894
+ }
895
+ return response.json();
896
+ }
897
+ var InvalidUrlError = class extends mempool.Errors.BaseError {
898
+ constructor(url) {
899
+ super(`URL "${url}" is not http/https.`);
900
+ __publicField(this, "name", "Router.InvalidUrlError");
901
+ }
902
+ };
903
+ var HttpUnauthorizedError = class extends mempool.Errors.BaseError {
904
+ constructor() {
905
+ super("Unauthorized.", {
906
+ metaMessages: ["Ensure that an API key is provided."]
907
+ });
908
+ __publicField(this, "name", "Router.HttpUnauthorizedError");
909
+ }
910
+ };
911
+ var HttpForbiddenError = class extends mempool.Errors.BaseError {
912
+ constructor() {
913
+ super("Forbidden.", {
914
+ metaMessages: ["Ensure that the API key is valid."]
915
+ });
916
+ __publicField(this, "name", "Router.HttpForbiddenError");
917
+ }
918
+ };
919
+ var HttpRateLimitError = class extends mempool.Errors.BaseError {
920
+ constructor() {
921
+ super("Rate limit exceeded.", {
922
+ metaMessages: [
923
+ "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
924
+ ]
925
+ });
926
+ __publicField(this, "name", "Router.HttpRateLimitError");
927
+ }
928
+ };
929
+ var HttpGetOffersFailedError = class extends mempool.Errors.BaseError {
930
+ constructor(message, { details } = {}) {
931
+ super(message, {
932
+ metaMessages: [details]
933
+ });
934
+ __publicField(this, "name", "Router.HttpGetOffersFailedError");
935
+ }
936
+ };
937
+
938
+ // src/core/Callback.ts
939
+ var Callback_exports = {};
940
+ __export(Callback_exports, {
941
+ CallbackType: () => CallbackType,
942
+ WhitelistedCallbackAddresses: () => WhitelistedCallbackAddresses,
943
+ buildLiquidity: () => buildLiquidity,
944
+ decode: () => decode2,
945
+ encode: () => encode2,
946
+ getCallbackIdForOffer: () => getCallbackIdForOffer
947
+ });
948
+ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
949
+ CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
950
+ CallbackType2["SellERC20Callback"] = "sell_erc20_callback";
951
+ return CallbackType2;
952
+ })(CallbackType || {});
953
+ var WhitelistedCallbackAddresses = {
954
+ ["buy_with_empty_callback" /* BuyWithEmptyCallback */]: [],
955
+ ["sell_erc20_callback" /* SellERC20Callback */]: [
956
+ "0x1111111111111111111111111111111111111111",
957
+ "0x2222222222222222222222222222222222222222"
958
+ // @TODO: update once deployed and add mapping per chain if needed
959
+ ].map((address) => address.toLowerCase())
960
+ };
961
+ function buildLiquidity(parameters) {
962
+ switch (parameters.type) {
963
+ case "buy_with_empty_callback" /* BuyWithEmptyCallback */: {
964
+ const { user, loanToken, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
965
+ const amountStr = amount.toString();
966
+ const id = `${user}-${chainId.toString()}-${parameters.type}-${loanToken}`.toLowerCase();
967
+ const poolId = `${user}-${chainId.toString()}-${loanToken}`.toLowerCase();
968
+ return {
969
+ userPosition: {
970
+ id,
971
+ availableLiquidityQueueId: id,
972
+ user: user.toLowerCase(),
973
+ chainId,
974
+ amount: amountStr,
975
+ updatedAt
976
+ },
977
+ queues: [
978
+ {
979
+ queue: {
980
+ queueId: id,
981
+ availableLiquidityPoolId: poolId,
982
+ index,
983
+ callbackAmount: "0",
984
+ updatedAt
985
+ },
986
+ pool: {
987
+ id: poolId,
988
+ amount: amountStr,
989
+ updatedAt
990
+ }
991
+ }
992
+ ]
993
+ };
994
+ }
995
+ case "sell_erc20_callback" /* SellERC20Callback */: {
996
+ const {
997
+ user,
998
+ termId,
999
+ offerHash,
1000
+ chainId,
1001
+ amount,
1002
+ collaterals,
1003
+ index = 0,
1004
+ updatedAt = /* @__PURE__ */ new Date()
1005
+ } = parameters;
1006
+ const amountStr = amount.toString();
1007
+ const id = `${user}-${chainId.toString()}-${parameters.type}-${termId}-${offerHash}`.toLowerCase();
1008
+ return {
1009
+ userPosition: {
1010
+ id,
1011
+ availableLiquidityQueueId: id,
1012
+ user: user.toLowerCase(),
1013
+ chainId,
1014
+ amount: amountStr,
1015
+ updatedAt
1016
+ },
1017
+ queues: collaterals.map((collateral) => {
1018
+ const poolId = `${user}-${chainId.toString()}-${collateral.collateralAddress}`.toLowerCase();
1019
+ return {
1020
+ queue: {
1021
+ queueId: id,
1022
+ availableLiquidityPoolId: poolId,
1023
+ index,
1024
+ callbackAmount: collateral.callbackAmount.toString(),
1025
+ updatedAt
1026
+ },
1027
+ pool: {
1028
+ id: poolId,
1029
+ amount: collateral.balance.toString(),
1030
+ updatedAt
1031
+ }
1032
+ };
1033
+ })
1034
+ };
1035
+ }
1036
+ default: {
1037
+ throw new Error(`CallbackType not implemented`);
1038
+ }
1039
+ }
1040
+ }
1041
+ function getCallbackIdForOffer(offer) {
1042
+ if (offer.buy && offer.callback.data === "0x") {
1043
+ return `${offer.offering}-${offer.chainId.toString()}-${"buy_with_empty_callback" /* BuyWithEmptyCallback */}-${offer.loanToken}`.toLowerCase();
1044
+ }
1045
+ if (!offer.buy && offer.callback.data !== "0x" && WhitelistedCallbackAddresses["sell_erc20_callback" /* SellERC20Callback */].includes(
1046
+ offer.callback.address.toLowerCase()
1047
+ )) {
1048
+ return `${offer.offering}-${offer.chainId.toString()}-${"sell_erc20_callback" /* SellERC20Callback */}-${mempool.Offer.termId(offer)}-${offer.hash}`.toLowerCase();
1049
+ }
1050
+ return null;
1051
+ }
1052
+ function decodeSellERC20CallbackData(data) {
1053
+ if (!data || data === "0x") throw new Error("Empty callback data");
1054
+ try {
1055
+ const [collaterals, amounts] = viem.decodeAbiParameters(
1056
+ [{ type: "address[]" }, { type: "uint256[]" }],
1057
+ data
1058
+ );
1059
+ if (collaterals.length !== amounts.length) {
1060
+ throw new Error("Mismatched array lengths");
1061
+ }
1062
+ return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
1063
+ } catch (_) {
1064
+ throw new Error("Invalid SellERC20Callback callback data");
1065
+ }
1066
+ }
1067
+ function decode2(parameters) {
1068
+ const { type, data } = parameters;
1069
+ if (type === "sell_erc20_callback" /* SellERC20Callback */) {
1070
+ return decodeSellERC20CallbackData(data);
1071
+ }
1072
+ throw new Error(`CallbackType not implemented: ${type}`);
1073
+ }
1074
+ function encode2(parameters) {
1075
+ const { type, data } = parameters;
1076
+ if (type === "sell_erc20_callback" /* SellERC20Callback */) {
1077
+ return viem.encodeAbiParameters(
1078
+ [{ type: "address[]" }, { type: "uint256[]" }],
1079
+ [data.collaterals, data.amounts]
1080
+ );
1081
+ }
1082
+ throw new Error(`CallbackType not implemented: ${type}`);
1083
+ }
1084
+
1085
+ // src/core/Liquidity.ts
1086
+ var Liquidity_exports = {};
1087
+ __export(Liquidity_exports, {
1088
+ create: () => create,
1089
+ serialize: () => serialize
1090
+ });
1091
+
1092
+ // src/core/Abi.ts
1093
+ var Oracle = [
1094
+ {
1095
+ type: "function",
1096
+ name: "price",
1097
+ inputs: [],
1098
+ outputs: [{ name: "", type: "uint256" }],
1099
+ stateMutability: "view"
1100
+ }
1101
+ ];
1102
+ var Morpho = [
1103
+ {
1104
+ type: "function",
1105
+ name: "collateralOf",
1106
+ inputs: [
1107
+ {
1108
+ name: "",
1109
+ type: "address",
1110
+ internalType: "address"
1111
+ },
1112
+ {
1113
+ name: "",
1114
+ type: "bytes32",
1115
+ internalType: "bytes32"
1116
+ },
1117
+ {
1118
+ name: "",
1119
+ type: "address",
1120
+ internalType: "address"
1121
+ }
1122
+ ],
1123
+ outputs: [
1124
+ {
1125
+ name: "",
1126
+ type: "uint256",
1127
+ internalType: "uint256"
1128
+ }
1129
+ ],
1130
+ stateMutability: "view"
1131
+ },
1132
+ {
1133
+ type: "function",
1134
+ name: "debtOf",
1135
+ inputs: [
1136
+ {
1137
+ name: "",
1138
+ type: "address",
1139
+ internalType: "address"
1140
+ },
1141
+ {
1142
+ name: "",
1143
+ type: "bytes32",
1144
+ internalType: "bytes32"
1145
+ }
1146
+ ],
1147
+ outputs: [
1148
+ {
1149
+ name: "",
1150
+ type: "uint256",
1151
+ internalType: "uint256"
1152
+ }
1153
+ ],
1154
+ stateMutability: "view"
1155
+ }
1156
+ ];
1157
+
1158
+ // src/core/Fetchers.ts
1159
+ async function fetchBalancesAndAllowances(parameters) {
1160
+ const { client, spender, pairs, options } = parameters;
1161
+ if (pairs.length === 0) return /* @__PURE__ */ new Map();
1162
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
1163
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
1164
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
1165
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
1166
+ const out = /* @__PURE__ */ new Map();
1167
+ for (const pairsBatch of mempool.Utils.batch(pairs, batchSize)) {
1168
+ const balanceCalls = [];
1169
+ const allowanceCalls = [];
1170
+ for (const { user, token } of pairsBatch) {
1171
+ balanceCalls.push({
1172
+ address: token,
1173
+ abi: viem.erc20Abi,
1174
+ functionName: "balanceOf",
1175
+ args: [user]
1176
+ });
1177
+ allowanceCalls.push({
1178
+ address: token,
1179
+ abi: viem.erc20Abi,
1180
+ functionName: "allowance",
1181
+ args: [user, spender]
1182
+ });
1183
+ }
1184
+ const [balances, allowances] = await Promise.all([
1185
+ mempool.Utils.retry(
1186
+ () => client.multicall({
1187
+ allowFailure: false,
1188
+ contracts: balanceCalls,
1189
+ ...blockNumber ? { blockNumber } : {}
1190
+ }),
1191
+ retryAttempts,
1192
+ retryDelayMs
1193
+ ),
1194
+ mempool.Utils.retry(
1195
+ () => client.multicall({
1196
+ allowFailure: false,
1197
+ contracts: allowanceCalls,
1198
+ ...blockNumber ? { blockNumber } : {}
1199
+ }),
1200
+ retryAttempts,
1201
+ retryDelayMs
1202
+ )
1203
+ ]);
1204
+ for (let i = 0; i < pairsBatch.length; i++) {
1205
+ const { user, token } = pairsBatch[i];
1206
+ const balance = balances[i];
1207
+ const allowance = allowances[i];
1208
+ let perUser = out.get(user);
1209
+ if (!perUser) {
1210
+ perUser = /* @__PURE__ */ new Map();
1211
+ out.set(user, perUser);
1212
+ }
1213
+ perUser.set(token, { balance, allowance });
1214
+ }
1215
+ }
1216
+ return out;
1043
1217
  }
1044
- async function match(config, parameters) {
1045
- const url = new URL(`${config.url.toString()}v1/offers/match`);
1046
- url.searchParams.set("side", parameters.side);
1047
- url.searchParams.set("chain_id", parameters.chainId.toString());
1048
- if (parameters.rate !== void 0) {
1049
- url.searchParams.set("rate", parameters.rate.toString());
1050
- }
1051
- if (parameters.collaterals?.length) {
1052
- const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${viem.formatUnits(lltv, 16)}`).join("#");
1053
- url.searchParams.set("collaterals", collateralsStr);
1054
- }
1055
- if (parameters.maturity !== void 0) {
1056
- url.searchParams.set("maturity", parameters.maturity.toString());
1057
- }
1058
- if (parameters.minMaturity !== void 0) {
1059
- url.searchParams.set("min_maturity", parameters.minMaturity.toString());
1060
- }
1061
- if (parameters.maxMaturity !== void 0) {
1062
- url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
1063
- }
1064
- if (parameters.loanToken) {
1065
- url.searchParams.set("loan_token", parameters.loanToken);
1066
- }
1067
- if (parameters.creator) {
1068
- url.searchParams.set("creator", parameters.creator);
1069
- }
1070
- if (parameters.status?.length) {
1071
- url.searchParams.set("status", parameters.status.join(","));
1072
- }
1073
- if (parameters.cursor) {
1074
- url.searchParams.set("cursor", parameters.cursor);
1075
- }
1076
- if (parameters.limit !== void 0) {
1077
- url.searchParams.set("limit", parameters.limit.toString());
1218
+ async function fetchOraclePrices(parameters) {
1219
+ const { client, oracles, options } = parameters;
1220
+ if (oracles.length === 0) return /* @__PURE__ */ new Map();
1221
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
1222
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
1223
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
1224
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
1225
+ const out = /* @__PURE__ */ new Map();
1226
+ for (const oraclesBatch of mempool.Utils.batch(oracles, batchSize)) {
1227
+ const priceCalls = [];
1228
+ for (const oracle of oraclesBatch) {
1229
+ priceCalls.push({
1230
+ address: oracle,
1231
+ abi: Oracle,
1232
+ functionName: "price",
1233
+ args: []
1234
+ });
1235
+ }
1236
+ const prices = await mempool.Utils.retry(
1237
+ () => client.multicall({
1238
+ allowFailure: false,
1239
+ contracts: priceCalls,
1240
+ ...blockNumber ? { blockNumber } : {}
1241
+ }),
1242
+ retryAttempts,
1243
+ retryDelayMs
1244
+ );
1245
+ for (let i = 0; i < oraclesBatch.length; i++) {
1246
+ const oracle = oraclesBatch[i];
1247
+ const price = prices[i];
1248
+ out.set(oracle, price);
1249
+ }
1078
1250
  }
1079
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
1080
- const routerOffers = offers.map(mempool.Format.fromSnakeCase).map(fromResponse);
1081
- return {
1082
- cursor: returnedCursor,
1083
- offers: routerOffers.map(from).map(toResponse)
1084
- };
1251
+ return out;
1085
1252
  }
1086
- async function getApi(config, url) {
1087
- const pathname = url.pathname;
1088
- let action;
1089
- switch (true) {
1090
- case pathname.includes("/v1/offers/match"):
1091
- action = "match_offers";
1092
- break;
1093
- case pathname.includes("/v1/offers"):
1094
- action = "get_offers";
1095
- break;
1096
- default:
1097
- throw new HttpGetOffersFailedError("Unknown endpoint", {
1098
- details: `Unsupported path: ${pathname}`
1253
+ async function fetchCollateralAndDebt(parameters) {
1254
+ const { client, morphoAddress, queries, options } = parameters;
1255
+ if (queries.length === 0) return /* @__PURE__ */ new Map();
1256
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
1257
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
1258
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
1259
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
1260
+ const out = /* @__PURE__ */ new Map();
1261
+ for (const queriesBatch of mempool.Utils.batch(queries, batchSize)) {
1262
+ const collateralCalls = [];
1263
+ const debtCalls = [];
1264
+ for (const { user, termId, collateralAssets } of queriesBatch) {
1265
+ debtCalls.push({
1266
+ address: morphoAddress,
1267
+ abi: Morpho,
1268
+ functionName: "debtOf",
1269
+ args: [user, termId]
1099
1270
  });
1271
+ for (const collateralAsset of collateralAssets) {
1272
+ collateralCalls.push({
1273
+ address: morphoAddress,
1274
+ abi: Morpho,
1275
+ functionName: "collateralOf",
1276
+ args: [user, termId, collateralAsset]
1277
+ });
1278
+ }
1279
+ }
1280
+ const [collateralResults, debtResults] = await Promise.all([
1281
+ mempool.Utils.retry(
1282
+ () => client.multicall({
1283
+ allowFailure: false,
1284
+ contracts: collateralCalls,
1285
+ ...blockNumber ? { blockNumber } : {}
1286
+ }),
1287
+ retryAttempts,
1288
+ retryDelayMs
1289
+ ),
1290
+ mempool.Utils.retry(
1291
+ () => client.multicall({
1292
+ allowFailure: false,
1293
+ contracts: debtCalls,
1294
+ ...blockNumber ? { blockNumber } : {}
1295
+ }),
1296
+ retryAttempts,
1297
+ retryDelayMs
1298
+ )
1299
+ ]);
1300
+ let collateralIndex = 0;
1301
+ for (let queryIndex = 0; queryIndex < queriesBatch.length; queryIndex++) {
1302
+ const { user, termId, collateralAssets } = queriesBatch[queryIndex];
1303
+ const debt = debtResults[queryIndex];
1304
+ const collateralByAsset = /* @__PURE__ */ new Map();
1305
+ for (const collateralAsset of collateralAssets) {
1306
+ const collateralAmount = collateralResults[collateralIndex];
1307
+ collateralByAsset.set(collateralAsset.toLowerCase(), collateralAmount);
1308
+ collateralIndex++;
1309
+ }
1310
+ let perUser = out.get(user);
1311
+ if (!perUser) {
1312
+ perUser = /* @__PURE__ */ new Map();
1313
+ out.set(user, perUser);
1314
+ }
1315
+ perUser.set(termId, {
1316
+ collateralByAsset,
1317
+ debt
1318
+ });
1319
+ }
1100
1320
  }
1101
- const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
1102
- if (!schemaParseResult.success) {
1103
- throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
1104
- details: schemaParseResult.error.issues[0]?.message
1105
- });
1106
- }
1107
- const response = await fetch(url.toString(), {
1108
- method: "GET",
1109
- headers: config.headers
1321
+ return out;
1322
+ }
1323
+
1324
+ // src/core/Liquidity.ts
1325
+ async function fetchMaxBorrowCapacity(parameters) {
1326
+ const { client, collaterals, existingDebt, options } = parameters;
1327
+ if (collaterals.length === 0) return 0n;
1328
+ const uniqueOracles = [...new Set(collaterals.map((c) => c.oracle))];
1329
+ const oraclePrices = await fetchOraclePrices({
1330
+ client,
1331
+ oracles: uniqueOracles,
1332
+ options
1110
1333
  });
1111
- if (!response.ok) {
1112
- switch (response.status) {
1113
- case 401:
1114
- throw new HttpUnauthorizedError();
1115
- case 403:
1116
- throw new HttpForbiddenError();
1117
- case 429:
1118
- throw new HttpRateLimitError();
1334
+ const ORACLE_PRICE_SCALE = 10n ** 36n;
1335
+ const PRECISION = 10n ** 18n;
1336
+ let maxDebt = 0n;
1337
+ for (const collateral of collaterals) {
1338
+ const price = oraclePrices.get(collateral.oracle);
1339
+ if (!price) continue;
1340
+ const collateralQuoted = collateral.totalAmount * price / ORACLE_PRICE_SCALE;
1341
+ const collateralMaxDebt = collateralQuoted * collateral.lltv / PRECISION;
1342
+ maxDebt = maxDebt + collateralMaxDebt;
1343
+ }
1344
+ if (existingDebt > 0n) {
1345
+ if (maxDebt > existingDebt) {
1346
+ maxDebt = maxDebt - existingDebt;
1347
+ } else {
1348
+ maxDebt = 0n;
1119
1349
  }
1120
- throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
1121
- details: await response.text()
1122
- });
1123
1350
  }
1124
- return response.json();
1351
+ return maxDebt;
1125
1352
  }
1126
- var InvalidUrlError = class extends mempool.Errors.BaseError {
1127
- constructor(url) {
1128
- super(`URL "${url}" is not http/https.`);
1129
- __publicField(this, "name", "Router.InvalidUrlError");
1130
- }
1353
+ var create = (config) => {
1354
+ const { client } = config;
1355
+ return {
1356
+ fetchBuyLiquidity: (parameters) => fetchBuyLiquidity({ ...parameters, client }),
1357
+ fetchSellLiquidity: (parameters) => fetchSellLiquidity({ ...parameters, client })
1358
+ };
1131
1359
  };
1132
- var HttpUnauthorizedError = class extends mempool.Errors.BaseError {
1133
- constructor() {
1134
- super("Unauthorized.", {
1135
- metaMessages: ["Ensure that an API key is provided."]
1136
- });
1137
- __publicField(this, "name", "Router.HttpUnauthorizedError");
1360
+ async function fetchBuyLiquidity(parameters) {
1361
+ const { client, chainId, spender, pairs, options } = parameters;
1362
+ const map = await fetchBalancesAndAllowances({
1363
+ client,
1364
+ spender,
1365
+ pairs: pairs.map(({ user, loanToken }) => ({ user, token: loanToken })),
1366
+ options
1367
+ });
1368
+ const out = [];
1369
+ for (const [user, perLoanToken] of map) {
1370
+ for (const [loanToken, { balance, allowance }] of perLoanToken) {
1371
+ const amount = balance < allowance ? balance : allowance;
1372
+ out.push(
1373
+ buildLiquidity({
1374
+ type: "buy_with_empty_callback" /* BuyWithEmptyCallback */,
1375
+ user,
1376
+ loanToken,
1377
+ chainId,
1378
+ amount,
1379
+ index: 0
1380
+ })
1381
+ );
1382
+ }
1138
1383
  }
1139
- };
1140
- var HttpForbiddenError = class extends mempool.Errors.BaseError {
1141
- constructor() {
1142
- super("Forbidden.", {
1143
- metaMessages: ["Ensure that the API key is valid."]
1384
+ return out;
1385
+ }
1386
+ async function fetchSellLiquidity(parameters) {
1387
+ const { client, chainId, spender, morphoAddress, offers, options } = parameters;
1388
+ const collateralPairs = [];
1389
+ for (const offer of offers) {
1390
+ const user = offer.offering;
1391
+ const callbackData = decode2({
1392
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1393
+ data: offer.callback.data
1144
1394
  });
1145
- __publicField(this, "name", "Router.HttpForbiddenError");
1395
+ for (const { collateral } of callbackData) {
1396
+ collateralPairs.push({ user, token: collateral });
1397
+ }
1146
1398
  }
1147
- };
1148
- var HttpRateLimitError = class extends mempool.Errors.BaseError {
1149
- constructor() {
1150
- super("Rate limit exceeded.", {
1151
- metaMessages: [
1152
- "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
1153
- ]
1399
+ const map = await fetchBalancesAndAllowances({
1400
+ client,
1401
+ spender,
1402
+ pairs: collateralPairs,
1403
+ options
1404
+ });
1405
+ const collateralDebt = [];
1406
+ for (const offer of offers) {
1407
+ const user = offer.offering;
1408
+ const termId = mempool.Offer.termId(offer);
1409
+ const collateralAssets = offer.collaterals.map((collateral) => collateral.asset);
1410
+ collateralDebt.push({
1411
+ user,
1412
+ termId,
1413
+ collateralAssets
1154
1414
  });
1155
- __publicField(this, "name", "Router.HttpRateLimitError");
1156
1415
  }
1157
- };
1158
- var HttpGetOffersFailedError = class extends mempool.Errors.BaseError {
1159
- constructor(message, { details } = {}) {
1160
- super(message, {
1161
- metaMessages: [details]
1416
+ const existingPositions = await fetchCollateralAndDebt({
1417
+ client,
1418
+ morphoAddress,
1419
+ queries: collateralDebt,
1420
+ options
1421
+ });
1422
+ const out = [];
1423
+ for (const offer of offers) {
1424
+ const user = offer.offering;
1425
+ const termId = mempool.Offer.termId(offer);
1426
+ const callbackData = decode2({
1427
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1428
+ data: offer.callback.data
1162
1429
  });
1163
- __publicField(this, "name", "Router.HttpGetOffersFailedError");
1430
+ const callbackCollaterals = callbackData.map((callbackItem) => {
1431
+ const userMap = map.get(user);
1432
+ const tokenData = userMap?.get(callbackItem.collateral);
1433
+ if (!tokenData) return null;
1434
+ const offerCollateral = offer.collaterals.find(
1435
+ (c) => c.asset.toLowerCase() === callbackItem.collateral.toLowerCase()
1436
+ );
1437
+ if (!offerCollateral) return null;
1438
+ const { balance, allowance } = tokenData;
1439
+ const availableAmount = balance < allowance ? balance : allowance;
1440
+ return {
1441
+ collateralAddress: callbackItem.collateral,
1442
+ balance: availableAmount,
1443
+ callbackAmount: callbackItem.amount
1444
+ };
1445
+ }).filter((collateral) => collateral !== null);
1446
+ if (callbackCollaterals.length === 0) continue;
1447
+ const existingPosition = existingPositions.get(user)?.get(termId);
1448
+ const collaterals = offer.collaterals.map((c) => {
1449
+ const callbackCollateral = callbackCollaterals.find(
1450
+ (cc) => cc.collateralAddress.toLowerCase() === c.asset.toLowerCase()
1451
+ );
1452
+ const callbackAmount = callbackCollateral?.balance ?? 0n;
1453
+ const existingAmount = existingPosition?.collateralByAsset.get(c.asset.toLowerCase()) ?? 0n;
1454
+ return {
1455
+ asset: c.asset,
1456
+ oracle: c.oracle,
1457
+ lltv: c.lltv,
1458
+ totalAmount: existingAmount + callbackAmount
1459
+ };
1460
+ });
1461
+ const existingDebt = existingPosition?.debt ?? 0n;
1462
+ const userPositionAmount = await fetchMaxBorrowCapacity({
1463
+ client,
1464
+ collaterals,
1465
+ existingDebt,
1466
+ options
1467
+ });
1468
+ out.push(
1469
+ buildLiquidity({
1470
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1471
+ user,
1472
+ termId,
1473
+ offerHash: offer.hash,
1474
+ chainId,
1475
+ amount: userPositionAmount,
1476
+ collaterals: callbackCollaterals,
1477
+ index: 0
1478
+ })
1479
+ );
1164
1480
  }
1165
- };
1481
+ return out;
1482
+ }
1483
+ function serialize(liquidity) {
1484
+ const normalized = {
1485
+ userPosition: {
1486
+ id: liquidity.userPosition.id,
1487
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1488
+ user: liquidity.userPosition.user,
1489
+ chainId: String(liquidity.userPosition.chainId),
1490
+ amount: String(liquidity.userPosition.amount)
1491
+ },
1492
+ queues: liquidity.queues.map((queueWithPool) => ({
1493
+ queue: {
1494
+ queueId: queueWithPool.queue.queueId,
1495
+ availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
1496
+ index: queueWithPool.queue.index
1497
+ },
1498
+ pool: {
1499
+ id: queueWithPool.pool.id,
1500
+ amount: String(queueWithPool.pool.amount)
1501
+ }
1502
+ })).sort(
1503
+ (left, right) => {
1504
+ const leftQueueId = left.queue.queueId || "";
1505
+ const rightQueueId = right.queue.queueId || "";
1506
+ if (leftQueueId < rightQueueId) return -1;
1507
+ if (leftQueueId > rightQueueId) return 1;
1508
+ const leftPoolId = left.pool.id;
1509
+ const rightPoolId = right.pool.id;
1510
+ if (leftPoolId < rightPoolId) return -1;
1511
+ if (leftPoolId > rightPoolId) return 1;
1512
+ const leftIndex = left.queue.index;
1513
+ const rightIndex = right.queue.index;
1514
+ if (leftIndex < rightIndex) return -1;
1515
+ if (leftIndex > rightIndex) return 1;
1516
+ return 0;
1517
+ }
1518
+ )
1519
+ };
1520
+ return JSON.stringify(normalized);
1521
+ }
1166
1522
 
1167
1523
  // src/core/Validation.ts
1168
1524
  var Validation_exports = {};
@@ -1268,7 +1624,7 @@ function morpho() {
1268
1624
  (offer, _) => {
1269
1625
  if (!offer.buy && offer.callback.data !== "0x") {
1270
1626
  const allowed = new Set(
1271
- WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].map(
1627
+ WhitelistedCallbackAddresses["sell_erc20_callback" /* SellERC20Callback */].map(
1272
1628
  (a) => a.toLowerCase()
1273
1629
  )
1274
1630
  );
@@ -1284,8 +1640,8 @@ function morpho() {
1284
1640
  (offer, _) => {
1285
1641
  if (!offer.buy && offer.callback.data !== "0x") {
1286
1642
  try {
1287
- const decoded = decode({
1288
- type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1643
+ const decoded = decode2({
1644
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1289
1645
  data: offer.callback.data
1290
1646
  });
1291
1647
  if (decoded.length === 0) {
@@ -1302,8 +1658,8 @@ function morpho() {
1302
1658
  (offer, _) => {
1303
1659
  if (!offer.buy && offer.callback.data !== "0x") {
1304
1660
  try {
1305
- const decoded = decode({
1306
- type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1661
+ const decoded = decode2({
1662
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1307
1663
  data: offer.callback.data
1308
1664
  });
1309
1665
  const offerCollaterals = new Set(
@@ -1319,10 +1675,19 @@ function morpho() {
1319
1675
  }
1320
1676
  }
1321
1677
  );
1678
+ const maturity = single("maturity", (offer, _) => {
1679
+ const allowedMaturities = [mempool.Maturity.from("end_of_month"), mempool.Maturity.from("end_of_next_month")];
1680
+ if (!allowedMaturities.includes(offer.maturity)) {
1681
+ return {
1682
+ message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}`
1683
+ };
1684
+ }
1685
+ });
1322
1686
  return [
1323
1687
  chainId,
1324
1688
  loanToken,
1325
1689
  expiry,
1690
+ maturity,
1326
1691
  // note: callback rules should be the last ones, since they do not mean that the offer is forever invalid
1327
1692
  // integrators should be able to choose if they want to keep the offer or not
1328
1693
  sellEmptyCallback,
@@ -1336,7 +1701,7 @@ function morpho() {
1336
1701
  exports.Callback = Callback_exports;
1337
1702
  exports.Cursor = Cursor_exports;
1338
1703
  exports.Liquidity = Liquidity_exports;
1339
- exports.Router = Client_exports;
1704
+ exports.RouterApi = Client_exports;
1340
1705
  exports.RouterOffer = RouterOffer_exports;
1341
1706
  exports.Validation = Validation_exports;
1342
1707
  exports.ValidationRule = ValidationRule_exports;