@morpho-dev/router 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
1
- import { Errors, Offer, Format, Time, Maturity, LLTV, Mempool } from '@morpho-dev/mempool';
1
+ import { Errors, LLTV, Maturity, Offer, Format, Time, Mempool } from '@morpho-dev/mempool';
2
2
  export * from '@morpho-dev/mempool';
3
3
  import { base, mainnet, optimism, foundry, abstractTestnet, acala, ancient8, ancient8Sepolia, anvil, apexTestnet, arbitrum, arbitrumGoerli, arbitrumNova, assetChainTestnet, astar, astarZkEVM, astarZkyoto, arbitrumSepolia, areonNetwork, areonNetworkTestnet, artelaTestnet, atletaOlympia, aurora, auroraTestnet, auroria, avalanche, avalancheFuji, b3Sepolia, b3, bahamut, baseGoerli, baseSepolia, beam, beamTestnet, bearNetworkChainMainnet, bearNetworkChainTestnet, berachainTestnet, berachainTestnetbArtio, bevmMainnet, bitkub, bitkubTestnet, bitTorrent, bitTorrentTestnet, blast, blastSepolia, bob, bobSepolia, boba, bobaSepolia, botanixTestnet, bronos, bronosTestnet, bsc, bscTestnet, bscGreenfield, btr, btrTestnet, bxn, bxnTestnet, canto, celo, celoAlfajores, chiliz, chips, classic, confluxESpace, confluxESpaceTestnet, coreDao, crab, cronos, cronoszkEVM, cronoszkEVMTestnet, cronosTestnet, crossbell, curtis, cyber, cyberTestnet, darwinia, dchain, dchainTestnet, defichainEvm, defichainEvmTestnet, degen, dfk, dodochainTestnet, dogechain, dreyerxMainnet, dreyerxTestnet, edgeless, edgelessTestnet, edgeware, edgewareTestnet, eon, eos, eosTestnet, etherlink, etherlinkTestnet, evmos, evmosTestnet, ekta, ektaTestnet, fantom, fantomSonicTestnet, fantomTestnet, fibo, filecoin, filecoinCalibration, filecoinHyperspace, flare, flareTestnet, flowPreviewnet, flowMainnet, flowTestnet, fluence, fluenceStage, fluenceTestnet, forma, fraxtal, fraxtalTestnet, funkiMainnet, funkiSepolia, fuse, fuseSparknet, iotex, iotexTestnet, jbc, jbcTestnet, karura, gobi, goerli, gnosis, gnosisChiado, gravity, ham, hardhat, harmonyOne, hashkeyTestnet, haqqMainnet, haqqTestedge2, hedera, hederaTestnet, hederaPreviewnet, holesky, immutableZkEvm, immutableZkEvmTestnet, inEVM, iota, iotaTestnet, kakarotSepolia, kava, kavaTestnet, kcc, klaytn, klaytnBaobab, kaia, kairos, koi, kroma, kromaSepolia, l3x, l3xTestnet, lightlinkPegasus, lightlinkPhoenix, linea, lineaGoerli, lineaSepolia, lineaTestnet, lisk, liskSepolia, localhost, lukso, luksoTestnet, lycan, lyra, mandala, manta, mantaSepoliaTestnet, mantaTestnet, mantle, mantleSepoliaTestnet, mantleTestnet, merlin, metachain, metachainIstanbul, metalL2, meter, meterTestnet, metis, metisGoerli, mev, mevTestnet, mintSepoliaTestnet, mode, modeTestnet, moonbaseAlpha, moonbeam, moonbeamDev, moonriver, morphHolesky, morphSepolia, nautilus, neonDevnet, neonMainnet, nexi, nexilix, oasys, oasisTestnet, okc, optimismGoerli, optimismSepolia, opBNB, opBNBTestnet, oortMainnetDev, otimDevnet, palm, palmTestnet, playfiAlbireo, pgn, pgnTestnet, phoenix, plinga, plumeTestnet, polygon, polygonAmoy, polygonMumbai, polygonZkEvm, polygonZkEvmCardona, polygonZkEvmTestnet, pulsechain, pulsechainV4, qMainnet, qTestnet, real, redbellyTestnet, redstone, reyaNetwork, rollux, rolluxTestnet, ronin, root, rootPorcini, rootstock, rootstockTestnet, rss3, rss3Sepolia, saigon, sapphire, sapphireTestnet, satoshiVM, satoshiVMTestnet, scroll, scrollSepolia, sei, seiDevnet, seiTestnet, sepolia, shapeSepolia, shimmer, shimmerTestnet, skaleBlockBrawlers, skaleCalypso, skaleCalypsoTestnet, skaleCryptoBlades, skaleCryptoColosseum, skaleEuropa, skaleEuropaTestnet, skaleExorde, skaleHumanProtocol, skaleNebula, skaleNebulaTestnet, skaleRazor, skaleTitan, skaleTitanTestnet, sketchpad, soneiumMinato, songbird, songbirdTestnet, sophonTestnet, spicy, shardeumSphinx, shibarium, shibariumTestnet, storyTestnet, stratis, syscoin, syscoinTestnet, taraxa, taiko, taikoHekla, taikoJolnir, taikoKatla, taikoTestnetSepolia, taraxaTestnet, telcoinTestnet, telos, telosTestnet, tenet, thaiChain, thunderTestnet, tron, unreal, vechain, wanchain, wanchainTestnet, wemix, wemixTestnet, xLayerTestnet, x1Testnet, xLayer, xai, xaiTestnet, xdc, xdcTestnet, xrSepolia, yooldoVerse, yooldoVerseTestnet, zetachain, zetachainAthensTestnet, zhejiang, zilliqa, zilliqaTestnet, zkFair, zkFairTestnet, zkLinkNova, zkLinkNovaSepoliaTestnet, zksync, zksyncInMemoryNode, zksyncLocalNode, zksyncSepoliaTestnet, zora, zoraSepolia, zoraTestnet, zircuitTestnet } from 'viem/chains';
4
4
  import { z } from 'zod/v4';
5
5
  import { createDocument } from 'zod-openapi';
6
- import { defineChain, hexToBytes as hexToBytes$1, bytesToHex as bytesToHex$1, maxUint256, parseUnits, keccak256 as keccak256$2, toHex, parseEther as parseEther$1, concatHex, pad, createClient, publicActions, walletActions, testActions, parseAbi as parseAbi$1, createTransport, withRetry, numberToHex as numberToHex$2, hexToBigInt as hexToBigInt$1, stringToHex as stringToHex$1, numberToBytes, encodeFunctionData as encodeFunctionData$1, createPublicClient, InternalRpcError, decodeFunctionResult as decodeFunctionResult$1 } from 'viem';
6
+ import { parseUnits, defineChain, hexToBytes as hexToBytes$1, bytesToHex as bytesToHex$1, maxUint256, formatUnits, keccak256 as keccak256$2, toHex, parseEther as parseEther$1, concatHex, pad, createClient, publicActions, walletActions, testActions, parseAbi as parseAbi$1, createTransport, withRetry, numberToHex as numberToHex$2, hexToBigInt as hexToBigInt$1, stringToHex as stringToHex$1, numberToBytes, encodeFunctionData as encodeFunctionData$1, createPublicClient, InternalRpcError, decodeFunctionResult as decodeFunctionResult$1 } from 'viem';
7
+ import { Base64 } from 'js-base64';
7
8
  import { serve as serve$1 } from '@hono/node-server';
8
9
  import { Hono } from 'hono';
9
10
  import fs from 'fs';
@@ -8231,8 +8232,6 @@ function* batch(array, batchSize) {
8231
8232
  yield array.slice(i, i + batchSize);
8232
8233
  }
8233
8234
  }
8234
-
8235
- // src/utils/cursor.ts
8236
8235
  function validateCursor(cursor) {
8237
8236
  if (!cursor || typeof cursor !== "object") {
8238
8237
  throw new Error("Cursor must be an object");
@@ -8303,11 +8302,11 @@ function validateCursor(cursor) {
8303
8302
  return true;
8304
8303
  }
8305
8304
  function encodeCursor(c) {
8306
- return Buffer.from(JSON.stringify(c)).toString("base64url");
8305
+ return Base64.encodeURL(JSON.stringify(c));
8307
8306
  }
8308
8307
  function decodeCursor(token) {
8309
8308
  if (!token) return null;
8310
- const decoded = JSON.parse(Buffer.from(token, "base64url").toString());
8309
+ const decoded = JSON.parse(Base64.decode(token));
8311
8310
  validateCursor(decoded);
8312
8311
  return decoded;
8313
8312
  }
@@ -8419,19 +8418,33 @@ var GetOffersQueryParams = z.object({
8419
8418
  example: "1500000000000000000"
8420
8419
  }),
8421
8420
  // Time range
8422
- min_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
8423
- description: "Minimum maturity timestamp (Unix timestamp in seconds)",
8424
- example: "1700000000"
8425
- }),
8426
- max_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
8427
- description: "Maximum maturity timestamp (Unix timestamp in seconds)",
8428
- example: "1800000000"
8429
- }),
8430
- min_expiry: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
8421
+ min_maturity: z.coerce.number().int().positive().transform((maturity, ctx) => {
8422
+ try {
8423
+ return Maturity.from(maturity);
8424
+ } catch (e) {
8425
+ ctx.addIssue({
8426
+ code: "custom",
8427
+ message: e.message
8428
+ });
8429
+ return z.NEVER;
8430
+ }
8431
+ }).optional(),
8432
+ max_maturity: z.coerce.number().int().positive().transform((maturity, ctx) => {
8433
+ try {
8434
+ return Maturity.from(maturity);
8435
+ } catch (e) {
8436
+ ctx.addIssue({
8437
+ code: "custom",
8438
+ message: e.message
8439
+ });
8440
+ return z.NEVER;
8441
+ }
8442
+ }).optional(),
8443
+ min_expiry: z.coerce.number().int().optional().meta({
8431
8444
  description: "Minimum expiry timestamp (Unix timestamp in seconds)",
8432
8445
  example: "1700000000"
8433
8446
  }),
8434
- max_expiry: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
8447
+ max_expiry: z.coerce.number().int().optional().meta({
8435
8448
  description: "Maximum expiry timestamp (Unix timestamp in seconds)",
8436
8449
  example: "1800000000"
8437
8450
  }),
@@ -8453,51 +8466,81 @@ var GetOffersQueryParams = z.object({
8453
8466
  {
8454
8467
  message: "Collateral tuple must be in format: asset:oracle:lltv#asset2:oracle2:lltv2. Oracle and lltv are optional. Asset must be 0x + 40 hex chars, oracle must be 0x + 40 hex chars, lltv must be a number (e.g., 80.5)."
8455
8468
  }
8456
- ).transform((val) => {
8469
+ ).transform((val, ctx) => {
8457
8470
  return val.split("#").map((tuple) => {
8458
8471
  const parts = tuple.split(":");
8459
8472
  if (parts.length === 0 || !parts[0]) {
8460
- throw new z.ZodError([
8461
- {
8462
- code: "custom",
8463
- message: "Asset address is required for each collateral tuple",
8464
- path: ["collateral_tuple"],
8465
- input: val
8466
- }
8467
- ]);
8473
+ ctx.addIssue({
8474
+ code: "custom",
8475
+ message: "Asset address is required for each collateral tuple",
8476
+ path: ["asset"],
8477
+ input: val
8478
+ });
8479
+ return z.NEVER;
8468
8480
  }
8469
8481
  const asset = parts[0]?.toLowerCase();
8470
8482
  const oracle = parts[1]?.toLowerCase();
8471
8483
  const lltv = parts[2] ? parseFloat(parts[2]) : void 0;
8472
8484
  if (lltv !== void 0 && (lltv < MIN_LLTV || lltv > MAX_LLTV)) {
8473
- throw new z.ZodError([
8474
- {
8485
+ ctx.addIssue({
8486
+ code: "custom",
8487
+ message: `LLTV must be between ${MIN_LLTV} and ${MAX_LLTV} (0-100%)`,
8488
+ path: ["lltv"],
8489
+ input: val
8490
+ });
8491
+ return z.NEVER;
8492
+ }
8493
+ let lltvValue;
8494
+ if (lltv !== void 0) {
8495
+ try {
8496
+ lltvValue = LLTV.from(parseUnits(lltv.toString(), 16));
8497
+ } catch (e) {
8498
+ ctx.issues.push({
8475
8499
  code: "custom",
8476
- message: `LLTV must be between ${MIN_LLTV} and ${MAX_LLTV} (0-100%)`,
8477
- path: ["collateral_tuple"],
8478
- input: val
8479
- }
8480
- ]);
8500
+ message: e instanceof LLTV.InvalidLLTVError || e instanceof LLTV.InvalidOptionError ? e.message : "Invalid LLTV.",
8501
+ input: lltv,
8502
+ path: ["lltv"]
8503
+ });
8504
+ return z.NEVER;
8505
+ }
8481
8506
  }
8482
8507
  return {
8483
8508
  asset,
8484
8509
  oracle,
8485
- lltv
8510
+ lltv: lltvValue
8486
8511
  };
8487
8512
  });
8488
8513
  }).optional().meta({
8489
8514
  description: "Filter by collateral combinations in format: asset:oracle:lltv#asset2:oracle2:lltv2. Oracle and lltv are optional. Use # to separate multiple combinations.",
8490
- example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:8000#0x9876543210987654321098765432109876543210::8000"
8515
+ example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:86#0x9876543210987654321098765432109876543210:94.5"
8491
8516
  }),
8492
- min_lltv: z.string().regex(/^\d+(\.\d+)?$/, {
8493
- message: "Min LLTV must be a valid number"
8494
- }).transform((val) => parseFloat(val)).pipe(z.number().min(0).max(100)).optional().meta({
8517
+ min_lltv: z.coerce.number().min(0, { message: "LLTV must be above 0" }).max(100, { message: "LLTV must be below 100" }).transform((lltv, ctx) => {
8518
+ try {
8519
+ return LLTV.from(parseUnits(lltv.toString(), 16));
8520
+ } catch (e) {
8521
+ ctx.addIssue({
8522
+ code: "custom",
8523
+ message: e.message,
8524
+ input: lltv
8525
+ });
8526
+ return z.NEVER;
8527
+ }
8528
+ }).optional().meta({
8495
8529
  description: "Minimum Loan-to-Value ratio (LLTV) for collateral (percentage as decimal, e.g., 80.5 = 80.5%)",
8496
8530
  example: "80.5"
8497
8531
  }),
8498
- max_lltv: z.string().regex(/^\d+(\.\d+)?$/, {
8499
- message: "Max LLTV must be a valid number"
8500
- }).transform((val) => parseFloat(val)).pipe(z.number().min(0).max(100)).optional().meta({
8532
+ max_lltv: z.coerce.number().min(0, { message: "LLTV must be above 0" }).max(100, { message: "LLTV must be below 100" }).transform((lltv, ctx) => {
8533
+ try {
8534
+ return LLTV.from(parseUnits(lltv.toString(), 16));
8535
+ } catch (e) {
8536
+ ctx.addIssue({
8537
+ code: "custom",
8538
+ message: e.message,
8539
+ input: lltv
8540
+ });
8541
+ return z.NEVER;
8542
+ }
8543
+ }).optional().meta({
8501
8544
  description: "Maximum Loan-to-Value ratio (LLTV) for collateral (percentage as decimal, e.g., 95.5 = 95.5%)",
8502
8545
  example: "95.5"
8503
8546
  }),
@@ -8590,68 +8633,89 @@ var MatchOffersQueryParams = z.object({
8590
8633
  }),
8591
8634
  // Collateral filtering
8592
8635
  collaterals: z.string().regex(
8593
- /^(0x[a-fA-F0-9]{40}:0x[a-fA-F0-9]{40}:\d+)(#0x[a-fA-F0-9]{40}:0x[a-fA-F0-9]{40}:\d+)*$/,
8636
+ /^(0x[a-fA-F0-9]{40}:0x[a-fA-F0-9]{40}:[0-9]+(\.[0-9]+)?)(#0x[a-fA-F0-9]{40}:0x[a-fA-F0-9]{40}:[0-9]+(\.[0-9]+)?)*$/,
8594
8637
  {
8595
8638
  message: "Collaterals must be in format: asset:oracle:lltv#asset2:oracle2:lltv2. All fields are required for each collateral."
8596
8639
  }
8597
- ).transform((val) => {
8640
+ ).transform((val, ctx) => {
8598
8641
  return val.split("#").map((collateral) => {
8599
8642
  const parts = collateral.split(":");
8600
8643
  if (parts.length !== 3) {
8601
- throw new z.ZodError([
8602
- {
8603
- code: "custom",
8604
- message: "Each collateral must have exactly 3 parts: asset:oracle:lltv",
8605
- path: ["collaterals"],
8606
- input: val
8607
- }
8608
- ]);
8644
+ ctx.addIssue({
8645
+ code: "custom",
8646
+ message: "Each collateral must have exactly 3 parts: asset:oracle:lltv",
8647
+ path: ["collaterals"],
8648
+ input: val
8649
+ });
8650
+ return z.NEVER;
8609
8651
  }
8610
8652
  const [asset, oracle, lltvStr] = parts;
8611
8653
  if (!asset || !oracle || !lltvStr) {
8612
- throw new z.ZodError([
8613
- {
8614
- code: "custom",
8615
- message: "Asset, oracle, and lltv are all required for each collateral",
8616
- path: ["collaterals"],
8617
- input: val
8618
- }
8619
- ]);
8654
+ ctx.addIssue({
8655
+ code: "custom",
8656
+ message: "Asset, oracle, and lltv are all required for each collateral",
8657
+ path: ["collaterals"],
8658
+ input: val
8659
+ });
8620
8660
  }
8621
- const lltv = BigInt(lltvStr);
8622
- if (lltv <= 0n) {
8623
- throw new z.ZodError([
8624
- {
8661
+ let lltvValue;
8662
+ if (lltvStr !== void 0) {
8663
+ try {
8664
+ lltvValue = LLTV.from(parseUnits(lltvStr, 16));
8665
+ } catch (e) {
8666
+ ctx.issues.push({
8625
8667
  code: "custom",
8626
- message: "LLTV must be a positive number",
8627
- path: ["collaterals"],
8628
- input: val
8629
- }
8630
- ]);
8668
+ message: e instanceof LLTV.InvalidLLTVError || e instanceof LLTV.InvalidOptionError ? e.message : "Invalid LLTV.",
8669
+ input: lltvStr,
8670
+ path: ["lltv"]
8671
+ });
8672
+ return z.NEVER;
8673
+ }
8631
8674
  }
8632
8675
  return {
8633
8676
  asset: asset.toLowerCase(),
8634
8677
  oracle: oracle.toLowerCase(),
8635
- lltv
8678
+ lltv: lltvValue
8636
8679
  };
8637
8680
  });
8638
8681
  }).optional().meta({
8639
8682
  description: "Collateral requirements in format: asset:oracle:lltv#asset2:oracle2:lltv2. Use # to separate multiple collaterals.",
8640
- example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:800000000000000000#0x9876543210987654321098765432109876543210:0xfedcbafedcbafedcbafedcbafedcbafedcbafedc:900000000000000000"
8683
+ example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:86#0x9876543210987654321098765432109876543210:0xfedcbafedcbafedcbafedcbafedcbafedcbafedc:94.5"
8641
8684
  }),
8642
8685
  // Maturity filtering
8643
- maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
8644
- description: "Exact maturity timestamp (Unix timestamp in seconds)",
8645
- example: "1700000000"
8646
- }),
8647
- min_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
8648
- description: "Minimum maturity timestamp (Unix timestamp in seconds, inclusive)",
8649
- example: "1700000000"
8650
- }),
8651
- max_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
8652
- description: "Maximum maturity timestamp (Unix timestamp in seconds, inclusive)",
8653
- example: "1800000000"
8654
- }),
8686
+ maturity: z.coerce.number().int().positive().transform((maturity, ctx) => {
8687
+ try {
8688
+ return Maturity.from(maturity);
8689
+ } catch (e) {
8690
+ ctx.addIssue({
8691
+ code: "custom",
8692
+ message: e.message
8693
+ });
8694
+ return z.NEVER;
8695
+ }
8696
+ }).optional(),
8697
+ min_maturity: z.coerce.number().int().positive().transform((maturity, ctx) => {
8698
+ try {
8699
+ return Maturity.from(maturity);
8700
+ } catch (e) {
8701
+ ctx.addIssue({
8702
+ code: "custom",
8703
+ message: e.message
8704
+ });
8705
+ return z.NEVER;
8706
+ }
8707
+ }).optional(),
8708
+ max_maturity: z.coerce.number().int().positive().transform((maturity, ctx) => {
8709
+ try {
8710
+ return Maturity.from(maturity);
8711
+ } catch (e) {
8712
+ ctx.addIssue({
8713
+ code: "custom",
8714
+ message: e.message
8715
+ });
8716
+ return z.NEVER;
8717
+ }
8718
+ }).optional(),
8655
8719
  // Asset and creator filtering
8656
8720
  loan_token: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
8657
8721
  message: "Loan asset must be a valid Ethereum address"
@@ -8716,8 +8780,10 @@ var schemas = {
8716
8780
  function parse(action, query) {
8717
8781
  return schemas[action].parse(query);
8718
8782
  }
8719
- function safeParse(action, query) {
8720
- return schemas[action].safeParse(query);
8783
+ function safeParse(action, query, error) {
8784
+ return schemas[action].safeParse(query, {
8785
+ error
8786
+ });
8721
8787
  }
8722
8788
 
8723
8789
  // src/core/apiSchema/openapi.ts
@@ -8769,7 +8835,7 @@ var paths = {
8769
8835
  }
8770
8836
  }
8771
8837
  },
8772
- "/v1/match-offers": {
8838
+ "/v1/offers/match": {
8773
8839
  get: {
8774
8840
  summary: "Match offers",
8775
8841
  description: "Find offers that match specific criteria",
@@ -8932,16 +8998,16 @@ async function get(config, parameters) {
8932
8998
  } else if (lltv !== void 0) {
8933
8999
  result += `:`;
8934
9000
  }
8935
- if (lltv !== void 0) result += `:${lltv}`;
9001
+ if (lltv !== void 0) result += `:${formatUnits(lltv, 16)}`;
8936
9002
  return result;
8937
9003
  }).join("#");
8938
9004
  url.searchParams.set("collateral_tuple", tupleStr);
8939
9005
  }
8940
9006
  if (parameters.minLltv !== void 0) {
8941
- url.searchParams.set("min_lltv", parameters.minLltv.toString());
9007
+ url.searchParams.set("min_lltv", formatUnits(parameters.minLltv, 16));
8942
9008
  }
8943
9009
  if (parameters.maxLltv !== void 0) {
8944
- url.searchParams.set("max_lltv", parameters.maxLltv.toString());
9010
+ url.searchParams.set("max_lltv", formatUnits(parameters.maxLltv, 16));
8945
9011
  }
8946
9012
  if (parameters.sortBy) {
8947
9013
  url.searchParams.set("sort_by", parameters.sortBy);
@@ -8963,14 +9029,14 @@ async function get(config, parameters) {
8963
9029
  };
8964
9030
  }
8965
9031
  async function match(config, parameters) {
8966
- const url = new URL(`${config.url.toString()}v1/match-offers`);
9032
+ const url = new URL(`${config.url.toString()}v1/offers/match`);
8967
9033
  url.searchParams.set("side", parameters.side);
8968
9034
  url.searchParams.set("chain_id", parameters.chainId.toString());
8969
9035
  if (parameters.rate !== void 0) {
8970
9036
  url.searchParams.set("rate", parameters.rate.toString());
8971
9037
  }
8972
9038
  if (parameters.collaterals?.length) {
8973
- const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${lltv}`).join("#");
9039
+ const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${formatUnits(lltv, 16)}`).join("#");
8974
9040
  url.searchParams.set("collaterals", collateralsStr);
8975
9041
  }
8976
9042
  if (parameters.maturity !== void 0) {
@@ -9008,12 +9074,12 @@ async function getApi(config, url) {
9008
9074
  const pathname = url.pathname;
9009
9075
  let action;
9010
9076
  switch (true) {
9077
+ case pathname.includes("/v1/offers/match"):
9078
+ action = "match_offers";
9079
+ break;
9011
9080
  case pathname.includes("/v1/offers"):
9012
9081
  action = "get_offers";
9013
9082
  break;
9014
- case pathname.includes("/v1/match-offers"):
9015
- action = "match_offers";
9016
- break;
9017
9083
  default:
9018
9084
  throw new HttpGetOffersFailedError("Unknown endpoint", {
9019
9085
  details: `Unsupported path: ${pathname}`
@@ -9224,6 +9290,7 @@ function memory(parameters) {
9224
9290
  }
9225
9291
  creators && (creators = creators.map((c) => c.toLowerCase()));
9226
9292
  loanTokens && (loanTokens = loanTokens.map((lt) => lt.toLowerCase()));
9293
+ callbackAddresses && (callbackAddresses = callbackAddresses.map((ca) => ca.toLowerCase()));
9227
9294
  collateralAssets && (collateralAssets = collateralAssets.map((ca) => ca.toLowerCase()));
9228
9295
  collateralOracles && (collateralOracles = collateralOracles.map((co) => co.toLowerCase()));
9229
9296
  collateralTuple && (collateralTuple = collateralTuple.map((ct) => ({
@@ -9260,12 +9327,8 @@ function memory(parameters) {
9260
9327
  )
9261
9328
  )
9262
9329
  ));
9263
- minLltv && (offers = offers.filter(
9264
- (o) => o.collaterals.every((c) => c.lltv >= LLTV.from(parseUnits(minLltv.toString(), 16)))
9265
- ));
9266
- maxLltv && (offers = offers.filter(
9267
- (o) => o.collaterals.every((c) => c.lltv <= LLTV.from(parseUnits(maxLltv.toString(), 16)))
9268
- ));
9330
+ minLltv && (offers = offers.filter((o) => o.collaterals.every((c) => c.lltv >= minLltv)));
9331
+ maxLltv && (offers = offers.filter((o) => o.collaterals.every((c) => c.lltv <= maxLltv)));
9269
9332
  offers = offers.sort((a, b) => sort(sortBy, sortOrder, a, b));
9270
9333
  let nextCursor = null;
9271
9334
  if (offers.length > limit) {
@@ -9312,8 +9375,8 @@ function memory(parameters) {
9312
9375
  limit = 20
9313
9376
  } = params;
9314
9377
  const now = Time.now();
9315
- const buy = side !== "buy";
9316
- const sortOrder = buy ? "asc" : "desc";
9378
+ const isBuying = side === "buy";
9379
+ const sortOrder = isBuying ? "desc" : "asc";
9317
9380
  let offers = Array.from(map.values()).map((o) => ({
9318
9381
  ...o,
9319
9382
  consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
@@ -9326,22 +9389,27 @@ function memory(parameters) {
9326
9389
  offers = offers.filter(
9327
9390
  (o) => sortOrder === "asc" ? o.rate >= BigInt(cursor.rate) : o.rate <= BigInt(cursor.rate)
9328
9391
  );
9329
- offers = offers.filter((o) => o.hash !== cursor.hash);
9330
9392
  }
9331
- offers = offers.filter((o) => o.buy === buy);
9393
+ offers = offers.filter((o) => o.buy === !isBuying);
9332
9394
  offers = offers.filter((o) => o.chainId === BigInt(chainId));
9333
9395
  offers = offers.filter((o) => o.expiry >= now);
9334
- rate && (offers = offers.filter((o) => buy ? o.rate >= rate : o.rate <= rate));
9396
+ rate && (offers = offers.filter((o) => isBuying ? o.rate >= rate : o.rate <= rate));
9335
9397
  collaterals.length > 0 && (offers = offers.filter(
9336
- (o) => buy ? collaterals.every((c) => {
9337
- return o.collaterals.some(
9338
- (oc) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
9339
- );
9340
- }) : o.collaterals.every((oc) => {
9341
- return collaterals.some(
9342
- (c) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
9343
- );
9344
- })
9398
+ (o) => isBuying ? (
9399
+ // when wanting to buy, sell offer collaterals ⊆ user buy collaterals
9400
+ o.collaterals.every((oc) => {
9401
+ return collaterals.some(
9402
+ (c) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
9403
+ );
9404
+ })
9405
+ ) : (
9406
+ // when wanting to sell, user sell collaterals ⊆ buy offer collaterals
9407
+ collaterals.every((c) => {
9408
+ return o.collaterals.some(
9409
+ (oc) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
9410
+ );
9411
+ })
9412
+ )
9345
9413
  ));
9346
9414
  maturity && (offers = offers.filter((o) => o.maturity === maturity));
9347
9415
  minMaturity && (offers = offers.filter((o) => o.maturity >= minMaturity));
@@ -9379,6 +9447,7 @@ function memory(parameters) {
9379
9447
  }
9380
9448
  offers = Array.from(byGroup.values());
9381
9449
  offers = offers.sort((a, b) => sort("rate", sortOrder, a, b));
9450
+ cursor && (offers = offers.filter((o) => o.hash !== cursor.hash));
9382
9451
  let nextCursor = null;
9383
9452
  if (offers.length > limit) {
9384
9453
  const last = offers[limit - 1];
@@ -9411,9 +9480,12 @@ function memory(parameters) {
9411
9480
  return deleted;
9412
9481
  },
9413
9482
  updateStatus: async (parameters2) => {
9414
- if (!map.has(parameters2.offerHash.toLowerCase())) return;
9415
- map.set(parameters2.offerHash.toLowerCase(), {
9416
- ...map.get(parameters2.offerHash.toLowerCase()),
9483
+ const key = parameters2.offerHash.toLowerCase();
9484
+ const existing = map.get(key);
9485
+ if (!existing) return;
9486
+ if (existing.status === parameters2.status) return;
9487
+ map.set(key, {
9488
+ ...existing,
9417
9489
  status: parameters2.status,
9418
9490
  metadata: parameters2.metadata
9419
9491
  });
@@ -9479,7 +9551,7 @@ async function serve(parameters) {
9479
9551
  return Mempool.error(err, c);
9480
9552
  }
9481
9553
  });
9482
- app.get("/v1/match-offers", async (c) => {
9554
+ app.get("/v1/offers/match", async (c) => {
9483
9555
  try {
9484
9556
  const params = parse("match_offers", c.req.query());
9485
9557
  const offers = await store.findMatchingOffers({