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