@morpho-dev/router 0.1.6 → 0.1.8

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,12 +1,22 @@
1
- import { Errors, LLTV, Offer, Format, Time, Maturity, Utils } from '@morpho-dev/mempool';
1
+ import { Errors, LLTV, Offer, Format, Utils, Chain, Maturity, Time, Mempool } from '@morpho-dev/mempool';
2
2
  export * from '@morpho-dev/mempool';
3
- import { Base64 } from 'js-base64';
4
3
  import { z } from 'zod/v4';
5
4
  import { createDocument } from 'zod-openapi';
6
- import { parseUnits, maxUint256, formatUnits, erc20Abi } from 'viem';
5
+ import { parseUnits, maxUint256, publicActions, parseEventLogs, formatUnits, createWalletClient, http, erc20Abi, stringify } from 'viem';
6
+ import { Base64 } from 'js-base64';
7
+ import { getBlockNumber } from 'viem/actions';
8
+ import { AsyncLocalStorage } from 'async_hooks';
9
+ import { desc, and, eq, sql, gte, lte, asc, inArray } from 'drizzle-orm';
10
+ import { pgSchema, timestamp, varchar, bigint, text, boolean, integer, numeric, index, serial, jsonb, uniqueIndex, primaryKey } from 'drizzle-orm/pg-core';
11
+ import path from 'path';
12
+ import { PGlite } from '@electric-sql/pglite';
13
+ import { drizzle } from 'drizzle-orm/node-postgres';
14
+ import { migrate } from 'drizzle-orm/node-postgres/migrator';
15
+ import { drizzle as drizzle$1 } from 'drizzle-orm/pglite';
16
+ import { migrate as migrate$1 } from 'drizzle-orm/pglite/migrator';
17
+ import { Pool } from 'pg';
7
18
  import { serve as serve$1 } from '@hono/node-server';
8
19
  import { Hono } from 'hono';
9
- import { AsyncLocalStorage } from 'async_hooks';
10
20
 
11
21
  var __defProp = Object.defineProperty;
12
22
  var __export = (target, all) => {
@@ -14,60 +24,17 @@ var __export = (target, all) => {
14
24
  __defProp(target, name, { get: all[name], enumerable: true });
15
25
  };
16
26
 
17
- // src/Callback.ts
18
- var Callback_exports = {};
19
- __export(Callback_exports, {
20
- CallbackType: () => CallbackType,
21
- buildLiquidity: () => buildLiquidity,
22
- getCallbackIdForOffer: () => getCallbackIdForOffer
27
+ // src/core/apiSchema/index.ts
28
+ var apiSchema_exports = {};
29
+ __export(apiSchema_exports, {
30
+ OpenApi: () => OpenApi,
31
+ fromResponse: () => fromResponse,
32
+ parse: () => parse,
33
+ safeParse: () => safeParse,
34
+ toResponse: () => toResponse
23
35
  });
24
- var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
25
- CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
26
- return CallbackType2;
27
- })(CallbackType || {});
28
- function buildLiquidity(parameters) {
29
- const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
30
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
31
- throw new Error(`CallbackType not implemented: ${type}`);
32
- const amountStr = amount.toString();
33
- const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
34
- return {
35
- userPosition: {
36
- id,
37
- availableLiquidityQueueId: id,
38
- user: user.toLowerCase(),
39
- chainId,
40
- amount: amountStr,
41
- updatedAt
42
- },
43
- queues: [
44
- {
45
- queue: {
46
- queueId: id,
47
- availableLiquidityPoolId: id,
48
- index,
49
- updatedAt
50
- },
51
- pool: {
52
- id,
53
- amount: amountStr,
54
- updatedAt
55
- }
56
- }
57
- ]
58
- };
59
- }
60
- function getCallbackIdForOffer(offer) {
61
- if (offer.buy && offer.callback.data === "0x") {
62
- const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
63
- const user = offer.offering;
64
- const loanToken = offer.loanToken;
65
- return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
66
- }
67
- return null;
68
- }
69
36
 
70
- // src/Cursor.ts
37
+ // src/core/Cursor.ts
71
38
  var Cursor_exports = {};
72
39
  __export(Cursor_exports, {
73
40
  decode: () => decode,
@@ -153,17 +120,7 @@ function decode(token) {
153
120
  return decoded;
154
121
  }
155
122
 
156
- // src/core/apiSchema/index.ts
157
- var apiSchema_exports = {};
158
- __export(apiSchema_exports, {
159
- OpenApi: () => OpenApi,
160
- fromResponse: () => fromResponse,
161
- parse: () => parse,
162
- safeParse: () => safeParse,
163
- toResponse: () => toResponse
164
- });
165
-
166
- // src/RouterOffer.ts
123
+ // src/core/RouterOffer.ts
167
124
  var RouterOffer_exports = {};
168
125
  __export(RouterOffer_exports, {
169
126
  InvalidRouterOfferError: () => InvalidRouterOfferError,
@@ -740,513 +697,1586 @@ var OpenApi = createDocument({
740
697
 
741
698
  // src/core/apiSchema/utils.ts
742
699
  function toResponse(routerOffer) {
743
- const { consumed, status, metadata, ...offer } = routerOffer;
700
+ const { consumed: consumed2, status, metadata, ...offer } = routerOffer;
744
701
  return {
745
702
  offer,
746
- consumed,
703
+ consumed: consumed2,
747
704
  status,
748
705
  metadata
749
706
  };
750
707
  }
751
708
  function fromResponse(offerResponse) {
752
- const { offer, consumed, status, metadata } = offerResponse;
709
+ const { offer, consumed: consumed2, status, metadata } = offerResponse;
753
710
  return {
754
711
  ...offer,
755
- consumed,
712
+ consumed: consumed2,
756
713
  status,
757
714
  metadata
758
715
  };
759
716
  }
760
717
 
761
- // src/core/router/index.ts
762
- var router_exports = {};
763
- __export(router_exports, {
764
- APIError: () => APIError,
765
- BadRequestError: () => BadRequestError,
766
- HttpForbiddenError: () => HttpForbiddenError,
767
- HttpGetOffersFailedError: () => HttpGetOffersFailedError,
768
- HttpRateLimitError: () => HttpRateLimitError,
769
- HttpUnauthorizedError: () => HttpUnauthorizedError,
770
- InternalServerError: () => InternalServerError,
771
- InvalidUrlError: () => InvalidUrlError,
772
- NotFoundError: () => NotFoundError,
773
- ValidationError: () => ValidationError,
774
- connect: () => connect,
775
- error: () => error,
776
- get: () => get,
777
- handleZodError: () => handleZodError,
778
- match: () => match,
779
- serve: () => serve,
780
- success: () => success
718
+ // src/core/Callback.ts
719
+ var Callback_exports = {};
720
+ __export(Callback_exports, {
721
+ CallbackType: () => CallbackType,
722
+ buildLiquidity: () => buildLiquidity,
723
+ getCallbackIdForOffer: () => getCallbackIdForOffer
781
724
  });
782
- function connect(opts) {
783
- const u = new URL(opts?.url || "https://router.morpho.dev");
784
- if (u.protocol !== "http:" && u.protocol !== "https:") {
785
- throw new InvalidUrlError(u.toString());
786
- }
787
- const headers = opts?.headers ?? new Headers();
788
- headers.set("Content-Type", "application/json");
789
- opts?.apiKey !== void 0 ? headers.set("X-API-Key", opts.apiKey) : null;
790
- const config = {
791
- url: u,
792
- headers
793
- };
725
+ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
726
+ CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
727
+ return CallbackType2;
728
+ })(CallbackType || {});
729
+ function buildLiquidity(parameters) {
730
+ const { type, user, contract, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
731
+ if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
732
+ throw new Error(`CallbackType not implemented: ${type}`);
733
+ const amountStr = amount.toString();
734
+ const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
794
735
  return {
795
- ...config,
796
- get: (parameters) => get(config, parameters),
797
- match: (parameters) => match(config, parameters)
736
+ userPosition: {
737
+ id,
738
+ availableLiquidityQueueId: id,
739
+ user: user.toLowerCase(),
740
+ chainId,
741
+ amount: amountStr,
742
+ updatedAt
743
+ },
744
+ queues: [
745
+ {
746
+ queue: {
747
+ queueId: id,
748
+ availableLiquidityPoolId: id,
749
+ index: index2,
750
+ updatedAt
751
+ },
752
+ pool: {
753
+ id,
754
+ amount: amountStr,
755
+ updatedAt
756
+ }
757
+ }
758
+ ]
798
759
  };
799
760
  }
800
- async function get(config, parameters) {
801
- const url = new URL(`${config.url.toString()}v1/offers`);
802
- if (parameters.creators?.length) {
803
- url.searchParams.set("creators", parameters.creators.join(","));
804
- }
805
- if (parameters.side) {
806
- url.searchParams.set("side", parameters.side);
807
- }
808
- if (parameters.chains?.length) {
809
- url.searchParams.set("chains", parameters.chains.join(","));
810
- }
811
- if (parameters.loanTokens?.length) {
812
- url.searchParams.set("loan_tokens", parameters.loanTokens.join(","));
761
+ function getCallbackIdForOffer(offer) {
762
+ if (offer.buy && offer.callback.data === "0x") {
763
+ const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
764
+ const user = offer.offering;
765
+ const loanToken = offer.loanToken;
766
+ return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
813
767
  }
814
- if (parameters.status?.length) {
815
- url.searchParams.set("status", parameters.status.join(","));
768
+ return null;
769
+ }
770
+
771
+ // src/core/Collector/index.ts
772
+ var Collector_exports = {};
773
+ __export(Collector_exports, {
774
+ createBuyWithEmptyCallbackLiquidityCollector: () => createBuyWithEmptyCallbackLiquidityCollector,
775
+ createChainReorgsCollector: () => createChainReorgsCollector,
776
+ createConsumedEventsCollector: () => createConsumedEventsCollector,
777
+ createMempoolCollector: () => createMempoolCollector,
778
+ start: () => start
779
+ });
780
+
781
+ // src/core/Liquidity.ts
782
+ var Liquidity_exports = {};
783
+ __export(Liquidity_exports, {
784
+ fetch: () => fetch2,
785
+ fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
786
+ serialize: () => serialize
787
+ });
788
+ async function fetchBalancesAndAllowances(parameters) {
789
+ const { client, spender, pairs, options } = parameters;
790
+ if (pairs.length === 0) return /* @__PURE__ */ new Map();
791
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
792
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
793
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
794
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
795
+ const out = /* @__PURE__ */ new Map();
796
+ for (const pairsBatch of Utils.batch(pairs, batchSize)) {
797
+ const balanceContracts = [];
798
+ const allowanceContracts = [];
799
+ for (const { user, token } of pairsBatch) {
800
+ balanceContracts.push({
801
+ address: token,
802
+ abi: erc20Abi,
803
+ functionName: "balanceOf",
804
+ args: [user]
805
+ });
806
+ allowanceContracts.push({
807
+ address: token,
808
+ abi: erc20Abi,
809
+ functionName: "allowance",
810
+ args: [user, spender]
811
+ });
812
+ }
813
+ const [balances, allowances] = await Promise.all([
814
+ Utils.retry(
815
+ () => client.multicall({
816
+ allowFailure: false,
817
+ contracts: balanceContracts,
818
+ ...blockNumber ? { blockNumber } : {}
819
+ }),
820
+ retryAttempts,
821
+ retryDelayMs
822
+ ),
823
+ Utils.retry(
824
+ () => client.multicall({
825
+ allowFailure: false,
826
+ contracts: allowanceContracts,
827
+ ...blockNumber ? { blockNumber } : {}
828
+ }),
829
+ retryAttempts,
830
+ retryDelayMs
831
+ )
832
+ ]);
833
+ for (let i = 0; i < pairsBatch.length; i++) {
834
+ const { user, token } = pairsBatch[i];
835
+ const balance = balances[i];
836
+ const allowance = allowances[i];
837
+ let perUser = out.get(user);
838
+ if (!perUser) {
839
+ perUser = /* @__PURE__ */ new Map();
840
+ out.set(user, perUser);
841
+ }
842
+ perUser.set(token, { balance, allowance });
843
+ }
816
844
  }
817
- if (parameters.callbackAddresses?.length) {
818
- url.searchParams.set("callback_addresses", parameters.callbackAddresses.join(","));
845
+ return out;
846
+ }
847
+ async function fetch2(parameters) {
848
+ const { client, chainId, spender, type, pairs, options } = parameters;
849
+ if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
850
+ throw new Error(`CallbackType not implemented: ${type}`);
851
+ const map = await fetchBalancesAndAllowances({
852
+ client,
853
+ spender,
854
+ pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
855
+ options
856
+ });
857
+ const out = [];
858
+ for (const [user, perContract] of map) {
859
+ for (const [contract, { balance, allowance }] of perContract) {
860
+ const amount = balance < allowance ? balance : allowance;
861
+ out.push(
862
+ buildLiquidity({
863
+ type,
864
+ user,
865
+ contract,
866
+ chainId,
867
+ amount: amount.toString(),
868
+ index: 0
869
+ })
870
+ );
871
+ }
819
872
  }
820
- if (parameters.minAmount !== void 0) {
821
- url.searchParams.set("min_amount", parameters.minAmount.toString());
822
- }
823
- if (parameters.maxAmount !== void 0) {
824
- url.searchParams.set("max_amount", parameters.maxAmount.toString());
825
- }
826
- if (parameters.minRate !== void 0) {
827
- url.searchParams.set("min_rate", parameters.minRate.toString());
828
- }
829
- if (parameters.maxRate !== void 0) {
830
- url.searchParams.set("max_rate", parameters.maxRate.toString());
831
- }
832
- if (parameters.minMaturity !== void 0) {
833
- url.searchParams.set("min_maturity", parameters.minMaturity.toString());
834
- }
835
- if (parameters.maxMaturity !== void 0) {
836
- url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
837
- }
838
- if (parameters.minExpiry !== void 0) {
839
- url.searchParams.set("min_expiry", parameters.minExpiry.toString());
840
- }
841
- if (parameters.maxExpiry !== void 0) {
842
- url.searchParams.set("max_expiry", parameters.maxExpiry.toString());
843
- }
844
- if (parameters.collateralAssets?.length) {
845
- url.searchParams.set("collateral_assets", parameters.collateralAssets.join(","));
846
- }
847
- if (parameters.collateralOracles?.length) {
848
- url.searchParams.set("collateral_oracles", parameters.collateralOracles.join(","));
849
- }
850
- if (parameters.collateralTuple?.length) {
851
- const tupleStr = parameters.collateralTuple.map(({ asset, oracle, lltv }) => {
852
- let result = asset;
853
- if (oracle) {
854
- result += `:${oracle}`;
855
- } else if (lltv !== void 0) {
856
- result += `:`;
873
+ return out;
874
+ }
875
+ function serialize(liquidity) {
876
+ const normalized = {
877
+ userPosition: {
878
+ id: liquidity.userPosition.id,
879
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
880
+ user: liquidity.userPosition.user,
881
+ chainId: String(liquidity.userPosition.chainId),
882
+ amount: String(liquidity.userPosition.amount)
883
+ },
884
+ queues: liquidity.queues.map((queueWithPool) => ({
885
+ queue: {
886
+ queueId: queueWithPool.queue.queueId,
887
+ availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
888
+ index: queueWithPool.queue.index
889
+ },
890
+ pool: {
891
+ id: queueWithPool.pool.id,
892
+ amount: String(queueWithPool.pool.amount)
857
893
  }
858
- if (lltv !== void 0) result += `:${formatUnits(lltv, 16)}`;
859
- return result;
860
- }).join("#");
861
- url.searchParams.set("collateral_tuple", tupleStr);
862
- }
863
- if (parameters.minLltv !== void 0) {
864
- url.searchParams.set("min_lltv", formatUnits(parameters.minLltv, 16));
865
- }
866
- if (parameters.maxLltv !== void 0) {
867
- url.searchParams.set("max_lltv", formatUnits(parameters.maxLltv, 16));
868
- }
869
- if (parameters.sortBy) {
870
- url.searchParams.set("sort_by", parameters.sortBy);
871
- }
872
- if (parameters.sortOrder) {
873
- url.searchParams.set("sort_order", parameters.sortOrder);
874
- }
875
- if (parameters.cursor) {
876
- url.searchParams.set("cursor", parameters.cursor);
877
- }
878
- if (parameters.limit !== void 0) {
879
- url.searchParams.set("limit", parameters.limit.toString());
880
- }
881
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
882
- const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
883
- return {
884
- cursor: returnedCursor,
885
- offers: routerOffers.map(from).map(toResponse)
894
+ })).sort(
895
+ (left, right) => {
896
+ const leftQueueId = left.queue.queueId || "";
897
+ const rightQueueId = right.queue.queueId || "";
898
+ if (leftQueueId < rightQueueId) return -1;
899
+ if (leftQueueId > rightQueueId) return 1;
900
+ const leftPoolId = left.pool.id;
901
+ const rightPoolId = right.pool.id;
902
+ if (leftPoolId < rightPoolId) return -1;
903
+ if (leftPoolId > rightPoolId) return 1;
904
+ const leftIndex = left.queue.index;
905
+ const rightIndex = right.queue.index;
906
+ if (leftIndex < rightIndex) return -1;
907
+ if (leftIndex > rightIndex) return 1;
908
+ return 0;
909
+ }
910
+ )
886
911
  };
912
+ return JSON.stringify(normalized);
887
913
  }
888
- async function match(config, parameters) {
889
- const url = new URL(`${config.url.toString()}v1/offers/match`);
890
- url.searchParams.set("side", parameters.side);
891
- url.searchParams.set("chain_id", parameters.chainId.toString());
892
- if (parameters.rate !== void 0) {
893
- url.searchParams.set("rate", parameters.rate.toString());
894
- }
895
- if (parameters.collaterals?.length) {
896
- const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${formatUnits(lltv, 16)}`).join("#");
897
- url.searchParams.set("collaterals", collateralsStr);
898
- }
899
- if (parameters.maturity !== void 0) {
900
- url.searchParams.set("maturity", parameters.maturity.toString());
901
- }
902
- if (parameters.minMaturity !== void 0) {
903
- url.searchParams.set("min_maturity", parameters.minMaturity.toString());
904
- }
905
- if (parameters.maxMaturity !== void 0) {
906
- url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
907
- }
908
- if (parameters.loanToken) {
909
- url.searchParams.set("loan_token", parameters.loanToken);
910
- }
911
- if (parameters.creator) {
912
- url.searchParams.set("creator", parameters.creator);
913
- }
914
- if (parameters.status?.length) {
915
- url.searchParams.set("status", parameters.status.join(","));
916
- }
917
- if (parameters.cursor) {
918
- url.searchParams.set("cursor", parameters.cursor);
919
- }
920
- if (parameters.limit !== void 0) {
921
- url.searchParams.set("limit", parameters.limit.toString());
922
- }
923
- const { cursor: returnedCursor, data: offers } = await getApi(config, url);
924
- const routerOffers = offers.map(Format.fromSnakeCase).map(fromResponse);
914
+
915
+ // src/core/Logger.ts
916
+ var Logger_exports = {};
917
+ __export(Logger_exports, {
918
+ LogLevelValues: () => LogLevelValues,
919
+ defaultLogger: () => defaultLogger,
920
+ getLogger: () => getLogger,
921
+ runWithLogger: () => runWithLogger,
922
+ silentLogger: () => silentLogger
923
+ });
924
+ var LogLevelValues = [
925
+ "silent",
926
+ "trace",
927
+ "debug",
928
+ "info",
929
+ "warn",
930
+ "error",
931
+ "fatal"
932
+ ];
933
+ function defaultLogger(minLevel) {
934
+ const threshold = minLevel ?? "info";
935
+ const levelIndexByName = LogLevelValues.reduce(
936
+ (acc, lvl, idx) => {
937
+ acc[lvl] = idx;
938
+ return acc;
939
+ },
940
+ {}
941
+ );
942
+ const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
943
+ const wrap = (consoleMethod, methodLevel) => isEnabled(methodLevel) ? (entry) => {
944
+ console[consoleMethod](stringify({ level: methodLevel, ...entry }));
945
+ } : () => {
946
+ };
925
947
  return {
926
- cursor: returnedCursor,
927
- offers: routerOffers.map(from).map(toResponse)
948
+ trace: wrap("trace", "trace"),
949
+ debug: wrap("debug", "debug"),
950
+ info: wrap("info", "info"),
951
+ warn: wrap("warn", "warn"),
952
+ error: wrap("error", "error"),
953
+ fatal: wrap("error", "fatal")
928
954
  };
929
955
  }
930
- async function getApi(config, url) {
931
- const pathname = url.pathname;
932
- let action;
933
- switch (true) {
934
- case pathname.includes("/v1/offers/match"):
935
- action = "match_offers";
936
- break;
937
- case pathname.includes("/v1/offers"):
938
- action = "get_offers";
939
- break;
940
- default:
941
- throw new HttpGetOffersFailedError("Unknown endpoint", {
942
- details: `Unsupported path: ${pathname}`
956
+ function silentLogger() {
957
+ const noop = () => {
958
+ };
959
+ return { trace: noop, debug: noop, info: noop, warn: noop, error: noop, fatal: noop };
960
+ }
961
+ var loggerContext = new AsyncLocalStorage();
962
+ function runWithLogger(logger, fn) {
963
+ return loggerContext.run(logger, fn);
964
+ }
965
+ function getLogger() {
966
+ return loggerContext.getStore() ?? defaultLogger();
967
+ }
968
+
969
+ // src/core/Collector/BuyWithEmptyCallbackLiquidityCollector.ts
970
+ function createBuyWithEmptyCallbackLiquidityCollector(params) {
971
+ const {
972
+ client,
973
+ collectorBlockStore,
974
+ liquidityStore,
975
+ offerStore,
976
+ chain,
977
+ options: { maxBatchSize = 1e3, interval = 3e4 } = {}
978
+ } = params;
979
+ const collector = "buy_with_empty_callback_liquidity";
980
+ async function getUniqueUserTokenPairs() {
981
+ const unique = /* @__PURE__ */ new Set();
982
+ const pairs = [];
983
+ let cursor;
984
+ do {
985
+ const { offers: offers2, nextCursor } = await offerStore.getAll({
986
+ query: {
987
+ side: "buy",
988
+ chains: [Number(chain.id)],
989
+ cursor,
990
+ limit: 1e3
991
+ }
943
992
  });
944
- }
945
- const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
946
- if (!schemaParseResult.success) {
947
- throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
948
- details: schemaParseResult.error.issues[0]?.message
993
+ cursor = nextCursor ?? void 0;
994
+ for (const offer of offers2) {
995
+ const creator = offer.offering;
996
+ const loanToken = offer.loanToken;
997
+ const key = `${offer.offering}-${offer.loanToken}`.toLowerCase();
998
+ if (unique.has(key)) continue;
999
+ unique.add(key);
1000
+ pairs.push({ user: creator, token: loanToken });
1001
+ }
1002
+ } while (cursor);
1003
+ return pairs;
1004
+ }
1005
+ const collect = Utils.lazy((emit) => {
1006
+ const logger = getLogger();
1007
+ logger.info({
1008
+ collector,
1009
+ chainId: chain.id,
1010
+ msg: "start",
1011
+ interval,
1012
+ maxBatchSize
949
1013
  });
950
- }
951
- const response = await fetch(url.toString(), {
952
- method: "GET",
953
- headers: config.headers
1014
+ return Utils.poll(
1015
+ async () => {
1016
+ const publicClient = client.extend(publicActions);
1017
+ const pairs = await getUniqueUserTokenPairs();
1018
+ const latestBlockNumber = Number(await getBlockNumber(publicClient));
1019
+ logger.debug({
1020
+ collector,
1021
+ chainId: chain.id,
1022
+ msg: "snapshot started",
1023
+ blockNumber: latestBlockNumber
1024
+ });
1025
+ const liquidityEntries = await fetch2({
1026
+ client: publicClient,
1027
+ chainId: chain.id,
1028
+ spender: chain.morpho,
1029
+ type: "buy_with_empty_callback" /* BuyWithEmptyCallback */,
1030
+ pairs: pairs.map(({ user, token }) => ({ user, contract: token })),
1031
+ options: {
1032
+ blockNumber: latestBlockNumber,
1033
+ batchSize: maxBatchSize
1034
+ }
1035
+ });
1036
+ logger.debug({
1037
+ collector,
1038
+ chainId: chain.id,
1039
+ msg: `found ${liquidityEntries.length} liquidity entries`,
1040
+ blockNumber: latestBlockNumber
1041
+ });
1042
+ const existingLiquidityEntries = await liquidityStore.getAll();
1043
+ const existingById = /* @__PURE__ */ new Map();
1044
+ for (const liquidityEntry of existingLiquidityEntries) {
1045
+ const liquidityId = liquidityEntry.userPosition.id;
1046
+ const serializedEntry = serialize(liquidityEntry);
1047
+ existingById.set(liquidityId, serializedEntry);
1048
+ }
1049
+ for (const liquidity of liquidityEntries) {
1050
+ const serialized = serialize(liquidity);
1051
+ const prevSerialized = existingById.get(liquidity.userPosition.id);
1052
+ if (prevSerialized === serialized) continue;
1053
+ await liquidityStore.save({ liquidity });
1054
+ }
1055
+ await collectorBlockStore.saveBlockNumber({
1056
+ collectorName: collector,
1057
+ chainId: chain.id,
1058
+ blockNumber: latestBlockNumber
1059
+ });
1060
+ logger.info({
1061
+ collector,
1062
+ chainId: chain.id,
1063
+ msg: "snapshot done",
1064
+ blockNumber: latestBlockNumber
1065
+ });
1066
+ emit(latestBlockNumber);
1067
+ },
1068
+ { interval }
1069
+ );
954
1070
  });
955
- if (!response.ok) {
956
- switch (response.status) {
957
- case 401:
958
- throw new HttpUnauthorizedError();
959
- case 403:
960
- throw new HttpForbiddenError();
961
- case 429:
962
- throw new HttpRateLimitError();
1071
+ const onReorg = (_lastFinalizedBlockNumber) => {
1072
+ };
1073
+ return {
1074
+ name: collector,
1075
+ lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName: collector, chainId: chain.id }),
1076
+ collect,
1077
+ onReorg
1078
+ };
1079
+ }
1080
+ function createChainReorgsCollector(parameters) {
1081
+ const collectorName = "chain_reorgs";
1082
+ const {
1083
+ client,
1084
+ subscribers,
1085
+ collectorStore,
1086
+ chain,
1087
+ options: { maxBatchSize = 25, interval } = {}
1088
+ } = parameters;
1089
+ let finalizedBlock = null;
1090
+ let unfinalizedBlocks = [];
1091
+ const commonAncestor = (block) => {
1092
+ const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
1093
+ if (parent) return parent;
1094
+ if (finalizedBlock == null) throw new Error("Failed to get common ancestor");
1095
+ return finalizedBlock;
1096
+ };
1097
+ const reconcile = async (block) => {
1098
+ if (block.hash === null || block.number === null || block.parentHash === null)
1099
+ throw new Error("Failed to get block");
1100
+ const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
1101
+ if (latestBlock === void 0) {
1102
+ unfinalizedBlocks.push({
1103
+ hash: block.hash,
1104
+ number: block.number,
1105
+ parentHash: block.parentHash
1106
+ });
1107
+ return;
963
1108
  }
964
- throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
965
- details: await response.text()
1109
+ if (latestBlock.hash === block.hash) return;
1110
+ if (latestBlock.number >= block.number) {
1111
+ const ancestor = commonAncestor(block);
1112
+ console.log(
1113
+ `reorg detected, latestBlock.number: ${latestBlock.number} > block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1114
+ );
1115
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1116
+ unfinalizedBlocks = [];
1117
+ return;
1118
+ }
1119
+ if (latestBlock.number + 1n < block.number) {
1120
+ console.log(
1121
+ `missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
1122
+ );
1123
+ const missingBlockNumbers = (() => {
1124
+ const missingBlockNumbers2 = [];
1125
+ let start2 = latestBlock.number + 1n;
1126
+ const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
1127
+ while (start2 < threshold) {
1128
+ missingBlockNumbers2.push(start2);
1129
+ start2 = start2 + 1n;
1130
+ }
1131
+ return missingBlockNumbers2;
1132
+ })();
1133
+ const missingBlocks = await Promise.all(
1134
+ missingBlockNumbers.map(
1135
+ (blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
1136
+ )
1137
+ );
1138
+ for (const missingBlock of missingBlocks) await reconcile(missingBlock);
1139
+ await reconcile(block);
1140
+ return;
1141
+ }
1142
+ if (block.parentHash !== latestBlock.hash) {
1143
+ const ancestor = commonAncestor(block);
1144
+ console.log(
1145
+ `reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1146
+ );
1147
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1148
+ unfinalizedBlocks = [];
1149
+ return;
1150
+ }
1151
+ unfinalizedBlocks.push({
1152
+ hash: block.hash,
1153
+ number: block.number,
1154
+ parentHash: block.parentHash
966
1155
  });
967
- }
968
- return response.json();
1156
+ };
1157
+ const collect = Utils.lazy((emit) => {
1158
+ let tick = 0;
1159
+ const fetchFinalizedBlock = async () => {
1160
+ if (tick % 20 === 0 || finalizedBlock === null) {
1161
+ finalizedBlock = await client.getBlock({
1162
+ blockTag: "finalized",
1163
+ includeTransactions: false
1164
+ });
1165
+ if (finalizedBlock === null) throw new Error("Failed to get finalized block");
1166
+ }
1167
+ tick++;
1168
+ };
1169
+ return Utils.poll(
1170
+ async () => {
1171
+ await fetchFinalizedBlock();
1172
+ const latestBlock = await client.getBlock({
1173
+ blockTag: "latest",
1174
+ includeTransactions: false
1175
+ });
1176
+ await reconcile(latestBlock);
1177
+ const lastBlockNumber = Number(latestBlock.number);
1178
+ await collectorStore.saveBlockNumber({
1179
+ collectorName: "chain_reorgs",
1180
+ chainId: chain.id,
1181
+ blockNumber: lastBlockNumber
1182
+ });
1183
+ emit(lastBlockNumber);
1184
+ },
1185
+ { interval: interval ?? 3e4 }
1186
+ );
1187
+ });
1188
+ return {
1189
+ name: collectorName,
1190
+ lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName, chainId: chain.id }),
1191
+ collect,
1192
+ onReorg: (_) => {
1193
+ }
1194
+ };
969
1195
  }
970
- var InvalidUrlError = class extends Errors.BaseError {
971
- name = "Router.InvalidUrlError";
972
- constructor(url) {
973
- super(`URL "${url}" is not http/https.`);
974
- }
975
- };
976
- var HttpUnauthorizedError = class extends Errors.BaseError {
977
- name = "Router.HttpUnauthorizedError";
978
- constructor() {
979
- super("Unauthorized.", {
980
- metaMessages: ["Ensure that an API key is provided."]
981
- });
982
- }
983
- };
984
- var HttpForbiddenError = class extends Errors.BaseError {
985
- name = "Router.HttpForbiddenError";
986
- constructor() {
987
- super("Forbidden.", {
988
- metaMessages: ["Ensure that the API key is valid."]
1196
+
1197
+ // src/core/Collector/Collector.ts
1198
+ function start(collector) {
1199
+ let stopped = false;
1200
+ const it = collector.collect();
1201
+ (async () => {
1202
+ while (!stopped) await it.next();
1203
+ await it.return();
1204
+ })();
1205
+ return () => {
1206
+ stopped = true;
1207
+ };
1208
+ }
1209
+ function createConsumedEventsCollector(parameters) {
1210
+ const {
1211
+ client,
1212
+ contractAddress,
1213
+ offerStore,
1214
+ collectorBlockStore,
1215
+ options: { maxBatchSize = 1e3, interval = 3e4 } = {},
1216
+ chainId
1217
+ } = parameters;
1218
+ const collector = "consumed_events";
1219
+ const collect = Utils.lazy((emit) => {
1220
+ const logger = getLogger();
1221
+ logger.info({
1222
+ collector,
1223
+ chainId: chainId.toString(),
1224
+ msg: "start",
1225
+ interval,
1226
+ maxBatchSize
989
1227
  });
1228
+ return Utils.poll(
1229
+ async () => {
1230
+ const lastSyncedBlock = await collectorBlockStore.getBlockNumber({
1231
+ collectorName: collector,
1232
+ chainId
1233
+ });
1234
+ logger.debug({ collector, chainId, msg: "stream started", blockNumber: lastSyncedBlock });
1235
+ const stream = Chain.streamLogs({
1236
+ client,
1237
+ contractAddress,
1238
+ event: consumedEvent,
1239
+ blockNumberGte: lastSyncedBlock,
1240
+ order: "asc",
1241
+ options: { maxBatchSize }
1242
+ });
1243
+ let blockNumber = lastSyncedBlock;
1244
+ for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
1245
+ try {
1246
+ const parsedLogs = parseEventLogs({ abi: [consumedEvent], logs });
1247
+ logger.debug({ collector, chainId, msg: `found ${parsedLogs.length} consumed events` });
1248
+ for (const log of parsedLogs) {
1249
+ if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
1250
+ logger.debug({
1251
+ collector,
1252
+ chainId,
1253
+ msg: "skipping log because it is missing required fields"
1254
+ });
1255
+ continue;
1256
+ }
1257
+ const offer = fromConsumedLog({
1258
+ blockNumber: log.blockNumber,
1259
+ logIndex: log.logIndex,
1260
+ chainId: Number(chainId),
1261
+ transactionHash: log.transactionHash,
1262
+ user: log.args.user,
1263
+ nonce: log.args.nonce,
1264
+ amount: log.args.amount
1265
+ });
1266
+ await offerStore.updateConsumedAmount({
1267
+ id: offer.id,
1268
+ chainId: offer.chainId,
1269
+ offering: offer.offering,
1270
+ nonce: offer.nonce,
1271
+ consumed: offer.amount
1272
+ });
1273
+ }
1274
+ blockNumber = lastStreamBlockNumber;
1275
+ await collectorBlockStore.saveBlockNumber({
1276
+ collectorName: collector,
1277
+ chainId,
1278
+ blockNumber
1279
+ });
1280
+ emit(blockNumber);
1281
+ } catch (err) {
1282
+ logger.error({ err, msg: "Failed to process offer_consumed events" });
1283
+ }
1284
+ }
1285
+ logger.info({
1286
+ collector,
1287
+ chainId,
1288
+ msg: "stream finished",
1289
+ startBlock: lastSyncedBlock,
1290
+ endBlock: blockNumber
1291
+ });
1292
+ },
1293
+ { interval }
1294
+ );
1295
+ });
1296
+ const onReorg = (_lastFinalizedBlockNumber) => {
1297
+ };
1298
+ return {
1299
+ name: collector,
1300
+ lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName: collector, chainId }),
1301
+ onReorg,
1302
+ collect
1303
+ };
1304
+ }
1305
+
1306
+ // src/core/Validation.ts
1307
+ var Validation_exports = {};
1308
+ __export(Validation_exports, {
1309
+ run: () => run
1310
+ });
1311
+ async function run(parameters) {
1312
+ const { items, rules, ctx = {}, chunkSize } = parameters;
1313
+ const issues = [];
1314
+ let validItems = items.slice();
1315
+ for (const rule of rules) {
1316
+ if (validItems.length === 0) return { valid: [], issues };
1317
+ const indicesToRemove = /* @__PURE__ */ new Set();
1318
+ if (rule.kind === "single") {
1319
+ for (let i = 0; i < validItems.length; i++) {
1320
+ const item = validItems[i];
1321
+ const issue = await rule.run(item, ctx);
1322
+ if (issue) {
1323
+ issues.push({ ...issue, ruleName: rule.name, item });
1324
+ indicesToRemove.add(i);
1325
+ }
1326
+ }
1327
+ } else if (rule.kind === "batch") {
1328
+ const exec = async (slice, offset) => {
1329
+ const map = await rule.run(slice, ctx);
1330
+ for (let i = 0; i < slice.length; i++) {
1331
+ const issue = map.get(i);
1332
+ if (issue !== void 0) {
1333
+ issues.push({ ...issue, ruleName: rule.name, item: slice[i] });
1334
+ indicesToRemove.add(offset + i);
1335
+ }
1336
+ }
1337
+ };
1338
+ if (!chunkSize) await exec(validItems, 0);
1339
+ else {
1340
+ for (let i = 0; i < validItems.length; i += chunkSize) {
1341
+ await exec(validItems.slice(i, i + chunkSize), i);
1342
+ }
1343
+ }
1344
+ }
1345
+ validItems = validItems.filter((_, i) => !indicesToRemove.has(i));
990
1346
  }
991
- };
992
- var HttpRateLimitError = class extends Errors.BaseError {
993
- name = "Router.HttpRateLimitError";
994
- constructor() {
995
- super("Rate limit exceeded.", {
996
- metaMessages: [
997
- "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
998
- ]
1347
+ return {
1348
+ valid: validItems,
1349
+ issues
1350
+ };
1351
+ }
1352
+
1353
+ // src/core/ValidationRule.ts
1354
+ var ValidationRule_exports = {};
1355
+ __export(ValidationRule_exports, {
1356
+ batch: () => batch,
1357
+ morpho: () => morpho,
1358
+ single: () => single
1359
+ });
1360
+ function single(name, run2) {
1361
+ return { kind: "single", name, run: run2 };
1362
+ }
1363
+ function batch(name, run2) {
1364
+ return { kind: "batch", name, run: run2 };
1365
+ }
1366
+ function morpho() {
1367
+ const chainId = single("chain_id", (offer, { chain }) => {
1368
+ if (chain.id !== offer.chainId) {
1369
+ return {
1370
+ message: `Chain ID ${offer.chainId} is not the same as the chain ID in the context (${chain.id})`
1371
+ };
1372
+ }
1373
+ });
1374
+ const loanToken = single("loan_token", (offer, { chain }) => {
1375
+ const tokens = new Set(
1376
+ Array.from(chain.whitelistedAssets.values()).map((a) => a.toLowerCase())
1377
+ );
1378
+ if (!tokens.has(offer.loanToken.toLowerCase())) {
1379
+ return {
1380
+ message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
1381
+ };
1382
+ }
1383
+ });
1384
+ const expiry = single("expiry", (offer, _) => {
1385
+ if (offer.expiry < Math.floor(Date.now() / 1e3)) {
1386
+ return { message: "Expiry mismatch" };
1387
+ }
1388
+ });
1389
+ const callback = single("empty_callback", (offer, _) => {
1390
+ if (!offer.buy || offer.callback.data !== "0x") {
1391
+ return { message: "Callback not supported yet." };
1392
+ }
1393
+ });
1394
+ return [
1395
+ chainId,
1396
+ loanToken,
1397
+ expiry,
1398
+ // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1399
+ // integrators should be able to choose if they want to keep the offer or not
1400
+ callback
1401
+ ];
1402
+ }
1403
+
1404
+ // src/core/Collector/MempoolCollector.ts
1405
+ function createMempoolCollector(parameters) {
1406
+ const {
1407
+ mempool,
1408
+ offerStore,
1409
+ collectorBlockStore,
1410
+ chain,
1411
+ options: { maxBatchSize = 1e3, interval = 3e4 } = {}
1412
+ } = parameters;
1413
+ const collector = "mempool_offers";
1414
+ const collect = Utils.lazy((emit) => {
1415
+ const logger = getLogger();
1416
+ logger.info({
1417
+ collector,
1418
+ chainId: chain.id,
1419
+ msg: "start",
1420
+ interval,
1421
+ maxBatchSize
999
1422
  });
1000
- }
1001
- };
1002
- var HttpGetOffersFailedError = class extends Errors.BaseError {
1003
- name = "Router.HttpGetOffersFailedError";
1004
- constructor(message, { details } = {}) {
1005
- super(message, {
1006
- metaMessages: [details]
1423
+ let lastSyncedBlock = null;
1424
+ return mempool.watch({
1425
+ lastSyncedBlock: async () => {
1426
+ lastSyncedBlock = await collectorBlockStore.getBlockNumber({
1427
+ collectorName: collector,
1428
+ chainId: chain.id
1429
+ });
1430
+ logger.debug({
1431
+ collector,
1432
+ chainId: chain.id,
1433
+ msg: "stream started",
1434
+ blockNumber: lastSyncedBlock
1435
+ });
1436
+ return lastSyncedBlock;
1437
+ },
1438
+ polling: { maxBatchSize, interval },
1439
+ onOffers: async (offers2, blockNumber) => {
1440
+ logger.debug({ collector, chainId: chain.id, msg: `found ${offers2.length} offers` });
1441
+ try {
1442
+ const { valid: validOffers, issues } = await run({
1443
+ items: offers2,
1444
+ rules: morpho(),
1445
+ ctx: { chain }
1446
+ });
1447
+ const invalidOffersToSave = [];
1448
+ const issueToStatus = {
1449
+ empty_callback: "callback_not_supported",
1450
+ sell_offers_empty_callback: "callback_not_supported",
1451
+ buy_offers_empty_callback: "callback_error"
1452
+ };
1453
+ for (const issue of issues) {
1454
+ const status = issueToStatus[issue.ruleName];
1455
+ if (status) {
1456
+ invalidOffersToSave.push({
1457
+ offer: issue.item,
1458
+ status,
1459
+ metadata: { issue: issue.ruleName }
1460
+ });
1461
+ }
1462
+ }
1463
+ await offerStore.createMany([
1464
+ ...validOffers.map((offer) => ({
1465
+ offer,
1466
+ status: "valid"
1467
+ })),
1468
+ ...invalidOffersToSave
1469
+ ]);
1470
+ } catch (err) {
1471
+ logger.error({
1472
+ err,
1473
+ msg: "Failed to process offer_created events, falling back to unverified status"
1474
+ });
1475
+ await offerStore.createMany(
1476
+ offers2.map((offer) => ({
1477
+ offer,
1478
+ status: "unverified",
1479
+ metadata: { issue: "Offer processing failed" }
1480
+ }))
1481
+ );
1482
+ }
1483
+ await collectorBlockStore.saveBlockNumber({
1484
+ collectorName: collector,
1485
+ chainId: chain.id,
1486
+ blockNumber
1487
+ });
1488
+ emit(blockNumber);
1489
+ if (lastSyncedBlock === null) return;
1490
+ logger.info({
1491
+ collector,
1492
+ chainId: chain.id,
1493
+ msg: "stream finished",
1494
+ startBlock: lastSyncedBlock,
1495
+ endBlock: blockNumber
1496
+ });
1497
+ }
1007
1498
  });
1008
- }
1009
- };
1499
+ });
1500
+ const onReorg = (_lastFinalizedBlockNumber) => {
1501
+ };
1502
+ return {
1503
+ name: collector,
1504
+ lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName: collector, chainId: chain.id }),
1505
+ collect,
1506
+ onReorg
1507
+ };
1508
+ }
1010
1509
 
1011
- // src/OfferStore/index.ts
1012
- var OfferStore_exports = {};
1013
- __export(OfferStore_exports, {
1510
+ // src/core/CollectorBlockStore.ts
1511
+ var CollectorBlockStore_exports = {};
1512
+ __export(CollectorBlockStore_exports, {
1513
+ create: () => create,
1014
1514
  memory: () => memory
1015
1515
  });
1016
- function memory(parameters) {
1017
- const map = parameters.offers;
1018
- const filled = parameters.filled;
1019
- const consumedIds = /* @__PURE__ */ new Set();
1020
- const create = async (parameters2) => {
1021
- if (map.has(parameters2.offer.hash.toLowerCase())) return parameters2.offer.hash;
1022
- const callbackId = getCallbackIdForOffer(parameters2.offer);
1023
- map.set(parameters2.offer.hash.toLowerCase(), {
1024
- ...parameters2.offer,
1025
- ...callbackId ? { callbackId } : {},
1026
- status: parameters2.status,
1027
- metadata: parameters2.metadata
1028
- });
1029
- const chainId = parameters2.offer.chainId;
1030
- const address = parameters2.offer.offering.toLowerCase();
1031
- const nonce = parameters2.offer.nonce;
1032
- const filledForChain = filled.get(chainId) || /* @__PURE__ */ new Map();
1033
- const filledForOffering = filledForChain.get(address) || /* @__PURE__ */ new Map();
1034
- if (!filledForOffering.has(nonce)) filledForOffering.set(nonce, 0n);
1035
- filledForChain.set(address, filledForOffering);
1036
- filled.set(chainId, filledForChain);
1037
- return Promise.resolve(parameters2.offer.hash);
1516
+
1517
+ // src/core/OfferStore/drizzle/schema.ts
1518
+ var schema_exports = {};
1519
+ __export(schema_exports, {
1520
+ VERSION: () => VERSION,
1521
+ availableLiquidityPools: () => availableLiquidityPools,
1522
+ availableLiquidityQueues: () => availableLiquidityQueues,
1523
+ collectorBlockNumbers: () => collectorBlockNumbers,
1524
+ consumed: () => consumed,
1525
+ offerCollaterals: () => offerCollaterals,
1526
+ offerStatus: () => offerStatus,
1527
+ offers: () => offers,
1528
+ userPositions: () => userPositions
1529
+ });
1530
+ var VERSION = "offers_v1.1";
1531
+ var s = pgSchema(VERSION);
1532
+ var offers = s.table(
1533
+ "offers",
1534
+ {
1535
+ hash: varchar("hash", { length: 66 }).primaryKey(),
1536
+ offering: varchar("offering", { length: 42 }).notNull(),
1537
+ assets: numeric("assets", { precision: 78, scale: 0 }).notNull(),
1538
+ rate: bigint("rate", { mode: "bigint" }).notNull(),
1539
+ maturity: integer("maturity").notNull(),
1540
+ expiry: integer("expiry").notNull(),
1541
+ start: integer("start").notNull(),
1542
+ nonce: bigint("nonce", { mode: "bigint" }).notNull(),
1543
+ buy: boolean("buy").notNull(),
1544
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
1545
+ loanToken: varchar("loan_token", { length: 42 }).notNull(),
1546
+ callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
1547
+ callbackData: text("callback_data").notNull(),
1548
+ callbackGasLimit: bigint("callback_gas_limit", { mode: "bigint" }).notNull(),
1549
+ signature: varchar("signature", { length: 132 }),
1550
+ callbackId: varchar("callback_id", { length: 256 }),
1551
+ createdAt: timestamp("created_at").defaultNow().notNull()
1552
+ },
1553
+ (table) => [
1554
+ index("offers_offering_idx").on(table.offering),
1555
+ index("offers_buy_idx").on(table.buy),
1556
+ index("offers_chain_id_idx").on(table.chainId),
1557
+ index("offers_loan_token_idx").on(table.loanToken),
1558
+ index("offers_maturity_idx").on(table.maturity),
1559
+ index("offers_expiry_idx").on(table.expiry),
1560
+ index("offers_rate_idx").on(table.rate),
1561
+ index("offers_assets_idx").on(table.assets),
1562
+ // Compound indices for cursor pagination with hash
1563
+ index("offers_rate_hash_idx").on(table.rate, table.hash),
1564
+ index("offers_maturity_hash_idx").on(table.maturity, table.hash),
1565
+ index("offers_expiry_hash_idx").on(table.expiry, table.hash),
1566
+ index("offers_assets_hash_idx").on(table.assets, table.hash)
1567
+ ]
1568
+ );
1569
+ var offerCollaterals = s.table(
1570
+ "offer_collaterals",
1571
+ {
1572
+ id: serial("id").primaryKey(),
1573
+ offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
1574
+ asset: varchar("asset", { length: 42 }).notNull(),
1575
+ oracle: varchar("oracle", { length: 42 }).notNull(),
1576
+ lltv: bigint("lltv", { mode: "bigint" }).notNull()
1577
+ },
1578
+ (table) => [
1579
+ index("offer_collaterals_offer_hash_idx").on(table.offerHash),
1580
+ index("offer_collaterals_asset_idx").on(table.asset),
1581
+ index("offer_collaterals_oracle_idx").on(table.oracle),
1582
+ // Composite index
1583
+ index("offer_collaterals_tuple_idx").on(table.asset, table.oracle, table.lltv)
1584
+ ]
1585
+ );
1586
+ var offerStatus = s.table(
1587
+ "offer_status",
1588
+ {
1589
+ id: serial("id").primaryKey(),
1590
+ offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
1591
+ status: text("status").$type().notNull(),
1592
+ metadata: jsonb("metadata"),
1593
+ createdAt: timestamp("created_at").defaultNow().notNull()
1594
+ },
1595
+ (table) => [
1596
+ index("offer_status_offer_hash_created_at_idx").on(table.offerHash, desc(table.createdAt)),
1597
+ index("offer_status_status_idx").on(table.status)
1598
+ ]
1599
+ );
1600
+ var consumed = s.table(
1601
+ "consumed_per_user_and_nonce",
1602
+ {
1603
+ id: varchar("id", { length: 255 }).primaryKey(),
1604
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
1605
+ offering: varchar("offering", { length: 42 }).notNull(),
1606
+ nonce: bigint("nonce", { mode: "bigint" }).notNull(),
1607
+ consumed: numeric("consumed", { precision: 78, scale: 0 }).notNull(),
1608
+ createdAt: timestamp("created_at").defaultNow().notNull()
1609
+ },
1610
+ (table) => [
1611
+ index("consumed_per_user_and_nonce_chain_id_offering_nonce_created_at_idx").on(
1612
+ table.chainId,
1613
+ table.offering,
1614
+ table.nonce,
1615
+ desc(table.createdAt)
1616
+ )
1617
+ ]
1618
+ );
1619
+ var collectorBlockNumbers = s.table(
1620
+ "collector_block_numbers",
1621
+ {
1622
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
1623
+ name: text("name").notNull(),
1624
+ blockNumber: bigint("block_number", { mode: "number" }).notNull(),
1625
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
1626
+ },
1627
+ (table) => [uniqueIndex("collector_block_numbers_chain_name_idx").on(table.chainId, table.name)]
1628
+ );
1629
+ var availableLiquidityPools = s.table("available_liquidity_pools", {
1630
+ id: varchar("id", { length: 255 }).primaryKey(),
1631
+ amount: numeric("amount", { precision: 78, scale: 0 }).notNull(),
1632
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
1633
+ });
1634
+ var availableLiquidityQueues = s.table(
1635
+ "available_liquidity_queues",
1636
+ {
1637
+ queueId: varchar("queue_id", { length: 255 }).notNull(),
1638
+ availableLiquidityPoolId: varchar("available_liquidity_pool_id", { length: 255 }).notNull().references(() => availableLiquidityPools.id, { onDelete: "cascade" }),
1639
+ index: integer("index").notNull(),
1640
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
1641
+ },
1642
+ (table) => [
1643
+ primaryKey({
1644
+ columns: [table.queueId, table.availableLiquidityPoolId],
1645
+ name: "available_liquidity_queues_pk"
1646
+ }),
1647
+ index("available_liquidity_queues_queue_index_idx").on(table.queueId, table.index)
1648
+ ]
1649
+ );
1650
+ var userPositions = s.table(
1651
+ "user_positions",
1652
+ {
1653
+ id: varchar("id", { length: 255 }).primaryKey(),
1654
+ availableLiquidityQueueId: varchar("available_liquidity_queue_id", { length: 255 }).notNull(),
1655
+ user: varchar("user", { length: 255 }).notNull(),
1656
+ chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
1657
+ amount: numeric("amount", { precision: 78, scale: 0 }).notNull(),
1658
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
1659
+ },
1660
+ (table) => [
1661
+ index("user_positions_available_liquidity_queue_id_idx").on(table.availableLiquidityQueueId)
1662
+ ]
1663
+ );
1664
+
1665
+ // src/core/CollectorBlockStore.ts
1666
+ var create = (config) => {
1667
+ const db = config.db;
1668
+ return {
1669
+ getBlockNumber: async (parameters) => {
1670
+ const name = parameters.collectorName.toLowerCase();
1671
+ const result = await db.select({ blockNumber: collectorBlockNumbers.blockNumber }).from(collectorBlockNumbers).where(
1672
+ and(
1673
+ eq(collectorBlockNumbers.name, name),
1674
+ eq(collectorBlockNumbers.chainId, parameters.chainId)
1675
+ )
1676
+ ).limit(1);
1677
+ if (result.length === 0)
1678
+ return Chain.getChain(parameters.chainId)?.mempool.deploymentBlock || 0;
1679
+ return Number(result[0].blockNumber);
1680
+ },
1681
+ saveBlockNumber: async (parameters) => {
1682
+ const name = parameters.collectorName.toLowerCase();
1683
+ await db.insert(collectorBlockNumbers).values({
1684
+ chainId: parameters.chainId,
1685
+ name,
1686
+ blockNumber: parameters.blockNumber,
1687
+ updatedAt: /* @__PURE__ */ new Date()
1688
+ }).onConflictDoUpdate({
1689
+ target: [collectorBlockNumbers.chainId, collectorBlockNumbers.name],
1690
+ set: {
1691
+ blockNumber: parameters.blockNumber,
1692
+ updatedAt: /* @__PURE__ */ new Date()
1693
+ }
1694
+ });
1695
+ }
1038
1696
  };
1039
- const sort = (sortBy, sortOrder, a, b) => {
1040
- sortBy = sortBy || "expiry";
1041
- sortOrder = sortOrder || "desc";
1042
- const sortKey = sortBy === "amount" ? "assets" : sortBy;
1043
- if (a[sortKey] === b[sortKey]) {
1044
- if (a.hash === b.hash) return 0;
1045
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : b.hash > a.hash ? 1 : -1;
1697
+ };
1698
+ function memory() {
1699
+ const blockNumbers = /* @__PURE__ */ new Map();
1700
+ return {
1701
+ getBlockNumber: async (parameters) => {
1702
+ const name = parameters.collectorName.toLowerCase();
1703
+ return blockNumbers.get(name)?.get(parameters.chainId) || Chain.getChain(parameters.chainId)?.mempool.deploymentBlock || 0;
1704
+ },
1705
+ saveBlockNumber: async (parameters) => {
1706
+ const name = parameters.collectorName.toLowerCase();
1707
+ if (!blockNumbers.has(name)) {
1708
+ blockNumbers.set(name, /* @__PURE__ */ new Map());
1709
+ }
1710
+ if (!blockNumbers.get(name)?.has(parameters.chainId)) {
1711
+ blockNumbers.get(name).set(parameters.chainId, parameters.blockNumber);
1712
+ }
1713
+ blockNumbers.get(name).set(parameters.chainId, parameters.blockNumber);
1046
1714
  }
1047
- switch (sortBy) {
1048
- case "rate":
1049
- if (a.rate === b.rate) {
1050
- if (a.hash === b.hash) return 0;
1051
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1052
- }
1053
- return sortOrder === "asc" ? a.rate > b.rate ? 1 : -1 : b.rate > a.rate ? 1 : -1;
1054
- case "maturity":
1055
- if (a.maturity === b.maturity) {
1056
- if (a.hash === b.hash) return 0;
1057
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1715
+ };
1716
+ }
1717
+
1718
+ // src/core/LiquidityStore.ts
1719
+ var LiquidityStore_exports = {};
1720
+ __export(LiquidityStore_exports, {
1721
+ create: () => create2,
1722
+ memory: () => memory2
1723
+ });
1724
+ var create2 = (config) => {
1725
+ const db = config.db;
1726
+ return {
1727
+ getByUserPositionId: async (parameters) => {
1728
+ const up = await db.select().from(userPositions).where(eq(userPositions.id, parameters.userPositionId)).limit(1);
1729
+ if (up.length === 0) return null;
1730
+ const userPositionRow = up[0];
1731
+ const rows = await db.select({ queue: availableLiquidityQueues, pool: availableLiquidityPools }).from(availableLiquidityQueues).innerJoin(
1732
+ availableLiquidityPools,
1733
+ eq(availableLiquidityPools.id, availableLiquidityQueues.availableLiquidityPoolId)
1734
+ ).where(eq(availableLiquidityQueues.queueId, userPositionRow.availableLiquidityQueueId));
1735
+ const queues = rows.map((row) => ({
1736
+ queue: row.queue,
1737
+ pool: row.pool
1738
+ }));
1739
+ return { userPosition: userPositionRow, queues };
1740
+ },
1741
+ getAll: async () => {
1742
+ const rows = await db.select({
1743
+ userPosition: userPositions,
1744
+ queue: availableLiquidityQueues,
1745
+ pool: availableLiquidityPools
1746
+ }).from(userPositions).innerJoin(
1747
+ availableLiquidityQueues,
1748
+ eq(availableLiquidityQueues.queueId, userPositions.availableLiquidityQueueId)
1749
+ ).innerJoin(
1750
+ availableLiquidityPools,
1751
+ eq(availableLiquidityPools.id, availableLiquidityQueues.availableLiquidityPoolId)
1752
+ );
1753
+ const byUserPosition = /* @__PURE__ */ new Map();
1754
+ for (const row of rows) {
1755
+ const id = row.userPosition.id;
1756
+ if (!byUserPosition.has(id)) {
1757
+ byUserPosition.set(id, { userPosition: row.userPosition, queues: [] });
1058
1758
  }
1059
- return sortOrder === "asc" ? a.maturity > b.maturity ? 1 : -1 : b.maturity > a.maturity ? 1 : -1;
1060
- case "expiry":
1061
- if (a.expiry === b.expiry) {
1062
- if (a.hash === b.hash) return 0;
1063
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1759
+ byUserPosition.get(id).queues.push({ queue: row.queue, pool: row.pool });
1760
+ }
1761
+ return Array.from(byUserPosition.values());
1762
+ },
1763
+ save: async (parameters) => {
1764
+ const { liquidity } = parameters;
1765
+ await db.transaction(async (tx) => {
1766
+ for (const qp of liquidity.queues) {
1767
+ await tx.insert(availableLiquidityPools).values({
1768
+ id: qp.pool.id,
1769
+ amount: qp.pool.amount,
1770
+ updatedAt: /* @__PURE__ */ new Date()
1771
+ }).onConflictDoUpdate({
1772
+ target: availableLiquidityPools.id,
1773
+ set: {
1774
+ amount: qp.pool.amount,
1775
+ updatedAt: /* @__PURE__ */ new Date()
1776
+ }
1777
+ });
1064
1778
  }
1065
- return sortOrder === "asc" ? a.expiry > b.expiry ? 1 : -1 : b.expiry > a.expiry ? 1 : -1;
1066
- case "amount":
1067
- if (a.assets === b.assets) {
1068
- if (a.hash === b.hash) return 0;
1069
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1779
+ for (const qp of liquidity.queues) {
1780
+ await tx.insert(availableLiquidityQueues).values({
1781
+ queueId: qp.queue.queueId,
1782
+ availableLiquidityPoolId: qp.pool.id,
1783
+ index: qp.queue.index,
1784
+ updatedAt: /* @__PURE__ */ new Date()
1785
+ }).onConflictDoUpdate({
1786
+ target: [
1787
+ availableLiquidityQueues.queueId,
1788
+ availableLiquidityQueues.availableLiquidityPoolId
1789
+ ],
1790
+ set: {
1791
+ index: qp.queue.index,
1792
+ updatedAt: /* @__PURE__ */ new Date()
1793
+ }
1794
+ });
1070
1795
  }
1071
- return sortOrder === "asc" ? a.assets > b.assets ? 1 : -1 : b.assets > a.assets ? 1 : -1;
1072
- default:
1073
- if (a.expiry === b.expiry) {
1074
- if (a.hash === b.hash) return 0;
1075
- return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
1796
+ await tx.insert(userPositions).values({
1797
+ id: liquidity.userPosition.id,
1798
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1799
+ user: liquidity.userPosition.user,
1800
+ chainId: liquidity.userPosition.chainId,
1801
+ amount: liquidity.userPosition.amount,
1802
+ updatedAt: /* @__PURE__ */ new Date()
1803
+ }).onConflictDoUpdate({
1804
+ target: userPositions.id,
1805
+ set: {
1806
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1807
+ user: liquidity.userPosition.user,
1808
+ chainId: liquidity.userPosition.chainId,
1809
+ amount: liquidity.userPosition.amount,
1810
+ updatedAt: /* @__PURE__ */ new Date()
1811
+ }
1812
+ });
1813
+ });
1814
+ }
1815
+ };
1816
+ };
1817
+ function memory2() {
1818
+ const poolsById = /* @__PURE__ */ new Map();
1819
+ const queuesByComposite = /* @__PURE__ */ new Map();
1820
+ const queueIndexByQueueId = /* @__PURE__ */ new Map();
1821
+ const userPositionsById = /* @__PURE__ */ new Map();
1822
+ return {
1823
+ getByUserPositionId: async (parameters) => {
1824
+ const up = userPositionsById.get(parameters.userPositionId);
1825
+ if (!up) return null;
1826
+ const compositeKeys = queueIndexByQueueId.get(up.availableLiquidityQueueId) || /* @__PURE__ */ new Set();
1827
+ const queues = [];
1828
+ for (const key of compositeKeys) {
1829
+ const q = queuesByComposite.get(key);
1830
+ if (!q) continue;
1831
+ const p = poolsById.get(q.availableLiquidityPoolId);
1832
+ if (!p) continue;
1833
+ queues.push({ queue: q, pool: p });
1834
+ }
1835
+ return { userPosition: up, queues };
1836
+ },
1837
+ getAll: async () => {
1838
+ const results = [];
1839
+ for (const up of userPositionsById.values()) {
1840
+ const compositeKeys = queueIndexByQueueId.get(up.availableLiquidityQueueId) || /* @__PURE__ */ new Set();
1841
+ const queues = [];
1842
+ for (const key of compositeKeys) {
1843
+ const q = queuesByComposite.get(key);
1844
+ if (!q) continue;
1845
+ const p = poolsById.get(q.availableLiquidityPoolId);
1846
+ if (!p) continue;
1847
+ queues.push({ queue: q, pool: p });
1076
1848
  }
1077
- return sortOrder === "asc" ? a.expiry > b.expiry ? 1 : -1 : b.expiry > a.expiry ? 1 : -1;
1849
+ results.push({ userPosition: up, queues });
1850
+ }
1851
+ return results;
1852
+ },
1853
+ save: async (parameters) => {
1854
+ const { liquidity } = parameters;
1855
+ for (const qp of liquidity.queues) {
1856
+ poolsById.set(qp.pool.id, {
1857
+ id: qp.pool.id,
1858
+ amount: qp.pool.amount,
1859
+ updatedAt: /* @__PURE__ */ new Date()
1860
+ });
1861
+ }
1862
+ for (const qp of liquidity.queues) {
1863
+ const qid = qp.queue.queueId;
1864
+ if (!qid) continue;
1865
+ const composite = `${qid}::${qp.pool.id}`;
1866
+ queuesByComposite.set(composite, {
1867
+ queueId: qid,
1868
+ availableLiquidityPoolId: qp.pool.id,
1869
+ index: qp.queue.index,
1870
+ updatedAt: /* @__PURE__ */ new Date()
1871
+ });
1872
+ if (!queueIndexByQueueId.has(qid)) queueIndexByQueueId.set(qid, /* @__PURE__ */ new Set());
1873
+ queueIndexByQueueId.get(qid).add(composite);
1874
+ }
1875
+ userPositionsById.set(liquidity.userPosition.id, {
1876
+ id: liquidity.userPosition.id,
1877
+ availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1878
+ user: liquidity.userPosition.user,
1879
+ chainId: liquidity.userPosition.chainId,
1880
+ amount: liquidity.userPosition.amount,
1881
+ updatedAt: /* @__PURE__ */ new Date()
1882
+ });
1078
1883
  }
1079
1884
  };
1885
+ }
1886
+
1887
+ // src/core/OfferStore/index.ts
1888
+ var OfferStore_exports = {};
1889
+ __export(OfferStore_exports, {
1890
+ create: () => create3
1891
+ });
1892
+ function create3(config) {
1893
+ const db = config.db;
1080
1894
  return {
1081
- create,
1082
- createMany: async (parameters2) => {
1083
- return Promise.all(
1084
- parameters2.map((p) => create({ offer: p.offer, status: p.status, metadata: p.metadata }))
1085
- );
1895
+ create: async (parameters) => {
1896
+ const { offer, status, metadata } = parameters;
1897
+ return await db.transaction(async (tx) => {
1898
+ const callbackId = getCallbackIdForOffer(offer);
1899
+ const result = await tx.insert(offers).values({
1900
+ hash: offer.hash.toLowerCase(),
1901
+ offering: offer.offering.toLowerCase(),
1902
+ assets: offer.assets.toString(),
1903
+ rate: offer.rate,
1904
+ maturity: offer.maturity,
1905
+ expiry: offer.expiry,
1906
+ start: offer.start,
1907
+ nonce: offer.nonce,
1908
+ buy: offer.buy,
1909
+ chainId: offer.chainId,
1910
+ loanToken: offer.loanToken.toLowerCase(),
1911
+ callbackAddress: offer.callback.address.toLowerCase(),
1912
+ callbackData: offer.callback.data,
1913
+ callbackGasLimit: offer.callback.gasLimit,
1914
+ signature: offer.signature,
1915
+ callbackId: callbackId ?? null
1916
+ }).onConflictDoNothing().returning();
1917
+ if (result.length === 0) {
1918
+ return offer.hash;
1919
+ }
1920
+ for (const collateral of offer.collaterals) {
1921
+ await tx.insert(offerCollaterals).values({
1922
+ offerHash: offer.hash.toLowerCase(),
1923
+ asset: collateral.asset.toLowerCase(),
1924
+ oracle: collateral.oracle.toLowerCase(),
1925
+ lltv: collateral.lltv
1926
+ });
1927
+ }
1928
+ await tx.insert(offerStatus).values({
1929
+ offerHash: offer.hash.toLowerCase(),
1930
+ status,
1931
+ metadata
1932
+ });
1933
+ return offer.hash;
1934
+ });
1935
+ },
1936
+ createMany: async (parameters) => {
1937
+ return await db.transaction(async (tx) => {
1938
+ const hashes = [];
1939
+ for (const { offer, status, metadata } of parameters) {
1940
+ const callbackId = getCallbackIdForOffer(offer);
1941
+ const result = await tx.insert(offers).values({
1942
+ hash: offer.hash.toLowerCase(),
1943
+ offering: offer.offering.toLowerCase(),
1944
+ assets: offer.assets.toString(),
1945
+ rate: offer.rate,
1946
+ maturity: offer.maturity,
1947
+ expiry: offer.expiry,
1948
+ start: offer.start,
1949
+ nonce: offer.nonce,
1950
+ buy: offer.buy,
1951
+ chainId: offer.chainId,
1952
+ loanToken: offer.loanToken.toLowerCase(),
1953
+ callbackAddress: offer.callback.address.toLowerCase(),
1954
+ callbackData: offer.callback.data,
1955
+ callbackGasLimit: offer.callback.gasLimit,
1956
+ signature: offer.signature,
1957
+ callbackId: callbackId ?? null
1958
+ }).onConflictDoNothing().returning();
1959
+ if (result.length === 0) continue;
1960
+ for (const collateral of offer.collaterals) {
1961
+ await tx.insert(offerCollaterals).values({
1962
+ offerHash: offer.hash.toLowerCase(),
1963
+ asset: collateral.asset.toLowerCase(),
1964
+ oracle: collateral.oracle.toLowerCase(),
1965
+ lltv: collateral.lltv
1966
+ });
1967
+ }
1968
+ await tx.insert(offerStatus).values({
1969
+ offerHash: offer.hash.toLowerCase(),
1970
+ status,
1971
+ metadata
1972
+ });
1973
+ hashes.push(offer.hash);
1974
+ }
1975
+ return hashes;
1976
+ });
1086
1977
  },
1087
1978
  getAll: async (params) => {
1088
- const { query } = params || {};
1089
- let {
1090
- creators,
1091
- side,
1092
- chains,
1093
- loanTokens,
1094
- status = ["valid"],
1095
- callbackAddresses,
1096
- minAmount,
1097
- maxAmount,
1098
- minRate,
1099
- maxRate,
1100
- minMaturity,
1101
- maxMaturity,
1102
- minExpiry,
1103
- maxExpiry,
1104
- collateralAssets,
1105
- collateralOracles,
1106
- collateralTuple,
1107
- minLltv,
1108
- maxLltv,
1109
- sortBy = "expiry",
1110
- sortOrder = "desc",
1111
- cursor: queryCursor,
1112
- limit = 20
1113
- } = query || {};
1979
+ const { query } = params ?? {};
1980
+ const conditions = [];
1114
1981
  const now = Time.now();
1115
- const buy = side === "buy";
1116
- let offers = Array.from(map.values()).map((o) => ({
1117
- ...o,
1118
- consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1119
- })).filter((o) => o.consumed < o.assets);
1120
- const cursor = decode(queryCursor);
1121
- if (cursor) {
1122
- if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
1123
- throw new Error("Cursor does not match the current sort parameters");
1982
+ if (query?.creators && query.creators.length > 0) {
1983
+ conditions.push(
1984
+ inArray(
1985
+ offers.offering,
1986
+ query.creators.map((c) => c.toLowerCase())
1987
+ )
1988
+ );
1989
+ }
1990
+ if (query?.side) {
1991
+ conditions.push(eq(offers.buy, query.side === "buy"));
1992
+ }
1993
+ if (query?.chains && query.chains.length > 0) {
1994
+ conditions.push(
1995
+ inArray(
1996
+ offers.chainId,
1997
+ query.chains.map((chain) => BigInt(chain))
1998
+ )
1999
+ );
2000
+ }
2001
+ if (query?.loanTokens && query.loanTokens.length > 0) {
2002
+ conditions.push(
2003
+ inArray(
2004
+ offers.loanToken,
2005
+ query.loanTokens.map((t) => t.toLowerCase())
2006
+ )
2007
+ );
2008
+ }
2009
+ if (query?.callbackAddresses && query.callbackAddresses.length > 0) {
2010
+ conditions.push(
2011
+ inArray(
2012
+ offers.callbackAddress,
2013
+ query.callbackAddresses.map((c) => c.toLowerCase())
2014
+ )
2015
+ );
2016
+ }
2017
+ conditions.push(gte(offers.expiry, now));
2018
+ if (query?.minAmount !== void 0) {
2019
+ conditions.push(gte(offers.assets, query.minAmount.toString()));
2020
+ }
2021
+ if (query?.maxAmount !== void 0) {
2022
+ conditions.push(lte(offers.assets, query.maxAmount.toString()));
2023
+ }
2024
+ if (query?.minRate !== void 0) {
2025
+ conditions.push(gte(offers.rate, query.minRate));
2026
+ }
2027
+ if (query?.maxRate !== void 0) {
2028
+ conditions.push(lte(offers.rate, query.maxRate));
2029
+ }
2030
+ if (query?.minMaturity !== void 0) {
2031
+ conditions.push(gte(offers.maturity, query.minMaturity));
2032
+ }
2033
+ if (query?.maxMaturity !== void 0) {
2034
+ conditions.push(lte(offers.maturity, query.maxMaturity));
2035
+ }
2036
+ if (query?.minExpiry !== void 0) {
2037
+ conditions.push(gte(offers.expiry, query.minExpiry));
2038
+ }
2039
+ if (query?.maxExpiry !== void 0) {
2040
+ conditions.push(lte(offers.expiry, query.maxExpiry));
2041
+ }
2042
+ if (query?.collateralAssets && query.collateralAssets.length > 0) {
2043
+ conditions.push(
2044
+ inArray(
2045
+ offerCollaterals.asset,
2046
+ query.collateralAssets.map((a) => a.toLowerCase())
2047
+ )
2048
+ );
2049
+ }
2050
+ if (query?.collateralOracles && query.collateralOracles.length > 0) {
2051
+ conditions.push(
2052
+ inArray(
2053
+ offerCollaterals.oracle,
2054
+ query.collateralOracles.map((o) => o.toLowerCase())
2055
+ )
2056
+ );
2057
+ }
2058
+ if (query?.collateralTuple && query.collateralTuple.length > 0) {
2059
+ const tupleClauses = query.collateralTuple.map((tuple) => {
2060
+ const parts = [
2061
+ sql`${offerCollaterals.asset} = ${tuple.asset.toLowerCase()}`
2062
+ ];
2063
+ if (tuple.oracle) {
2064
+ parts.push(sql`${offerCollaterals.oracle} = ${tuple.oracle.toLowerCase()}`);
2065
+ }
2066
+ if (tuple.lltv !== void 0) {
2067
+ parts.push(sql`${offerCollaterals.lltv} = ${tuple.lltv}`);
2068
+ }
2069
+ let clause = parts[0];
2070
+ for (let i = 1; i < parts.length; i++) {
2071
+ clause = sql`${clause} AND ${parts[i]}`;
2072
+ }
2073
+ return sql`(${clause})`;
2074
+ }).filter((c) => c !== void 0);
2075
+ if (tupleClauses.length > 0) {
2076
+ let combined = tupleClauses[0];
2077
+ for (let i = 1; i < tupleClauses.length; i++) {
2078
+ combined = sql`${combined} OR ${tupleClauses[i]}`;
2079
+ }
2080
+ conditions.push(sql`(${combined})`);
1124
2081
  }
1125
- switch (cursor.sort) {
2082
+ }
2083
+ if (query?.minLltv !== void 0) {
2084
+ conditions.push(gte(offerCollaterals.lltv, parseUnits(query.minLltv.toString(), 16)));
2085
+ }
2086
+ if (query?.maxLltv !== void 0) {
2087
+ conditions.push(lte(offerCollaterals.lltv, parseUnits(query.maxLltv.toString(), 16)));
2088
+ }
2089
+ const sortBy = query?.sortBy ?? "expiry";
2090
+ const sortOrder = query?.sortOrder ?? "desc";
2091
+ const sortColumn = (() => {
2092
+ switch (sortBy) {
1126
2093
  case "rate":
1127
- offers = offers.filter(
1128
- (o) => (sortOrder === "asc" ? o.rate >= BigInt(cursor.rate) : o.rate <= BigInt(cursor.rate)) && (o.rate !== BigInt(cursor.rate) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1129
- );
1130
- break;
2094
+ return offers.rate;
1131
2095
  case "maturity":
1132
- offers = offers.filter(
1133
- (o) => (sortOrder === "asc" ? o.maturity >= BigInt(cursor.maturity) : o.maturity <= BigInt(cursor.maturity)) && (o.maturity !== Maturity.from(cursor.maturity) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1134
- );
1135
- break;
2096
+ return offers.maturity;
1136
2097
  case "expiry":
1137
- offers = offers.filter(
1138
- (o) => (sortOrder === "asc" ? o.expiry >= cursor.expiry : o.expiry <= cursor.expiry) && (o.expiry !== cursor.expiry || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1139
- );
1140
- break;
2098
+ return offers.expiry;
1141
2099
  case "amount":
1142
- offers = offers.filter(
1143
- (o) => (sortOrder === "asc" ? o.assets >= BigInt(cursor.assets) : o.assets <= BigInt(cursor.assets)) && (o.assets !== BigInt(cursor.assets) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
1144
- );
1145
- break;
2100
+ return offers.assets;
1146
2101
  default:
1147
- throw new Error("Invalid sort parameter");
2102
+ return offers.expiry;
2103
+ }
2104
+ })();
2105
+ const cursor = decode(query?.cursor);
2106
+ if (cursor) {
2107
+ if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
2108
+ throw new Error("Cursor does not match the current sort parameters");
1148
2109
  }
1149
- offers = offers.filter((o) => o.hash !== cursor.hash);
2110
+ const op = sortOrder === "asc" ? sql`>` : sql`<`;
2111
+ const cursorVal = (() => {
2112
+ switch (sortBy) {
2113
+ case "rate":
2114
+ return BigInt(cursor.rate);
2115
+ case "amount":
2116
+ return BigInt(cursor.assets);
2117
+ case "maturity":
2118
+ return cursor.maturity;
2119
+ case "expiry":
2120
+ return cursor.expiry;
2121
+ default:
2122
+ return cursor.expiry;
2123
+ }
2124
+ })();
2125
+ conditions.push(sql`(${sortColumn}, ${offers.hash}) ${op} (${cursorVal}, ${cursor.hash})`);
1150
2126
  }
1151
- creators && (creators = creators.map((c) => c.toLowerCase()));
1152
- loanTokens && (loanTokens = loanTokens.map((lt) => lt.toLowerCase()));
1153
- callbackAddresses && (callbackAddresses = callbackAddresses.map((ca) => ca.toLowerCase()));
1154
- collateralAssets && (collateralAssets = collateralAssets.map((ca) => ca.toLowerCase()));
1155
- collateralOracles && (collateralOracles = collateralOracles.map((co) => co.toLowerCase()));
1156
- collateralTuple && (collateralTuple = collateralTuple.map((ct) => ({
1157
- asset: ct.asset.toLowerCase(),
1158
- oracle: ct.oracle?.toLowerCase()
1159
- })));
1160
- offers = offers.filter((o) => o.expiry >= now);
1161
- creators && (offers = offers.filter((o) => creators.includes(o.offering.toLowerCase())));
1162
- side && (offers = offers.filter((o) => o.buy === buy));
1163
- chains && (offers = offers.filter((o) => chains.includes(Number(o.chainId))));
1164
- loanTokens && (offers = offers.filter((o) => loanTokens.includes(o.loanToken.toLowerCase())));
1165
- status && (offers = offers.filter((o) => status.includes(o.status)));
1166
- callbackAddresses && (offers = offers.filter(
1167
- (o) => callbackAddresses.includes(o.callback.address.toLowerCase())
1168
- ));
1169
- minAmount && (offers = offers.filter((o) => o.assets >= minAmount));
1170
- maxAmount && (offers = offers.filter((o) => o.assets <= maxAmount));
1171
- minRate && (offers = offers.filter((o) => o.rate >= minRate));
1172
- maxRate && (offers = offers.filter((o) => o.rate <= maxRate));
1173
- minMaturity && (offers = offers.filter((o) => o.maturity >= minMaturity));
1174
- maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
1175
- minExpiry && (offers = offers.filter((o) => o.expiry >= minExpiry));
1176
- maxExpiry && (offers = offers.filter((o) => o.expiry <= maxExpiry));
1177
- collateralAssets && (offers = offers.filter(
1178
- (o) => o.collaterals.some((c) => collateralAssets.includes(c.asset.toLowerCase()))
1179
- ));
1180
- collateralOracles && (offers = offers.filter(
1181
- (o) => o.collaterals.some((c) => collateralOracles.includes(c.oracle.toLowerCase()))
1182
- ));
1183
- collateralTuple && (offers = offers.filter(
1184
- (o) => o.collaterals.some(
1185
- (c) => collateralTuple.some(
1186
- (ct) => c.asset.toLowerCase() === ct.asset.toLowerCase() && (ct.oracle ? c.oracle.toLowerCase() === ct.oracle.toLowerCase() : true) && (ct.lltv ? c.lltv === LLTV.from(BigInt(ct.lltv)) : true)
1187
- )
2127
+ const limit = query?.limit ?? 20;
2128
+ const latestStatus = db.select({
2129
+ status: offerStatus.status,
2130
+ metadata: offerStatus.metadata
2131
+ }).from(offerStatus).where(eq(offerStatus.offerHash, offers.hash)).orderBy(desc(offerStatus.createdAt)).limit(1).as("latest_status");
2132
+ const sumConsumed = db.select({
2133
+ consumed: sql`COALESCE(SUM(${consumed.consumed}), 0)`.as("consumed")
2134
+ }).from(consumed).where(
2135
+ and(
2136
+ eq(consumed.offering, offers.offering),
2137
+ eq(consumed.nonce, offers.nonce),
2138
+ eq(consumed.chainId, offers.chainId)
2139
+ )
2140
+ ).as("sum_consumed");
2141
+ const results = await db.select({
2142
+ hash: offers.hash,
2143
+ offering: offers.offering,
2144
+ assets: offers.assets,
2145
+ consumed: sumConsumed.consumed,
2146
+ rate: offers.rate,
2147
+ maturity: offers.maturity,
2148
+ expiry: offers.expiry,
2149
+ start: offers.start,
2150
+ nonce: offers.nonce,
2151
+ buy: offers.buy,
2152
+ chainId: offers.chainId,
2153
+ loanToken: offers.loanToken,
2154
+ callbackAddress: offers.callbackAddress,
2155
+ callbackData: offers.callbackData,
2156
+ callbackGasLimit: offers.callbackGasLimit,
2157
+ signature: offers.signature,
2158
+ createdAt: offers.createdAt,
2159
+ collateralAsset: offerCollaterals.asset,
2160
+ collateralOracle: offerCollaterals.oracle,
2161
+ collateralLltv: offerCollaterals.lltv,
2162
+ status: latestStatus.status,
2163
+ metadata: latestStatus.metadata
2164
+ }).from(offers).leftJoin(offerCollaterals, eq(offers.hash, offerCollaterals.offerHash)).leftJoinLateral(latestStatus, sql`true`).leftJoinLateral(sumConsumed, sql`true`).where(
2165
+ and(
2166
+ conditions.length > 0 ? and(...conditions) : sql`true`,
2167
+ query?.status && query.status.length > 0 ? inArray(latestStatus.status, query.status) : eq(latestStatus.status, "valid"),
2168
+ sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) > 0`
1188
2169
  )
1189
- ));
1190
- minLltv && (offers = offers.filter(
1191
- (o) => o.collaterals.every((c) => c.lltv >= parseUnits(minLltv.toString(), 16))
1192
- ));
1193
- maxLltv && (offers = offers.filter(
1194
- (o) => o.collaterals.every((c) => c.lltv <= parseUnits(maxLltv.toString(), 16))
1195
- ));
1196
- offers = offers.sort((a, b) => sort(sortBy, sortOrder, a, b));
2170
+ ).orderBy(
2171
+ ...sortOrder === "asc" ? [asc(sortColumn), asc(offers.hash)] : [desc(sortColumn), desc(offers.hash)]
2172
+ ).limit(limit);
2173
+ const offerMap = /* @__PURE__ */ new Map();
2174
+ for (const row of results) {
2175
+ const offer = offerMap.get(row.hash) || {
2176
+ hash: row.hash,
2177
+ offering: row.offering,
2178
+ assets: BigInt(row.assets),
2179
+ consumed: BigInt(row.consumed),
2180
+ rate: row.rate,
2181
+ maturity: row.maturity,
2182
+ expiry: row.expiry,
2183
+ start: row.start,
2184
+ nonce: row.nonce,
2185
+ buy: row.buy,
2186
+ chainId: row.chainId,
2187
+ loanToken: row.loanToken,
2188
+ callbackAddress: row.callbackAddress,
2189
+ callbackData: row.callbackData,
2190
+ callbackGasLimit: row.callbackGasLimit,
2191
+ signature: row.signature,
2192
+ createdAt: row.createdAt,
2193
+ collaterals: [],
2194
+ status: row.status,
2195
+ metadata: row.metadata
2196
+ };
2197
+ if (row.collateralAsset && row.collateralOracle && row.collateralLltv) {
2198
+ offer.collaterals.push({
2199
+ asset: row.collateralAsset,
2200
+ oracle: row.collateralOracle,
2201
+ lltv: LLTV.from(row.collateralLltv)
2202
+ });
2203
+ }
2204
+ offerMap.set(row.hash, offer);
2205
+ }
1197
2206
  let nextCursor = null;
1198
- if (offers.length > limit) {
1199
- const last = offers[limit - 1];
2207
+ if (results.length === limit && results.length > 0) {
2208
+ const lastRow = results[results.length - 1];
1200
2209
  const base = {
1201
2210
  sort: sortBy,
1202
2211
  dir: sortOrder,
1203
- hash: last.hash
2212
+ hash: lastRow.hash
1204
2213
  };
1205
2214
  switch (sortBy) {
1206
2215
  case "rate":
1207
- base.rate = last.rate.toString();
2216
+ base.rate = lastRow.rate.toString();
1208
2217
  break;
1209
2218
  case "amount":
1210
- base.assets = last.assets.toString();
2219
+ base.assets = lastRow.assets.toString();
1211
2220
  break;
1212
2221
  case "maturity":
1213
- base.maturity = last.maturity;
2222
+ base.maturity = lastRow.maturity;
1214
2223
  break;
1215
2224
  default:
1216
- base.expiry = last.expiry;
2225
+ base.expiry = lastRow.expiry;
1217
2226
  }
1218
2227
  nextCursor = encode(base);
1219
2228
  }
1220
- offers = offers.slice(0, limit);
1221
- const data = offers.map((o) => ({
1222
- ...Offer.from({
1223
- offering: o.offering,
1224
- assets: o.assets,
1225
- rate: o.rate,
1226
- maturity: Maturity.from(o.maturity),
1227
- expiry: o.expiry,
1228
- start: o.start,
1229
- nonce: o.nonce,
1230
- buy: o.buy,
1231
- chainId: o.chainId,
1232
- loanToken: o.loanToken,
1233
- collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
2229
+ const transformedResults = [];
2230
+ for (const offerData of offerMap.values()) {
2231
+ const offer = Offer.from({
2232
+ offering: offerData.offering,
2233
+ assets: offerData.assets,
2234
+ rate: offerData.rate,
2235
+ maturity: Maturity.from(offerData.maturity),
2236
+ expiry: offerData.expiry,
2237
+ start: offerData.start,
2238
+ nonce: offerData.nonce,
2239
+ buy: offerData.buy,
2240
+ chainId: offerData.chainId,
2241
+ loanToken: offerData.loanToken,
2242
+ collaterals: offerData.collaterals.map((c) => ({
2243
+ asset: c.asset,
2244
+ oracle: c.oracle,
2245
+ lltv: LLTV.from(c.lltv)
2246
+ })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
1234
2247
  callback: {
1235
- address: o.callback.address,
1236
- data: o.callback.data,
1237
- gasLimit: o.callback.gasLimit
2248
+ address: offerData.callbackAddress,
2249
+ data: offerData.callbackData,
2250
+ gasLimit: offerData.callbackGasLimit
1238
2251
  },
1239
- ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
1240
- }),
1241
- consumed: o.consumed,
1242
- status: o.status,
1243
- ...o.metadata ? { metadata: o.metadata } : {}
1244
- }));
1245
- return {
1246
- offers: data,
1247
- nextCursor
1248
- };
2252
+ ...offerData.signature !== null ? { signature: offerData.signature } : void 0
2253
+ });
2254
+ transformedResults.push({
2255
+ ...offer,
2256
+ consumed: offerData.consumed,
2257
+ status: offerData.status,
2258
+ ...offerData.metadata !== null ? { metadata: offerData.metadata } : void 0
2259
+ });
2260
+ }
2261
+ return { offers: transformedResults, nextCursor };
1249
2262
  },
2263
+ /**
2264
+ * Returns offers that match specified offer based on the provided parameters.
2265
+ *
2266
+ * Rules for filtering:
2267
+ * - If offer expiry is in the past compared to now, it should not be included
2268
+ * - If offer maturity is in the past compared to now, it should not be included
2269
+ * - It should return if buyOffer.rate >= sellOffer.rate
2270
+ * - It should return if buyOffer.userAddress != sellOffer.userAddress
2271
+ * - It should return if maturity matches
2272
+ * - It should return if loanToken matches
2273
+ * - If the incoming intent is a buy / lend offer, only consider existing sell / borrow offers.
2274
+ * - If the incoming intent is a sell / borrow offer, only consider existing buy / lend offers.
2275
+ * - When the intent is lend: require offer_collaterals ⊆ intent_collaterals
2276
+ * - When the intent is borrow: require intent_collaterals ⊆ offer_collaterals
2277
+ * - It should sort by increasing rate if the input offer is a buy/lend offer
2278
+ * - It should sort by decreasing rate if the input offer is a sell/borrow offer
2279
+ */
1250
2280
  findMatchingOffers: async (params) => {
1251
2281
  const {
1252
2282
  side,
@@ -1258,645 +2288,872 @@ function memory(parameters) {
1258
2288
  maxMaturity,
1259
2289
  loanToken,
1260
2290
  creator,
1261
- cursor: queryCursor,
2291
+ cursor,
1262
2292
  limit = 20
1263
2293
  } = params;
1264
- const now = Time.now();
1265
- const isBuying = side === "buy";
1266
- const sortOrder = isBuying ? "desc" : "asc";
1267
- let offers = Array.from(map.values()).map((o) => ({
1268
- ...o,
1269
- consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
1270
- })).filter((o) => o.consumed < o.assets);
1271
- const cursor = decode(queryCursor);
1272
- if (cursor) {
1273
- if (cursor.sort !== "rate" || cursor.dir !== sortOrder) {
2294
+ const isIncomingBuy = side === "buy";
2295
+ const nowEpochSeconds = Math.floor(Date.now() / 1e3);
2296
+ const rateSortDirection = isIncomingBuy ? "desc" : "asc";
2297
+ const baseWhereClauses = [];
2298
+ baseWhereClauses.push(eq(offers.buy, !isIncomingBuy));
2299
+ baseWhereClauses.push(eq(offers.chainId, BigInt(chainId)));
2300
+ baseWhereClauses.push(gte(offers.expiry, nowEpochSeconds));
2301
+ baseWhereClauses.push(gte(offers.maturity, nowEpochSeconds));
2302
+ if (maturity) baseWhereClauses.push(eq(offers.maturity, maturity));
2303
+ if (minMaturity) baseWhereClauses.push(gte(offers.maturity, minMaturity));
2304
+ if (maxMaturity) baseWhereClauses.push(lte(offers.maturity, maxMaturity));
2305
+ if (loanToken) baseWhereClauses.push(eq(offers.loanToken, loanToken.toLowerCase()));
2306
+ if (creator) baseWhereClauses.push(eq(offers.offering, creator.toLowerCase()));
2307
+ if (rate)
2308
+ baseWhereClauses.push(isIncomingBuy ? gte(offers.rate, rate) : lte(offers.rate, rate));
2309
+ const parsedCursor = decode(cursor);
2310
+ if (parsedCursor) {
2311
+ if (parsedCursor.sort !== "rate" || parsedCursor.dir !== rateSortDirection) {
1274
2312
  throw new Error("Cursor does not match the current sort parameters");
1275
2313
  }
1276
- offers = offers.filter(
1277
- (o) => sortOrder === "asc" ? o.rate >= BigInt(cursor.rate) : o.rate <= BigInt(cursor.rate)
1278
- );
1279
2314
  }
1280
- offers = offers.filter((o) => o.buy === !isBuying);
1281
- offers = offers.filter((o) => o.chainId === BigInt(chainId));
1282
- offers = offers.filter((o) => o.expiry >= now);
1283
- rate && (offers = offers.filter((o) => isBuying ? o.rate >= rate : o.rate <= rate));
1284
- collaterals.length > 0 && (offers = offers.filter(
1285
- (o) => isBuying ? (
1286
- // when wanting to buy, sell offer collaterals ⊆ user buy collaterals
1287
- o.collaterals.every((oc) => {
1288
- return collaterals.some(
1289
- (c) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
1290
- );
1291
- })
1292
- ) : (
1293
- // when wanting to sell, user sell collaterals ⊆ buy offer collaterals
1294
- collaterals.every((c) => {
1295
- return o.collaterals.some(
1296
- (oc) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
1297
- );
1298
- })
2315
+ if (collaterals.length > 0) {
2316
+ const collateralValues = collaterals.map(
2317
+ (c) => `('${c.asset.toLowerCase()}', '${c.oracle.toLowerCase()}', ${c.lltv.toString()})`
2318
+ ).join(", ");
2319
+ const userCollaterals = sql`(VALUES ${sql.raw(collateralValues)}) AS user_collaterals(asset, oracle, lltv)`;
2320
+ const sellPredicate = sql`
2321
+ NOT EXISTS (
2322
+ SELECT 1 FROM ${userCollaterals}
2323
+ WHERE NOT EXISTS (
2324
+ SELECT 1 FROM ${offerCollaterals} offer_collaterals
2325
+ WHERE offer_collaterals.offer_hash = ${offers.hash}
2326
+ AND offer_collaterals.asset = user_collaterals.asset
2327
+ AND offer_collaterals.oracle = user_collaterals.oracle
2328
+ AND offer_collaterals.lltv = user_collaterals.lltv
2329
+ )
2330
+ )
2331
+ `;
2332
+ const buyPredicate = sql`
2333
+ NOT EXISTS (
2334
+ SELECT 1 FROM ${offerCollaterals} offer_collaterals
2335
+ WHERE offer_collaterals.offer_hash = ${offers.hash}
2336
+ AND NOT EXISTS (
2337
+ SELECT 1 FROM ${userCollaterals}
2338
+ WHERE user_collaterals.asset = offer_collaterals.asset
2339
+ AND user_collaterals.oracle = offer_collaterals.oracle
2340
+ AND user_collaterals.lltv = offer_collaterals.lltv
2341
+ )
2342
+ )
2343
+ `;
2344
+ baseWhereClauses.push(isIncomingBuy ? buyPredicate : sellPredicate);
2345
+ }
2346
+ const latestStatus = db.select({
2347
+ status: offerStatus.status,
2348
+ metadata: offerStatus.metadata
2349
+ }).from(offerStatus).where(eq(offerStatus.offerHash, offers.hash)).orderBy(desc(offerStatus.createdAt)).limit(1).as("latest_status");
2350
+ const sumConsumed = db.select({
2351
+ consumed: sql`COALESCE(SUM(${consumed.consumed}), 0)`.as("consumed")
2352
+ }).from(consumed).where(
2353
+ and(
2354
+ eq(consumed.offering, offers.offering),
2355
+ eq(consumed.nonce, offers.nonce),
2356
+ eq(consumed.chainId, offers.chainId)
1299
2357
  )
1300
- ));
1301
- maturity && (offers = offers.filter((o) => o.maturity === maturity));
1302
- minMaturity && (offers = offers.filter((o) => o.maturity >= minMaturity));
1303
- maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
1304
- loanToken && (offers = offers.filter((o) => o.loanToken.toLowerCase() === loanToken.toLowerCase()));
1305
- creator && (offers = offers.filter((o) => o.offering.toLowerCase() === creator.toLowerCase()));
1306
- offers = offers.filter((o) => ["valid"].includes(o.status));
1307
- const byGroup = /* @__PURE__ */ new Map();
1308
- for (const offer of offers) {
1309
- const groupKey = `${offer.chainId}-${offer.offering.toLowerCase()}-${offer.nonce}-${offer.buy}`;
1310
- const current = byGroup.get(groupKey);
1311
- if (!current) {
1312
- byGroup.set(groupKey, offer);
1313
- continue;
2358
+ ).as("sum_consumed");
2359
+ const statusCondition = eq(latestStatus.status, "valid");
2360
+ const bestOffers = db.selectDistinctOn(
2361
+ // group key
2362
+ [offers.offering, offers.nonce, offers.buy],
2363
+ {
2364
+ hash: offers.hash,
2365
+ offering: offers.offering,
2366
+ assets: offers.assets,
2367
+ consumed: sumConsumed.consumed,
2368
+ remaining: sql`${offers.assets} - COALESCE(${sumConsumed.consumed}, 0)`.as("remaining"),
2369
+ rate: offers.rate,
2370
+ maturity: offers.maturity,
2371
+ expiry: offers.expiry,
2372
+ start: offers.start,
2373
+ nonce: offers.nonce,
2374
+ buy: offers.buy,
2375
+ chainId: offers.chainId,
2376
+ loanToken: offers.loanToken,
2377
+ callbackAddress: offers.callbackAddress,
2378
+ callbackData: offers.callbackData,
2379
+ callbackGasLimit: offers.callbackGasLimit,
2380
+ signature: offers.signature,
2381
+ callbackId: offers.callbackId,
2382
+ status: latestStatus.status,
2383
+ metadata: latestStatus.metadata
1314
2384
  }
1315
- const remainingCandidate = offer.assets - offer.consumed;
1316
- const remainingCurrent = current.assets - current.consumed;
1317
- let candidateIsBetter = false;
1318
- if (offer.buy) {
1319
- if (offer.rate !== current.rate) candidateIsBetter = offer.rate < current.rate;
1320
- else if (remainingCandidate !== remainingCurrent)
1321
- candidateIsBetter = remainingCandidate > remainingCurrent;
1322
- else if (offer.maturity !== current.maturity)
1323
- candidateIsBetter = offer.maturity > current.maturity;
1324
- else candidateIsBetter = offer.hash < current.hash;
1325
- } else {
1326
- if (offer.rate !== current.rate) candidateIsBetter = offer.rate > current.rate;
1327
- else if (remainingCandidate !== remainingCurrent)
1328
- candidateIsBetter = remainingCandidate > remainingCurrent;
1329
- else if (offer.maturity !== current.maturity)
1330
- candidateIsBetter = offer.maturity > current.maturity;
1331
- else candidateIsBetter = offer.hash < current.hash;
2385
+ ).from(offers).leftJoinLateral(latestStatus, sql`true`).leftJoinLateral(sumConsumed, sql`true`).where(
2386
+ and(
2387
+ and(...baseWhereClauses),
2388
+ statusCondition,
2389
+ sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) > 0`
2390
+ )
2391
+ ).orderBy(
2392
+ offers.offering,
2393
+ offers.nonce,
2394
+ offers.buy,
2395
+ // 1 price (direction depends on side)
2396
+ sql`CASE WHEN ${offers.buy} THEN ${offers.rate} ELSE -${offers.rate} END`,
2397
+ // 2 size (remaining)
2398
+ sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) DESC`,
2399
+ // 3 term (longer maturity)
2400
+ desc(offers.maturity)
2401
+ ).as("best_offers");
2402
+ const queueLiquidity = db.select({
2403
+ callbackId: userPositions.id,
2404
+ userAmount: userPositions.amount,
2405
+ // user's per-callback cap
2406
+ queueLiquidity: sql`COALESCE(SUM(${availableLiquidityPools.amount}), 0)`.as(
2407
+ "queue_liquidity"
2408
+ )
2409
+ }).from(userPositions).leftJoin(
2410
+ availableLiquidityQueues,
2411
+ eq(userPositions.availableLiquidityQueueId, availableLiquidityQueues.queueId)
2412
+ ).leftJoin(
2413
+ availableLiquidityPools,
2414
+ eq(availableLiquidityQueues.availableLiquidityPoolId, availableLiquidityPools.id)
2415
+ ).groupBy(userPositions.id).as("queue_liquidity");
2416
+ const sortExpr = sql`CASE WHEN ${bestOffers.buy} THEN ${bestOffers.rate} ELSE -${bestOffers.rate} END`;
2417
+ const offersWithEligibility = db.select({
2418
+ hash: bestOffers.hash,
2419
+ offering: bestOffers.offering,
2420
+ assets: bestOffers.assets,
2421
+ consumed: bestOffers.consumed,
2422
+ remaining: bestOffers.remaining,
2423
+ rate: bestOffers.rate,
2424
+ maturity: bestOffers.maturity,
2425
+ expiry: bestOffers.expiry,
2426
+ start: bestOffers.start,
2427
+ nonce: bestOffers.nonce,
2428
+ buy: bestOffers.buy,
2429
+ chainId: bestOffers.chainId,
2430
+ loanToken: bestOffers.loanToken,
2431
+ callbackAddress: bestOffers.callbackAddress,
2432
+ callbackData: bestOffers.callbackData,
2433
+ callbackGasLimit: bestOffers.callbackGasLimit,
2434
+ signature: bestOffers.signature,
2435
+ callbackId: bestOffers.callbackId,
2436
+ status: bestOffers.status,
2437
+ metadata: bestOffers.metadata,
2438
+ // liquidity caps
2439
+ userAmount: sql`COALESCE(${queueLiquidity.userAmount}, 0)`.as("user_amount"),
2440
+ queueLiquidity: sql`COALESCE(${queueLiquidity.queueLiquidity}, 0)`.as(
2441
+ "queue_liquidity"
2442
+ ),
2443
+ // running total of remaining per callback, ordered by the *same* priority as selection
2444
+ cumulativeRemaining: sql`
2445
+ SUM(${bestOffers.remaining}) OVER (
2446
+ PARTITION BY ${bestOffers.callbackId}
2447
+ ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
2448
+ ROWS UNBOUNDED PRECEDING
2449
+ )
2450
+ `.as("cumulative_remaining"),
2451
+ eligible: sql`
2452
+ CASE
2453
+ WHEN ${bestOffers.buy} = false THEN true
2454
+ WHEN ${bestOffers.remaining} <= 0 THEN false
2455
+ ELSE ( SUM(${bestOffers.remaining}) OVER (
2456
+ PARTITION BY ${bestOffers.callbackId}
2457
+ ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
2458
+ ROWS UNBOUNDED PRECEDING
2459
+ )
2460
+ <= LEAST(
2461
+ COALESCE(${queueLiquidity.userAmount}, 0),
2462
+ COALESCE(${queueLiquidity.queueLiquidity}, 0)
2463
+ )
2464
+ )
2465
+ END
2466
+ `.as("eligible")
2467
+ }).from(bestOffers).leftJoin(queueLiquidity, eq(bestOffers.callbackId, queueLiquidity.callbackId)).as("offers_with_eligibility");
2468
+ const hardCap = limit * 5 + 1 + (parsedCursor ? 1 : 0);
2469
+ const validOffers = db.select({
2470
+ // pass-through all fields + a global row_number for hard cap
2471
+ hash: offersWithEligibility.hash,
2472
+ offering: offersWithEligibility.offering,
2473
+ assets: offersWithEligibility.assets,
2474
+ consumed: offersWithEligibility.consumed,
2475
+ remaining: offersWithEligibility.remaining,
2476
+ rate: offersWithEligibility.rate,
2477
+ maturity: offersWithEligibility.maturity,
2478
+ expiry: offersWithEligibility.expiry,
2479
+ start: offersWithEligibility.start,
2480
+ nonce: offersWithEligibility.nonce,
2481
+ buy: offersWithEligibility.buy,
2482
+ chainId: offersWithEligibility.chainId,
2483
+ loanToken: offersWithEligibility.loanToken,
2484
+ callbackAddress: offersWithEligibility.callbackAddress,
2485
+ callbackData: offersWithEligibility.callbackData,
2486
+ callbackGasLimit: offersWithEligibility.callbackGasLimit,
2487
+ signature: offersWithEligibility.signature,
2488
+ callbackId: offersWithEligibility.callbackId,
2489
+ status: offersWithEligibility.status,
2490
+ metadata: offersWithEligibility.metadata,
2491
+ userAmount: offersWithEligibility.userAmount,
2492
+ queueLiquidity: offersWithEligibility.queueLiquidity,
2493
+ cumulativeRemaining: offersWithEligibility.cumulativeRemaining,
2494
+ eligible: offersWithEligibility.eligible,
2495
+ // sort expression is the same again as in offersWithEligibility
2496
+ rowNumber: sql`
2497
+ ROW_NUMBER() OVER (
2498
+ ORDER BY
2499
+ CASE WHEN ${offersWithEligibility.buy} THEN ${offersWithEligibility.rate} ELSE -${offersWithEligibility.rate} END,
2500
+ ${asc(offersWithEligibility.hash)}
2501
+ )
2502
+ `.as("row_number")
2503
+ }).from(offersWithEligibility).where(sql`${offersWithEligibility.eligible} = true`).limit(hardCap).as("valid_offers");
2504
+ const cursorTuple = parsedCursor ? {
2505
+ rate: BigInt(parsedCursor.rate),
2506
+ hash: parsedCursor.hash
2507
+ } : null;
2508
+ const withCollats = await db.select({
2509
+ // base fields
2510
+ hash: sql`${validOffers.hash}`,
2511
+ offering: sql`${validOffers.offering}`,
2512
+ assets: validOffers.assets,
2513
+ consumed: validOffers.consumed,
2514
+ rate: validOffers.rate,
2515
+ maturity: validOffers.maturity,
2516
+ expiry: validOffers.expiry,
2517
+ start: validOffers.start,
2518
+ nonce: validOffers.nonce,
2519
+ buy: validOffers.buy,
2520
+ chainId: validOffers.chainId,
2521
+ loanToken: sql`${validOffers.loanToken}`,
2522
+ callbackAddress: sql`${validOffers.callbackAddress}`,
2523
+ callbackData: sql`${validOffers.callbackData}`,
2524
+ callbackGasLimit: validOffers.callbackGasLimit,
2525
+ signature: sql`${validOffers.signature}`,
2526
+ callbackId: validOffers.callbackId,
2527
+ status: sql`${validOffers.status}`,
2528
+ metadata: validOffers.metadata,
2529
+ // collateral fields
2530
+ collateralAsset: sql`${offerCollaterals.asset}`,
2531
+ collateralOracle: sql`${offerCollaterals.oracle}`,
2532
+ collateralLltv: offerCollaterals.lltv
2533
+ }).from(validOffers).leftJoin(offerCollaterals, eq(validOffers.hash, offerCollaterals.offerHash)).where(
2534
+ and(
2535
+ ...cursorTuple ? [
2536
+ sql`(${validOffers.rate}, ${validOffers.hash}) ${rateSortDirection === "asc" ? sql`>` : sql`<`} (${cursorTuple.rate}, ${cursorTuple.hash})`
2537
+ ] : []
2538
+ )
2539
+ ).orderBy(
2540
+ rateSortDirection === "asc" ? asc(validOffers.rate) : desc(validOffers.rate),
2541
+ asc(validOffers.hash)
2542
+ );
2543
+ const buildOffersMap = (rows, skipHash) => {
2544
+ const map = /* @__PURE__ */ new Map();
2545
+ for (const row of rows) {
2546
+ const entry = map.get(row.hash) ?? { base: row, collaterals: [] };
2547
+ if (row.collateralAsset && row.collateralOracle && row.collateralLltv) {
2548
+ entry.collaterals.push({
2549
+ asset: row.collateralAsset,
2550
+ oracle: row.collateralOracle,
2551
+ lltv: LLTV.from(row.collateralLltv)
2552
+ });
2553
+ }
2554
+ map.set(row.hash, entry);
1332
2555
  }
1333
- if (candidateIsBetter) byGroup.set(groupKey, offer);
1334
- }
1335
- offers = Array.from(byGroup.values());
1336
- offers = offers.sort((a, b) => sort("rate", sortOrder, a, b));
1337
- cursor && (offers = offers.filter((o) => o.hash !== cursor.hash));
2556
+ return map;
2557
+ };
2558
+ const allEntries = Array.from(buildOffersMap(withCollats).values());
2559
+ const pageEntries = allEntries.slice(0, limit);
1338
2560
  let nextCursor = null;
1339
- if (offers.length > limit) {
1340
- const last = offers[limit - 1];
2561
+ if (allEntries.length > limit) {
2562
+ const last = pageEntries[pageEntries.length - 1].base;
1341
2563
  nextCursor = encode({
1342
2564
  sort: "rate",
1343
- dir: sortOrder,
2565
+ dir: rateSortDirection,
1344
2566
  hash: last.hash,
1345
2567
  rate: last.rate.toString()
1346
2568
  });
1347
2569
  }
1348
- offers = offers.slice(0, limit);
1349
- const data = offers.map((o) => ({
2570
+ const data = pageEntries.map(({ base, collaterals: cols }) => ({
1350
2571
  ...Offer.from({
1351
- offering: o.offering,
1352
- assets: o.assets,
1353
- rate: o.rate,
1354
- maturity: Maturity.from(o.maturity),
1355
- expiry: o.expiry,
1356
- start: o.start,
1357
- nonce: o.nonce,
1358
- buy: o.buy,
1359
- chainId: o.chainId,
1360
- loanToken: o.loanToken,
1361
- collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
2572
+ offering: base.offering,
2573
+ assets: BigInt(base.assets),
2574
+ rate: base.rate,
2575
+ maturity: Maturity.from(base.maturity),
2576
+ expiry: base.expiry,
2577
+ start: base.start,
2578
+ nonce: base.nonce,
2579
+ buy: base.buy,
2580
+ chainId: base.chainId,
2581
+ loanToken: base.loanToken,
2582
+ collaterals: cols.sort(
2583
+ (a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())
2584
+ ),
1362
2585
  callback: {
1363
- address: o.callback.address,
1364
- data: o.callback.data,
1365
- gasLimit: o.callback.gasLimit
2586
+ address: base.callbackAddress,
2587
+ data: base.callbackData,
2588
+ gasLimit: base.callbackGasLimit
1366
2589
  },
1367
- ...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
2590
+ ...base.signature !== null ? { signature: base.signature } : void 0
1368
2591
  }),
1369
- consumed: o.consumed,
1370
- status: o.status,
1371
- ...o.metadata ? { metadata: o.metadata } : {}
2592
+ consumed: BigInt(base.consumed),
2593
+ status: base.status,
2594
+ ...base.metadata ? { metadata: base.metadata } : {}
1372
2595
  }));
1373
- return {
1374
- offers: data,
1375
- nextCursor
1376
- };
2596
+ return { offers: data, nextCursor };
1377
2597
  },
1378
2598
  delete: async (hash) => {
1379
- if (!map.has(hash.toLowerCase())) return false;
1380
- map.delete(hash.toLowerCase());
1381
- return true;
2599
+ const result = await db.delete(offers).where(eq(offers.hash, hash.toLowerCase()));
2600
+ return result.affectedRows > 0;
1382
2601
  },
1383
2602
  deleteMany: async (hashes) => {
1384
- let deleted = 0;
1385
- for (const hash of hashes) {
1386
- if (map.has(hash.toLowerCase())) {
1387
- map.delete(hash.toLowerCase());
1388
- deleted++;
1389
- }
2603
+ if (hashes.length === 0) {
2604
+ return 0;
1390
2605
  }
1391
- return deleted;
2606
+ return await db.transaction(async (tx) => {
2607
+ const normalizedHashes = hashes.map((hash) => hash.toLowerCase());
2608
+ const result = await tx.delete(offers).where(
2609
+ sql`${offers.hash} = ANY(${sql.raw(`ARRAY[${normalizedHashes.map((h) => `'${h}'`).join(", ")}]`)})`
2610
+ );
2611
+ return result.affectedRows;
2612
+ });
1392
2613
  },
1393
- updateStatus: async (parameters2) => {
1394
- const key = parameters2.offerHash.toLowerCase();
1395
- const existing = map.get(key);
1396
- if (!existing) return;
1397
- if (existing.status === parameters2.status) return;
1398
- map.set(key, {
1399
- ...existing,
1400
- status: parameters2.status,
1401
- metadata: parameters2.metadata
2614
+ updateStatus: async (parameters) => {
2615
+ const latest = await db.select({ status: offerStatus.status }).from(offerStatus).where(eq(offerStatus.offerHash, parameters.offerHash.toLowerCase())).orderBy(desc(offerStatus.createdAt)).limit(1);
2616
+ if (latest.length > 0 && latest[0].status === parameters.status) return;
2617
+ await db.insert(offerStatus).values({
2618
+ offerHash: parameters.offerHash.toLowerCase(),
2619
+ status: parameters.status,
2620
+ metadata: parameters.metadata
1402
2621
  });
1403
2622
  },
1404
- updateConsumedAmount: async (parameters2) => {
1405
- if (consumedIds.has(parameters2.id)) return;
1406
- consumedIds.add(parameters2.id);
1407
- const chainId = parameters2.chainId;
1408
- const address = parameters2.offering.toLowerCase();
1409
- const nonce = parameters2.nonce;
1410
- const filledForChain = filled.get(chainId) || /* @__PURE__ */ new Map();
1411
- const filledForOffering = filledForChain.get(address) || /* @__PURE__ */ new Map();
1412
- const current = filledForOffering.get(nonce) || 0n;
1413
- filledForOffering.set(nonce, current + parameters2.consumed);
1414
- filledForChain.set(address, filledForOffering);
1415
- filled.set(chainId, filledForChain);
2623
+ updateConsumedAmount: async (parameters) => {
2624
+ await db.insert(consumed).values({
2625
+ id: parameters.id,
2626
+ chainId: parameters.chainId,
2627
+ offering: parameters.offering.toLowerCase(),
2628
+ nonce: parameters.nonce,
2629
+ consumed: parameters.consumed.toString()
2630
+ }).onConflictDoNothing();
1416
2631
  }
1417
2632
  };
1418
2633
  }
1419
2634
 
1420
- // src/core/router/Server.ts
1421
- async function serve(parameters) {
1422
- const app = new Hono();
1423
- const store = parameters.store ? parameters.store : memory({
1424
- offers: /* @__PURE__ */ new Map(),
1425
- filled: /* @__PURE__ */ new Map()
1426
- });
1427
- app.get("/v1/offers", async (c) => {
1428
- try {
1429
- const params = parse("get_offers", c.req.query());
1430
- const offers = await store.getAll({
1431
- query: {
1432
- creators: params.creators,
1433
- side: params.side,
1434
- chains: params.chains,
1435
- loanTokens: params.loan_tokens,
1436
- status: params.status,
1437
- callbackAddresses: params.callback_addresses,
1438
- minAmount: params.min_amount,
1439
- maxAmount: params.max_amount,
1440
- minRate: params.min_rate,
1441
- maxRate: params.max_rate,
1442
- minMaturity: params.min_maturity,
1443
- maxMaturity: params.max_maturity,
1444
- minExpiry: params.min_expiry,
1445
- maxExpiry: params.max_expiry,
1446
- collateralAssets: params.collateral_assets,
1447
- collateralOracles: params.collateral_oracles,
1448
- collateralTuple: params.collateral_tuple,
1449
- minLltv: params.min_lltv,
1450
- maxLltv: params.max_lltv,
1451
- sortBy: params.sort_by,
1452
- sortOrder: params.sort_order,
1453
- cursor: params.cursor,
1454
- limit: params.limit
1455
- }
1456
- });
1457
- return success(c, {
1458
- data: offers.offers.map(
1459
- (offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
1460
- ),
1461
- cursor: offers.nextCursor ?? null
1462
- });
1463
- } catch (err) {
1464
- console.error(err);
1465
- return error(err, c);
1466
- }
1467
- });
1468
- app.get("/v1/offers/match", async (c) => {
1469
- try {
1470
- const params = parse("match_offers", c.req.query());
1471
- const offers = await store.findMatchingOffers({
1472
- side: params.side,
1473
- chainId: params.chain_id,
1474
- rate: params.rate,
1475
- collaterals: params.collaterals,
1476
- maturity: params.maturity,
1477
- minMaturity: params.min_maturity,
1478
- maxMaturity: params.max_maturity,
1479
- loanToken: params.loan_token,
1480
- creator: params.creator,
1481
- cursor: params.cursor,
1482
- limit: params.limit
1483
- });
1484
- return success(c, {
1485
- data: offers.offers.map(
1486
- (offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
1487
- ),
1488
- cursor: offers.nextCursor ?? null
1489
- });
1490
- } catch (err) {
1491
- console.error(err);
1492
- return error(err, c);
1493
- }
1494
- });
1495
- serve$1(
1496
- {
1497
- fetch: app.fetch,
1498
- port: parameters.port
1499
- },
1500
- (info) => {
1501
- console.log(`Started local router @ http://localhost:${info.port}`);
1502
- }
1503
- );
2635
+ // src/core/OfferStore/PG.ts
2636
+ var PG_exports = {};
2637
+ __export(PG_exports, {
2638
+ applyMigrations: () => applyMigrations,
2639
+ clean: () => clean,
2640
+ connect: () => connect
2641
+ });
2642
+ function connect(parameters) {
2643
+ if (parameters.type === "pg") {
2644
+ const pool2 = new Pool({ connectionString: parameters.endpoint });
2645
+ const client2 = drizzle(pool2, { schema: schema_exports });
2646
+ return Object.assign(client2, { name: "pg", pool: pool2 });
2647
+ }
2648
+ const pool = new PGlite();
2649
+ const client = drizzle$1(pool, { schema: schema_exports });
2650
+ return Object.assign(client, { name: "pglite", pool });
1504
2651
  }
1505
- function error(error2, c) {
1506
- if (error2 instanceof APIError) {
1507
- return handleAPIError(error2, c);
2652
+ async function applyMigrations(pg) {
2653
+ const migrationsFolder = process.env.AWS_LAMBDA_FUNCTION_NAME ? path.join(process.cwd(), "drizzle", VERSION) : path.join(__dirname, "drizzle", VERSION);
2654
+ await pg.execute(`create schema if not exists "${VERSION}"`);
2655
+ if (pg.name === "pg") {
2656
+ await migrate(pg, { migrationsFolder });
2657
+ return;
2658
+ }
2659
+ await migrate$1(pg, { migrationsFolder });
2660
+ }
2661
+ async function clean(pg) {
2662
+ await pg.execute(`drop schema if exists "${VERSION}" cascade`);
2663
+ await pg.execute(`create schema "${VERSION}"`);
2664
+ await pg.execute("drop schema if exists drizzle cascade");
2665
+ }
2666
+
2667
+ // src/core/router/index.ts
2668
+ var router_exports = {};
2669
+ __export(router_exports, {
2670
+ APIError: () => APIError,
2671
+ BadRequestError: () => BadRequestError,
2672
+ HttpForbiddenError: () => HttpForbiddenError,
2673
+ HttpGetOffersFailedError: () => HttpGetOffersFailedError,
2674
+ HttpRateLimitError: () => HttpRateLimitError,
2675
+ HttpUnauthorizedError: () => HttpUnauthorizedError,
2676
+ InternalServerError: () => InternalServerError,
2677
+ InvalidUrlError: () => InvalidUrlError,
2678
+ NotFoundError: () => NotFoundError,
2679
+ ValidationError: () => ValidationError,
2680
+ connect: () => connect2,
2681
+ error: () => error,
2682
+ get: () => get,
2683
+ handleZodError: () => handleZodError,
2684
+ match: () => match,
2685
+ serve: () => serve,
2686
+ success: () => success
2687
+ });
2688
+ function connect2(opts) {
2689
+ const u = new URL(opts?.url || "https://router.morpho.dev");
2690
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
2691
+ throw new InvalidUrlError(u.toString());
1508
2692
  }
1509
- if (error2 instanceof SyntaxError) {
1510
- return handleAPIError(new BadRequestError(error2.message), c);
2693
+ const headers = opts?.headers ?? new Headers();
2694
+ headers.set("Content-Type", "application/json");
2695
+ opts?.apiKey !== void 0 ? headers.set("X-API-Key", opts.apiKey) : null;
2696
+ const config = {
2697
+ url: u,
2698
+ headers
2699
+ };
2700
+ return {
2701
+ ...config,
2702
+ get: (parameters) => get(config, parameters),
2703
+ match: (parameters) => match(config, parameters)
2704
+ };
2705
+ }
2706
+ async function get(config, parameters) {
2707
+ const url = new URL(`${config.url.toString()}v1/offers`);
2708
+ if (parameters.creators?.length) {
2709
+ url.searchParams.set("creators", parameters.creators.join(","));
1511
2710
  }
1512
- if (error2 instanceof z.ZodError) {
1513
- return handleAPIError(handleZodError(error2), c);
2711
+ if (parameters.side) {
2712
+ url.searchParams.set("side", parameters.side);
1514
2713
  }
1515
- return handleAPIError(new InternalServerError(), c);
1516
- }
1517
- function success(c, {
1518
- data,
1519
- cursor
1520
- }) {
1521
- return c.json({
1522
- status: "success",
1523
- cursor,
1524
- data,
1525
- meta: {
1526
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1527
- }
1528
- });
1529
- }
1530
- var APIError = class extends Error {
1531
- constructor(statusCode, message, code, details) {
1532
- super(message);
1533
- this.statusCode = statusCode;
1534
- this.code = code;
1535
- this.details = details;
1536
- this.name = "APIError";
2714
+ if (parameters.chains?.length) {
2715
+ url.searchParams.set("chains", parameters.chains.join(","));
1537
2716
  }
1538
- };
1539
- var ValidationError = class extends APIError {
1540
- constructor(message, details) {
1541
- super(400, message, "VALIDATION_ERROR", details);
2717
+ if (parameters.loanTokens?.length) {
2718
+ url.searchParams.set("loan_tokens", parameters.loanTokens.join(","));
1542
2719
  }
1543
- };
1544
- var NotFoundError = class extends APIError {
1545
- constructor(message) {
1546
- super(404, message, "NOT_FOUND");
2720
+ if (parameters.status?.length) {
2721
+ url.searchParams.set("status", parameters.status.join(","));
1547
2722
  }
1548
- };
1549
- var InternalServerError = class extends APIError {
1550
- constructor(message = "Internal server error") {
1551
- super(500, message, "INTERNAL_SERVER_ERROR");
2723
+ if (parameters.callbackAddresses?.length) {
2724
+ url.searchParams.set("callback_addresses", parameters.callbackAddresses.join(","));
1552
2725
  }
1553
- };
1554
- var BadRequestError = class extends APIError {
1555
- constructor(message = "Invalid JSON format", details) {
1556
- super(400, message, "BAD_REQUEST", details);
2726
+ if (parameters.minAmount !== void 0) {
2727
+ url.searchParams.set("min_amount", parameters.minAmount.toString());
1557
2728
  }
1558
- };
1559
- function handleZodError(error2) {
1560
- const formattedErrors = error2.issues.map((err) => {
1561
- const field = err.path.join(".");
1562
- let issue = err.message;
1563
- if (err.code === "invalid_type") {
1564
- if (err.message.includes("received undefined")) {
1565
- issue = `${field} is required`;
1566
- } else {
1567
- issue = err.message;
2729
+ if (parameters.maxAmount !== void 0) {
2730
+ url.searchParams.set("max_amount", parameters.maxAmount.toString());
2731
+ }
2732
+ if (parameters.minRate !== void 0) {
2733
+ url.searchParams.set("min_rate", parameters.minRate.toString());
2734
+ }
2735
+ if (parameters.maxRate !== void 0) {
2736
+ url.searchParams.set("max_rate", parameters.maxRate.toString());
2737
+ }
2738
+ if (parameters.minMaturity !== void 0) {
2739
+ url.searchParams.set("min_maturity", parameters.minMaturity.toString());
2740
+ }
2741
+ if (parameters.maxMaturity !== void 0) {
2742
+ url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
2743
+ }
2744
+ if (parameters.minExpiry !== void 0) {
2745
+ url.searchParams.set("min_expiry", parameters.minExpiry.toString());
2746
+ }
2747
+ if (parameters.maxExpiry !== void 0) {
2748
+ url.searchParams.set("max_expiry", parameters.maxExpiry.toString());
2749
+ }
2750
+ if (parameters.collateralAssets?.length) {
2751
+ url.searchParams.set("collateral_assets", parameters.collateralAssets.join(","));
2752
+ }
2753
+ if (parameters.collateralOracles?.length) {
2754
+ url.searchParams.set("collateral_oracles", parameters.collateralOracles.join(","));
2755
+ }
2756
+ if (parameters.collateralTuple?.length) {
2757
+ const tupleStr = parameters.collateralTuple.map(({ asset, oracle, lltv }) => {
2758
+ let result = asset;
2759
+ if (oracle) {
2760
+ result += `:${oracle}`;
2761
+ } else if (lltv !== void 0) {
2762
+ result += `:`;
1568
2763
  }
1569
- } else if (err.code === "invalid_format") {
1570
- issue = `${field} has an invalid format`;
1571
- }
1572
- return {
1573
- field,
1574
- issue
1575
- };
1576
- });
1577
- return new ValidationError("Validation failed", formattedErrors);
2764
+ if (lltv !== void 0) result += `:${formatUnits(lltv, 16)}`;
2765
+ return result;
2766
+ }).join("#");
2767
+ url.searchParams.set("collateral_tuple", tupleStr);
2768
+ }
2769
+ if (parameters.minLltv !== void 0) {
2770
+ url.searchParams.set("min_lltv", formatUnits(parameters.minLltv, 16));
2771
+ }
2772
+ if (parameters.maxLltv !== void 0) {
2773
+ url.searchParams.set("max_lltv", formatUnits(parameters.maxLltv, 16));
2774
+ }
2775
+ if (parameters.sortBy) {
2776
+ url.searchParams.set("sort_by", parameters.sortBy);
2777
+ }
2778
+ if (parameters.sortOrder) {
2779
+ url.searchParams.set("sort_order", parameters.sortOrder);
2780
+ }
2781
+ if (parameters.cursor) {
2782
+ url.searchParams.set("cursor", parameters.cursor);
2783
+ }
2784
+ if (parameters.limit !== void 0) {
2785
+ url.searchParams.set("limit", parameters.limit.toString());
2786
+ }
2787
+ const { cursor: returnedCursor, data: offers2 } = await getApi(config, url);
2788
+ const routerOffers = offers2.map(Format.fromSnakeCase).map(fromResponse);
2789
+ return {
2790
+ cursor: returnedCursor,
2791
+ offers: routerOffers.map(from).map(toResponse)
2792
+ };
1578
2793
  }
1579
- function handleAPIError(error2, c) {
1580
- return c.json({
1581
- statusCode: error2.statusCode,
1582
- body: JSON.stringify({
1583
- status: "error",
1584
- error: {
1585
- code: error2.code,
1586
- message: error2.message,
1587
- ...error2.details && typeof error2.details === "object" ? { details: error2.details } : {}
1588
- },
1589
- meta: {
1590
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1591
- }
1592
- })
1593
- });
2794
+ async function match(config, parameters) {
2795
+ const url = new URL(`${config.url.toString()}v1/offers/match`);
2796
+ url.searchParams.set("side", parameters.side);
2797
+ url.searchParams.set("chain_id", parameters.chainId.toString());
2798
+ if (parameters.rate !== void 0) {
2799
+ url.searchParams.set("rate", parameters.rate.toString());
2800
+ }
2801
+ if (parameters.collaterals?.length) {
2802
+ const collateralsStr = parameters.collaterals.map(({ asset, oracle, lltv }) => `${asset}:${oracle}:${formatUnits(lltv, 16)}`).join("#");
2803
+ url.searchParams.set("collaterals", collateralsStr);
2804
+ }
2805
+ if (parameters.maturity !== void 0) {
2806
+ url.searchParams.set("maturity", parameters.maturity.toString());
2807
+ }
2808
+ if (parameters.minMaturity !== void 0) {
2809
+ url.searchParams.set("min_maturity", parameters.minMaturity.toString());
2810
+ }
2811
+ if (parameters.maxMaturity !== void 0) {
2812
+ url.searchParams.set("max_maturity", parameters.maxMaturity.toString());
2813
+ }
2814
+ if (parameters.loanToken) {
2815
+ url.searchParams.set("loan_token", parameters.loanToken);
2816
+ }
2817
+ if (parameters.creator) {
2818
+ url.searchParams.set("creator", parameters.creator);
2819
+ }
2820
+ if (parameters.status?.length) {
2821
+ url.searchParams.set("status", parameters.status.join(","));
2822
+ }
2823
+ if (parameters.cursor) {
2824
+ url.searchParams.set("cursor", parameters.cursor);
2825
+ }
2826
+ if (parameters.limit !== void 0) {
2827
+ url.searchParams.set("limit", parameters.limit.toString());
2828
+ }
2829
+ const { cursor: returnedCursor, data: offers2 } = await getApi(config, url);
2830
+ const routerOffers = offers2.map(Format.fromSnakeCase).map(fromResponse);
2831
+ return {
2832
+ cursor: returnedCursor,
2833
+ offers: routerOffers.map(from).map(toResponse)
2834
+ };
1594
2835
  }
1595
-
1596
- // src/Liquidity.ts
1597
- var Liquidity_exports = {};
1598
- __export(Liquidity_exports, {
1599
- fetch: () => fetch2,
1600
- fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
1601
- serialize: () => serialize
1602
- });
1603
- async function fetchBalancesAndAllowances(parameters) {
1604
- const { client, spender, pairs, options } = parameters;
1605
- if (pairs.length === 0) return /* @__PURE__ */ new Map();
1606
- const batchSize = Math.max(1, options?.batchSize ?? 5e3);
1607
- const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
1608
- const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
1609
- const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
1610
- const out = /* @__PURE__ */ new Map();
1611
- for (const pairsBatch of Utils.batch(pairs, batchSize)) {
1612
- const balanceContracts = [];
1613
- const allowanceContracts = [];
1614
- for (const { user, token } of pairsBatch) {
1615
- balanceContracts.push({
1616
- address: token,
1617
- abi: erc20Abi,
1618
- functionName: "balanceOf",
1619
- args: [user]
1620
- });
1621
- allowanceContracts.push({
1622
- address: token,
1623
- abi: erc20Abi,
1624
- functionName: "allowance",
1625
- args: [user, spender]
2836
+ async function getApi(config, url) {
2837
+ const pathname = url.pathname;
2838
+ let action;
2839
+ switch (true) {
2840
+ case pathname.includes("/v1/offers/match"):
2841
+ action = "match_offers";
2842
+ break;
2843
+ case pathname.includes("/v1/offers"):
2844
+ action = "get_offers";
2845
+ break;
2846
+ default:
2847
+ throw new HttpGetOffersFailedError("Unknown endpoint", {
2848
+ details: `Unsupported path: ${pathname}`
1626
2849
  });
1627
- }
1628
- const [balances, allowances] = await Promise.all([
1629
- Utils.retry(
1630
- () => client.multicall({
1631
- allowFailure: false,
1632
- contracts: balanceContracts,
1633
- ...blockNumber ? { blockNumber } : {}
1634
- }),
1635
- retryAttempts,
1636
- retryDelayMs
1637
- ),
1638
- Utils.retry(
1639
- () => client.multicall({
1640
- allowFailure: false,
1641
- contracts: allowanceContracts,
1642
- ...blockNumber ? { blockNumber } : {}
1643
- }),
1644
- retryAttempts,
1645
- retryDelayMs
1646
- )
1647
- ]);
1648
- for (let i = 0; i < pairsBatch.length; i++) {
1649
- const { user, token } = pairsBatch[i];
1650
- const balance = balances[i];
1651
- const allowance = allowances[i];
1652
- let perUser = out.get(user);
1653
- if (!perUser) {
1654
- perUser = /* @__PURE__ */ new Map();
1655
- out.set(user, perUser);
1656
- }
1657
- perUser.set(token, { balance, allowance });
1658
- }
1659
2850
  }
1660
- return out;
1661
- }
1662
- async function fetch2(parameters) {
1663
- const { client, chainId, spender, type, pairs, options } = parameters;
1664
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
1665
- throw new Error(`CallbackType not implemented: ${type}`);
1666
- const map = await fetchBalancesAndAllowances({
1667
- client,
1668
- spender,
1669
- pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
1670
- options
2851
+ const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
2852
+ if (!schemaParseResult.success) {
2853
+ throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
2854
+ details: schemaParseResult.error.issues[0]?.message
2855
+ });
2856
+ }
2857
+ const response = await fetch(url.toString(), {
2858
+ method: "GET",
2859
+ headers: config.headers
1671
2860
  });
1672
- const out = [];
1673
- for (const [user, perContract] of map) {
1674
- for (const [contract, { balance, allowance }] of perContract) {
1675
- const amount = balance < allowance ? balance : allowance;
1676
- out.push(
1677
- buildLiquidity({
1678
- type,
1679
- user,
1680
- contract,
1681
- chainId,
1682
- amount: amount.toString(),
1683
- index: 0
1684
- })
1685
- );
2861
+ if (!response.ok) {
2862
+ switch (response.status) {
2863
+ case 401:
2864
+ throw new HttpUnauthorizedError();
2865
+ case 403:
2866
+ throw new HttpForbiddenError();
2867
+ case 429:
2868
+ throw new HttpRateLimitError();
1686
2869
  }
2870
+ throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
2871
+ details: await response.text()
2872
+ });
1687
2873
  }
1688
- return out;
1689
- }
1690
- function serialize(liquidity) {
1691
- const normalized = {
1692
- userPosition: {
1693
- id: liquidity.userPosition.id,
1694
- availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
1695
- user: liquidity.userPosition.user,
1696
- chainId: String(liquidity.userPosition.chainId),
1697
- amount: String(liquidity.userPosition.amount)
1698
- },
1699
- queues: liquidity.queues.map((queueWithPool) => ({
1700
- queue: {
1701
- queueId: queueWithPool.queue.queueId,
1702
- availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
1703
- index: queueWithPool.queue.index
1704
- },
1705
- pool: {
1706
- id: queueWithPool.pool.id,
1707
- amount: String(queueWithPool.pool.amount)
1708
- }
1709
- })).sort(
1710
- (left, right) => {
1711
- const leftQueueId = left.queue.queueId || "";
1712
- const rightQueueId = right.queue.queueId || "";
1713
- if (leftQueueId < rightQueueId) return -1;
1714
- if (leftQueueId > rightQueueId) return 1;
1715
- const leftPoolId = left.pool.id;
1716
- const rightPoolId = right.pool.id;
1717
- if (leftPoolId < rightPoolId) return -1;
1718
- if (leftPoolId > rightPoolId) return 1;
1719
- const leftIndex = left.queue.index;
1720
- const rightIndex = right.queue.index;
1721
- if (leftIndex < rightIndex) return -1;
1722
- if (leftIndex > rightIndex) return 1;
1723
- return 0;
1724
- }
1725
- )
1726
- };
1727
- return JSON.stringify(normalized);
2874
+ return response.json();
1728
2875
  }
1729
-
1730
- // src/Logger.ts
1731
- var Logger_exports = {};
1732
- __export(Logger_exports, {
1733
- LogLevelValues: () => LogLevelValues,
1734
- defaultLogger: () => defaultLogger,
1735
- getLogger: () => getLogger,
1736
- runWithLogger: () => runWithLogger,
1737
- silentLogger: () => silentLogger
1738
- });
1739
- var LogLevelValues = [
1740
- "silent",
1741
- "trace",
1742
- "debug",
1743
- "info",
1744
- "warn",
1745
- "error",
1746
- "fatal"
1747
- ];
1748
- function defaultLogger(minLevel) {
1749
- const threshold = minLevel ?? "trace";
1750
- const levelIndexByName = LogLevelValues.reduce(
1751
- (acc, lvl, idx) => {
1752
- acc[lvl] = idx;
1753
- return acc;
1754
- },
1755
- {}
1756
- );
1757
- const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
1758
- return {
1759
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1760
- trace: isEnabled("trace") ? console.trace.bind(console) : () => {
1761
- },
1762
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1763
- debug: isEnabled("debug") ? console.debug.bind(console) : () => {
1764
- },
1765
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1766
- info: isEnabled("info") ? console.info.bind(console) : () => {
1767
- },
1768
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1769
- warn: isEnabled("warn") ? console.warn.bind(console) : () => {
1770
- },
1771
- // biome-ignore lint/suspicious/noConsole: console is used for logging
1772
- error: isEnabled("error") ? console.error.bind(console) : () => {
1773
- },
1774
- fatal: isEnabled("fatal") ? (...args) => console.error("[fatal]", ...args) : () => {
2876
+ var InvalidUrlError = class extends Errors.BaseError {
2877
+ name = "Router.InvalidUrlError";
2878
+ constructor(url) {
2879
+ super(`URL "${url}" is not http/https.`);
2880
+ }
2881
+ };
2882
+ var HttpUnauthorizedError = class extends Errors.BaseError {
2883
+ name = "Router.HttpUnauthorizedError";
2884
+ constructor() {
2885
+ super("Unauthorized.", {
2886
+ metaMessages: ["Ensure that an API key is provided."]
2887
+ });
2888
+ }
2889
+ };
2890
+ var HttpForbiddenError = class extends Errors.BaseError {
2891
+ name = "Router.HttpForbiddenError";
2892
+ constructor() {
2893
+ super("Forbidden.", {
2894
+ metaMessages: ["Ensure that the API key is valid."]
2895
+ });
2896
+ }
2897
+ };
2898
+ var HttpRateLimitError = class extends Errors.BaseError {
2899
+ name = "Router.HttpRateLimitError";
2900
+ constructor() {
2901
+ super("Rate limit exceeded.", {
2902
+ metaMessages: [
2903
+ "The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
2904
+ ]
2905
+ });
2906
+ }
2907
+ };
2908
+ var HttpGetOffersFailedError = class extends Errors.BaseError {
2909
+ name = "Router.HttpGetOffersFailedError";
2910
+ constructor(message, { details } = {}) {
2911
+ super(message, {
2912
+ metaMessages: [details]
2913
+ });
2914
+ }
2915
+ };
2916
+ async function serve(parameters) {
2917
+ const app = new Hono();
2918
+ const store = parameters.store;
2919
+ const logger = getLogger();
2920
+ app.get("/v1/offers", async (c) => {
2921
+ try {
2922
+ const params = parse("get_offers", c.req.query());
2923
+ const offers2 = await store.getAll({
2924
+ query: {
2925
+ creators: params.creators,
2926
+ side: params.side,
2927
+ chains: params.chains,
2928
+ loanTokens: params.loan_tokens,
2929
+ status: params.status,
2930
+ callbackAddresses: params.callback_addresses,
2931
+ minAmount: params.min_amount,
2932
+ maxAmount: params.max_amount,
2933
+ minRate: params.min_rate,
2934
+ maxRate: params.max_rate,
2935
+ minMaturity: params.min_maturity,
2936
+ maxMaturity: params.max_maturity,
2937
+ minExpiry: params.min_expiry,
2938
+ maxExpiry: params.max_expiry,
2939
+ collateralAssets: params.collateral_assets,
2940
+ collateralOracles: params.collateral_oracles,
2941
+ collateralTuple: params.collateral_tuple,
2942
+ minLltv: params.min_lltv,
2943
+ maxLltv: params.max_lltv,
2944
+ sortBy: params.sort_by,
2945
+ sortOrder: params.sort_order,
2946
+ cursor: params.cursor,
2947
+ limit: params.limit
2948
+ }
2949
+ });
2950
+ return success(c, {
2951
+ data: offers2.offers.map(
2952
+ (offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
2953
+ ),
2954
+ cursor: offers2.nextCursor ?? null
2955
+ });
2956
+ } catch (err) {
2957
+ console.error(err);
2958
+ return error(err, c);
1775
2959
  }
1776
- };
1777
- }
1778
- function silentLogger() {
1779
- return {
1780
- trace: () => {
1781
- },
1782
- debug: () => {
1783
- },
1784
- info: () => {
1785
- },
1786
- warn: () => {
1787
- },
1788
- error: () => {
2960
+ });
2961
+ app.get("/v1/offers/match", async (c) => {
2962
+ try {
2963
+ const params = parse("match_offers", c.req.query());
2964
+ const offers2 = await store.findMatchingOffers({
2965
+ side: params.side,
2966
+ chainId: params.chain_id,
2967
+ rate: params.rate,
2968
+ collaterals: params.collaterals,
2969
+ maturity: params.maturity,
2970
+ minMaturity: params.min_maturity,
2971
+ maxMaturity: params.max_maturity,
2972
+ loanToken: params.loan_token,
2973
+ creator: params.creator,
2974
+ cursor: params.cursor,
2975
+ limit: params.limit
2976
+ });
2977
+ return success(c, {
2978
+ data: offers2.offers.map(
2979
+ (offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
2980
+ ),
2981
+ cursor: offers2.nextCursor ?? null
2982
+ });
2983
+ } catch (err) {
2984
+ console.error(err);
2985
+ return error(err, c);
2986
+ }
2987
+ });
2988
+ serve$1(
2989
+ {
2990
+ fetch: app.fetch,
2991
+ port: parameters.port
1789
2992
  },
1790
- fatal: () => {
2993
+ (info) => {
2994
+ logger.info({ service: "api", msg: "Router server started", port: info.port });
1791
2995
  }
1792
- };
2996
+ );
1793
2997
  }
1794
- var loggerContext = new AsyncLocalStorage();
1795
- function runWithLogger(logger, fn) {
1796
- return loggerContext.run(logger, fn);
2998
+ function error(error2, c) {
2999
+ if (error2 instanceof APIError) {
3000
+ return handleAPIError(error2, c);
3001
+ }
3002
+ if (error2 instanceof SyntaxError) {
3003
+ return handleAPIError(new BadRequestError(error2.message), c);
3004
+ }
3005
+ if (error2 instanceof z.ZodError) {
3006
+ return handleAPIError(handleZodError(error2), c);
3007
+ }
3008
+ return handleAPIError(new InternalServerError(), c);
1797
3009
  }
1798
- function getLogger() {
1799
- return loggerContext.getStore() ?? defaultLogger();
3010
+ function success(c, {
3011
+ data,
3012
+ cursor
3013
+ }) {
3014
+ return c.json({
3015
+ status: "success",
3016
+ cursor,
3017
+ data,
3018
+ meta: {
3019
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3020
+ }
3021
+ });
1800
3022
  }
1801
-
1802
- // src/Validation.ts
1803
- var Validation_exports = {};
1804
- __export(Validation_exports, {
1805
- run: () => run
1806
- });
1807
- async function run(parameters) {
1808
- const { items, rules, ctx = {}, chunkSize } = parameters;
1809
- const issues = [];
1810
- let validItems = items.slice();
1811
- for (const rule of rules) {
1812
- if (validItems.length === 0) return { valid: [], issues };
1813
- const indicesToRemove = /* @__PURE__ */ new Set();
1814
- if (rule.kind === "single") {
1815
- for (let i = 0; i < validItems.length; i++) {
1816
- const item = validItems[i];
1817
- const issue = await rule.run(item, ctx);
1818
- if (issue) {
1819
- issues.push({ ...issue, ruleName: rule.name, item });
1820
- indicesToRemove.add(i);
1821
- }
1822
- }
1823
- } else if (rule.kind === "batch") {
1824
- const exec = async (slice, offset) => {
1825
- const map = await rule.run(slice, ctx);
1826
- for (let i = 0; i < slice.length; i++) {
1827
- const issue = map.get(i);
1828
- if (issue !== void 0) {
1829
- issues.push({ ...issue, ruleName: rule.name, item: slice[i] });
1830
- indicesToRemove.add(offset + i);
1831
- }
1832
- }
1833
- };
1834
- if (!chunkSize) await exec(validItems, 0);
1835
- else {
1836
- for (let i = 0; i < validItems.length; i += chunkSize) {
1837
- await exec(validItems.slice(i, i + chunkSize), i);
1838
- }
3023
+ var APIError = class extends Error {
3024
+ constructor(statusCode, message, code, details) {
3025
+ super(message);
3026
+ this.statusCode = statusCode;
3027
+ this.code = code;
3028
+ this.details = details;
3029
+ this.name = "APIError";
3030
+ }
3031
+ };
3032
+ var ValidationError = class extends APIError {
3033
+ constructor(message, details) {
3034
+ super(400, message, "VALIDATION_ERROR", details);
3035
+ }
3036
+ };
3037
+ var NotFoundError = class extends APIError {
3038
+ constructor(message) {
3039
+ super(404, message, "NOT_FOUND");
3040
+ }
3041
+ };
3042
+ var InternalServerError = class extends APIError {
3043
+ constructor(message = "Internal server error") {
3044
+ super(500, message, "INTERNAL_SERVER_ERROR");
3045
+ }
3046
+ };
3047
+ var BadRequestError = class extends APIError {
3048
+ constructor(message = "Invalid JSON format", details) {
3049
+ super(400, message, "BAD_REQUEST", details);
3050
+ }
3051
+ };
3052
+ function handleZodError(error2) {
3053
+ const formattedErrors = error2.issues.map((err) => {
3054
+ const field = err.path.join(".");
3055
+ let issue = err.message;
3056
+ if (err.code === "invalid_type") {
3057
+ if (err.message.includes("received undefined")) {
3058
+ issue = `${field} is required`;
3059
+ } else {
3060
+ issue = err.message;
1839
3061
  }
3062
+ } else if (err.code === "invalid_format") {
3063
+ issue = `${field} has an invalid format`;
1840
3064
  }
1841
- validItems = validItems.filter((_, i) => !indicesToRemove.has(i));
1842
- }
1843
- return {
1844
- valid: validItems,
1845
- issues
1846
- };
3065
+ return {
3066
+ field,
3067
+ issue
3068
+ };
3069
+ });
3070
+ return new ValidationError("Validation failed", formattedErrors);
3071
+ }
3072
+ function handleAPIError(error2, c) {
3073
+ return c.json({
3074
+ statusCode: error2.statusCode,
3075
+ body: JSON.stringify({
3076
+ status: "error",
3077
+ error: {
3078
+ code: error2.code,
3079
+ message: error2.message,
3080
+ ...error2.details && typeof error2.details === "object" ? { details: error2.details } : {}
3081
+ },
3082
+ meta: {
3083
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3084
+ }
3085
+ })
3086
+ });
1847
3087
  }
1848
3088
 
1849
- // src/ValidationRule.ts
1850
- var ValidationRule_exports = {};
1851
- __export(ValidationRule_exports, {
1852
- batch: () => batch,
1853
- morpho: () => morpho,
1854
- single: () => single
3089
+ // src/core/Services.ts
3090
+ var Services_exports = {};
3091
+ __export(Services_exports, {
3092
+ from: () => from2
1855
3093
  });
1856
- function single(name, run2) {
1857
- return { kind: "single", name, run: run2 };
1858
- }
1859
- function batch(name, run2) {
1860
- return { kind: "batch", name, run: run2 };
1861
- }
1862
- function morpho() {
1863
- const chainId = single("chain_id", (offer, { chain }) => {
1864
- if (chain.id !== offer.chainId) {
1865
- return {
1866
- message: `Chain ID ${offer.chainId} is not the same as the chain ID in the context (${chain.id})`
1867
- };
1868
- }
3094
+ function from2(config) {
3095
+ const { chain, rpcUrl, dbConfig } = config;
3096
+ const walletClient = createWalletClient({
3097
+ chain,
3098
+ transport: http(rpcUrl)
3099
+ }).extend(publicActions);
3100
+ if (dbConfig.type === "pg" && !dbConfig.endpoint) {
3101
+ throw new Error("dbOffer.endpoint is required when dbOffer.type is pg");
3102
+ }
3103
+ const DB = dbConfig.type === "pg" ? connect({ type: "pg", endpoint: dbConfig.endpoint }) : connect({ type: "pglite" });
3104
+ const offerStore = create3({ db: DB });
3105
+ const collectorBlockStore = create({
3106
+ db: DB
1869
3107
  });
1870
- const loanToken = single("loan_token", (offer, { chain }) => {
1871
- const tokens = new Set(
1872
- Array.from(chain.whitelistedAssets.values()).map((a) => a.toLowerCase())
1873
- );
1874
- if (!tokens.has(offer.loanToken.toLowerCase())) {
1875
- return {
1876
- message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
1877
- };
3108
+ const liquidityStore = create2({ db: DB });
3109
+ const interval = 1e4;
3110
+ const maxBatchSize = 1e3;
3111
+ const mempoolOffersCollector = createMempoolCollector({
3112
+ mempool: Mempool.connect({
3113
+ client: walletClient,
3114
+ mempoolAddress: chain.mempool.address
3115
+ }),
3116
+ offerStore,
3117
+ collectorBlockStore,
3118
+ chain,
3119
+ options: {
3120
+ interval,
3121
+ maxBatchSize
1878
3122
  }
1879
3123
  });
1880
- const expiry = single("expiry", (offer, _) => {
1881
- if (offer.expiry < Math.floor(Date.now() / 1e3)) {
1882
- return { message: "Expiry mismatch" };
3124
+ const consumedEventsCollector = createConsumedEventsCollector({
3125
+ client: walletClient,
3126
+ contractAddress: chain.morpho,
3127
+ offerStore,
3128
+ collectorBlockStore,
3129
+ chainId: chain.id,
3130
+ options: {
3131
+ interval,
3132
+ maxBatchSize
1883
3133
  }
1884
3134
  });
1885
- const callback = single("empty_callback", (offer, _) => {
1886
- if (!offer.buy || offer.callback.data !== "0x") {
1887
- return { message: "Callback not supported yet." };
3135
+ const buyWithEmptyCallbackLiquidityCollector = createBuyWithEmptyCallbackLiquidityCollector({
3136
+ client: walletClient,
3137
+ offerStore,
3138
+ collectorBlockStore,
3139
+ liquidityStore,
3140
+ chain,
3141
+ options: {
3142
+ interval,
3143
+ maxBatchSize
1888
3144
  }
1889
3145
  });
1890
- return [
1891
- chainId,
1892
- loanToken,
1893
- expiry,
1894
- // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1895
- // integrators should be able to choose if they want to keep the offer or not
1896
- callback
1897
- ];
3146
+ return {
3147
+ offerStore,
3148
+ collectorBlockStore,
3149
+ liquidityStore,
3150
+ mempoolOffersCollector,
3151
+ consumedEventsCollector,
3152
+ buyWithEmptyCallbackLiquidityCollector,
3153
+ DB
3154
+ };
1898
3155
  }
1899
3156
 
1900
- export { apiSchema_exports as ApiSchema, Callback_exports as Callback, Cursor_exports as Cursor, Liquidity_exports as Liquidity, Logger_exports as Logger, OfferStore_exports as OfferStore, router_exports as Router, RouterOffer_exports as RouterOffer, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
3157
+ export { apiSchema_exports as ApiSchema, Callback_exports as Callback, Collector_exports as Collector, CollectorBlockStore_exports as CollectorBlockStore, Cursor_exports as Cursor, Liquidity_exports as Liquidity, LiquidityStore_exports as LiquidityStore, Logger_exports as Logger, OfferStore_exports as OfferStore, schema_exports as OffersSchema, PG_exports as PG, router_exports as Router, RouterOffer_exports as RouterOffer, Services_exports as Services, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
1901
3158
  //# sourceMappingURL=index.node.mjs.map
1902
3159
  //# sourceMappingURL=index.node.mjs.map