@morpho-dev/router 0.6.0 → 0.7.0

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.
package/dist/cli.js CHANGED
@@ -152,7 +152,7 @@ function startActiveSpan(tracer, name, fn) {
152
152
  //#endregion
153
153
  //#region package.json
154
154
  var name = "@morpho-dev/router";
155
- var version = "0.6.0";
155
+ var version = "0.7.0";
156
156
  var description = "Router package for Morpho protocol";
157
157
 
158
158
  //#endregion
@@ -1696,13 +1696,18 @@ const assets = {
1696
1696
  "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1697
1697
  "0x6B175474E89094C44Da98b954EedeAC495271d0F",
1698
1698
  "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
1699
- "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
1699
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
1700
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
1701
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
1700
1702
  ],
1701
1703
  [ChainId.BASE.toString()]: [
1702
1704
  "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
1703
1705
  "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
1704
1706
  "0x4200000000000000000000000000000000000006",
1705
- "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
1707
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
1708
+ "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
1709
+ "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
1710
+ "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
1706
1711
  ],
1707
1712
  [ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
1708
1713
  "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
@@ -1718,6 +1723,43 @@ const assets = {
1718
1723
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
1719
1724
  ]
1720
1725
  };
1726
+ const oracles$1 = {
1727
+ [ChainId.ETHEREUM.toString()]: [
1728
+ "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
1729
+ "0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
1730
+ "0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
1731
+ "0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
1732
+ "0xbD60A6770b27E084E8617335ddE769241B0e71D8",
1733
+ "0xAe12416c1F21B0698c27fe042D9309C83baC6597"
1734
+ ],
1735
+ [ChainId.BASE.toString()]: [
1736
+ "0xD09048c8B568Dbf5f189302beA26c9edABFC4858",
1737
+ "0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4",
1738
+ "0x05D2618404668D725B66c0f32B39e4EC15B393dC",
1739
+ "0xE1bb8E5b4930eC9FeC7f7943FCF6227649F14B37",
1740
+ "0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9",
1741
+ "0x10b95702a0ce895972C91e432C4f7E19811D320E",
1742
+ "0x8C87DbD7A0c647A4291592Bc2994dbF95880fE2F",
1743
+ "0x4A11590e5326138B514E08A9B52202D42077Ca65",
1744
+ "0xa54122f0E0766258377Ffe732e454A3248f454F4"
1745
+ ],
1746
+ [ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
1747
+ "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
1748
+ "0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
1749
+ "0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
1750
+ "0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
1751
+ "0xbD60A6770b27E084E8617335ddE769241B0e71D8",
1752
+ "0xAe12416c1F21B0698c27fe042D9309C83baC6597"
1753
+ ],
1754
+ [ChainId.ANVIL.toString()]: [
1755
+ "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
1756
+ "0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
1757
+ "0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
1758
+ "0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
1759
+ "0xbD60A6770b27E084E8617335ddE769241B0e71D8",
1760
+ "0xAe12416c1F21B0698c27fe042D9309C83baC6597"
1761
+ ]
1762
+ };
1721
1763
  const configs = {
1722
1764
  ethereum: {
1723
1765
  callbacks: [
@@ -2916,6 +2958,16 @@ const token = ({ assetsByChainId }) => single("token", "Validates that offer loa
2916
2958
  if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
2917
2959
  });
2918
2960
  /**
2961
+ * A validation rule that checks if the offer's oracle addresses are allowed for its chain.
2962
+ * @param oraclesByChainId - Allowed oracles indexed by chain id.
2963
+ * @returns The issue that was found. If the offer is valid, this will be undefined.
2964
+ */
2965
+ const oracle = ({ oraclesByChainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the offer chain", (offer) => {
2966
+ const allowedOracles = oraclesByChainId[offer.chainId]?.map((oracle) => oracle.toLowerCase());
2967
+ if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${offer.chainId}` };
2968
+ if (offer.collaterals.some((collateral) => !allowedOracles.includes(collateral.oracle.toLowerCase()))) return { message: "Oracle is not allowed" };
2969
+ });
2970
+ /**
2919
2971
  * A batch validation rule that ensures all offers in a tree have the same maker address.
2920
2972
  * Returns an issue only for the first non-conforming offer.
2921
2973
  * This rule is signing-agnostic; signer verification is handled at the collector level.
@@ -2946,7 +2998,11 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
2946
2998
  //#region src/gatekeeper/morphoRules.ts
2947
2999
  const morphoRules = (chains) => {
2948
3000
  const assetsByChainId = {};
2949
- for (const chain of chains) assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
3001
+ const oraclesByChainId = {};
3002
+ for (const chain of chains) {
3003
+ assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
3004
+ oraclesByChainId[chain.id] = oracles$1[chain.id.toString()] ?? [];
3005
+ }
2950
3006
  return [
2951
3007
  sameMaker(),
2952
3008
  amountMutualExclusivity(),
@@ -2960,14 +3016,15 @@ const morphoRules = (chains) => {
2960
3016
  ],
2961
3017
  allowedAddresses: chains.flatMap((c) => getCallbackAddresses(c.name))
2962
3018
  }),
2963
- token({ assetsByChainId })
3019
+ token({ assetsByChainId }),
3020
+ oracle({ oraclesByChainId })
2964
3021
  ];
2965
3022
  };
2966
3023
 
2967
3024
  //#endregion
2968
3025
  //#region src/gatekeeper/ConfigRules.ts
2969
3026
  /**
2970
- * Build the configured rules (maturities + callback addresses + loan tokens) for the provided chains.
3027
+ * Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
2971
3028
  * @param chains - Chains to include in the configured rules.
2972
3029
  * @returns Sorted list of config rules.
2973
3030
  */
@@ -2999,6 +3056,12 @@ function buildConfigRules(chains) {
2999
3056
  chain_id: chain.id,
3000
3057
  address: normalizeAddress(address)
3001
3058
  });
3059
+ const oracles = oracles$1[chain.id.toString()] ?? [];
3060
+ for (const address of oracles) rules.push({
3061
+ type: "oracle",
3062
+ chain_id: chain.id,
3063
+ address: normalizeAddress(address)
3064
+ });
3002
3065
  }
3003
3066
  rules.sort(compareConfigRules);
3004
3067
  return rules;
@@ -3020,6 +3083,10 @@ function buildConfigRulesChecksum(rules) {
3020
3083
  hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
3021
3084
  continue;
3022
3085
  }
3086
+ if (rule.type === "oracle") {
3087
+ hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
3088
+ continue;
3089
+ }
3023
3090
  hash.update(`loan_token:${rule.chain_id}:${rule.address}\n`);
3024
3091
  }
3025
3092
  return hash.digest("hex");
@@ -3036,6 +3103,7 @@ function compareConfigRules(left, right) {
3036
3103
  return left.address.localeCompare(right.address);
3037
3104
  }
3038
3105
  if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
3106
+ if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
3039
3107
  return 0;
3040
3108
  }
3041
3109
 
@@ -3652,7 +3720,8 @@ var OfferListResponse = class extends SuccessResponse {};
3652
3720
  __decorate([ApiProperty({
3653
3721
  type: "string",
3654
3722
  nullable: true,
3655
- example: offerCursorExample
3723
+ example: offerCursorExample,
3724
+ description: "Pagination cursor. Offer hash (0x...) for maker queries; base64url-encoded cursor for obligation queries."
3656
3725
  })], OfferListResponse.prototype, "cursor", void 0);
3657
3726
  __decorate([ApiProperty({
3658
3727
  type: () => [OfferListItemResponse],
@@ -4205,11 +4274,17 @@ const configRulesLoanTokenExample = {
4205
4274
  chain_id: 1,
4206
4275
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
4207
4276
  };
4277
+ const configRulesOracleExample = {
4278
+ type: "oracle",
4279
+ chain_id: 1,
4280
+ address: "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83"
4281
+ };
4208
4282
  const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
4209
4283
  const configRulesPayloadExample = [
4210
4284
  configRulesMaturityExample,
4211
4285
  configRulesCallbackExample,
4212
- configRulesLoanTokenExample
4286
+ configRulesLoanTokenExample,
4287
+ configRulesOracleExample
4213
4288
  ];
4214
4289
  const configContractNames = [
4215
4290
  "mempool",
@@ -4360,7 +4435,7 @@ __decorate([
4360
4435
  name: "types",
4361
4436
  type: ["string"],
4362
4437
  required: false,
4363
- example: "maturity,loan_token",
4438
+ example: "maturity,loan_token,oracle",
4364
4439
  description: "Filter by rule types (comma-separated).",
4365
4440
  style: "form",
4366
4441
  explode: false
@@ -4590,6 +4665,9 @@ function isValidBase64urlJson(val) {
4590
4665
  return false;
4591
4666
  }
4592
4667
  }
4668
+ function isValidOfferHashCursor(val) {
4669
+ return /^0x[a-f0-9]{64}$/i.test(val);
4670
+ }
4593
4671
  const csvArray = (schema) => z$2.preprocess((value) => {
4594
4672
  if (value === void 0) return void 0;
4595
4673
  if (Array.isArray(value)) {
@@ -4615,10 +4693,11 @@ const PaginationQueryParams = z$2.object({
4615
4693
  const ConfigRuleTypes = z$2.enum([
4616
4694
  "maturity",
4617
4695
  "callback",
4618
- "loan_token"
4696
+ "loan_token",
4697
+ "oracle"
4619
4698
  ]);
4620
4699
  const GetConfigRulesQueryParams = z$2.object({
4621
- cursor: z$2.string().regex(/^(maturity|callback|loan_token):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
4700
+ cursor: z$2.string().regex(/^(maturity|callback|loan_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
4622
4701
  description: "Pagination cursor in type:chain_id:<value> format",
4623
4702
  example: "maturity:1:1730415600:end_of_next_month"
4624
4703
  }),
@@ -4628,7 +4707,7 @@ const GetConfigRulesQueryParams = z$2.object({
4628
4707
  }),
4629
4708
  types: csvArray(ConfigRuleTypes).meta({
4630
4709
  description: "Filter by rule types (comma-separated).",
4631
- example: "maturity,loan_token"
4710
+ example: "maturity,loan_token,oracle"
4632
4711
  }),
4633
4712
  chains: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
4634
4713
  description: "Filter by chain IDs (comma-separated).",
@@ -4649,8 +4728,11 @@ const GetConfigContractsQueryParams = z$2.object({
4649
4728
  example: "1,8453"
4650
4729
  })
4651
4730
  });
4652
- const GetOffersQueryParams = z$2.object({
4653
- ...PaginationQueryParams.shape,
4731
+ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend({
4732
+ cursor: z$2.string().optional().meta({
4733
+ description: "Pagination cursor. Use offer hash (0x...) for maker queries, base64url for obligation queries.",
4734
+ example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
4735
+ }),
4654
4736
  side: z$2.enum(["buy", "sell"]).optional().meta({
4655
4737
  description: "Side of the offer. Required when using obligation_id.",
4656
4738
  example: "buy"
@@ -4674,11 +4756,29 @@ const GetOffersQueryParams = z$2.object({
4674
4756
  });
4675
4757
  return;
4676
4758
  }
4677
- if (hasMaker) return;
4759
+ if (hasMaker) {
4760
+ if (val.cursor !== void 0 && !isValidOfferHashCursor(val.cursor)) ctx.addIssue({
4761
+ code: "custom",
4762
+ path: ["cursor"],
4763
+ message: "Cursor must be a 32-byte hex offer hash when filtering by maker"
4764
+ });
4765
+ return;
4766
+ }
4678
4767
  if (!hasObligation || !hasSide) ctx.addIssue({
4679
4768
  code: "custom",
4680
4769
  message: "Must provide either maker or both obligation_id and side"
4681
4770
  });
4771
+ if (val.cursor !== void 0 && !isValidBase64urlJson(val.cursor)) ctx.addIssue({
4772
+ code: "custom",
4773
+ path: ["cursor"],
4774
+ message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
4775
+ });
4776
+ }).transform((val) => {
4777
+ if (val.maker && val.cursor) return {
4778
+ ...val,
4779
+ cursor: val.cursor.toLowerCase()
4780
+ };
4781
+ return val;
4682
4782
  });
4683
4783
  const GetObligationsQueryParams = z$2.object({
4684
4784
  ...PaginationQueryParams.shape,
@@ -4827,6 +4927,7 @@ async function getConfigRules(query, chains) {
4827
4927
  function formatCursor$1(rule) {
4828
4928
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
4829
4929
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
4930
+ if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
4830
4931
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
4831
4932
  }
4832
4933
  function parseCursor$1(cursor) {
@@ -4859,13 +4960,16 @@ function parseCursor$1(cursor) {
4859
4960
  address: parseAddress(addressValue, "Cursor address")
4860
4961
  };
4861
4962
  }
4862
- const addressValue = rest.join(":");
4863
- if (!addressValue) throw new BadRequestError("Cursor must be in the format loan_token:chain_id:address");
4864
- return {
4865
- type,
4866
- chain_id,
4867
- address: parseAddress(addressValue, "Cursor address")
4868
- };
4963
+ if (type === "loan_token" || type === "oracle") {
4964
+ const addressValue = rest.join(":");
4965
+ if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
4966
+ return {
4967
+ type,
4968
+ chain_id,
4969
+ address: parseAddress(addressValue, "Cursor address")
4970
+ };
4971
+ }
4972
+ throw new BadRequestError("Cursor has an invalid rule type");
4869
4973
  }
4870
4974
  function findStartIndex$1(rules, cursor) {
4871
4975
  let low = 0;
@@ -4883,7 +4987,7 @@ function parseAddress(address, label) {
4883
4987
  return address.toLowerCase();
4884
4988
  }
4885
4989
  function isConfigRuleType(value) {
4886
- return value === "maturity" || value === "callback" || value === "loan_token";
4990
+ return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
4887
4991
  }
4888
4992
  function isMaturityType(value) {
4889
4993
  return Object.values(MaturityType).includes(value);
@@ -8266,9 +8370,9 @@ function create$6(db) {
8266
8370
  blockNumber: r.blockNumber
8267
8371
  }));
8268
8372
  },
8269
- upsert: async (oracles$1) => {
8270
- if (oracles$1.length === 0) return;
8271
- const rows = oracles$1.map((o) => ({
8373
+ upsert: async (oracles$2) => {
8374
+ if (oracles$2.length === 0) return;
8375
+ const rows = oracles$2.map((o) => ({
8272
8376
  chainId: o.chainId,
8273
8377
  address: o.address.toLowerCase(),
8274
8378
  price: o.price !== null ? o.price.toString() : null,
@@ -10637,15 +10741,33 @@ async function seedMockOffers(parameters) {
10637
10741
  const { db, gatekeeper, chainRegistry, count } = parameters;
10638
10742
  if (count <= 0) return [];
10639
10743
  const configuredChains = chainRegistry.list();
10640
- const tokens = {
10641
- "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": 6,
10642
- "0x6B175474E89094C44Da98b954EedeAC495271d0F": 18,
10643
- "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": 18,
10644
- "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599": 8,
10744
+ const assetsDecimals = {
10745
+ "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": 6,
10746
+ "0x6b175474e89094c44da98b954eedeac495271d0f": 18,
10747
+ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": 18,
10748
+ "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 8,
10749
+ "0x1abaea1f7c830bd89acc67ec4af516284b1bc33c": 6,
10750
+ "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0": 18,
10751
+ "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 6,
10752
+ "0x50c5725949a6f0c72e6c4a641f24049a917db0cb": 18,
10645
10753
  "0x4200000000000000000000000000000000000006": 18,
10646
- "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913": 6,
10647
- "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb": 18
10754
+ "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": 8,
10755
+ "0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": 18,
10756
+ "0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": 6,
10757
+ "0xce79ddb3152d52ff8fe65a4c7e058b035fcb560a": 18
10648
10758
  };
10759
+ const assetsByChainId = {};
10760
+ const oraclesByChainId = {};
10761
+ for (const chain of configuredChains) {
10762
+ const assets$1 = assets[chain.id.toString()]?.map((asset) => asset.toLowerCase()) ?? [];
10763
+ if (assets$1.length === 0) throw new Error(`No gatekeeper assets configured for chain ${chain.id}`);
10764
+ const missingDecimals = assets$1.filter((asset) => assetsDecimals[asset] === void 0);
10765
+ if (missingDecimals.length > 0) throw new Error(`Missing decimals for assets on chain ${chain.id}: ${missingDecimals.join(", ")}`);
10766
+ assetsByChainId[chain.id] = assets$1;
10767
+ const oracles = oracles$1[chain.id.toString()]?.map((oracle) => oracle.toLowerCase()) ?? [];
10768
+ if (oracles.length === 0) throw new Error(`No gatekeeper oracles configured for chain ${chain.id}`);
10769
+ oraclesByChainId[chain.id] = oracles;
10770
+ }
10649
10771
  const now = Math.floor(Date.now() / 1e3);
10650
10772
  const offerBlockNumber = 1;
10651
10773
  const positionBlockNumber = offerBlockNumber + 1;
@@ -10656,8 +10778,9 @@ async function seedMockOffers(parameters) {
10656
10778
  const offers = createMockOffers({
10657
10779
  count,
10658
10780
  chains: configuredChains,
10659
- loanTokens: Object.keys(tokens).map((key) => key),
10660
- assetsDecimals: tokens,
10781
+ assetsByChainId,
10782
+ oraclesByChainId,
10783
+ assetsDecimals,
10661
10784
  start,
10662
10785
  expiry,
10663
10786
  maturity,
@@ -10683,24 +10806,53 @@ async function getOffersFromFile(filePath) {
10683
10806
  return Array.isArray(data) ? data.map(from$12) : [from$12(data)];
10684
10807
  }
10685
10808
  function createMockOffers(parameters) {
10686
- const { count, chains, loanTokens, assetsDecimals, start, expiry, maturity, chainRegistry } = parameters;
10809
+ const { count, chains, assetsByChainId, oraclesByChainId, assetsDecimals, start, expiry, maturity, chainRegistry } = parameters;
10810
+ if (chains.length === 0) throw new Error("No chains configured for mock offers");
10687
10811
  return Array.from({ length: count }, () => {
10688
10812
  const buy = Math.random() < .5;
10813
+ const chain = chains[Math.floor(Math.random() * chains.length)];
10814
+ const allowedAssets = assetsByChainId[chain.id] ?? [];
10815
+ if (allowedAssets.length === 0) throw new Error(`No allowed assets configured for chain ${chain.id}`);
10816
+ const loanToken = allowedAssets[Math.floor(Math.random() * allowedAssets.length)];
10689
10817
  const offer = random({
10690
- chains,
10691
- loanTokens,
10818
+ chains: [chain],
10819
+ loanTokens: [loanToken],
10692
10820
  assetsDecimals,
10693
10821
  start,
10694
10822
  expiry,
10695
10823
  maturity,
10696
10824
  buy
10697
10825
  });
10826
+ const allowedOracles = oraclesByChainId[chain.id] ?? [];
10827
+ if (allowedOracles.length === 0) throw new Error(`No allowed oracles configured for chain ${chain.id}`);
10828
+ const collateralAssets = allowedAssets.filter((asset) => asset !== loanToken);
10829
+ if (collateralAssets.length === 0) throw new Error(`No collateral assets available for chain ${chain.id}`);
10830
+ const collateralCount = Math.min(collateralAssets.length, 1 + Math.floor(Math.random() * 3));
10831
+ const collateralPool = [...collateralAssets];
10832
+ const collateralSelections = [];
10833
+ while (collateralSelections.length < collateralCount && collateralPool.length > 0) {
10834
+ const index = Math.floor(Math.random() * collateralPool.length);
10835
+ const [value] = collateralPool.splice(index, 1);
10836
+ if (value !== void 0) collateralSelections.push(value);
10837
+ }
10838
+ collateralSelections.sort();
10839
+ const lltv = offer.collaterals[0]?.lltv ?? .86;
10840
+ const collaterals = collateralSelections.map((asset) => from$14({
10841
+ asset,
10842
+ oracle: allowedOracles[Math.floor(Math.random() * allowedOracles.length)],
10843
+ lltv
10844
+ }));
10698
10845
  if (!chainRegistry.getById(offer.chainId)) throw new Error(`Missing chain config for id ${offer.chainId}`);
10699
10846
  const callbackType = buy ? Type$1.BuyVaultV1Callback : Type$1.SellERC20Callback;
10700
10847
  const callbackAddress = buy ? BUY_CALLBACK_ADDRESS : SELL_CALLBACK_ADDRESS;
10701
- const callbackData = buildMockCallbackData(callbackType, offer);
10848
+ const callbackData = buildMockCallbackData(callbackType, {
10849
+ ...offer,
10850
+ collaterals
10851
+ });
10702
10852
  return from$12({
10703
10853
  ...offer,
10854
+ loanToken,
10855
+ collaterals,
10704
10856
  callback: {
10705
10857
  address: callbackAddress,
10706
10858
  data: callbackData
@@ -1986,7 +1986,7 @@ interface paths {
1986
1986
  chains?: number[];
1987
1987
  /**
1988
1988
  * @description Filter by rule types (comma-separated).
1989
- * @example maturity,loan_token
1989
+ * @example maturity,loan_token,oracle
1990
1990
  */
1991
1991
  types?: string[];
1992
1992
  /**
@@ -2613,6 +2613,11 @@ interface components {
2613
2613
  * "type": "loan_token",
2614
2614
  * "chain_id": 1,
2615
2615
  * "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
2616
+ * },
2617
+ * {
2618
+ * "type": "oracle",
2619
+ * "chain_id": 1,
2620
+ * "address": "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83"
2616
2621
  * }
2617
2622
  * ]
2618
2623
  */
@@ -2631,7 +2636,11 @@ interface components {
2631
2636
  callback_type?: string;
2632
2637
  };
2633
2638
  OfferListResponse: {
2634
- meta: components["schemas"]["Meta"]; /** @example eyJvZmZzZXQiOjEwMH0 */
2639
+ meta: components["schemas"]["Meta"];
2640
+ /**
2641
+ * @description Pagination cursor. Offer hash (0x...) for maker queries; base64url-encoded cursor for obligation queries.
2642
+ * @example eyJvZmZzZXQiOjEwMH0
2643
+ */
2635
2644
  cursor: string | null;
2636
2645
  /**
2637
2646
  * @description Offers matching the provided filters.
@@ -3219,19 +3228,32 @@ declare const schemas: {
3219
3228
  maturity: "maturity";
3220
3229
  callback: "callback";
3221
3230
  loan_token: "loan_token";
3231
+ oracle: "oracle";
3222
3232
  }>>>>;
3223
3233
  chains: z$1.ZodOptional<z$1.ZodPipe<z$1.ZodTransform<{} | null | undefined, unknown>, z$1.ZodArray<z$1.ZodPipe<z$1.ZodString, z$1.ZodTransform<number, string>>>>>;
3224
3234
  }, z$1.core.$strip>;
3225
- readonly get_offers: z$1.ZodObject<{
3235
+ readonly get_offers: z$1.ZodPipe<z$1.ZodObject<{
3236
+ limit: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodPipe<z$1.ZodPipe<z$1.ZodString, z$1.ZodTransform<number, string>>, z$1.ZodNumber>>>;
3237
+ cursor: z$1.ZodOptional<z$1.ZodString>;
3226
3238
  side: z$1.ZodOptional<z$1.ZodEnum<{
3227
3239
  buy: "buy";
3228
3240
  sell: "sell";
3229
3241
  }>>;
3230
3242
  obligation_id: z$1.ZodOptional<z$1.ZodPipe<z$1.ZodString, z$1.ZodTransform<`0x${string}`, string>>>;
3231
3243
  maker: z$1.ZodOptional<z$1.ZodPipe<z$1.ZodString, z$1.ZodTransform<`0x${string}`, string>>>;
3232
- cursor: z$1.ZodOptional<z$1.ZodString>;
3233
- limit: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodPipe<z$1.ZodPipe<z$1.ZodString, z$1.ZodTransform<number, string>>, z$1.ZodNumber>>>;
3234
- }, z$1.core.$strip>;
3244
+ }, z$1.core.$strip>, z$1.ZodTransform<{
3245
+ limit: number;
3246
+ cursor?: string | undefined;
3247
+ side?: "buy" | "sell" | undefined;
3248
+ obligation_id?: `0x${string}` | undefined;
3249
+ maker?: `0x${string}` | undefined;
3250
+ }, {
3251
+ limit: number;
3252
+ cursor?: string | undefined;
3253
+ side?: "buy" | "sell" | undefined;
3254
+ obligation_id?: `0x${string}` | undefined;
3255
+ maker?: `0x${string}` | undefined;
3256
+ }>>;
3235
3257
  readonly get_obligations: z$1.ZodObject<{
3236
3258
  cursor: z$1.ZodOptional<z$1.ZodString>;
3237
3259
  chains: z$1.ZodOptional<z$1.ZodPipe<z$1.ZodTransform<{} | null | undefined, unknown>, z$1.ZodArray<z$1.ZodPipe<z$1.ZodString, z$1.ZodTransform<number, string>>>>>;
@@ -3485,6 +3507,10 @@ type ConfigRule = {
3485
3507
  type: "loan_token";
3486
3508
  chain_id: Id;
3487
3509
  address: Address;
3510
+ } | {
3511
+ type: "oracle";
3512
+ chain_id: Id;
3513
+ address: Address;
3488
3514
  };
3489
3515
  type ValidationIssue = {
3490
3516
  index: number;
@@ -3579,9 +3605,9 @@ type GatekeeperParameters = {
3579
3605
  declare function create(parameters: GatekeeperParameters): Gatekeeper;
3580
3606
  //#endregion
3581
3607
  //#region src/gatekeeper/morphoRules.d.ts
3582
- declare const morphoRules: (chains: Chain$1[]) => (Rule<Offer, "mixed_maker"> | Rule<Offer, "amount_mutual_exclusivity"> | Rule<Offer, "chain_ids"> | Rule<Offer, "maturity"> | Rule<Offer, "callback"> | Rule<Offer, "token">)[];
3608
+ declare const morphoRules: (chains: Chain$1[]) => (Rule<Offer, "mixed_maker"> | Rule<Offer, "amount_mutual_exclusivity"> | Rule<Offer, "chain_ids"> | Rule<Offer, "maturity"> | Rule<Offer, "callback"> | Rule<Offer, "token"> | Rule<Offer, "oracle">)[];
3583
3609
  declare namespace Rules_d_exports {
3584
- export { ValidityParameters, amountMutualExclusivity, callback, chains, maturity, sameMaker, token, validity };
3610
+ export { ValidityParameters, amountMutualExclusivity, callback, chains, maturity, oracle, sameMaker, token, validity };
3585
3611
  }
3586
3612
  type ValidityParameters = {
3587
3613
  client: PublicClient<Transport, Chain$1>;
@@ -3620,6 +3646,16 @@ declare const token: ({
3620
3646
  }: {
3621
3647
  assetsByChainId: Partial<Record<Id, Address[]>>;
3622
3648
  }) => Rule<Offer, "token">;
3649
+ /**
3650
+ * A validation rule that checks if the offer's oracle addresses are allowed for its chain.
3651
+ * @param oraclesByChainId - Allowed oracles indexed by chain id.
3652
+ * @returns The issue that was found. If the offer is valid, this will be undefined.
3653
+ */
3654
+ declare const oracle: ({
3655
+ oraclesByChainId
3656
+ }: {
3657
+ oraclesByChainId: Partial<Record<Id, Address[]>>;
3658
+ }) => Rule<Offer, "oracle">;
3623
3659
  /**
3624
3660
  * A batch validation rule that ensures all offers in a tree have the same maker address.
3625
3661
  * Returns an issue only for the first non-conforming offer.