import { Errors, Offer, Format } from '@morpho-dev/mempool';
export * from '@morpho-dev/mempool';
import { base, mainnet } from 'viem/chains';
import { maxUint256, parseAbi } from 'viem';
import { z } from 'zod/v4';
import { createDocument } from 'zod-openapi';

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);

// src/Chain.ts
var Chain_exports = {};
__export(Chain_exports, {
  ChainId: () => ChainId,
  chainIds: () => chainIds,
  chainNames: () => chainNames,
  chains: () => chains,
  getChain: () => getChain
});
var chainNames = ["ethereum", "base"];
var ChainId = {
  ETHEREUM: BigInt(mainnet.id),
  BASE: BigInt(base.id)
};
var chainIds = new Set(Object.values(ChainId));
var chainNameLookup = new Map(Object.entries(ChainId).map(([key, value]) => [value, key]));
function getChain(chainId) {
  const chainName = chainNameLookup.get(chainId)?.toLowerCase();
  if (!chainName) {
    return void 0;
  }
  return chains[chainName];
}
var chains = {
  ethereum: {
    ...mainnet,
    id: ChainId.ETHEREUM,
    name: "ethereum",
    whitelistedAssets: new Set(
      [
        "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        // USDC
        "0x6B175474E89094C44Da98b954EedeAC495271d0F"
        // DAI
      ].map((address) => address.toLowerCase())
    ),
    morpho: "0x0000000000000000000000000000000000000000"
  },
  base: {
    ...base,
    id: ChainId.BASE,
    name: "base",
    whitelistedAssets: new Set(
      [
        "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        // USDC
        "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb"
        // DAI
      ].map((address) => address.toLowerCase())
    ),
    morpho: "0x0000000000000000000000000000000000000000"
  }
};

// src/core/Client.ts
var Client_exports = {};
__export(Client_exports, {
  HttpForbiddenError: () => HttpForbiddenError,
  HttpGetOffersFailedError: () => HttpGetOffersFailedError,
  HttpRateLimitError: () => HttpRateLimitError,
  HttpUnauthorizedError: () => HttpUnauthorizedError,
  InvalidUrlError: () => InvalidUrlError,
  connect: () => connect,
  get: () => get,
  match: () => match
});

// src/RouterOffer.ts
var RouterOffer_exports = {};
__export(RouterOffer_exports, {
  InvalidRouterOfferError: () => InvalidRouterOfferError,
  OfferStatusValues: () => OfferStatusValues,
  RouterOfferSchema: () => RouterOfferSchema,
  from: () => from,
  fromSnakeCase: () => fromSnakeCase,
  random: () => random,
  toSnakeCase: () => toSnakeCase
});
var OfferStatusValues = [
  "valid",
  "callback_not_supported",
  "callback_error",
  "unverified"
];
var RouterOfferSchema = (parameters) => Offer.OfferSchema(parameters).extend({
  consumed: z.bigint({ coerce: true }).min(0n).max(maxUint256),
  status: z.enum(OfferStatusValues),
  metadata: z.object({
    issue: z.string()
  }).optional()
});
function from(input) {
  try {
    const parsedOffer = RouterOfferSchema({ omitHash: true }).parse(input);
    const parsedHash = Offer.OfferHashSchema.parse(Offer.hash(parsedOffer));
    return {
      ...parsedOffer,
      hash: parsedHash
    };
  } catch (error) {
    throw new InvalidRouterOfferError(error);
  }
}
function fromSnakeCase(input) {
  return from(Format.fromSnakeCase(input));
}
function toSnakeCase(offer) {
  return Format.toSnakeCase(offer);
}
function random() {
  const baseOffer = Offer.random();
  return from({
    ...baseOffer,
    status: "valid",
    metadata: void 0,
    consumed: 0n
  });
}
var InvalidRouterOfferError = class extends Errors.BaseError {
  constructor(error) {
    super("Invalid router offer.", { cause: error });
    __publicField(this, "name", "RouterOffer.InvalidRouterOfferError");
  }
};

// src/utils/batch.ts
function* batch(array, batchSize) {
  for (let i = 0; i < array.length; i += batchSize) {
    yield array.slice(i, i + batchSize);
  }
}

// src/utils/cursor.ts
function validateCursor(cursor) {
  if (!cursor || typeof cursor !== "object") {
    throw new Error("Cursor must be an object");
  }
  const c = cursor;
  if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
    throw new Error(
      `Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
    );
  }
  if (!["asc", "desc"].includes(c.dir)) {
    throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
  }
  if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
    throw new Error(
      `Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
    );
  }
  const validations = {
    rate: {
      field: "rate",
      type: "string",
      pattern: /^\d+$/,
      error: "numeric string"
    },
    amount: {
      field: "assets",
      type: "string",
      pattern: /^\d+$/,
      error: "numeric string"
    },
    maturity: {
      field: "maturity",
      type: "number",
      validator: (val) => val > 0,
      error: "positive number"
    },
    expiry: {
      field: "expiry",
      type: "number",
      validator: (val) => val > 0,
      error: "positive number"
    }
  };
  const validation = validations[c.sort];
  if (!validation) {
    throw new Error(`Invalid sort field: ${c.sort}`);
  }
  const fieldValue = c[validation.field];
  if (!fieldValue) {
    throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
  }
  if (typeof fieldValue !== validation.type) {
    throw new Error(
      `${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
    );
  }
  if (validation.pattern && !validation.pattern.test(fieldValue)) {
    throw new Error(
      `Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
    );
  }
  if (validation.validator && !validation.validator(fieldValue)) {
    throw new Error(
      `Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
    );
  }
  return true;
}
function encodeCursor(c) {
  return Buffer.from(JSON.stringify(c)).toString("base64url");
}
function decodeCursor(token) {
  if (!token) return null;
  const decoded = JSON.parse(Buffer.from(token, "base64url").toString());
  validateCursor(decoded);
  return decoded;
}

// src/utils/wait.ts
async function wait(time) {
  return new Promise((res) => setTimeout(res, time));
}

// src/utils/poll.ts
function poll(fn, { interval }) {
  let active = true;
  const unwatch = () => active = false;
  const watch = async () => {
    await wait(interval);
    const poll2 = async () => {
      if (!active) return;
      await fn({ unpoll: unwatch });
      await wait(interval);
      poll2();
    };
    poll2();
  };
  watch();
  return unwatch;
}

// src/core/apiSchema/requests.ts
var MAX_LIMIT = 100;
var DEFAULT_LIMIT = 20;
var MAX_LLTV = 100;
var MIN_LLTV = 0;
var GetOffersQueryParams = z.object({
  // Core filtering parameters
  creators: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
    message: "Creators must be comma-separated Ethereum addresses"
  }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
    description: "Filter by multiple creator addresses (comma-separated)",
    example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
  }),
  side: z.enum(["buy", "sell"]).optional().meta({
    description: "Filter by offer type: buy offers or sell offers",
    example: "buy"
  }),
  chains: z.string().regex(/^\d+(,\d+)*$/, {
    message: "Chains must be comma-separated chain IDs"
  }).transform((val) => val.split(",").map(Number)).optional().meta({
    description: "Filter by multiple blockchain networks (comma-separated chain IDs)",
    example: "1,137,10"
  }),
  loan_tokens: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
    message: "Loan assets must be comma-separated Ethereum addresses"
  }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
    description: "Filter by multiple loan assets (comma-separated)",
    example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
  }),
  status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
    message: "Status must be comma-separated status values"
  }).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
    message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
  }).optional().meta({
    description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
    example: "valid,callback_error"
  }),
  callback_addresses: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
    message: "Callback addresses must be comma-separated Ethereum addresses"
  }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
    description: "Filter by multiple callback addresses (comma-separated)",
    example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
  }),
  // Asset range
  min_amount: z.bigint({ coerce: true }).positive({
    message: "Min amount must be a positive number"
  }).optional().meta({
    description: "Minimum amount of assets in the offer",
    example: "1000"
  }),
  max_amount: z.bigint({ coerce: true }).positive({
    message: "Max amount must be a positive number"
  }).optional().meta({
    description: "Maximum amount of assets in the offer",
    example: "10000"
  }),
  // Rate range
  min_rate: z.bigint({ coerce: true }).positive({
    message: "Min rate must be a positive number"
  }).optional().meta({
    description: "Minimum rate per asset (in wei)",
    example: "500000000000000000"
  }),
  max_rate: z.bigint({ coerce: true }).positive({
    message: "Max rate must be a positive number"
  }).optional().meta({
    description: "Maximum rate per asset (in wei)",
    example: "1500000000000000000"
  }),
  // Time range
  min_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
    description: "Minimum maturity timestamp (Unix timestamp in seconds)",
    example: "1700000000"
  }),
  max_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
    description: "Maximum maturity timestamp (Unix timestamp in seconds)",
    example: "1800000000"
  }),
  min_expiry: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
    description: "Minimum expiry timestamp (Unix timestamp in seconds)",
    example: "1700000000"
  }),
  max_expiry: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
    description: "Maximum expiry timestamp (Unix timestamp in seconds)",
    example: "1800000000"
  }),
  // Collateral filtering
  collateral_assets: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
    message: "Collateral assets must be comma-separated Ethereum addresses"
  }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
    description: "Filter by multiple collateral assets (comma-separated)",
    example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
  }),
  collateral_oracles: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
    message: "Collateral oracles must be comma-separated Ethereum addresses"
  }).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
    description: "Filter by multiple rate oracles (comma-separated)",
    example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
  }),
  collateral_tuple: z.string().regex(
    /^(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]+)?)?)*$/,
    {
      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)."
    }
  ).transform((val) => {
    return val.split("#").map((tuple) => {
      const parts = tuple.split(":");
      if (parts.length === 0 || !parts[0]) {
        throw new z.ZodError([
          {
            code: "custom",
            message: "Asset address is required for each collateral tuple",
            path: ["collateral_tuple"],
            input: val
          }
        ]);
      }
      const asset = parts[0]?.toLowerCase();
      const oracle = parts[1]?.toLowerCase();
      const lltv = parts[2] ? parseFloat(parts[2]) : void 0;
      if (lltv !== void 0 && (lltv < MIN_LLTV || lltv > MAX_LLTV)) {
        throw new z.ZodError([
          {
            code: "custom",
            message: `LLTV must be between ${MIN_LLTV} and ${MAX_LLTV} (0-100%)`,
            path: ["collateral_tuple"],
            input: val
          }
        ]);
      }
      return {
        asset,
        oracle,
        lltv
      };
    });
  }).optional().meta({
    description: "Filter by collateral combinations in format: asset:oracle:lltv#asset2:oracle2:lltv2. Oracle and lltv are optional. Use # to separate multiple combinations.",
    example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:8000#0x9876543210987654321098765432109876543210::8000"
  }),
  min_lltv: z.string().regex(/^\d+(\.\d+)?$/, {
    message: "Min LLTV must be a valid number"
  }).transform((val) => parseFloat(val)).pipe(z.number().min(0).max(100)).optional().meta({
    description: "Minimum Loan-to-Value ratio (LLTV) for collateral (percentage as decimal, e.g., 80.5 = 80.5%)",
    example: "80.5"
  }),
  max_lltv: z.string().regex(/^\d+(\.\d+)?$/, {
    message: "Max LLTV must be a valid number"
  }).transform((val) => parseFloat(val)).pipe(z.number().min(0).max(100)).optional().meta({
    description: "Maximum Loan-to-Value ratio (LLTV) for collateral (percentage as decimal, e.g., 95.5 = 95.5%)",
    example: "95.5"
  }),
  // Sorting parameters
  sort_by: z.enum(["rate", "maturity", "expiry", "amount"]).optional().meta({
    description: "Field to sort results by",
    example: "rate"
  }),
  sort_order: z.enum(["asc", "desc"]).optional().default("desc").meta({
    description: "Sort direction: asc (ascending) or desc (descending, default)",
    example: "desc"
  }),
  // Pagination
  cursor: z.string().optional().refine(
    (val) => {
      if (!val) return true;
      try {
        const decoded = decodeCursor(val);
        return decoded !== null;
      } catch (_error) {
        return false;
      }
    },
    {
      message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
    }
  ).meta({
    description: "Pagination cursor in base64url-encoded format",
    example: "eyJzb3J0IjoicHJpY2UiLCJkaXIiOiJkZXNjIiwicHJpY2UiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwiaGFzaCI6IjB4ZGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIifQ"
  }),
  limit: z.string().regex(/^[1-9]\d*$/, {
    message: "Limit must be a positive integer"
  }).transform((val) => Number.parseInt(val, 10)).pipe(
    z.number().max(MAX_LIMIT, {
      message: `Limit cannot exceed ${MAX_LIMIT}`
    })
  ).optional().default(DEFAULT_LIMIT).meta({
    description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT}`,
    example: 10
  })
}).refine(
  (data) => data.min_maturity === void 0 || data.max_maturity === void 0 || data.max_maturity >= data.min_maturity,
  {
    message: "max_maturity must be greater than or equal to min_maturity",
    path: ["max_maturity"]
  }
).refine(
  (data) => data.min_expiry === void 0 || data.max_expiry === void 0 || data.max_expiry >= data.min_expiry,
  {
    message: "max_expiry must be greater than or equal to min_expiry",
    path: ["max_expiry"]
  }
).refine(
  (data) => data.min_amount === void 0 || data.max_amount === void 0 || data.max_amount >= data.min_amount,
  {
    message: "max_amount must be greater than or equal to min_amount",
    path: ["max_amount"]
  }
).refine(
  (data) => data.min_rate === void 0 || data.max_rate === void 0 || data.max_rate >= data.min_rate,
  {
    message: "max_rate must be greater than or equal to min_rate",
    path: ["max_rate"]
  }
).refine(
  (data) => data.min_lltv === void 0 || data.max_lltv === void 0 || data.max_lltv >= data.min_lltv,
  {
    message: "max_lltv must be greater than or equal to min_lltv",
    path: ["max_lltv"]
  }
);
var MatchOffersQueryParams = z.object({
  // Required parameters
  side: z.enum(["buy", "sell"]).meta({
    description: "The desired side of the match: 'buy' if you want to buy, 'sell' if you want to sell. If your intent is to sell, buy offers will be returned, and vice versa.",
    example: "buy"
  }),
  chain_id: z.string().regex(/^\d+$/, {
    message: "Chain ID must be a positive integer"
  }).transform((val) => Number.parseInt(val, 10)).pipe(z.number().positive()).meta({
    description: "The blockchain network chain ID",
    example: "1"
  }),
  // Optional parameters
  rate: z.bigint({ coerce: true }).positive({
    message: "Rate must be a positive number"
  }).optional().meta({
    description: "Rate per asset (in wei) for matching offers",
    example: "1000000000000000000"
  }),
  // Collateral filtering
  collaterals: z.string().regex(
    /^(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+)*$/,
    {
      message: "Collaterals must be in format: asset:oracle:lltv#asset2:oracle2:lltv2. All fields are required for each collateral."
    }
  ).transform((val) => {
    return val.split("#").map((collateral) => {
      const parts = collateral.split(":");
      if (parts.length !== 3) {
        throw new z.ZodError([
          {
            code: "custom",
            message: "Each collateral must have exactly 3 parts: asset:oracle:lltv",
            path: ["collaterals"],
            input: val
          }
        ]);
      }
      const [asset, oracle, lltvStr] = parts;
      if (!asset || !oracle || !lltvStr) {
        throw new z.ZodError([
          {
            code: "custom",
            message: "Asset, oracle, and lltv are all required for each collateral",
            path: ["collaterals"],
            input: val
          }
        ]);
      }
      const lltv = BigInt(lltvStr);
      if (lltv <= 0n) {
        throw new z.ZodError([
          {
            code: "custom",
            message: "LLTV must be a positive number",
            path: ["collaterals"],
            input: val
          }
        ]);
      }
      return {
        asset: asset.toLowerCase(),
        oracle: oracle.toLowerCase(),
        lltv
      };
    });
  }).optional().meta({
    description: "Collateral requirements in format: asset:oracle:lltv#asset2:oracle2:lltv2. Use # to separate multiple collaterals.",
    example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:800000000000000000#0x9876543210987654321098765432109876543210:0xfedcbafedcbafedcbafedcbafedcbafedcbafedc:900000000000000000"
  }),
  // Maturity filtering
  maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
    description: "Exact maturity timestamp (Unix timestamp in seconds)",
    example: "1700000000"
  }),
  min_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
    description: "Minimum maturity timestamp (Unix timestamp in seconds, inclusive)",
    example: "1700000000"
  }),
  max_maturity: z.bigint({ coerce: true }).min(0n).optional().transform((val) => val === void 0 ? void 0 : Number(val)).meta({
    description: "Maximum maturity timestamp (Unix timestamp in seconds, inclusive)",
    example: "1800000000"
  }),
  // Asset and creator filtering
  loan_token: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
    message: "Loan asset must be a valid Ethereum address"
  }).transform((val) => val.toLowerCase()).optional().meta({
    description: "The loan asset address to match against",
    example: "0x1234567890123456789012345678901234567890"
  }),
  creator: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
    message: "Creator must be a valid Ethereum address"
  }).transform((val) => val.toLowerCase()).optional().meta({
    description: "Filter by a specific offer creator address",
    example: "0x1234567890123456789012345678901234567890"
  }),
  // Status filtering
  status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
    message: "Status must be comma-separated status values"
  }).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
    message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
  }).optional().meta({
    description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
    example: "valid,callback_error"
  }),
  // Pagination
  cursor: z.string().optional().refine(
    (val) => {
      if (!val) return true;
      try {
        const decoded = decodeCursor(val);
        return decoded !== null;
      } catch (_error) {
        return false;
      }
    },
    {
      message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
    }
  ).meta({
    description: "Pagination cursor in base64url-encoded format",
    example: "eyJzb3J0IjoicHJpY2UiLCJkaXIiOiJkZXNjIiwicHJpY2UiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwiaGFzaCI6IjB4ZGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIifQ"
  }),
  limit: z.string().regex(/^[1-9]\d*$/, {
    message: "Limit must be a positive integer"
  }).transform((val) => Number.parseInt(val, 10)).pipe(
    z.number().max(MAX_LIMIT, {
      message: `Limit cannot exceed ${MAX_LIMIT}`
    })
  ).optional().default(DEFAULT_LIMIT).meta({
    description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT}`,
    example: 10
  })
}).refine(
  (data) => data.min_maturity === void 0 || data.max_maturity === void 0 || data.max_maturity >= data.min_maturity,
  {
    message: "max_maturity must be greater than or equal to min_maturity",
    path: ["max_maturity"]
  }
);
var schemas = {
  get_offers: GetOffersQueryParams,
  match_offers: MatchOffersQueryParams
};
function safeParse(action, query) {
  return schemas[action].safeParse(query);
}

// src/core/apiSchema/openapi.ts
var successResponseSchema = z.object({
  status: z.literal("success"),
  cursor: z.string().nullable(),
  data: z.array(z.any()),
  meta: z.object({
    timestamp: z.string()
  })
});
var errorResponseSchema = z.object({
  status: z.literal("error"),
  error: z.object({
    code: z.string(),
    message: z.string(),
    details: z.any().optional()
  }),
  meta: z.object({
    timestamp: z.string()
  })
});
var paths = {
  "/v1/offers": {
    get: {
      summary: "Get offers",
      description: "Get all offers with optional filtering and pagination",
      tags: ["Offers"],
      requestParams: {
        query: GetOffersQueryParams
      },
      responses: {
        200: {
          description: "Success",
          content: {
            "application/json": {
              schema: successResponseSchema
            }
          }
        },
        400: {
          description: "Bad Request",
          content: {
            "application/json": {
              schema: errorResponseSchema
            }
          }
        }
      }
    }
  },
  "/v1/match-offers": {
    get: {
      summary: "Match offers",
      description: "Find offers that match specific criteria",
      tags: ["Offers"],
      requestParams: {
        query: MatchOffersQueryParams
      },
      responses: {
        200: {
          description: "Success",
          content: {
            "application/json": {
              schema: successResponseSchema
            }
          }
        },
        400: {
          description: "Bad Request",
          content: {
            "application/json": {
              schema: errorResponseSchema
            }
          }
        }
      }
    }
  }
};
createDocument({
  openapi: "3.1.0",
  info: {
    title: "Router API",
    version: "1.0.0",
    description: "API for the Morpho Router"
  },
  tags: [
    {
      name: "Offers"
    }
  ],
  servers: [
    {
      url: "https://router.morpho.dev",
      description: "Production server"
    },
    {
      url: "http://localhost:8081",
      description: "Local development server"
    }
  ],
  paths
});

// src/core/apiSchema/utils.ts
function toResponse(routerOffer) {
  const { consumed, status, metadata, ...offer } = routerOffer;
  return {
    offer,
    consumed,
    status,
    metadata
  };
}
function fromResponse(offerResponse) {
  const { offer, consumed, status, metadata } = offerResponse;
  return {
    ...offer,
    consumed,
    status,
    metadata
  };
}

// src/core/Client.ts
function connect(opts) {
  const u = new URL(opts?.url || "https://router.morpho.dev");
  if (u.protocol !== "http:" && u.protocol !== "https:") {
    throw new InvalidUrlError(u.toString());
  }
  const headers = new Headers();
  headers.set("Content-Type", "application/json");
  opts?.apiKey !== void 0 ? headers.set("X-API-Key", opts.apiKey) : null;
  const config = {
    url: u,
    headers
  };
  return {
    ...config,
    get: (parameters) => get(config, parameters),
    match: (parameters) => match(config, parameters)
  };
}
async function get(config, parameters) {
  const url = new URL(`${config.url.toString()}v1/offers`);
  if (parameters.creators?.length) {
    url.searchParams.set("creators", parameters.creators.join(","));
  }
  if (parameters.side) {
    url.searchParams.set("side", parameters.side);
  }
  if (parameters.chains?.length) {
    url.searchParams.set("chains", parameters.chains.join(","));
  }
  if (parameters.loanTokens?.length) {
    url.searchParams.set("loan_tokens", parameters.loanTokens.join(","));
  }
  if (parameters.status?.length) {
    url.searchParams.set("status", parameters.status.join(","));
  }
  if (parameters.callbackAddresses?.length) {
    url.searchParams.set("callback_addresses", parameters.callbackAddresses.join(","));
  }
  if (parameters.minAmount !== void 0) {
    url.searchParams.set("min_amount", parameters.minAmount.toString());
  }
  if (parameters.maxAmount !== void 0) {
    url.searchParams.set("max_amount", parameters.maxAmount.toString());
  }
  if (parameters.minRate !== void 0) {
    url.searchParams.set("min_rate", parameters.minRate.toString());
  }
  if (parameters.maxRate !== void 0) {
    url.searchParams.set("max_rate", parameters.maxRate.toString());
  }
  if (parameters.minMaturity !== void 0) {
    url.searchParams.set("min_maturity", parameters.minMaturity.toString());
  }
  if (parameters.maxMaturity !== void 0) {
    url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
  }
  if (parameters.minExpiry !== void 0) {
    url.searchParams.set("min_expiry", parameters.minExpiry.toString());
  }
  if (parameters.maxExpiry !== void 0) {
    url.searchParams.set("max_expiry", parameters.maxExpiry.toString());
  }
  if (parameters.collateralAssets?.length) {
    url.searchParams.set("collateral_assets", parameters.collateralAssets.join(","));
  }
  if (parameters.collateralOracles?.length) {
    url.searchParams.set("collateral_oracles", parameters.collateralOracles.join(","));
  }
  if (parameters.collateralTuple?.length) {
    const tupleStr = parameters.collateralTuple.map(({ asset, oracle, lltv }) => {
      let result = asset;
      if (oracle) {
        result += `:${oracle}`;
      } else if (lltv !== void 0) {
        result += `:`;
      }
      if (lltv !== void 0) result += `:${lltv}`;
      return result;
    }).join("#");
    url.searchParams.set("collateral_tuple", tupleStr);
  }
  if (parameters.minLltv !== void 0) {
    url.searchParams.set("min_lltv", parameters.minLltv.toString());
  }
  if (parameters.maxLltv !== void 0) {
    url.searchParams.set("max_lltv", parameters.maxLltv.toString());
  }
  if (parameters.sortBy) {
    url.searchParams.set("sort_by", parameters.sortBy);
  }
  if (parameters.sortOrder) {
    url.searchParams.set("sort_order", parameters.sortOrder);
  }
  if (parameters.cursor) {
    url.searchParams.set("cursor", parameters.cursor);
  }
  if (parameters.limit !== void 0) {
    url.searchParams.set("limit", parameters.limit.toString());
  }
  const { cursor: returnedCursor, data: offers } = await getApi(config, url);
  const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
  return {
    cursor: returnedCursor,
    offers: routerOffers.map(from).map(toResponse)
  };
}
async function match(config, parameters) {
  const url = new URL(`${config.url.toString()}v1/match-offers`);
  url.searchParams.set("side", parameters.side);
  url.searchParams.set("chain_id", parameters.chainId.toString());
  if (parameters.rate !== void 0) {
    url.searchParams.set("rate", parameters.rate.toString());
  }
  if (parameters.collaterals?.length) {
    const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${lltv}`).join("#");
    url.searchParams.set("collaterals", collateralsStr);
  }
  if (parameters.maturity !== void 0) {
    url.searchParams.set("maturity", parameters.maturity.toString());
  }
  if (parameters.minMaturity !== void 0) {
    url.searchParams.set("min_maturity", parameters.minMaturity.toString());
  }
  if (parameters.maxMaturity !== void 0) {
    url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
  }
  if (parameters.loanToken) {
    url.searchParams.set("loan_token", parameters.loanToken);
  }
  if (parameters.creator) {
    url.searchParams.set("creator", parameters.creator);
  }
  if (parameters.status?.length) {
    url.searchParams.set("status", parameters.status.join(","));
  }
  if (parameters.cursor) {
    url.searchParams.set("cursor", parameters.cursor);
  }
  if (parameters.limit !== void 0) {
    url.searchParams.set("limit", parameters.limit.toString());
  }
  const { cursor: returnedCursor, data: offers } = await getApi(config, url);
  const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
  return {
    cursor: returnedCursor,
    offers: routerOffers.map(from).map(toResponse)
  };
}
async function getApi(config, url) {
  const pathname = url.pathname;
  let action;
  switch (true) {
    case pathname.includes("/v1/offers"):
      action = "get_offers";
      break;
    case pathname.includes("/v1/match-offers"):
      action = "match_offers";
      break;
    default:
      throw new HttpGetOffersFailedError("Unknown endpoint", {
        details: `Unsupported path: ${pathname}`
      });
  }
  const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
  if (!schemaParseResult.success) {
    throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
      details: schemaParseResult.error.issues[0]?.message
    });
  }
  const response = await fetch(url.toString(), {
    method: "GET",
    headers: config.headers
  });
  if (!response.ok) {
    switch (response.status) {
      case 401:
        throw new HttpUnauthorizedError();
      case 403:
        throw new HttpForbiddenError();
      case 429:
        throw new HttpRateLimitError();
    }
    throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
      details: await response.text()
    });
  }
  return response.json();
}
var InvalidUrlError = class extends Errors.BaseError {
  constructor(url) {
    super(`URL "${url}" is not http/https.`);
    __publicField(this, "name", "Router.InvalidUrlError");
  }
};
var HttpUnauthorizedError = class extends Errors.BaseError {
  constructor() {
    super("Unauthorized.", {
      metaMessages: ["Ensure that an API key is provided."]
    });
    __publicField(this, "name", "Router.HttpUnauthorizedError");
  }
};
var HttpForbiddenError = class extends Errors.BaseError {
  constructor() {
    super("Forbidden.", {
      metaMessages: ["Ensure that the API key is valid."]
    });
    __publicField(this, "name", "Router.HttpForbiddenError");
  }
};
var HttpRateLimitError = class extends Errors.BaseError {
  constructor() {
    super("Rate limit exceeded.", {
      metaMessages: [
        "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
      ]
    });
    __publicField(this, "name", "Router.HttpRateLimitError");
  }
};
var HttpGetOffersFailedError = class extends Errors.BaseError {
  constructor(message, { details } = {}) {
    super(message, {
      metaMessages: [details]
    });
    __publicField(this, "name", "Router.HttpGetOffersFailedError");
  }
};

// src/RouterEvent.ts
var RouterEvent_exports = {};
__export(RouterEvent_exports, {
  buildId: () => buildId,
  types: () => types
});
var types = ["offer_created", "offer_matched", "offer_validation"];
function buildId(event) {
  switch (event.type) {
    case "offer_created":
      return `offer_created:${event.offer.hash.toLowerCase()}`;
    case "offer_matched":
      return `offer_matched:${event.offer.hash.toLowerCase()}`;
    case "offer_validation":
      return `offer_validation:${event.offer.hash.toLowerCase()}`;
    default: {
      throw new Error("Unhandled event type");
    }
  }
}

// src/Validation.ts
var Validation_exports = {};
__export(Validation_exports, {
  run: () => run
});
async function run(parameters) {
  const { items, rules, ctx = {}, chunkSize } = parameters;
  const issues = [];
  let validItems = items.slice();
  for (const rule of rules) {
    if (validItems.length === 0) return { valid: [], issues };
    const indicesToRemove = /* @__PURE__ */ new Set();
    if (rule.kind === "single") {
      for (let i = 0; i < validItems.length; i++) {
        const item = validItems[i];
        const issue = await rule.run(item, ctx);
        if (issue) {
          issues.push({ ...issue, ruleName: rule.name, item });
          indicesToRemove.add(i);
        }
      }
    } else if (rule.kind === "batch") {
      const exec = async (slice, offset) => {
        const map = await rule.run(slice, ctx);
        for (let i = 0; i < slice.length; i++) {
          const issue = map.get(i);
          if (issue !== void 0) {
            issues.push({ ...issue, ruleName: rule.name, item: slice[i] });
            indicesToRemove.add(offset + i);
          }
        }
      };
      if (!chunkSize) await exec(validItems, 0);
      else {
        for (let i = 0; i < validItems.length; i += chunkSize) {
          await exec(validItems.slice(i, i + chunkSize), i);
        }
      }
    }
    validItems = validItems.filter((_, i) => !indicesToRemove.has(i));
  }
  return {
    valid: validItems,
    issues
  };
}

// src/ValidationRule.ts
var ValidationRule_exports = {};
__export(ValidationRule_exports, {
  batch: () => batch2,
  morpho: () => morpho,
  single: () => single
});
function single(name, run2) {
  return { kind: "single", name, run: run2 };
}
function batch2(name, run2) {
  return { kind: "batch", name, run: run2 };
}
function morpho(parameters) {
  const { whitelistedChains } = parameters;
  const whitelistedChainIds = new Set(whitelistedChains.map((chain) => chain.id));
  const whitelistedLoanTokensPerChain = new Map(
    whitelistedChains.map((chain) => [
      chain.id,
      new Set(Array.from(chain.whitelistedAssets).map((a) => a.toLowerCase()))
    ])
  );
  const morphoPerChain = new Map(
    whitelistedChains.map((chain) => [chain.id, chain.morpho.toLowerCase()])
  );
  const chainId = single("chain_id", (offer, _) => {
    if (!whitelistedChainIds.has(offer.chainId)) {
      return { message: `Chain ID ${offer.chainId} is not whitelisted` };
    }
  });
  const loanToken = single("loan_token", (offer, _) => {
    if (!whitelistedLoanTokensPerChain.get(offer.chainId)?.has(offer.loanToken.toLowerCase())) {
      return {
        message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
      };
    }
  });
  const expiry = single("expiry", (offer, _) => {
    if (offer.expiry < Math.floor(Date.now() / 1e3)) {
      return { message: "Expiry mismatch" };
    }
  });
  const emptyCallback = single("empty_callback", (offer, _) => {
    if (offer.callback.data !== "0x") {
      return { message: "Callback data is not empty. Not supported yet." };
    }
  });
  const sellOffersEmptyCallback = single(
    "sell_offers_empty_callback",
    (offer, _) => {
      if (!offer.buy && offer.callback.data === "0x") {
        return { message: "Sell offers with empty callback are not supported yet." };
      }
    }
  );
  const buyOffersEmptyCallback = batch2(
    "buy_offers_empty_callback",
    async (offers, { publicClients }) => {
      const issues = /* @__PURE__ */ new Map();
      const hashToIndex = /* @__PURE__ */ new Map();
      for (let i = 0; i < offers.length; i++) {
        const offer = offers[i];
        hashToIndex.set(offer.hash, i);
      }
      const { buyOffers, sellOffers: _sellOffers } = offers.reduce(
        (acc, offer) => {
          offer.buy ? acc.buyOffers.push(offer) : issues.set(hashToIndex.get(offer.hash), {
            message: "Onchain callback for sell offers is not supported yet."
          });
          return acc;
        },
        { buyOffers: [], sellOffers: [] }
      );
      const buyOffersPerLoanAsset = /* @__PURE__ */ new Map();
      for (const offer of buyOffers) {
        const chainName = getChain(offer.chainId)?.name;
        const loanTokens = buyOffersPerLoanAsset.get(chainName) ?? /* @__PURE__ */ new Map();
        const offers2 = loanTokens.get(offer.loanToken.toLowerCase()) ?? [];
        offers2.push(offer);
        loanTokens.set(offer.loanToken.toLowerCase(), offers2);
        buyOffersPerLoanAsset.set(chainName, loanTokens);
      }
      await Promise.all(
        Array.from(buyOffersPerLoanAsset.entries()).map(async ([name, loanTokens]) => {
          const chainName = name;
          const publicClient = publicClients[chainName];
          const morpho2 = morphoPerChain.get(chains[chainName].id);
          if (!publicClient) {
            const offers2 = Array.from(loanTokens.values()).flat();
            for (const offer of offers2) {
              issues.set(hashToIndex.get(offer.hash), {
                message: `Public client for chain "${chainName}" is not available`
              });
            }
            return;
          }
          const balances = /* @__PURE__ */ new Map();
          const allowances = /* @__PURE__ */ new Map();
          for (const [loanToken2, offers2] of loanTokens) {
            const data = await Promise.all(
              offers2.flatMap((offer) => [
                publicClient.readContract({
                  address: loanToken2,
                  abi: parseAbi([
                    "function balanceOf(address owner) view returns (uint256 balance)"
                  ]),
                  functionName: "balanceOf",
                  args: [offer.offering]
                }),
                publicClient.readContract({
                  address: loanToken2,
                  abi: parseAbi([
                    "function allowance(address owner, address spender) public view returns (uint256 remaining)"
                  ]),
                  functionName: "allowance",
                  args: [offer.offering, morpho2]
                })
              ])
            );
            for (let i = 0; i < offers2.length; i++) {
              const user = offers2[i].offering.toLowerCase();
              const balance = data[i * 2] || 0n;
              const allowance = data[i * 2 + 1] || 0n;
              const userBalances = balances.get(user) ?? /* @__PURE__ */ new Map();
              userBalances.set(loanToken2.toLowerCase(), balance);
              const userAllowances = allowances.get(user) ?? /* @__PURE__ */ new Map();
              userAllowances.set(loanToken2.toLowerCase(), allowance);
              balances.set(user, userBalances);
              allowances.set(user, userAllowances);
            }
          }
          for (const offer of Array.from(loanTokens.values()).flat()) {
            const user = offer.offering.toLowerCase();
            const userBalances = balances.get(user);
            const balance = userBalances?.get(offer.loanToken.toLowerCase());
            if (balance < offer.assets) {
              issues.set(hashToIndex.get(offer.hash), {
                message: `Insufficient balance for ${offer.loanToken} on chain ${offer.chainId} (${balance.toString()} < ${offer.assets.toString()})`
              });
              continue;
            }
            const userAllowances = allowances.get(user);
            const allowance = userAllowances?.get(offer.loanToken.toLowerCase());
            if (allowance < offer.assets) {
              issues.set(hashToIndex.get(offer.hash), {
                message: `Insufficient allowance for ${offer.loanToken} on chain ${offer.chainId} (${allowance.toString()} < ${offer.assets.toString()})`
              });
            }
          }
        })
      );
      return issues;
    }
  );
  return [
    chainId,
    loanToken,
    expiry,
    // note: callback onchain check should be done last since it does not mean that the offer is forever invalid
    // integrators should be able to keep the offers that have an invalid callback onchain and be sure that is the only check that is not valid
    emptyCallback,
    sellOffersEmptyCallback,
    buyOffersEmptyCallback
  ];
}

export { Chain_exports as Chain, Client_exports as Router, RouterEvent_exports as RouterEvent, RouterOffer_exports as RouterOffer, Validation_exports as Validation, ValidationRule_exports as ValidationRule, batch, decodeCursor, encodeCursor, poll, validateCursor, wait };
//# sourceMappingURL=index.browser.mjs.map
//# sourceMappingURL=index.browser.mjs.map