@morpho-dev/router 0.1.11 → 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 (32) 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/0007_snapshot.json → router_v1.1/meta/0000_snapshot.json} +21 -21
  4. package/dist/drizzle/router_v1.1/meta/_journal.json +13 -0
  5. package/dist/index.browser.d.cts +186 -159
  6. package/dist/index.browser.d.ts +186 -159
  7. package/dist/index.browser.js +807 -491
  8. package/dist/index.browser.js.map +1 -1
  9. package/dist/index.browser.mjs +809 -493
  10. package/dist/index.browser.mjs.map +1 -1
  11. package/dist/index.node.d.cts +1795 -1759
  12. package/dist/index.node.d.ts +1795 -1759
  13. package/dist/index.node.js +2923 -2495
  14. package/dist/index.node.js.map +1 -1
  15. package/dist/index.node.mjs +2925 -2496
  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/0006_add-callback-amount-to-queues-table.sql +0 -1
  24. package/dist/drizzle/offers_v1.1/0007_add-index-to-created-at.sql +0 -2
  25. package/dist/drizzle/offers_v1.1/meta/0000_snapshot.json +0 -827
  26. package/dist/drizzle/offers_v1.1/meta/0001_snapshot.json +0 -827
  27. package/dist/drizzle/offers_v1.1/meta/0002_snapshot.json +0 -833
  28. package/dist/drizzle/offers_v1.1/meta/0003_snapshot.json +0 -833
  29. package/dist/drizzle/offers_v1.1/meta/0004_snapshot.json +0 -839
  30. package/dist/drizzle/offers_v1.1/meta/0005_snapshot.json +0 -877
  31. package/dist/drizzle/offers_v1.1/meta/0006_snapshot.json +0 -884
  32. package/dist/drizzle/offers_v1.1/meta/_journal.json +0 -62
@@ -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,157 +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
- ].map((address) => address.toLowerCase())
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
- switch (parameters.type) {
42
- case "buy_with_empty_callback" /* BuyWithEmptyCallback */: {
43
- const { user, loanToken, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
44
- const amountStr = amount.toString();
45
- const id = `${user}-${chainId.toString()}-${parameters.type}-${loanToken}`.toLowerCase();
46
- const poolId = `${user}-${chainId.toString()}-${loanToken}`.toLowerCase();
47
- return {
48
- userPosition: {
49
- id,
50
- availableLiquidityQueueId: id,
51
- user: user.toLowerCase(),
52
- chainId,
53
- amount: amountStr,
54
- updatedAt
55
- },
56
- queues: [
57
- {
58
- queue: {
59
- queueId: id,
60
- availableLiquidityPoolId: poolId,
61
- index,
62
- callbackAmount: "0",
63
- updatedAt
64
- },
65
- pool: {
66
- id: poolId,
67
- amount: amountStr,
68
- updatedAt
69
- }
70
- }
71
- ]
72
- };
73
- }
74
- case "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */: {
75
- const {
76
- user,
77
- termId,
78
- chainId,
79
- amount,
80
- collaterals,
81
- index = 0,
82
- updatedAt = /* @__PURE__ */ new Date()
83
- } = parameters;
84
- const amountStr = amount.toString();
85
- const id = `${user}-${chainId.toString()}-${parameters.type}-${termId}`.toLowerCase();
86
- return {
87
- userPosition: {
88
- id,
89
- availableLiquidityQueueId: id,
90
- user: user.toLowerCase(),
91
- chainId,
92
- amount: amountStr,
93
- updatedAt
94
- },
95
- queues: collaterals.map((collateral) => {
96
- const poolId = `${user}-${chainId.toString()}-${collateral.collateralAddress}`.toLowerCase();
97
- return {
98
- queue: {
99
- queueId: id,
100
- availableLiquidityPoolId: poolId,
101
- index,
102
- callbackAmount: collateral.callbackAmount.toString(),
103
- updatedAt
104
- },
105
- pool: {
106
- id: poolId,
107
- amount: collateral.balance.toString(),
108
- updatedAt
109
- }
110
- };
111
- })
112
- };
113
- }
114
- default: {
115
- throw new Error(`CallbackType not implemented`);
116
- }
66
+ function from(input) {
67
+ try {
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);
117
76
  }
118
77
  }
119
- function getCallbackIdForOffer(offer) {
120
- if (offer.buy && offer.callback.data === "0x") {
121
- return `${offer.offering}-${offer.chainId.toString()}-${"buy_with_empty_callback" /* BuyWithEmptyCallback */}-${offer.loanToken}`.toLowerCase();
122
- }
123
- if (!offer.buy && offer.callback.data !== "0x" && WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].includes(
124
- offer.callback.address.toLowerCase()
125
- )) {
126
- return `${offer.offering}-${offer.chainId.toString()}-${"sell_withdraw_from_wallet" /* SellWithdrawFromWallet */}-${mempool.Offer.termId(offer)}`.toLowerCase();
127
- }
128
- return null;
78
+ function fromSnakeCase(input) {
79
+ return from(mempool.Format.fromSnakeCase(input));
129
80
  }
130
- function decodeSellWithdrawFromWalletData(data) {
131
- if (!data || data === "0x") throw new Error("Empty callback data");
132
- try {
133
- const [collaterals, amounts] = viem.decodeAbiParameters(
134
- [{ type: "address[]" }, { type: "uint256[]" }],
135
- data
136
- );
137
- if (collaterals.length !== amounts.length) {
138
- throw new Error("Mismatched array lengths");
139
- }
140
- return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
141
- } catch (_) {
142
- throw new Error("Invalid SellWithdrawFromWallet callback data");
143
- }
81
+ function toSnakeCase(offer) {
82
+ return mempool.Format.toSnakeCase(offer);
144
83
  }
145
- function decode(parameters) {
146
- const { type, data } = parameters;
147
- if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
148
- return decodeSellWithdrawFromWalletData(data);
149
- }
150
- throw new Error(`CallbackType not implemented: ${type}`);
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
+ });
151
92
  }
152
- function encode(parameters) {
153
- const { type, data } = parameters;
154
- if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
155
- return viem.encodeAbiParameters(
156
- [{ type: "address[]" }, { type: "uint256[]" }],
157
- [data.collaterals, data.amounts]
158
- );
159
- }
160
- throw new Error(`CallbackType not implemented: ${type}`);
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
+ };
161
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
+ };
162
109
 
163
110
  // src/core/Cursor.ts
164
111
  var Cursor_exports = {};
165
112
  __export(Cursor_exports, {
166
- decode: () => decode2,
167
- encode: () => encode2,
113
+ decode: () => decode,
114
+ encode: () => encode,
168
115
  validate: () => validate
169
116
  });
170
117
  function validate(cursor) {
@@ -236,242 +183,17 @@ function validate(cursor) {
236
183
  }
237
184
  return true;
238
185
  }
239
- function encode2(c) {
186
+ function encode(c) {
240
187
  return jsBase64.Base64.encodeURL(JSON.stringify(c));
241
188
  }
242
- function decode2(token) {
189
+ function decode(token) {
243
190
  if (!token) return null;
244
191
  const decoded = JSON.parse(jsBase64.Base64.decode(token));
245
192
  validate(decoded);
246
193
  return decoded;
247
194
  }
248
195
 
249
- // src/core/Liquidity.ts
250
- var Liquidity_exports = {};
251
- __export(Liquidity_exports, {
252
- fetch: () => fetch2,
253
- fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
254
- serialize: () => serialize
255
- });
256
- async function fetchBalancesAndAllowances(parameters) {
257
- const { client, spender, pairs, options } = parameters;
258
- if (pairs.length === 0) return /* @__PURE__ */ new Map();
259
- const batchSize = Math.max(1, options?.batchSize ?? 5e3);
260
- const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
261
- const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
262
- const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
263
- const out = /* @__PURE__ */ new Map();
264
- for (const pairsBatch of mempool.Utils.batch(pairs, batchSize)) {
265
- const balanceContracts = [];
266
- const allowanceContracts = [];
267
- for (const { user, token } of pairsBatch) {
268
- balanceContracts.push({
269
- address: token,
270
- abi: viem.erc20Abi,
271
- functionName: "balanceOf",
272
- args: [user]
273
- });
274
- allowanceContracts.push({
275
- address: token,
276
- abi: viem.erc20Abi,
277
- functionName: "allowance",
278
- args: [user, spender]
279
- });
280
- }
281
- const [balances, allowances] = await Promise.all([
282
- mempool.Utils.retry(
283
- () => client.multicall({
284
- allowFailure: false,
285
- contracts: balanceContracts,
286
- ...blockNumber ? { blockNumber } : {}
287
- }),
288
- retryAttempts,
289
- retryDelayMs
290
- ),
291
- mempool.Utils.retry(
292
- () => client.multicall({
293
- allowFailure: false,
294
- contracts: allowanceContracts,
295
- ...blockNumber ? { blockNumber } : {}
296
- }),
297
- retryAttempts,
298
- retryDelayMs
299
- )
300
- ]);
301
- for (let i = 0; i < pairsBatch.length; i++) {
302
- const { user, token } = pairsBatch[i];
303
- const balance = balances[i];
304
- const allowance = allowances[i];
305
- let perUser = out.get(user);
306
- if (!perUser) {
307
- perUser = /* @__PURE__ */ new Map();
308
- out.set(user, perUser);
309
- }
310
- perUser.set(token, { balance, allowance });
311
- }
312
- }
313
- return out;
314
- }
315
- async function fetch2(parameters) {
316
- const { client, chainId, spender, type, pairs, options } = parameters;
317
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
318
- throw new Error(`CallbackType not implemented: ${type}`);
319
- const map = await fetchBalancesAndAllowances({
320
- client,
321
- spender,
322
- pairs: pairs.map(({ user, loanToken }) => ({ user, token: loanToken })),
323
- options
324
- });
325
- const out = [];
326
- for (const [user, perLoanToken] of map) {
327
- for (const [loanToken, { balance, allowance }] of perLoanToken) {
328
- const amount = balance < allowance ? balance : allowance;
329
- out.push(
330
- buildLiquidity({
331
- type,
332
- user,
333
- loanToken,
334
- chainId,
335
- amount,
336
- index: 0
337
- })
338
- );
339
- }
340
- }
341
- return out;
342
- }
343
- function serialize(liquidity) {
344
- const normalized = {
345
- userPosition: {
346
- id: liquidity.userPosition.id,
347
- availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
348
- user: liquidity.userPosition.user,
349
- chainId: String(liquidity.userPosition.chainId),
350
- amount: String(liquidity.userPosition.amount)
351
- },
352
- queues: liquidity.queues.map((queueWithPool) => ({
353
- queue: {
354
- queueId: queueWithPool.queue.queueId,
355
- availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
356
- index: queueWithPool.queue.index
357
- },
358
- pool: {
359
- id: queueWithPool.pool.id,
360
- amount: String(queueWithPool.pool.amount)
361
- }
362
- })).sort(
363
- (left, right) => {
364
- const leftQueueId = left.queue.queueId || "";
365
- const rightQueueId = right.queue.queueId || "";
366
- if (leftQueueId < rightQueueId) return -1;
367
- if (leftQueueId > rightQueueId) return 1;
368
- const leftPoolId = left.pool.id;
369
- const rightPoolId = right.pool.id;
370
- if (leftPoolId < rightPoolId) return -1;
371
- if (leftPoolId > rightPoolId) return 1;
372
- const leftIndex = left.queue.index;
373
- const rightIndex = right.queue.index;
374
- if (leftIndex < rightIndex) return -1;
375
- if (leftIndex > rightIndex) return 1;
376
- return 0;
377
- }
378
- )
379
- };
380
- return JSON.stringify(normalized);
381
- }
382
-
383
- // src/core/RouterOffer.ts
384
- var RouterOffer_exports = {};
385
- __export(RouterOffer_exports, {
386
- InvalidRouterOfferError: () => InvalidRouterOfferError,
387
- OfferStatusValues: () => OfferStatusValues,
388
- RouterOfferSchema: () => RouterOfferSchema,
389
- consumedEvent: () => consumedEvent,
390
- from: () => from,
391
- fromConsumedLog: () => fromConsumedLog,
392
- fromSnakeCase: () => fromSnakeCase,
393
- random: () => random,
394
- toSnakeCase: () => toSnakeCase
395
- });
396
- var OfferStatusValues = [
397
- "valid",
398
- "callback_not_supported",
399
- "callback_error",
400
- "unverified"
401
- ];
402
- var RouterOfferSchema = (parameters) => mempool.Offer.OfferSchema(parameters).extend({
403
- consumed: v4.z.bigint({ coerce: true }).min(0n).max(viem.maxUint256),
404
- status: v4.z.enum(OfferStatusValues),
405
- metadata: v4.z.object({
406
- issue: v4.z.string()
407
- }).optional()
408
- });
409
- var consumedEvent = {
410
- type: "event",
411
- name: "Consumed",
412
- inputs: [
413
- { name: "user", type: "address", indexed: true, internalType: "address" },
414
- { name: "nonce", type: "uint256", indexed: true, internalType: "uint256" },
415
- { name: "amount", type: "uint256", indexed: false, internalType: "uint256" }
416
- ],
417
- anonymous: false
418
- };
419
- function from(input) {
420
- try {
421
- const parsedOffer = RouterOfferSchema({ omitHash: true }).parse(input);
422
- const parsedHash = mempool.Offer.OfferHashSchema.parse(mempool.Offer.hash(parsedOffer));
423
- return {
424
- ...parsedOffer,
425
- hash: parsedHash
426
- };
427
- } catch (error) {
428
- throw new InvalidRouterOfferError(error);
429
- }
430
- }
431
- function fromSnakeCase(input) {
432
- return from(mempool.Format.fromSnakeCase(input));
433
- }
434
- function toSnakeCase(offer) {
435
- return mempool.Format.toSnakeCase(offer);
436
- }
437
- function random() {
438
- const baseOffer = mempool.Offer.random();
439
- return from({
440
- ...baseOffer,
441
- status: "valid",
442
- metadata: void 0,
443
- consumed: 0n
444
- });
445
- }
446
- function fromConsumedLog(parameters) {
447
- const { blockNumber, logIndex, chainId, transactionHash, user, nonce, amount } = parameters;
448
- return {
449
- id: `${blockNumber.toString()}-${logIndex.toString()}-${chainId}-${transactionHash}`,
450
- chainId: BigInt(chainId),
451
- offering: user,
452
- nonce,
453
- amount
454
- };
455
- }
456
- var InvalidRouterOfferError = class extends mempool.Errors.BaseError {
457
- constructor(error) {
458
- super("Invalid router offer.", { cause: error });
459
- __publicField(this, "name", "RouterOffer.InvalidRouterOfferError");
460
- }
461
- };
462
-
463
- // src/core/router/Client.ts
464
- var Client_exports = {};
465
- __export(Client_exports, {
466
- HttpForbiddenError: () => HttpForbiddenError,
467
- HttpGetOffersFailedError: () => HttpGetOffersFailedError,
468
- HttpRateLimitError: () => HttpRateLimitError,
469
- HttpUnauthorizedError: () => HttpUnauthorizedError,
470
- InvalidUrlError: () => InvalidUrlError,
471
- connect: () => connect,
472
- get: () => get,
473
- match: () => match
474
- });
196
+ // src/api/Schema/requests.ts
475
197
  var MAX_LIMIT = 100;
476
198
  var DEFAULT_LIMIT = 20;
477
199
  var MAX_LLTV = 100;
@@ -648,7 +370,7 @@ var GetOffersQueryParams = v4.z.object({
648
370
  (val) => {
649
371
  if (!val) return true;
650
372
  try {
651
- const decoded = decode2(val);
373
+ const decoded = decode(val);
652
374
  return decoded !== null;
653
375
  } catch (_error) {
654
376
  return false;
@@ -820,7 +542,7 @@ var MatchOffersQueryParams = v4.z.object({
820
542
  (val) => {
821
543
  if (!val) return true;
822
544
  try {
823
- const decoded = decode2(val);
545
+ const decoded = decode(val);
824
546
  return decoded !== null;
825
547
  } catch (_error) {
826
548
  return false;
@@ -860,7 +582,7 @@ function safeParse(action, query, error) {
860
582
  });
861
583
  }
862
584
 
863
- // src/core/apiSchema/openapi.ts
585
+ // src/api/Schema/openapi.ts
864
586
  var successResponseSchema = v4.z.object({
865
587
  status: v4.z.literal("success"),
866
588
  cursor: v4.z.string().nullable(),
@@ -963,7 +685,7 @@ zodOpenapi.createDocument({
963
685
  paths
964
686
  });
965
687
 
966
- // src/core/apiSchema/utils.ts
688
+ // src/api/Schema/utils.ts
967
689
  function toResponse(routerOffer) {
968
690
  const { consumed, status, metadata, ...offer } = routerOffer;
969
691
  return {
@@ -983,7 +705,7 @@ function fromResponse(offerResponse) {
983
705
  };
984
706
  }
985
707
 
986
- // src/core/router/Client.ts
708
+ // src/api/Client.ts
987
709
  function connect(opts) {
988
710
  const u = new URL(opts?.url || "https://router.morpho.dev");
989
711
  if (u.protocol !== "http:" && u.protocol !== "https:") {
@@ -1083,135 +805,720 @@ async function get(config, parameters) {
1083
805
  if (parameters.limit !== void 0) {
1084
806
  url.searchParams.set("limit", parameters.limit.toString());
1085
807
  }
1086
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
1087
- const routerOffers = offers.map(mempool.Format.fromSnakeCase).map(fromResponse);
1088
- return {
1089
- cursor: returnedCursor,
1090
- offers: routerOffers.map(from).map(toResponse)
1091
- };
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;
1092
1217
  }
1093
- async function match(config, parameters) {
1094
- const url = new URL(`${config.url.toString()}v1/offers/match`);
1095
- url.searchParams.set("side", parameters.side);
1096
- url.searchParams.set("chain_id", parameters.chainId.toString());
1097
- if (parameters.rate !== void 0) {
1098
- url.searchParams.set("rate", parameters.rate.toString());
1099
- }
1100
- if (parameters.collaterals?.length) {
1101
- const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${viem.formatUnits(lltv, 16)}`).join("#");
1102
- url.searchParams.set("collaterals", collateralsStr);
1103
- }
1104
- if (parameters.maturity !== void 0) {
1105
- url.searchParams.set("maturity", parameters.maturity.toString());
1106
- }
1107
- if (parameters.minMaturity !== void 0) {
1108
- url.searchParams.set("min_maturity", parameters.minMaturity.toString());
1109
- }
1110
- if (parameters.maxMaturity !== void 0) {
1111
- url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
1112
- }
1113
- if (parameters.loanToken) {
1114
- url.searchParams.set("loan_token", parameters.loanToken);
1115
- }
1116
- if (parameters.creator) {
1117
- url.searchParams.set("creator", parameters.creator);
1118
- }
1119
- if (parameters.status?.length) {
1120
- url.searchParams.set("status", parameters.status.join(","));
1121
- }
1122
- if (parameters.cursor) {
1123
- url.searchParams.set("cursor", parameters.cursor);
1124
- }
1125
- if (parameters.limit !== void 0) {
1126
- 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
+ }
1127
1250
  }
1128
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
1129
- const routerOffers = offers.map(mempool.Format.fromSnakeCase).map(fromResponse);
1130
- return {
1131
- cursor: returnedCursor,
1132
- offers: routerOffers.map(from).map(toResponse)
1133
- };
1251
+ return out;
1134
1252
  }
1135
- async function getApi(config, url) {
1136
- const pathname = url.pathname;
1137
- let action;
1138
- switch (true) {
1139
- case pathname.includes("/v1/offers/match"):
1140
- action = "match_offers";
1141
- break;
1142
- case pathname.includes("/v1/offers"):
1143
- action = "get_offers";
1144
- break;
1145
- default:
1146
- throw new HttpGetOffersFailedError("Unknown endpoint", {
1147
- 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]
1148
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
+ }
1149
1320
  }
1150
- const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
1151
- if (!schemaParseResult.success) {
1152
- throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
1153
- details: schemaParseResult.error.issues[0]?.message
1154
- });
1155
- }
1156
- const response = await fetch(url.toString(), {
1157
- method: "GET",
1158
- 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
1159
1333
  });
1160
- if (!response.ok) {
1161
- switch (response.status) {
1162
- case 401:
1163
- throw new HttpUnauthorizedError();
1164
- case 403:
1165
- throw new HttpForbiddenError();
1166
- case 429:
1167
- 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;
1168
1349
  }
1169
- throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
1170
- details: await response.text()
1171
- });
1172
1350
  }
1173
- return response.json();
1351
+ return maxDebt;
1174
1352
  }
1175
- var InvalidUrlError = class extends mempool.Errors.BaseError {
1176
- constructor(url) {
1177
- super(`URL "${url}" is not http/https.`);
1178
- __publicField(this, "name", "Router.InvalidUrlError");
1179
- }
1353
+ var create = (config) => {
1354
+ const { client } = config;
1355
+ return {
1356
+ fetchBuyLiquidity: (parameters) => fetchBuyLiquidity({ ...parameters, client }),
1357
+ fetchSellLiquidity: (parameters) => fetchSellLiquidity({ ...parameters, client })
1358
+ };
1180
1359
  };
1181
- var HttpUnauthorizedError = class extends mempool.Errors.BaseError {
1182
- constructor() {
1183
- super("Unauthorized.", {
1184
- metaMessages: ["Ensure that an API key is provided."]
1185
- });
1186
- __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
+ }
1187
1383
  }
1188
- };
1189
- var HttpForbiddenError = class extends mempool.Errors.BaseError {
1190
- constructor() {
1191
- super("Forbidden.", {
1192
- 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
1193
1394
  });
1194
- __publicField(this, "name", "Router.HttpForbiddenError");
1395
+ for (const { collateral } of callbackData) {
1396
+ collateralPairs.push({ user, token: collateral });
1397
+ }
1195
1398
  }
1196
- };
1197
- var HttpRateLimitError = class extends mempool.Errors.BaseError {
1198
- constructor() {
1199
- super("Rate limit exceeded.", {
1200
- metaMessages: [
1201
- "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
1202
- ]
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
1203
1414
  });
1204
- __publicField(this, "name", "Router.HttpRateLimitError");
1205
1415
  }
1206
- };
1207
- var HttpGetOffersFailedError = class extends mempool.Errors.BaseError {
1208
- constructor(message, { details } = {}) {
1209
- super(message, {
1210
- 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
1211
1429
  });
1212
- __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
+ );
1213
1480
  }
1214
- };
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
+ }
1215
1522
 
1216
1523
  // src/core/Validation.ts
1217
1524
  var Validation_exports = {};
@@ -1317,7 +1624,7 @@ function morpho() {
1317
1624
  (offer, _) => {
1318
1625
  if (!offer.buy && offer.callback.data !== "0x") {
1319
1626
  const allowed = new Set(
1320
- WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].map(
1627
+ WhitelistedCallbackAddresses["sell_erc20_callback" /* SellERC20Callback */].map(
1321
1628
  (a) => a.toLowerCase()
1322
1629
  )
1323
1630
  );
@@ -1333,8 +1640,8 @@ function morpho() {
1333
1640
  (offer, _) => {
1334
1641
  if (!offer.buy && offer.callback.data !== "0x") {
1335
1642
  try {
1336
- const decoded = decode({
1337
- type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1643
+ const decoded = decode2({
1644
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1338
1645
  data: offer.callback.data
1339
1646
  });
1340
1647
  if (decoded.length === 0) {
@@ -1351,8 +1658,8 @@ function morpho() {
1351
1658
  (offer, _) => {
1352
1659
  if (!offer.buy && offer.callback.data !== "0x") {
1353
1660
  try {
1354
- const decoded = decode({
1355
- type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1661
+ const decoded = decode2({
1662
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1356
1663
  data: offer.callback.data
1357
1664
  });
1358
1665
  const offerCollaterals = new Set(
@@ -1368,10 +1675,19 @@ function morpho() {
1368
1675
  }
1369
1676
  }
1370
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
+ });
1371
1686
  return [
1372
1687
  chainId,
1373
1688
  loanToken,
1374
1689
  expiry,
1690
+ maturity,
1375
1691
  // note: callback rules should be the last ones, since they do not mean that the offer is forever invalid
1376
1692
  // integrators should be able to choose if they want to keep the offer or not
1377
1693
  sellEmptyCallback,
@@ -1385,7 +1701,7 @@ function morpho() {
1385
1701
  exports.Callback = Callback_exports;
1386
1702
  exports.Cursor = Cursor_exports;
1387
1703
  exports.Liquidity = Liquidity_exports;
1388
- exports.Router = Client_exports;
1704
+ exports.RouterApi = Client_exports;
1389
1705
  exports.RouterOffer = RouterOffer_exports;
1390
1706
  exports.Validation = Validation_exports;
1391
1707
  exports.ValidationRule = ValidationRule_exports;