@morpho-dev/router 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +7 -7
  2. package/dist/drizzle/{offers_v1.1 → router_v1.1}/0000_init.sql +47 -39
  3. package/dist/drizzle/{offers_v1.1/meta/0005_snapshot.json → router_v1.1/meta/0000_snapshot.json} +76 -21
  4. package/dist/drizzle/router_v1.1/meta/_journal.json +13 -0
  5. package/dist/index.browser.d.cts +185 -143
  6. package/dist/index.browser.d.ts +185 -143
  7. package/dist/index.browser.js +808 -443
  8. package/dist/index.browser.js.map +1 -1
  9. package/dist/index.browser.mjs +810 -445
  10. package/dist/index.browser.mjs.map +1 -1
  11. package/dist/index.node.d.cts +1861 -1792
  12. package/dist/index.node.d.ts +1861 -1792
  13. package/dist/index.node.js +2921 -2384
  14. package/dist/index.node.js.map +1 -1
  15. package/dist/index.node.mjs +2923 -2385
  16. package/dist/index.node.mjs.map +1 -1
  17. package/package.json +4 -4
  18. package/dist/drizzle/offers_v1.1/0001_new_table_for_collectors_block_numbers.sql +0 -5
  19. package/dist/drizzle/offers_v1.1/0002_update-liquidity-tables.sql +0 -8
  20. package/dist/drizzle/offers_v1.1/0003_add-not-null-for-queue-id.sql +0 -1
  21. package/dist/drizzle/offers_v1.1/0004_add-callback-id-to-offer.sql +0 -1
  22. package/dist/drizzle/offers_v1.1/0005_add-missing-indices-to-liquidity-tables.sql +0 -2
  23. package/dist/drizzle/offers_v1.1/meta/0000_snapshot.json +0 -827
  24. package/dist/drizzle/offers_v1.1/meta/0001_snapshot.json +0 -827
  25. package/dist/drizzle/offers_v1.1/meta/0002_snapshot.json +0 -833
  26. package/dist/drizzle/offers_v1.1/meta/0003_snapshot.json +0 -833
  27. package/dist/drizzle/offers_v1.1/meta/0004_snapshot.json +0 -839
  28. package/dist/drizzle/offers_v1.1/meta/_journal.json +0 -48
@@ -1,9 +1,9 @@
1
- import { Errors, LLTV, Utils, Offer, 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,108 +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
- ]
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
- const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
41
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
42
- throw new Error(`CallbackType not implemented: ${type}`);
43
- const amountStr = amount.toString();
44
- const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
45
- return {
46
- userPosition: {
47
- id,
48
- availableLiquidityQueueId: id,
49
- user: user.toLowerCase(),
50
- chainId,
51
- amount: amountStr,
52
- updatedAt
53
- },
54
- queues: [
55
- {
56
- queue: {
57
- queueId: id,
58
- availableLiquidityPoolId: id,
59
- index,
60
- updatedAt
61
- },
62
- pool: {
63
- id,
64
- amount: amountStr,
65
- updatedAt
66
- }
67
- }
68
- ]
69
- };
70
- }
71
- function getCallbackIdForOffer(offer) {
72
- if (offer.buy && offer.callback.data === "0x") {
73
- const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
74
- const user = offer.offering;
75
- const loanToken = offer.loanToken;
76
- return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
77
- }
78
- return null;
79
- }
80
- function decodeSellWithdrawFromWalletData(data) {
81
- if (!data || data === "0x") throw new Error("Empty callback data");
65
+ function from(input) {
82
66
  try {
83
- const [collaterals, amounts] = decodeAbiParameters(
84
- [{ type: "address[]" }, { type: "uint256[]" }],
85
- data
86
- );
87
- if (collaterals.length !== amounts.length) {
88
- throw new Error("Mismatched array lengths");
89
- }
90
- return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
91
- } catch (_) {
92
- throw new Error("Invalid SellWithdrawFromWallet callback data");
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);
93
75
  }
94
76
  }
95
- function decode(parameters) {
96
- const { type, data } = parameters;
97
- if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
98
- return decodeSellWithdrawFromWalletData(data);
99
- }
100
- throw new Error(`CallbackType not implemented: ${type}`);
77
+ function fromSnakeCase(input) {
78
+ return from(Format.fromSnakeCase(input));
101
79
  }
102
- function encode(parameters) {
103
- const { type, data } = parameters;
104
- if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
105
- return encodeAbiParameters(
106
- [{ type: "address[]" }, { type: "uint256[]" }],
107
- [data.collaterals, data.amounts]
108
- );
109
- }
110
- throw new Error(`CallbackType not implemented: ${type}`);
80
+ function toSnakeCase(offer) {
81
+ return Format.toSnakeCase(offer);
82
+ }
83
+ function random() {
84
+ const baseOffer = Offer.random();
85
+ return from({
86
+ ...baseOffer,
87
+ status: "valid",
88
+ metadata: void 0,
89
+ consumed: 0n
90
+ });
111
91
  }
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
+ };
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
+ };
112
108
 
113
109
  // src/core/Cursor.ts
114
110
  var Cursor_exports = {};
115
111
  __export(Cursor_exports, {
116
- decode: () => decode2,
117
- encode: () => encode2,
112
+ decode: () => decode,
113
+ encode: () => encode,
118
114
  validate: () => validate
119
115
  });
120
116
  function validate(cursor) {
@@ -186,242 +182,17 @@ function validate(cursor) {
186
182
  }
187
183
  return true;
188
184
  }
189
- function encode2(c) {
185
+ function encode(c) {
190
186
  return Base64.encodeURL(JSON.stringify(c));
191
187
  }
192
- function decode2(token) {
188
+ function decode(token) {
193
189
  if (!token) return null;
194
190
  const decoded = JSON.parse(Base64.decode(token));
195
191
  validate(decoded);
196
192
  return decoded;
197
193
  }
198
194
 
199
- // src/core/Liquidity.ts
200
- var Liquidity_exports = {};
201
- __export(Liquidity_exports, {
202
- fetch: () => fetch2,
203
- fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
204
- serialize: () => serialize
205
- });
206
- async function fetchBalancesAndAllowances(parameters) {
207
- const { client, spender, pairs, options } = parameters;
208
- if (pairs.length === 0) return /* @__PURE__ */ new Map();
209
- const batchSize = Math.max(1, options?.batchSize ?? 5e3);
210
- const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
211
- const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
212
- const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
213
- const out = /* @__PURE__ */ new Map();
214
- for (const pairsBatch of Utils.batch(pairs, batchSize)) {
215
- const balanceContracts = [];
216
- const allowanceContracts = [];
217
- for (const { user, token } of pairsBatch) {
218
- balanceContracts.push({
219
- address: token,
220
- abi: erc20Abi,
221
- functionName: "balanceOf",
222
- args: [user]
223
- });
224
- allowanceContracts.push({
225
- address: token,
226
- abi: erc20Abi,
227
- functionName: "allowance",
228
- args: [user, spender]
229
- });
230
- }
231
- const [balances, allowances] = await Promise.all([
232
- Utils.retry(
233
- () => client.multicall({
234
- allowFailure: false,
235
- contracts: balanceContracts,
236
- ...blockNumber ? { blockNumber } : {}
237
- }),
238
- retryAttempts,
239
- retryDelayMs
240
- ),
241
- Utils.retry(
242
- () => client.multicall({
243
- allowFailure: false,
244
- contracts: allowanceContracts,
245
- ...blockNumber ? { blockNumber } : {}
246
- }),
247
- retryAttempts,
248
- retryDelayMs
249
- )
250
- ]);
251
- for (let i = 0; i < pairsBatch.length; i++) {
252
- const { user, token } = pairsBatch[i];
253
- const balance = balances[i];
254
- const allowance = allowances[i];
255
- let perUser = out.get(user);
256
- if (!perUser) {
257
- perUser = /* @__PURE__ */ new Map();
258
- out.set(user, perUser);
259
- }
260
- perUser.set(token, { balance, allowance });
261
- }
262
- }
263
- return out;
264
- }
265
- async function fetch2(parameters) {
266
- const { client, chainId, spender, type, pairs, options } = parameters;
267
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
268
- throw new Error(`CallbackType not implemented: ${type}`);
269
- const map = await fetchBalancesAndAllowances({
270
- client,
271
- spender,
272
- pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
273
- options
274
- });
275
- const out = [];
276
- for (const [user, perContract] of map) {
277
- for (const [contract, { balance, allowance }] of perContract) {
278
- const amount = balance < allowance ? balance : allowance;
279
- out.push(
280
- buildLiquidity({
281
- type,
282
- user,
283
- contract,
284
- chainId,
285
- amount: amount.toString(),
286
- index: 0
287
- })
288
- );
289
- }
290
- }
291
- return out;
292
- }
293
- function serialize(liquidity) {
294
- const normalized = {
295
- userPosition: {
296
- id: liquidity.userPosition.id,
297
- availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
298
- user: liquidity.userPosition.user,
299
- chainId: String(liquidity.userPosition.chainId),
300
- amount: String(liquidity.userPosition.amount)
301
- },
302
- queues: liquidity.queues.map((queueWithPool) => ({
303
- queue: {
304
- queueId: queueWithPool.queue.queueId,
305
- availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
306
- index: queueWithPool.queue.index
307
- },
308
- pool: {
309
- id: queueWithPool.pool.id,
310
- amount: String(queueWithPool.pool.amount)
311
- }
312
- })).sort(
313
- (left, right) => {
314
- const leftQueueId = left.queue.queueId || "";
315
- const rightQueueId = right.queue.queueId || "";
316
- if (leftQueueId < rightQueueId) return -1;
317
- if (leftQueueId > rightQueueId) return 1;
318
- const leftPoolId = left.pool.id;
319
- const rightPoolId = right.pool.id;
320
- if (leftPoolId < rightPoolId) return -1;
321
- if (leftPoolId > rightPoolId) return 1;
322
- const leftIndex = left.queue.index;
323
- const rightIndex = right.queue.index;
324
- if (leftIndex < rightIndex) return -1;
325
- if (leftIndex > rightIndex) return 1;
326
- return 0;
327
- }
328
- )
329
- };
330
- return JSON.stringify(normalized);
331
- }
332
-
333
- // src/core/RouterOffer.ts
334
- var RouterOffer_exports = {};
335
- __export(RouterOffer_exports, {
336
- InvalidRouterOfferError: () => InvalidRouterOfferError,
337
- OfferStatusValues: () => OfferStatusValues,
338
- RouterOfferSchema: () => RouterOfferSchema,
339
- consumedEvent: () => consumedEvent,
340
- from: () => from,
341
- fromConsumedLog: () => fromConsumedLog,
342
- fromSnakeCase: () => fromSnakeCase,
343
- random: () => random,
344
- toSnakeCase: () => toSnakeCase
345
- });
346
- var OfferStatusValues = [
347
- "valid",
348
- "callback_not_supported",
349
- "callback_error",
350
- "unverified"
351
- ];
352
- var RouterOfferSchema = (parameters) => Offer.OfferSchema(parameters).extend({
353
- consumed: z.bigint({ coerce: true }).min(0n).max(maxUint256),
354
- status: z.enum(OfferStatusValues),
355
- metadata: z.object({
356
- issue: z.string()
357
- }).optional()
358
- });
359
- var consumedEvent = {
360
- type: "event",
361
- name: "Consumed",
362
- inputs: [
363
- { name: "user", type: "address", indexed: true, internalType: "address" },
364
- { name: "nonce", type: "uint256", indexed: true, internalType: "uint256" },
365
- { name: "amount", type: "uint256", indexed: false, internalType: "uint256" }
366
- ],
367
- anonymous: false
368
- };
369
- function from(input) {
370
- try {
371
- const parsedOffer = RouterOfferSchema({ omitHash: true }).parse(input);
372
- const parsedHash = Offer.OfferHashSchema.parse(Offer.hash(parsedOffer));
373
- return {
374
- ...parsedOffer,
375
- hash: parsedHash
376
- };
377
- } catch (error) {
378
- throw new InvalidRouterOfferError(error);
379
- }
380
- }
381
- function fromSnakeCase(input) {
382
- return from(Format.fromSnakeCase(input));
383
- }
384
- function toSnakeCase(offer) {
385
- return Format.toSnakeCase(offer);
386
- }
387
- function random() {
388
- const baseOffer = Offer.random();
389
- return from({
390
- ...baseOffer,
391
- status: "valid",
392
- metadata: void 0,
393
- consumed: 0n
394
- });
395
- }
396
- function fromConsumedLog(parameters) {
397
- const { blockNumber, logIndex, chainId, transactionHash, user, nonce, amount } = parameters;
398
- return {
399
- id: `${blockNumber.toString()}-${logIndex.toString()}-${chainId}-${transactionHash}`,
400
- chainId: BigInt(chainId),
401
- offering: user,
402
- nonce,
403
- amount
404
- };
405
- }
406
- var InvalidRouterOfferError = class extends Errors.BaseError {
407
- constructor(error) {
408
- super("Invalid router offer.", { cause: error });
409
- __publicField(this, "name", "RouterOffer.InvalidRouterOfferError");
410
- }
411
- };
412
-
413
- // src/core/router/Client.ts
414
- var Client_exports = {};
415
- __export(Client_exports, {
416
- HttpForbiddenError: () => HttpForbiddenError,
417
- HttpGetOffersFailedError: () => HttpGetOffersFailedError,
418
- HttpRateLimitError: () => HttpRateLimitError,
419
- HttpUnauthorizedError: () => HttpUnauthorizedError,
420
- InvalidUrlError: () => InvalidUrlError,
421
- connect: () => connect,
422
- get: () => get,
423
- match: () => match
424
- });
195
+ // src/api/Schema/requests.ts
425
196
  var MAX_LIMIT = 100;
426
197
  var DEFAULT_LIMIT = 20;
427
198
  var MAX_LLTV = 100;
@@ -598,7 +369,7 @@ var GetOffersQueryParams = z.object({
598
369
  (val) => {
599
370
  if (!val) return true;
600
371
  try {
601
- const decoded = decode2(val);
372
+ const decoded = decode(val);
602
373
  return decoded !== null;
603
374
  } catch (_error) {
604
375
  return false;
@@ -770,7 +541,7 @@ var MatchOffersQueryParams = z.object({
770
541
  (val) => {
771
542
  if (!val) return true;
772
543
  try {
773
- const decoded = decode2(val);
544
+ const decoded = decode(val);
774
545
  return decoded !== null;
775
546
  } catch (_error) {
776
547
  return false;
@@ -810,7 +581,7 @@ function safeParse(action, query, error) {
810
581
  });
811
582
  }
812
583
 
813
- // src/core/apiSchema/openapi.ts
584
+ // src/api/Schema/openapi.ts
814
585
  var successResponseSchema = z.object({
815
586
  status: z.literal("success"),
816
587
  cursor: z.string().nullable(),
@@ -913,7 +684,7 @@ createDocument({
913
684
  paths
914
685
  });
915
686
 
916
- // src/core/apiSchema/utils.ts
687
+ // src/api/Schema/utils.ts
917
688
  function toResponse(routerOffer) {
918
689
  const { consumed, status, metadata, ...offer } = routerOffer;
919
690
  return {
@@ -933,7 +704,7 @@ function fromResponse(offerResponse) {
933
704
  };
934
705
  }
935
706
 
936
- // src/core/router/Client.ts
707
+ // src/api/Client.ts
937
708
  function connect(opts) {
938
709
  const u = new URL(opts?.url || "https://router.morpho.dev");
939
710
  if (u.protocol !== "http:" && u.protocol !== "https:") {
@@ -1033,135 +804,720 @@ async function get(config, parameters) {
1033
804
  if (parameters.limit !== void 0) {
1034
805
  url.searchParams.set("limit", parameters.limit.toString());
1035
806
  }
1036
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
1037
- const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
1038
- return {
1039
- cursor: returnedCursor,
1040
- offers: routerOffers.map(from).map(toResponse)
1041
- };
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;
1042
1216
  }
1043
- async function match(config, parameters) {
1044
- const url = new URL(`${config.url.toString()}v1/offers/match`);
1045
- url.searchParams.set("side", parameters.side);
1046
- url.searchParams.set("chain_id", parameters.chainId.toString());
1047
- if (parameters.rate !== void 0) {
1048
- url.searchParams.set("rate", parameters.rate.toString());
1049
- }
1050
- if (parameters.collaterals?.length) {
1051
- const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${formatUnits(lltv, 16)}`).join("#");
1052
- url.searchParams.set("collaterals", collateralsStr);
1053
- }
1054
- if (parameters.maturity !== void 0) {
1055
- url.searchParams.set("maturity", parameters.maturity.toString());
1056
- }
1057
- if (parameters.minMaturity !== void 0) {
1058
- url.searchParams.set("min_maturity", parameters.minMaturity.toString());
1059
- }
1060
- if (parameters.maxMaturity !== void 0) {
1061
- url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
1062
- }
1063
- if (parameters.loanToken) {
1064
- url.searchParams.set("loan_token", parameters.loanToken);
1065
- }
1066
- if (parameters.creator) {
1067
- url.searchParams.set("creator", parameters.creator);
1068
- }
1069
- if (parameters.status?.length) {
1070
- url.searchParams.set("status", parameters.status.join(","));
1071
- }
1072
- if (parameters.cursor) {
1073
- url.searchParams.set("cursor", parameters.cursor);
1074
- }
1075
- if (parameters.limit !== void 0) {
1076
- 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
+ }
1077
1249
  }
1078
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
1079
- const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
1080
- return {
1081
- cursor: returnedCursor,
1082
- offers: routerOffers.map(from).map(toResponse)
1083
- };
1250
+ return out;
1084
1251
  }
1085
- async function getApi(config, url) {
1086
- const pathname = url.pathname;
1087
- let action;
1088
- switch (true) {
1089
- case pathname.includes("/v1/offers/match"):
1090
- action = "match_offers";
1091
- break;
1092
- case pathname.includes("/v1/offers"):
1093
- action = "get_offers";
1094
- break;
1095
- default:
1096
- throw new HttpGetOffersFailedError("Unknown endpoint", {
1097
- 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]
1098
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
+ }
1099
1319
  }
1100
- const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
1101
- if (!schemaParseResult.success) {
1102
- throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
1103
- details: schemaParseResult.error.issues[0]?.message
1104
- });
1105
- }
1106
- const response = await fetch(url.toString(), {
1107
- method: "GET",
1108
- 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
1109
1332
  });
1110
- if (!response.ok) {
1111
- switch (response.status) {
1112
- case 401:
1113
- throw new HttpUnauthorizedError();
1114
- case 403:
1115
- throw new HttpForbiddenError();
1116
- case 429:
1117
- 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;
1118
1348
  }
1119
- throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
1120
- details: await response.text()
1121
- });
1122
1349
  }
1123
- return response.json();
1350
+ return maxDebt;
1124
1351
  }
1125
- var InvalidUrlError = class extends Errors.BaseError {
1126
- constructor(url) {
1127
- super(`URL "${url}" is not http/https.`);
1128
- __publicField(this, "name", "Router.InvalidUrlError");
1129
- }
1352
+ var create = (config) => {
1353
+ const { client } = config;
1354
+ return {
1355
+ fetchBuyLiquidity: (parameters) => fetchBuyLiquidity({ ...parameters, client }),
1356
+ fetchSellLiquidity: (parameters) => fetchSellLiquidity({ ...parameters, client })
1357
+ };
1130
1358
  };
1131
- var HttpUnauthorizedError = class extends Errors.BaseError {
1132
- constructor() {
1133
- super("Unauthorized.", {
1134
- metaMessages: ["Ensure that an API key is provided."]
1135
- });
1136
- __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
+ }
1137
1382
  }
1138
- };
1139
- var HttpForbiddenError = class extends Errors.BaseError {
1140
- constructor() {
1141
- super("Forbidden.", {
1142
- 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
1143
1393
  });
1144
- __publicField(this, "name", "Router.HttpForbiddenError");
1394
+ for (const { collateral } of callbackData) {
1395
+ collateralPairs.push({ user, token: collateral });
1396
+ }
1145
1397
  }
1146
- };
1147
- var HttpRateLimitError = class extends Errors.BaseError {
1148
- constructor() {
1149
- super("Rate limit exceeded.", {
1150
- metaMessages: [
1151
- "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
1152
- ]
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
1153
1413
  });
1154
- __publicField(this, "name", "Router.HttpRateLimitError");
1155
1414
  }
1156
- };
1157
- var HttpGetOffersFailedError = class extends Errors.BaseError {
1158
- constructor(message, { details } = {}) {
1159
- super(message, {
1160
- 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
1161
1428
  });
1162
- __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
+ );
1163
1479
  }
1164
- };
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
+ }
1165
1521
 
1166
1522
  // src/core/Validation.ts
1167
1523
  var Validation_exports = {};
@@ -1267,7 +1623,7 @@ function morpho() {
1267
1623
  (offer, _) => {
1268
1624
  if (!offer.buy && offer.callback.data !== "0x") {
1269
1625
  const allowed = new Set(
1270
- WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].map(
1626
+ WhitelistedCallbackAddresses["sell_erc20_callback" /* SellERC20Callback */].map(
1271
1627
  (a) => a.toLowerCase()
1272
1628
  )
1273
1629
  );
@@ -1283,8 +1639,8 @@ function morpho() {
1283
1639
  (offer, _) => {
1284
1640
  if (!offer.buy && offer.callback.data !== "0x") {
1285
1641
  try {
1286
- const decoded = decode({
1287
- type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1642
+ const decoded = decode2({
1643
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1288
1644
  data: offer.callback.data
1289
1645
  });
1290
1646
  if (decoded.length === 0) {
@@ -1301,8 +1657,8 @@ function morpho() {
1301
1657
  (offer, _) => {
1302
1658
  if (!offer.buy && offer.callback.data !== "0x") {
1303
1659
  try {
1304
- const decoded = decode({
1305
- type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1660
+ const decoded = decode2({
1661
+ type: "sell_erc20_callback" /* SellERC20Callback */,
1306
1662
  data: offer.callback.data
1307
1663
  });
1308
1664
  const offerCollaterals = new Set(
@@ -1318,10 +1674,19 @@ function morpho() {
1318
1674
  }
1319
1675
  }
1320
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
+ });
1321
1685
  return [
1322
1686
  chainId,
1323
1687
  loanToken,
1324
1688
  expiry,
1689
+ maturity,
1325
1690
  // note: callback rules should be the last ones, since they do not mean that the offer is forever invalid
1326
1691
  // integrators should be able to choose if they want to keep the offer or not
1327
1692
  sellEmptyCallback,
@@ -1332,6 +1697,6 @@ function morpho() {
1332
1697
  ];
1333
1698
  }
1334
1699
 
1335
- 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 };
1336
1701
  //# sourceMappingURL=index.browser.mjs.map
1337
1702
  //# sourceMappingURL=index.browser.mjs.map