@morpho-dev/router 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2040 -2156
- package/dist/index.browser.d.mts +154 -290
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.d.ts +154 -290
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +223 -464
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +223 -464
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +155 -301
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.d.ts +154 -300
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +1966 -2190
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +1970 -2188
- package/dist/index.node.mjs.map +1 -1
- package/docs/integrator.md +5 -6
- package/package.json +1 -1
package/dist/index.node.mjs
CHANGED
|
@@ -12,10 +12,12 @@ import "@opentelemetry/propagator-aws-xray";
|
|
|
12
12
|
import "@opentelemetry/resources";
|
|
13
13
|
import "@opentelemetry/sdk-trace-node";
|
|
14
14
|
import "@opentelemetry/semantic-conventions";
|
|
15
|
+
import { and, asc, eq, gt, gte, inArray, lte, ne, sql } from "drizzle-orm";
|
|
15
16
|
import { anvil, base, mainnet } from "viem/chains";
|
|
16
17
|
import * as z$1 from "zod";
|
|
17
18
|
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
|
|
18
19
|
import { gzip, ungzip } from "pako";
|
|
20
|
+
import { bigint, boolean, foreignKey, index, integer, numeric, pgSchema, primaryKey, serial, text, timestamp, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
|
19
21
|
import { serve } from "@hono/node-server";
|
|
20
22
|
import { Hono } from "hono";
|
|
21
23
|
import { cors } from "hono/cors";
|
|
@@ -29,8 +31,6 @@ import { readFile } from "node:fs/promises";
|
|
|
29
31
|
import { dirname, resolve } from "node:path";
|
|
30
32
|
import { fileURLToPath } from "node:url";
|
|
31
33
|
import { marked } from "marked";
|
|
32
|
-
import { and, asc, eq, gt, gte, inArray, lte, ne, sql } from "drizzle-orm";
|
|
33
|
-
import { bigint, boolean, foreignKey, index, integer, numeric, pgSchema, primaryKey, serial, text, timestamp, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
|
34
34
|
import createOpenApiFetchClient from "openapi-fetch";
|
|
35
35
|
import { PGlite } from "@electric-sql/pglite";
|
|
36
36
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
@@ -1104,107 +1104,14 @@ const Morpho = [
|
|
|
1104
1104
|
//#region src/core/Callback.ts
|
|
1105
1105
|
var Callback_exports = /* @__PURE__ */ __exportAll({
|
|
1106
1106
|
Type: () => Type$1,
|
|
1107
|
-
decode: () => decode$2,
|
|
1108
|
-
decodeBuyERC20: () => decodeBuyERC20,
|
|
1109
|
-
decodeBuyVaultV1Callback: () => decodeBuyVaultV1Callback,
|
|
1110
|
-
decodeSellERC20Callback: () => decodeSellERC20Callback,
|
|
1111
|
-
encode: () => encode$2,
|
|
1112
|
-
encodeBuyERC20: () => encodeBuyERC20,
|
|
1113
|
-
encodeBuyVaultV1Callback: () => encodeBuyVaultV1Callback,
|
|
1114
|
-
encodeSellERC20Callback: () => encodeSellERC20Callback,
|
|
1115
1107
|
isEmptyCallback: () => isEmptyCallback
|
|
1116
1108
|
});
|
|
1117
1109
|
let Type$1 = /* @__PURE__ */ function(Type) {
|
|
1118
1110
|
Type["BuyWithEmptyCallback"] = "buy_with_empty_callback";
|
|
1119
|
-
Type["
|
|
1120
|
-
Type["BuyVaultV1Callback"] = "buy_vault_v1_callback";
|
|
1121
|
-
Type["SellERC20Callback"] = "sell_erc20_callback";
|
|
1111
|
+
Type["SellWithEmptyCallback"] = "sell_with_empty_callback";
|
|
1122
1112
|
return Type;
|
|
1123
1113
|
}({});
|
|
1124
1114
|
const isEmptyCallback = (offer) => offer.callback.data === "0x";
|
|
1125
|
-
function decode$2(type, data) {
|
|
1126
|
-
switch (type) {
|
|
1127
|
-
case Type$1.BuyERC20: return decodeBuyERC20(data);
|
|
1128
|
-
case Type$1.BuyVaultV1Callback: return decodeBuyVaultV1Callback(data);
|
|
1129
|
-
case Type$1.SellERC20Callback: return decodeSellERC20Callback(data);
|
|
1130
|
-
default: throw new Error("Invalid callback type");
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
function encode$2(type, data) {
|
|
1134
|
-
switch (type) {
|
|
1135
|
-
case Type$1.BuyERC20:
|
|
1136
|
-
if (!("tokens" in data)) throw new Error("Invalid callback data");
|
|
1137
|
-
return encodeBuyERC20(data);
|
|
1138
|
-
case Type$1.BuyVaultV1Callback:
|
|
1139
|
-
if (!("vaults" in data)) throw new Error("Invalid callback data");
|
|
1140
|
-
return encodeBuyVaultV1Callback(data);
|
|
1141
|
-
case Type$1.SellERC20Callback:
|
|
1142
|
-
if (!("collaterals" in data)) throw new Error("Invalid callback data");
|
|
1143
|
-
return encodeSellERC20Callback(data);
|
|
1144
|
-
default: throw new Error("Invalid callback type");
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
/**
|
|
1148
|
-
* Decodes BuyERC20 callback data into positions.
|
|
1149
|
-
* @param data - The ABI-encoded callback data containing token addresses and amounts.
|
|
1150
|
-
* @returns Array of positions with contract address and amount.
|
|
1151
|
-
* @throws If data is empty, malformed, or arrays have mismatched lengths.
|
|
1152
|
-
*/
|
|
1153
|
-
function decodeBuyERC20(data) {
|
|
1154
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1155
|
-
let tokens;
|
|
1156
|
-
let amounts;
|
|
1157
|
-
try {
|
|
1158
|
-
[tokens, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1159
|
-
} catch (_) {
|
|
1160
|
-
throw new Error("Invalid BuyERC20 callback data");
|
|
1161
|
-
}
|
|
1162
|
-
if (tokens.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1163
|
-
return tokens.map((token, index) => ({
|
|
1164
|
-
contract: token,
|
|
1165
|
-
amount: amounts[index]
|
|
1166
|
-
}));
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Encodes BuyERC20 callback parameters into ABI-encoded data.
|
|
1170
|
-
* @param parameters - The tokens and amounts to encode.
|
|
1171
|
-
* @returns ABI-encoded hex string.
|
|
1172
|
-
*/
|
|
1173
|
-
function encodeBuyERC20(parameters) {
|
|
1174
|
-
return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.tokens, parameters.amounts]);
|
|
1175
|
-
}
|
|
1176
|
-
function decodeBuyVaultV1Callback(data) {
|
|
1177
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1178
|
-
try {
|
|
1179
|
-
const [vaults, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1180
|
-
if (vaults.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1181
|
-
return vaults.map((v, i) => ({
|
|
1182
|
-
contract: v,
|
|
1183
|
-
amount: amounts[i]
|
|
1184
|
-
}));
|
|
1185
|
-
} catch (_) {
|
|
1186
|
-
throw new Error("Invalid BuyVaultV1Callback callback data");
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
function decodeSellERC20Callback(data) {
|
|
1190
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1191
|
-
try {
|
|
1192
|
-
const [collaterals, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1193
|
-
if (collaterals.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1194
|
-
return collaterals.map((c, i) => ({
|
|
1195
|
-
contract: c,
|
|
1196
|
-
amount: amounts[i]
|
|
1197
|
-
}));
|
|
1198
|
-
} catch (_) {
|
|
1199
|
-
throw new Error("Invalid SellERC20Callback callback data");
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
function encodeBuyVaultV1Callback(parameters) {
|
|
1203
|
-
return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.vaults, parameters.amounts]);
|
|
1204
|
-
}
|
|
1205
|
-
function encodeSellERC20Callback(parameters) {
|
|
1206
|
-
return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.collaterals, parameters.amounts]);
|
|
1207
|
-
}
|
|
1208
1115
|
|
|
1209
1116
|
//#endregion
|
|
1210
1117
|
//#region src/core/Chain.ts
|
|
@@ -1726,11 +1633,9 @@ var Liquidity_exports = /* @__PURE__ */ __exportAll({
|
|
|
1726
1633
|
calculateMaxDebt: () => calculateMaxDebt,
|
|
1727
1634
|
generateAllowancePoolId: () => generateAllowancePoolId,
|
|
1728
1635
|
generateBalancePoolId: () => generateBalancePoolId,
|
|
1729
|
-
generateBuyVaultCallbackPoolId: () => generateBuyVaultCallbackPoolId,
|
|
1730
1636
|
generateDebtPoolId: () => generateDebtPoolId,
|
|
1731
1637
|
generateMarketLiquidityPoolId: () => generateMarketLiquidityPoolId,
|
|
1732
1638
|
generateObligationCollateralPoolId: () => generateObligationCollateralPoolId,
|
|
1733
|
-
generateSellERC20CallbackPoolId: () => generateSellERC20CallbackPoolId,
|
|
1734
1639
|
generateUserVaultPositionPoolId: () => generateUserVaultPositionPoolId,
|
|
1735
1640
|
generateVaultPositionPoolId: () => generateVaultPositionPoolId
|
|
1736
1641
|
});
|
|
@@ -1759,14 +1664,6 @@ function generateAllowancePoolId(parameters) {
|
|
|
1759
1664
|
return `${user}-${chainId.toString()}-${token}-allowance`.toLowerCase();
|
|
1760
1665
|
}
|
|
1761
1666
|
/**
|
|
1762
|
-
* Generate pool ID for sell ERC20 callback pools.
|
|
1763
|
-
* Each offer has its own callback pool to prevent liquidity conflicts.
|
|
1764
|
-
*/
|
|
1765
|
-
function generateSellERC20CallbackPoolId(parameters) {
|
|
1766
|
-
const { user, chainId, obligationId, token, offerHash } = parameters;
|
|
1767
|
-
return `${user}-${chainId.toString()}-${obligationId}-${token}-${offerHash}-sell_erc20_callback`.toLowerCase();
|
|
1768
|
-
}
|
|
1769
|
-
/**
|
|
1770
1667
|
* Generate pool ID for obligation collateral pools.
|
|
1771
1668
|
* Obligation collateral pools represent collateral already deposited in the obligation.
|
|
1772
1669
|
* These pools are shared across all offers with the same obligation.
|
|
@@ -1776,13 +1673,6 @@ function generateObligationCollateralPoolId(parameters) {
|
|
|
1776
1673
|
return `${user}-${chainId.toString()}-${obligationId}-${token}-obligation-collateral`.toLowerCase();
|
|
1777
1674
|
}
|
|
1778
1675
|
/**
|
|
1779
|
-
* Generate pool ID for buy vault callback pools.
|
|
1780
|
-
*/
|
|
1781
|
-
function generateBuyVaultCallbackPoolId(parameters) {
|
|
1782
|
-
const { user, chainId, vault, offerHash } = parameters;
|
|
1783
|
-
return `${user}-${chainId.toString()}-${vault}-${offerHash}-${Type$1.BuyVaultV1Callback}`.toLowerCase();
|
|
1784
|
-
}
|
|
1785
|
-
/**
|
|
1786
1676
|
* Generate pool ID for debt pools.
|
|
1787
1677
|
*/
|
|
1788
1678
|
function generateDebtPoolId(parameters) {
|
|
@@ -2117,6 +2007,7 @@ var Offer_exports = /* @__PURE__ */ __exportAll({
|
|
|
2117
2007
|
obligationId: () => obligationId,
|
|
2118
2008
|
random: () => random$1,
|
|
2119
2009
|
serialize: () => serialize,
|
|
2010
|
+
takeEvent: () => takeEvent,
|
|
2120
2011
|
toSnakeCase: () => toSnakeCase,
|
|
2121
2012
|
types: () => types
|
|
2122
2013
|
});
|
|
@@ -2235,7 +2126,7 @@ function random$1(config) {
|
|
|
2235
2126
|
const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
|
|
2236
2127
|
const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
|
|
2237
2128
|
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
|
|
2238
|
-
|
|
2129
|
+
collateralCandidates[int(collateralCandidates.length)];
|
|
2239
2130
|
const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
|
|
2240
2131
|
const maturity = config?.maturity ?? from$16(maturityOption);
|
|
2241
2132
|
const lltv = from$18(weightedChoice([
|
|
@@ -2262,21 +2153,10 @@ function random$1(config) {
|
|
|
2262
2153
|
const unit = BigInt(10) ** BigInt(loanTokenDecimals);
|
|
2263
2154
|
const amountBase = BigInt(100 + int(999901));
|
|
2264
2155
|
const assetsScaled = config?.assets ?? amountBase * unit;
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
};
|
|
2270
|
-
const sellCallbackAddress = "0x3333333333333333333333333333333333333333";
|
|
2271
|
-
const amount = assetsScaled * 1000000000000000000000n;
|
|
2272
|
-
return {
|
|
2273
|
-
address: sellCallbackAddress,
|
|
2274
|
-
data: encodeSellERC20Callback({
|
|
2275
|
-
collaterals: [collateralAsset],
|
|
2276
|
-
amounts: [amount]
|
|
2277
|
-
})
|
|
2278
|
-
};
|
|
2279
|
-
})();
|
|
2156
|
+
const emptyCallback = {
|
|
2157
|
+
address: zeroAddress,
|
|
2158
|
+
data: "0x"
|
|
2159
|
+
};
|
|
2280
2160
|
return from$14({
|
|
2281
2161
|
maker: config?.maker ?? address(),
|
|
2282
2162
|
assets: assetsScaled,
|
|
@@ -2295,7 +2175,7 @@ function random$1(config) {
|
|
|
2295
2175
|
...random$3(),
|
|
2296
2176
|
lltv
|
|
2297
2177
|
})).sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
2298
|
-
callback: config?.callback ??
|
|
2178
|
+
callback: config?.callback ?? emptyCallback
|
|
2299
2179
|
});
|
|
2300
2180
|
}
|
|
2301
2181
|
const weightedChoice = (pairs) => {
|
|
@@ -2585,6 +2465,94 @@ function decode$1(data) {
|
|
|
2585
2465
|
});
|
|
2586
2466
|
}
|
|
2587
2467
|
/**
|
|
2468
|
+
* ABI for the Take event emitted by the Morpho V2 contract.
|
|
2469
|
+
*/
|
|
2470
|
+
const takeEvent = {
|
|
2471
|
+
type: "event",
|
|
2472
|
+
name: "Take",
|
|
2473
|
+
inputs: [
|
|
2474
|
+
{
|
|
2475
|
+
name: "caller",
|
|
2476
|
+
type: "address",
|
|
2477
|
+
indexed: false,
|
|
2478
|
+
internalType: "address"
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
name: "id",
|
|
2482
|
+
type: "bytes32",
|
|
2483
|
+
indexed: true,
|
|
2484
|
+
internalType: "bytes32"
|
|
2485
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
name: "maker",
|
|
2488
|
+
type: "address",
|
|
2489
|
+
indexed: true,
|
|
2490
|
+
internalType: "address"
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
name: "taker",
|
|
2494
|
+
type: "address",
|
|
2495
|
+
indexed: true,
|
|
2496
|
+
internalType: "address"
|
|
2497
|
+
},
|
|
2498
|
+
{
|
|
2499
|
+
name: "offerIsBuy",
|
|
2500
|
+
type: "bool",
|
|
2501
|
+
indexed: false,
|
|
2502
|
+
internalType: "bool"
|
|
2503
|
+
},
|
|
2504
|
+
{
|
|
2505
|
+
name: "buyerAssets",
|
|
2506
|
+
type: "uint256",
|
|
2507
|
+
indexed: false,
|
|
2508
|
+
internalType: "uint256"
|
|
2509
|
+
},
|
|
2510
|
+
{
|
|
2511
|
+
name: "sellerAssets",
|
|
2512
|
+
type: "uint256",
|
|
2513
|
+
indexed: false,
|
|
2514
|
+
internalType: "uint256"
|
|
2515
|
+
},
|
|
2516
|
+
{
|
|
2517
|
+
name: "obligationUnits",
|
|
2518
|
+
type: "uint256",
|
|
2519
|
+
indexed: false,
|
|
2520
|
+
internalType: "uint256"
|
|
2521
|
+
},
|
|
2522
|
+
{
|
|
2523
|
+
name: "obligationShares",
|
|
2524
|
+
type: "uint256",
|
|
2525
|
+
indexed: false,
|
|
2526
|
+
internalType: "uint256"
|
|
2527
|
+
},
|
|
2528
|
+
{
|
|
2529
|
+
name: "buyerIsLender",
|
|
2530
|
+
type: "bool",
|
|
2531
|
+
indexed: false,
|
|
2532
|
+
internalType: "bool"
|
|
2533
|
+
},
|
|
2534
|
+
{
|
|
2535
|
+
name: "sellerIsBorrower",
|
|
2536
|
+
type: "bool",
|
|
2537
|
+
indexed: false,
|
|
2538
|
+
internalType: "bool"
|
|
2539
|
+
},
|
|
2540
|
+
{
|
|
2541
|
+
name: "group",
|
|
2542
|
+
type: "bytes32",
|
|
2543
|
+
indexed: false,
|
|
2544
|
+
internalType: "bytes32"
|
|
2545
|
+
},
|
|
2546
|
+
{
|
|
2547
|
+
name: "consumed",
|
|
2548
|
+
type: "uint256",
|
|
2549
|
+
indexed: false,
|
|
2550
|
+
internalType: "uint256"
|
|
2551
|
+
}
|
|
2552
|
+
],
|
|
2553
|
+
anonymous: false
|
|
2554
|
+
};
|
|
2555
|
+
/**
|
|
2588
2556
|
* ABI for the Consume event emitted by the Obligation contract.
|
|
2589
2557
|
*/
|
|
2590
2558
|
const consumedEvent = {
|
|
@@ -3336,160 +3304,701 @@ var SignatureDomainError = class extends BaseError {
|
|
|
3336
3304
|
const BrandTypeId = Symbol.for("mempool/Brand");
|
|
3337
3305
|
|
|
3338
3306
|
//#endregion
|
|
3339
|
-
//#region src/
|
|
3340
|
-
|
|
3341
|
-
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
3342
|
-
const logger = getLogger();
|
|
3343
|
-
let startBlock = blockNumber;
|
|
3344
|
-
let reorgDetected = false;
|
|
3345
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
3346
|
-
const stream = streamLogs({
|
|
3347
|
-
client,
|
|
3348
|
-
contractAddress: client.chain.custom.morpho.address,
|
|
3349
|
-
event: consumedEvent,
|
|
3350
|
-
blockNumberGte: blockNumber,
|
|
3351
|
-
blockNumberLte: latestBlockNumberChain,
|
|
3352
|
-
order: "asc",
|
|
3353
|
-
options: {
|
|
3354
|
-
maxBatchSize,
|
|
3355
|
-
blockWindow
|
|
3356
|
-
}
|
|
3357
|
-
});
|
|
3358
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
3359
|
-
const parsedLogs = parseEventLogs({
|
|
3360
|
-
abi: [consumedEvent],
|
|
3361
|
-
logs
|
|
3362
|
-
});
|
|
3363
|
-
const events = [];
|
|
3364
|
-
for (const log of parsedLogs) {
|
|
3365
|
-
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
3366
|
-
logger.debug({
|
|
3367
|
-
collector,
|
|
3368
|
-
chainId: client.chain.id,
|
|
3369
|
-
msg: "Skipping log because it is missing required fields"
|
|
3370
|
-
});
|
|
3371
|
-
continue;
|
|
3372
|
-
}
|
|
3373
|
-
events.push({
|
|
3374
|
-
id: `${log.blockNumber.toString()}-${log.logIndex.toString()}-${client.chain.id}-${log.transactionHash}`,
|
|
3375
|
-
chainId: client.chain.id,
|
|
3376
|
-
maker: log.args.user,
|
|
3377
|
-
group: log.args.group,
|
|
3378
|
-
amount: log.args.amount,
|
|
3379
|
-
blockNumber: Number(log.blockNumber)
|
|
3380
|
-
});
|
|
3381
|
-
}
|
|
3382
|
-
await db.transaction(async (dbTx) => {
|
|
3383
|
-
try {
|
|
3384
|
-
await dbTx.consumed.create(events);
|
|
3385
|
-
if (events.length > 0) logger.info({
|
|
3386
|
-
msg: `Events indexed`,
|
|
3387
|
-
collector,
|
|
3388
|
-
count: events.length,
|
|
3389
|
-
chain_id: client.chain.id,
|
|
3390
|
-
block_range: [startBlock, lastStreamBlockNumber]
|
|
3391
|
-
});
|
|
3392
|
-
} catch (err) {
|
|
3393
|
-
logger.error({
|
|
3394
|
-
err,
|
|
3395
|
-
msg: "Failed to process offer_consumed events"
|
|
3396
|
-
});
|
|
3397
|
-
}
|
|
3398
|
-
blockNumber = lastStreamBlockNumber;
|
|
3399
|
-
try {
|
|
3400
|
-
await dbTx.blocks.advanceCollector({
|
|
3401
|
-
collectorName: collector,
|
|
3402
|
-
chainId: client.chain.id,
|
|
3403
|
-
blockNumber,
|
|
3404
|
-
epoch
|
|
3405
|
-
});
|
|
3406
|
-
} catch (_) {
|
|
3407
|
-
try {
|
|
3408
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
3409
|
-
collectorName: collector,
|
|
3410
|
-
chainId: client.chain.id
|
|
3411
|
-
});
|
|
3412
|
-
blockNumber = ancestor.blockNumber;
|
|
3413
|
-
const deleted = await dbTx.consumed.delete({
|
|
3414
|
-
chainId: client.chain.id,
|
|
3415
|
-
blockNumberGte: blockNumber + 1
|
|
3416
|
-
});
|
|
3417
|
-
logger.info({
|
|
3418
|
-
collector,
|
|
3419
|
-
chain_id: client.chain.id,
|
|
3420
|
-
msg: `Reorg detected, events deleted`,
|
|
3421
|
-
count: deleted,
|
|
3422
|
-
block_number: blockNumber
|
|
3423
|
-
});
|
|
3424
|
-
await dbTx.blocks.advanceCollector({
|
|
3425
|
-
collectorName: collector,
|
|
3426
|
-
chainId: client.chain.id,
|
|
3427
|
-
blockNumber,
|
|
3428
|
-
epoch: ancestor.epoch
|
|
3429
|
-
});
|
|
3430
|
-
reorgDetected = true;
|
|
3431
|
-
} catch (err) {
|
|
3432
|
-
const msg = "Failed to delete consumed events when handling reorg.";
|
|
3433
|
-
logger.error({
|
|
3434
|
-
collector,
|
|
3435
|
-
chainId: client.chain.id,
|
|
3436
|
-
msg,
|
|
3437
|
-
err
|
|
3438
|
-
});
|
|
3439
|
-
throw new Error(msg);
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
});
|
|
3443
|
-
if (reorgDetected) return;
|
|
3444
|
-
yield blockNumber;
|
|
3445
|
-
startBlock = blockNumber;
|
|
3446
|
-
}
|
|
3447
|
-
}
|
|
3307
|
+
//#region src/database/drizzle/VERSION.ts
|
|
3308
|
+
const VERSION = "router_v1.6";
|
|
3448
3309
|
|
|
3449
3310
|
//#endregion
|
|
3450
|
-
//#region src/
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3311
|
+
//#region src/database/drizzle/schema.ts
|
|
3312
|
+
var schema_exports = /* @__PURE__ */ __exportAll({
|
|
3313
|
+
PositionTypes: () => PositionTypes,
|
|
3314
|
+
StatusCode: () => StatusCode,
|
|
3315
|
+
TABLE_NAMES: () => TABLE_NAMES,
|
|
3316
|
+
VERSIONED_TABLE_NAMES: () => VERSIONED_TABLE_NAMES,
|
|
3317
|
+
callbacks: () => callbacks,
|
|
3318
|
+
chains: () => chains$1,
|
|
3319
|
+
collectors: () => collectors,
|
|
3320
|
+
consumedEvents: () => consumedEvents,
|
|
3321
|
+
groups: () => groups,
|
|
3322
|
+
lots: () => lots,
|
|
3323
|
+
merklePaths: () => merklePaths,
|
|
3324
|
+
obligationCollateralsV2: () => obligationCollateralsV2,
|
|
3325
|
+
obligations: () => obligations,
|
|
3326
|
+
offers: () => offers,
|
|
3327
|
+
offersCallbacks: () => offersCallbacks,
|
|
3328
|
+
offsets: () => offsets,
|
|
3329
|
+
oracles: () => oracles$1,
|
|
3330
|
+
positionTypes: () => positionTypes,
|
|
3331
|
+
positions: () => positions,
|
|
3332
|
+
status: () => status,
|
|
3333
|
+
transfers: () => transfers,
|
|
3334
|
+
trees: () => trees,
|
|
3335
|
+
validations: () => validations
|
|
3336
|
+
});
|
|
3337
|
+
const s = pgSchema(VERSION);
|
|
3338
|
+
var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
|
|
3339
|
+
EnumTableName["OBLIGATIONS"] = "obligations";
|
|
3340
|
+
EnumTableName["GROUPS"] = "groups";
|
|
3341
|
+
EnumTableName["CONSUMED_EVENTS"] = "consumed_events";
|
|
3342
|
+
EnumTableName["OBLIGATION_COLLATERALS_V2"] = "obligation_collaterals_v2";
|
|
3343
|
+
EnumTableName["ORACLES"] = "oracles";
|
|
3344
|
+
EnumTableName["OFFERS"] = "offers";
|
|
3345
|
+
EnumTableName["OFFERS_CALLBACKS"] = "offers_callbacks";
|
|
3346
|
+
EnumTableName["CALLBACKS"] = "callbacks";
|
|
3347
|
+
EnumTableName["POSITIONS"] = "positions";
|
|
3348
|
+
EnumTableName["TRANSFERS"] = "transfers";
|
|
3349
|
+
EnumTableName["VALIDATIONS"] = "validations";
|
|
3350
|
+
EnumTableName["COLLECTORS"] = "collectors";
|
|
3351
|
+
EnumTableName["CHAINS"] = "chains";
|
|
3352
|
+
EnumTableName["LOTS"] = "lots";
|
|
3353
|
+
EnumTableName["OFFSETS"] = "offsets";
|
|
3354
|
+
EnumTableName["TREES"] = "trees";
|
|
3355
|
+
EnumTableName["MERKLE_PATHS"] = "merkle_paths";
|
|
3356
|
+
return EnumTableName;
|
|
3357
|
+
}(EnumTableName || {});
|
|
3358
|
+
const TABLE_NAMES = Object.values(EnumTableName);
|
|
3359
|
+
const VERSIONED_TABLE_NAMES = TABLE_NAMES.map((table) => `"${VERSION}"."${table}"`);
|
|
3360
|
+
const obligations = s.table(EnumTableName.OBLIGATIONS, {
|
|
3361
|
+
obligationId: varchar("obligation_id", { length: 66 }).primaryKey(),
|
|
3362
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3363
|
+
loanToken: varchar("loan_token", { length: 42 }).notNull(),
|
|
3364
|
+
maturity: integer("maturity").notNull()
|
|
3365
|
+
});
|
|
3366
|
+
const groups = s.table(EnumTableName.GROUPS, {
|
|
3367
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3368
|
+
maker: varchar("maker", { length: 42 }).notNull(),
|
|
3369
|
+
group: varchar("group", { length: 66 }).notNull(),
|
|
3370
|
+
consumed: numeric("consumed", {
|
|
3371
|
+
precision: 78,
|
|
3372
|
+
scale: 0
|
|
3373
|
+
}).notNull(),
|
|
3374
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3375
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3376
|
+
}, (table) => [primaryKey({
|
|
3377
|
+
columns: [
|
|
3378
|
+
table.chainId,
|
|
3379
|
+
table.maker,
|
|
3380
|
+
table.group
|
|
3381
|
+
],
|
|
3382
|
+
name: "groups_pk"
|
|
3383
|
+
}), index("groups_chain_id_maker_group_consumed_idx").on(table.chainId, table.maker, table.group, table.consumed)]);
|
|
3384
|
+
const consumedEvents = s.table(EnumTableName.CONSUMED_EVENTS, {
|
|
3385
|
+
eventId: varchar("event_id", { length: 128 }).primaryKey(),
|
|
3386
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3387
|
+
maker: varchar("maker", { length: 42 }).notNull(),
|
|
3388
|
+
group: varchar("group", { length: 66 }).notNull(),
|
|
3389
|
+
amount: numeric("amount", {
|
|
3390
|
+
precision: 78,
|
|
3391
|
+
scale: 0
|
|
3392
|
+
}).notNull(),
|
|
3393
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3394
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
3395
|
+
}, (t) => [
|
|
3396
|
+
foreignKey({
|
|
3397
|
+
columns: [
|
|
3398
|
+
t.chainId,
|
|
3399
|
+
t.maker,
|
|
3400
|
+
t.group
|
|
3401
|
+
],
|
|
3402
|
+
foreignColumns: [
|
|
3403
|
+
groups.chainId,
|
|
3404
|
+
groups.maker,
|
|
3405
|
+
groups.group
|
|
3406
|
+
],
|
|
3407
|
+
name: "consumed_events_groups_fk"
|
|
3408
|
+
}).onDelete("cascade"),
|
|
3409
|
+
index("consumed_events_group_idx").on(t.chainId, t.maker, t.group),
|
|
3410
|
+
index("consumed_events_block_number_idx").on(t.blockNumber)
|
|
3411
|
+
]);
|
|
3412
|
+
const obligationCollateralsV2 = s.table(EnumTableName.OBLIGATION_COLLATERALS_V2, {
|
|
3413
|
+
obligationId: varchar("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
|
|
3414
|
+
asset: varchar("asset", { length: 42 }).notNull(),
|
|
3415
|
+
oracleChainId: bigint("oracle_chain_id", { mode: "number" }).$type().notNull(),
|
|
3416
|
+
oracleAddress: varchar("oracle_address", { length: 42 }).notNull(),
|
|
3417
|
+
lltv: bigint("lltv", { mode: "bigint" }).notNull(),
|
|
3418
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3419
|
+
}, (table) => [
|
|
3420
|
+
primaryKey({
|
|
3421
|
+
columns: [table.obligationId, table.asset],
|
|
3422
|
+
name: "obligation_collaterals_v2_pk"
|
|
3423
|
+
}),
|
|
3424
|
+
foreignKey({
|
|
3425
|
+
columns: [table.oracleChainId, table.oracleAddress],
|
|
3426
|
+
foreignColumns: [oracles$1.chainId, oracles$1.address],
|
|
3427
|
+
name: "obligation_collaterals_v2_oracles_fk"
|
|
3428
|
+
}),
|
|
3429
|
+
index("obligation_collaterals_v2_obligation_id_idx").on(table.obligationId),
|
|
3430
|
+
index("obligation_collaterals_v2_oracle_fk_idx").on(table.oracleChainId, table.oracleAddress)
|
|
3431
|
+
]);
|
|
3432
|
+
const oracles$1 = s.table(EnumTableName.ORACLES, {
|
|
3433
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3434
|
+
address: varchar("address", { length: 42 }).notNull(),
|
|
3435
|
+
price: numeric("price", {
|
|
3436
|
+
precision: 78,
|
|
3437
|
+
scale: 0
|
|
3438
|
+
}),
|
|
3439
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3440
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3441
|
+
}, (table) => [primaryKey({
|
|
3442
|
+
columns: [table.chainId, table.address],
|
|
3443
|
+
name: "oracles_pk"
|
|
3444
|
+
})]);
|
|
3445
|
+
const offers = s.table(EnumTableName.OFFERS, {
|
|
3446
|
+
hash: varchar("hash", { length: 66 }).primaryKey(),
|
|
3447
|
+
obligationId: varchar("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
|
|
3448
|
+
assets: numeric("assets", {
|
|
3449
|
+
precision: 78,
|
|
3450
|
+
scale: 0
|
|
3451
|
+
}).notNull(),
|
|
3452
|
+
obligationUnits: numeric("obligation_units", {
|
|
3453
|
+
precision: 78,
|
|
3454
|
+
scale: 0
|
|
3455
|
+
}).notNull().default("0"),
|
|
3456
|
+
obligationShares: numeric("obligation_shares", {
|
|
3457
|
+
precision: 78,
|
|
3458
|
+
scale: 0
|
|
3459
|
+
}).notNull().default("0"),
|
|
3460
|
+
price: numeric("price", {
|
|
3461
|
+
precision: 78,
|
|
3462
|
+
scale: 0
|
|
3463
|
+
}).notNull(),
|
|
3464
|
+
maturity: integer("maturity").notNull(),
|
|
3465
|
+
expiry: integer("expiry").notNull(),
|
|
3466
|
+
start: integer("start").notNull(),
|
|
3467
|
+
groupChainId: bigint("group_chain_id", { mode: "number" }).$type().notNull(),
|
|
3468
|
+
groupMaker: varchar("group_maker", { length: 42 }).notNull(),
|
|
3469
|
+
group: varchar("group_group", { length: 66 }).notNull(),
|
|
3470
|
+
session: varchar("session", { length: 66 }).notNull(),
|
|
3471
|
+
buy: boolean("buy").notNull(),
|
|
3472
|
+
callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
|
|
3473
|
+
callbackData: text("callback_data").notNull(),
|
|
3474
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3475
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3476
|
+
}, (table) => [
|
|
3477
|
+
foreignKey({
|
|
3478
|
+
columns: [
|
|
3479
|
+
table.groupChainId,
|
|
3480
|
+
table.groupMaker,
|
|
3481
|
+
table.group
|
|
3482
|
+
],
|
|
3483
|
+
foreignColumns: [
|
|
3484
|
+
groups.chainId,
|
|
3485
|
+
groups.maker,
|
|
3486
|
+
groups.group
|
|
3487
|
+
],
|
|
3488
|
+
name: "offers_groups_fk"
|
|
3489
|
+
}).onDelete("cascade"),
|
|
3490
|
+
index("offers_group_fk_idx").on(table.groupChainId, table.groupMaker, table.group),
|
|
3491
|
+
index("offers_group_and_hash_idx").on(table.groupChainId, table.groupMaker, table.group, table.hash),
|
|
3492
|
+
index("offers_obligation_id_side_idx").on(table.obligationId, table.buy)
|
|
3493
|
+
]);
|
|
3494
|
+
const offersCallbacks = s.table(EnumTableName.OFFERS_CALLBACKS, {
|
|
3495
|
+
offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
|
|
3496
|
+
callbackId: varchar("callback_id", { length: 66 })
|
|
3497
|
+
}, (table) => [primaryKey({
|
|
3498
|
+
columns: [table.offerHash, table.callbackId],
|
|
3499
|
+
name: "offers_callbacks_pk"
|
|
3500
|
+
})]);
|
|
3501
|
+
const callbacks = s.table(EnumTableName.CALLBACKS, {
|
|
3502
|
+
id: varchar("id", { length: 66 }).primaryKey(),
|
|
3503
|
+
positionChainId: bigint("position_chain_id", { mode: "number" }).$type().notNull(),
|
|
3504
|
+
positionContract: varchar("position_contract", { length: 42 }).notNull(),
|
|
3505
|
+
positionUser: varchar("position_user", { length: 42 }).notNull(),
|
|
3506
|
+
amount: numeric("amount", {
|
|
3507
|
+
precision: 78,
|
|
3508
|
+
scale: 0
|
|
3509
|
+
})
|
|
3510
|
+
}, (table) => [foreignKey({
|
|
3511
|
+
columns: [
|
|
3512
|
+
table.positionChainId,
|
|
3513
|
+
table.positionContract,
|
|
3514
|
+
table.positionUser
|
|
3515
|
+
],
|
|
3516
|
+
foreignColumns: [
|
|
3517
|
+
positions.chainId,
|
|
3518
|
+
positions.contract,
|
|
3519
|
+
positions.user
|
|
3520
|
+
],
|
|
3521
|
+
name: "callbacks_positions_fk"
|
|
3522
|
+
}).onDelete("cascade")]);
|
|
3523
|
+
const lots = s.table(EnumTableName.LOTS, {
|
|
3524
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3525
|
+
user: varchar("user", { length: 42 }).notNull(),
|
|
3526
|
+
contract: varchar("contract", { length: 42 }).notNull(),
|
|
3527
|
+
group: varchar("group", { length: 66 }).notNull(),
|
|
3528
|
+
lower: numeric("lower", {
|
|
3529
|
+
precision: 78,
|
|
3530
|
+
scale: 0
|
|
3531
|
+
}).notNull(),
|
|
3532
|
+
upper: numeric("upper", {
|
|
3533
|
+
precision: 78,
|
|
3534
|
+
scale: 0
|
|
3535
|
+
}).notNull()
|
|
3536
|
+
}, (table) => [
|
|
3537
|
+
primaryKey({
|
|
3538
|
+
columns: [
|
|
3539
|
+
table.chainId,
|
|
3540
|
+
table.user,
|
|
3541
|
+
table.contract,
|
|
3542
|
+
table.group
|
|
3543
|
+
],
|
|
3544
|
+
name: "lots_pk"
|
|
3545
|
+
}),
|
|
3546
|
+
foreignKey({
|
|
3547
|
+
columns: [
|
|
3548
|
+
table.chainId,
|
|
3549
|
+
table.contract,
|
|
3550
|
+
table.user
|
|
3551
|
+
],
|
|
3552
|
+
foreignColumns: [
|
|
3553
|
+
positions.chainId,
|
|
3554
|
+
positions.contract,
|
|
3555
|
+
positions.user
|
|
3556
|
+
],
|
|
3557
|
+
name: "lots_positions_fk"
|
|
3558
|
+
}).onDelete("cascade"),
|
|
3559
|
+
foreignKey({
|
|
3560
|
+
columns: [
|
|
3561
|
+
table.chainId,
|
|
3562
|
+
table.user,
|
|
3563
|
+
table.group
|
|
3564
|
+
],
|
|
3565
|
+
foreignColumns: [
|
|
3566
|
+
groups.chainId,
|
|
3567
|
+
groups.maker,
|
|
3568
|
+
groups.group
|
|
3569
|
+
],
|
|
3570
|
+
name: "lots_groups_fk"
|
|
3571
|
+
}).onDelete("cascade")
|
|
3572
|
+
]);
|
|
3573
|
+
const offsets = s.table(EnumTableName.OFFSETS, {
|
|
3574
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3575
|
+
user: varchar("user", { length: 42 }).notNull(),
|
|
3576
|
+
contract: varchar("contract", { length: 42 }).notNull(),
|
|
3577
|
+
group: varchar("group", { length: 66 }).notNull(),
|
|
3578
|
+
value: numeric("value", {
|
|
3579
|
+
precision: 78,
|
|
3580
|
+
scale: 0
|
|
3581
|
+
}).notNull()
|
|
3582
|
+
}, (table) => [primaryKey({
|
|
3583
|
+
columns: [
|
|
3584
|
+
table.chainId,
|
|
3585
|
+
table.user,
|
|
3586
|
+
table.contract,
|
|
3587
|
+
table.group
|
|
3588
|
+
],
|
|
3589
|
+
name: "offsets_pk"
|
|
3590
|
+
}), foreignKey({
|
|
3591
|
+
columns: [
|
|
3592
|
+
table.chainId,
|
|
3593
|
+
table.contract,
|
|
3594
|
+
table.user
|
|
3595
|
+
],
|
|
3596
|
+
foreignColumns: [
|
|
3597
|
+
positions.chainId,
|
|
3598
|
+
positions.contract,
|
|
3599
|
+
positions.user
|
|
3600
|
+
],
|
|
3601
|
+
name: "offsets_positions_fk"
|
|
3602
|
+
}).onDelete("cascade")]);
|
|
3603
|
+
const PositionTypes = s.enum("position_type", Object.values(Type));
|
|
3604
|
+
const positionTypes = s.table("position_types", {
|
|
3605
|
+
id: serial("id").primaryKey(),
|
|
3606
|
+
type: PositionTypes("type").notNull()
|
|
3607
|
+
});
|
|
3608
|
+
const positions = s.table(EnumTableName.POSITIONS, {
|
|
3609
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3610
|
+
contract: varchar("contract", { length: 42 }).notNull(),
|
|
3611
|
+
user: varchar("user", { length: 42 }).notNull(),
|
|
3612
|
+
positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
|
|
3613
|
+
balance: numeric("balance", {
|
|
3614
|
+
precision: 78,
|
|
3615
|
+
scale: 0
|
|
3616
|
+
}),
|
|
3617
|
+
asset: varchar("asset", { length: 42 }),
|
|
3618
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3619
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3620
|
+
}, (table) => [primaryKey({
|
|
3621
|
+
columns: [
|
|
3622
|
+
table.chainId,
|
|
3623
|
+
table.contract,
|
|
3624
|
+
table.user
|
|
3625
|
+
],
|
|
3626
|
+
name: "positions_pk"
|
|
3627
|
+
})]);
|
|
3628
|
+
const transfers = s.table(EnumTableName.TRANSFERS, {
|
|
3629
|
+
eventId: varchar("event_id", { length: 128 }).primaryKey(),
|
|
3630
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3631
|
+
contract: varchar("contract", { length: 42 }).notNull(),
|
|
3632
|
+
from: varchar("from", { length: 42 }).notNull(),
|
|
3633
|
+
to: varchar("to", { length: 42 }).notNull(),
|
|
3634
|
+
value: numeric("value", {
|
|
3635
|
+
precision: 78,
|
|
3636
|
+
scale: 0
|
|
3637
|
+
}).notNull(),
|
|
3638
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3639
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
3640
|
+
}, (table) => [
|
|
3641
|
+
foreignKey({
|
|
3642
|
+
columns: [
|
|
3643
|
+
table.chainId,
|
|
3644
|
+
table.contract,
|
|
3645
|
+
table.from
|
|
3646
|
+
],
|
|
3647
|
+
foreignColumns: [
|
|
3648
|
+
positions.chainId,
|
|
3649
|
+
positions.contract,
|
|
3650
|
+
positions.user
|
|
3651
|
+
],
|
|
3652
|
+
name: "transfers_positions_from_fk"
|
|
3653
|
+
}).onDelete("cascade"),
|
|
3654
|
+
foreignKey({
|
|
3655
|
+
columns: [
|
|
3656
|
+
table.chainId,
|
|
3657
|
+
table.contract,
|
|
3658
|
+
table.to
|
|
3659
|
+
],
|
|
3660
|
+
foreignColumns: [
|
|
3661
|
+
positions.chainId,
|
|
3662
|
+
positions.contract,
|
|
3663
|
+
positions.user
|
|
3664
|
+
],
|
|
3665
|
+
name: "transfers_positions_to_fk"
|
|
3666
|
+
}).onDelete("cascade"),
|
|
3667
|
+
index("transfers_chain_contract_user_idx").on(table.chainId, table.contract, table.from, table.to, table.blockNumber)
|
|
3668
|
+
]);
|
|
3669
|
+
const StatusCode = s.enum("status_code", Object.values(Status));
|
|
3670
|
+
const status = s.table("status", {
|
|
3671
|
+
id: serial("id").primaryKey(),
|
|
3672
|
+
code: StatusCode("code").unique()
|
|
3673
|
+
});
|
|
3674
|
+
const validations = s.table("validations", {
|
|
3675
|
+
offerHash: varchar("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
|
|
3676
|
+
statusId: integer("status_id").notNull().references(() => status.id, { onDelete: "no action" }),
|
|
3677
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3678
|
+
});
|
|
3679
|
+
const collectors = s.table(EnumTableName.COLLECTORS, {
|
|
3680
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull().references(() => chains$1.chainId, { onDelete: "no action" }),
|
|
3681
|
+
name: text("name").$type().notNull(),
|
|
3682
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3683
|
+
epoch: numeric("epoch", {
|
|
3684
|
+
precision: 78,
|
|
3685
|
+
scale: 0
|
|
3686
|
+
}).default("0").notNull(),
|
|
3687
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3688
|
+
}, (table) => [uniqueIndex("collectors_chain_name_idx").on(table.chainId, table.name)]);
|
|
3689
|
+
const chains$1 = s.table(EnumTableName.CHAINS, {
|
|
3690
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
3691
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
3692
|
+
epoch: numeric("epoch", {
|
|
3693
|
+
precision: 78,
|
|
3694
|
+
scale: 0
|
|
3695
|
+
}).default("0").notNull(),
|
|
3696
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
3697
|
+
}, (table) => [uniqueIndex("chains_chain_id_idx").on(table.chainId)]);
|
|
3698
|
+
const trees = s.table(EnumTableName.TREES, {
|
|
3699
|
+
root: varchar("root", { length: 66 }).primaryKey(),
|
|
3700
|
+
rootSignature: varchar("root_signature", { length: 132 }).notNull(),
|
|
3701
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
3702
|
+
});
|
|
3703
|
+
const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
3704
|
+
offerHash: varchar("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
|
|
3705
|
+
treeRoot: varchar("tree_root", { length: 66 }).notNull().references(() => trees.root, { onDelete: "cascade" }),
|
|
3706
|
+
proofNodes: text("proof_nodes").notNull(),
|
|
3707
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
3708
|
+
}, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
|
|
3709
|
+
|
|
3710
|
+
//#endregion
|
|
3711
|
+
//#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
|
|
3712
|
+
const buildGroupKey = (parameters) => {
|
|
3713
|
+
return `${parameters.chainId}-${parameters.maker.toLowerCase()}-${parameters.group.toLowerCase()}`;
|
|
3714
|
+
};
|
|
3715
|
+
async function* collectConsumedEvents(parameters) {
|
|
3716
|
+
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
3717
|
+
const logger = getLogger();
|
|
3718
|
+
let startBlock = blockNumber;
|
|
3719
|
+
let reorgDetected = false;
|
|
3720
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
3721
|
+
const stream = streamLogs({
|
|
3722
|
+
client,
|
|
3723
|
+
contractAddress: client.chain.custom.morpho.address,
|
|
3724
|
+
blockNumberGte: blockNumber,
|
|
3725
|
+
blockNumberLte: latestBlockNumberChain,
|
|
3726
|
+
order: "asc",
|
|
3727
|
+
options: {
|
|
3728
|
+
maxBatchSize,
|
|
3729
|
+
blockWindow
|
|
3730
|
+
}
|
|
3731
|
+
});
|
|
3732
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
3733
|
+
const parsedLogs = parseEventLogs({
|
|
3734
|
+
abi: [consumedEvent, takeEvent],
|
|
3735
|
+
logs,
|
|
3736
|
+
strict: false
|
|
3737
|
+
});
|
|
3738
|
+
const normalizedLogs = [];
|
|
3739
|
+
const groups$3 = /* @__PURE__ */ new Map();
|
|
3740
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
3741
|
+
const recordLog = (log) => {
|
|
3742
|
+
normalizedLogs.push(log);
|
|
3743
|
+
eventIds.add(log.id);
|
|
3744
|
+
const groupKey = buildGroupKey({
|
|
3745
|
+
chainId: log.chainId,
|
|
3746
|
+
maker: log.maker,
|
|
3747
|
+
group: log.group
|
|
3748
|
+
});
|
|
3749
|
+
if (!groups$3.has(groupKey)) groups$3.set(groupKey, {
|
|
3750
|
+
chainId: log.chainId,
|
|
3751
|
+
maker: log.maker.toLowerCase(),
|
|
3752
|
+
group: log.group.toLowerCase()
|
|
3753
|
+
});
|
|
3754
|
+
};
|
|
3755
|
+
for (const rawLog of parsedLogs) {
|
|
3756
|
+
if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
|
|
3757
|
+
logger.debug({
|
|
3758
|
+
collector,
|
|
3759
|
+
chainId: client.chain.id,
|
|
3760
|
+
msg: "Skipping log because it is missing required fields"
|
|
3761
|
+
});
|
|
3762
|
+
continue;
|
|
3763
|
+
}
|
|
3764
|
+
if (rawLog.eventName === consumedEvent.name) {
|
|
3765
|
+
const consumeArgs = rawLog.args;
|
|
3766
|
+
if (consumeArgs.user === void 0 || consumeArgs.group === void 0 || consumeArgs.amount === void 0) {
|
|
3767
|
+
logger.debug({
|
|
3768
|
+
collector,
|
|
3769
|
+
chainId: client.chain.id,
|
|
3770
|
+
msg: "Skipping Consume log because it is missing required args"
|
|
3771
|
+
});
|
|
3772
|
+
continue;
|
|
3773
|
+
}
|
|
3774
|
+
recordLog({
|
|
3775
|
+
kind: "consume",
|
|
3776
|
+
id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
|
|
3777
|
+
chainId: client.chain.id,
|
|
3778
|
+
maker: consumeArgs.user,
|
|
3779
|
+
group: consumeArgs.group,
|
|
3780
|
+
amount: consumeArgs.amount,
|
|
3781
|
+
blockNumber: Number(rawLog.blockNumber)
|
|
3782
|
+
});
|
|
3783
|
+
continue;
|
|
3784
|
+
}
|
|
3785
|
+
if (rawLog.eventName === takeEvent.name) {
|
|
3786
|
+
const takeArgs = rawLog.args;
|
|
3787
|
+
if (takeArgs.maker === void 0 || takeArgs.group === void 0 || takeArgs.consumed === void 0) {
|
|
3788
|
+
logger.debug({
|
|
3789
|
+
collector,
|
|
3790
|
+
chainId: client.chain.id,
|
|
3791
|
+
msg: "Skipping Take log because it is missing required args"
|
|
3792
|
+
});
|
|
3793
|
+
continue;
|
|
3794
|
+
}
|
|
3795
|
+
recordLog({
|
|
3796
|
+
kind: "take",
|
|
3797
|
+
id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
|
|
3798
|
+
chainId: client.chain.id,
|
|
3799
|
+
maker: takeArgs.maker,
|
|
3800
|
+
group: takeArgs.group,
|
|
3801
|
+
consumed: takeArgs.consumed,
|
|
3802
|
+
blockNumber: Number(rawLog.blockNumber)
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
await db.transaction(async (dbTx) => {
|
|
3807
|
+
const existingEventIds = /* @__PURE__ */ new Set();
|
|
3808
|
+
if (eventIds.size > 0) {
|
|
3809
|
+
const ids = Array.from(eventIds);
|
|
3810
|
+
for (let index = 0; index < ids.length; index += 500) {
|
|
3811
|
+
const slice = ids.slice(index, index + 500);
|
|
3812
|
+
const { rows } = await dbTx.execute(sql`
|
|
3813
|
+
SELECT event_id
|
|
3814
|
+
FROM ${consumedEvents}
|
|
3815
|
+
WHERE event_id IN (${sql.join(slice.map((id) => sql`${id}`), sql`,`)});
|
|
3816
|
+
`);
|
|
3817
|
+
for (const row of rows) existingEventIds.add(row.event_id);
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
const consumedByGroup = /* @__PURE__ */ new Map();
|
|
3821
|
+
if (groups$3.size > 0) {
|
|
3822
|
+
const groupList = Array.from(groups$3.values());
|
|
3823
|
+
for (let index = 0; index < groupList.length; index += 500) {
|
|
3824
|
+
const slice = groupList.slice(index, index + 500);
|
|
3825
|
+
const { rows } = await dbTx.execute(sql`
|
|
3826
|
+
WITH targets(chain_id, maker, "group") AS (
|
|
3827
|
+
VALUES ${sql.join(slice.map((group) => sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), sql`,`)}
|
|
3828
|
+
)
|
|
3829
|
+
SELECT
|
|
3830
|
+
targets.chain_id,
|
|
3831
|
+
targets.maker,
|
|
3832
|
+
targets."group",
|
|
3833
|
+
COALESCE(g.consumed, 0)::numeric AS consumed
|
|
3834
|
+
FROM targets
|
|
3835
|
+
LEFT JOIN ${groups} g
|
|
3836
|
+
ON g.chain_id = targets.chain_id
|
|
3837
|
+
AND g.maker = targets.maker
|
|
3838
|
+
AND g."group" = targets."group";
|
|
3839
|
+
`);
|
|
3840
|
+
for (const row of rows) {
|
|
3841
|
+
const groupKey = buildGroupKey({
|
|
3842
|
+
chainId: Number(row.chain_id),
|
|
3843
|
+
maker: row.maker,
|
|
3844
|
+
group: row.group
|
|
3845
|
+
});
|
|
3846
|
+
consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
const events = [];
|
|
3851
|
+
for (const log of normalizedLogs) {
|
|
3852
|
+
if (existingEventIds.has(log.id)) continue;
|
|
3853
|
+
const groupKey = buildGroupKey({
|
|
3854
|
+
chainId: log.chainId,
|
|
3855
|
+
maker: log.maker,
|
|
3856
|
+
group: log.group
|
|
3857
|
+
});
|
|
3858
|
+
const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
|
|
3859
|
+
if (log.kind === "consume") {
|
|
3860
|
+
events.push({
|
|
3861
|
+
id: log.id,
|
|
3862
|
+
chainId: log.chainId,
|
|
3863
|
+
maker: log.maker,
|
|
3864
|
+
group: log.group,
|
|
3865
|
+
amount: log.amount,
|
|
3866
|
+
blockNumber: log.blockNumber
|
|
3867
|
+
});
|
|
3868
|
+
consumedByGroup.set(groupKey, previousConsumed + log.amount);
|
|
3869
|
+
continue;
|
|
3870
|
+
}
|
|
3871
|
+
const delta = log.consumed - previousConsumed;
|
|
3872
|
+
if (delta <= 0n) {
|
|
3873
|
+
logger.debug({
|
|
3874
|
+
collector,
|
|
3875
|
+
chainId: client.chain.id,
|
|
3876
|
+
msg: "Skipping Take log because consumed did not increase",
|
|
3877
|
+
previous_consumed: previousConsumed.toString(),
|
|
3878
|
+
consumed: log.consumed.toString()
|
|
3879
|
+
});
|
|
3880
|
+
continue;
|
|
3881
|
+
}
|
|
3882
|
+
events.push({
|
|
3883
|
+
id: log.id,
|
|
3884
|
+
chainId: log.chainId,
|
|
3885
|
+
maker: log.maker,
|
|
3886
|
+
group: log.group,
|
|
3887
|
+
amount: delta,
|
|
3888
|
+
blockNumber: log.blockNumber
|
|
3889
|
+
});
|
|
3890
|
+
consumedByGroup.set(groupKey, log.consumed);
|
|
3891
|
+
}
|
|
3892
|
+
try {
|
|
3893
|
+
await dbTx.consumed.create(events);
|
|
3894
|
+
if (events.length > 0) logger.info({
|
|
3895
|
+
msg: `Events indexed`,
|
|
3896
|
+
collector,
|
|
3897
|
+
count: events.length,
|
|
3898
|
+
chain_id: client.chain.id,
|
|
3899
|
+
block_range: [startBlock, lastStreamBlockNumber]
|
|
3900
|
+
});
|
|
3901
|
+
} catch (err) {
|
|
3902
|
+
logger.error({
|
|
3903
|
+
err,
|
|
3904
|
+
msg: "Failed to process consumed events"
|
|
3905
|
+
});
|
|
3906
|
+
}
|
|
3907
|
+
blockNumber = lastStreamBlockNumber;
|
|
3908
|
+
try {
|
|
3909
|
+
await dbTx.blocks.advanceCollector({
|
|
3910
|
+
collectorName: collector,
|
|
3911
|
+
chainId: client.chain.id,
|
|
3912
|
+
blockNumber,
|
|
3913
|
+
epoch
|
|
3914
|
+
});
|
|
3915
|
+
} catch (_) {
|
|
3916
|
+
try {
|
|
3917
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
3918
|
+
collectorName: collector,
|
|
3919
|
+
chainId: client.chain.id
|
|
3920
|
+
});
|
|
3921
|
+
blockNumber = ancestor.blockNumber;
|
|
3922
|
+
const deleted = await dbTx.consumed.delete({
|
|
3923
|
+
chainId: client.chain.id,
|
|
3924
|
+
blockNumberGte: blockNumber + 1
|
|
3925
|
+
});
|
|
3926
|
+
logger.info({
|
|
3927
|
+
collector,
|
|
3928
|
+
chain_id: client.chain.id,
|
|
3929
|
+
msg: `Reorg detected, events deleted`,
|
|
3930
|
+
count: deleted,
|
|
3931
|
+
block_number: blockNumber
|
|
3932
|
+
});
|
|
3933
|
+
await dbTx.blocks.advanceCollector({
|
|
3934
|
+
collectorName: collector,
|
|
3935
|
+
chainId: client.chain.id,
|
|
3936
|
+
blockNumber,
|
|
3937
|
+
epoch: ancestor.epoch
|
|
3938
|
+
});
|
|
3939
|
+
reorgDetected = true;
|
|
3940
|
+
} catch (err) {
|
|
3941
|
+
const msg = "Failed to delete consumed events when handling reorg.";
|
|
3942
|
+
logger.error({
|
|
3943
|
+
collector,
|
|
3944
|
+
chainId: client.chain.id,
|
|
3945
|
+
msg,
|
|
3946
|
+
err
|
|
3947
|
+
});
|
|
3948
|
+
throw new Error(msg, { cause: err });
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
});
|
|
3952
|
+
if (reorgDetected) return;
|
|
3953
|
+
yield blockNumber;
|
|
3954
|
+
startBlock = blockNumber;
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
//#endregion
|
|
3959
|
+
//#region src/indexer/collectors/CollectFunctions/collectOffers.ts
|
|
3960
|
+
async function* collectOffersV2(parameters) {
|
|
3961
|
+
let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
3962
|
+
const logger = getLogger();
|
|
3963
|
+
let startBlock = blockNumber;
|
|
3964
|
+
let reorgDetected = false;
|
|
3965
|
+
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
3966
|
+
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
3967
|
+
logger.error({
|
|
3968
|
+
msg,
|
|
3969
|
+
chain_id: client.chain.id
|
|
3970
|
+
});
|
|
3971
|
+
throw new Error(msg);
|
|
3972
|
+
}
|
|
3973
|
+
const signatureDomain = {
|
|
3974
|
+
chainId: client.chain.id,
|
|
3975
|
+
verifyingContract: client.chain.custom.morpho.address
|
|
3976
|
+
};
|
|
3977
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
3978
|
+
const stream = streamLogs({
|
|
3979
|
+
client,
|
|
3980
|
+
contractAddress: client.chain.custom.mempool.address,
|
|
3981
|
+
event: {
|
|
3982
|
+
type: "event",
|
|
3983
|
+
name: "Event",
|
|
3984
|
+
inputs: [{
|
|
3985
|
+
name: "data",
|
|
3986
|
+
type: "bytes",
|
|
3987
|
+
indexed: false,
|
|
3988
|
+
internalType: "bytes"
|
|
3989
|
+
}],
|
|
3990
|
+
anonymous: false
|
|
3991
|
+
},
|
|
3992
|
+
blockNumberGte: blockNumber,
|
|
3993
|
+
blockNumberLte: latestBlockNumberChain,
|
|
3994
|
+
order: "asc",
|
|
3995
|
+
options: {
|
|
3996
|
+
maxBatchSize,
|
|
3997
|
+
blockWindow
|
|
3998
|
+
}
|
|
3999
|
+
});
|
|
4000
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
4001
|
+
blockNumber = lastStreamBlockNumber;
|
|
3493
4002
|
const decodedTrees = [];
|
|
3494
4003
|
for (const log of logs) {
|
|
3495
4004
|
if (!log) continue;
|
|
@@ -3575,9 +4084,8 @@ async function* collectOffersV2(parameters) {
|
|
|
3575
4084
|
offers: offersWithBlock,
|
|
3576
4085
|
hashes: insertedHashes
|
|
3577
4086
|
});
|
|
3578
|
-
const { callbacks, positions, lots } =
|
|
4087
|
+
const { callbacks, positions, lots } = decodeCallbacks({
|
|
3579
4088
|
chainId: client.chain.id,
|
|
3580
|
-
gatekeeper,
|
|
3581
4089
|
offers: insertedOffers
|
|
3582
4090
|
});
|
|
3583
4091
|
if (positions.length > 0) await dbTx.positions.upsert(positions);
|
|
@@ -3640,83 +4148,44 @@ async function* collectOffersV2(parameters) {
|
|
|
3640
4148
|
startBlock = blockNumber;
|
|
3641
4149
|
}
|
|
3642
4150
|
}
|
|
3643
|
-
|
|
3644
|
-
const {
|
|
4151
|
+
function decodeCallbacks(parameters) {
|
|
4152
|
+
const { offers } = parameters;
|
|
3645
4153
|
if (offers.length === 0) return {
|
|
3646
4154
|
callbacks: [],
|
|
3647
4155
|
positions: [],
|
|
3648
4156
|
lots: []
|
|
3649
4157
|
};
|
|
3650
|
-
const addresses = offers.filter((entry) => entry.offer.callback.data !== "0x").map((entry) => entry.offer.callback.address);
|
|
3651
|
-
if (addresses.length === 0) return {
|
|
3652
|
-
callbacks: [],
|
|
3653
|
-
positions: [],
|
|
3654
|
-
lots: []
|
|
3655
|
-
};
|
|
3656
|
-
let response;
|
|
3657
|
-
try {
|
|
3658
|
-
response = await gatekeeper.getCallbackTypes({ callbacks: [{
|
|
3659
|
-
chain_id: chainId,
|
|
3660
|
-
addresses
|
|
3661
|
-
}] });
|
|
3662
|
-
} catch (err) {
|
|
3663
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
3664
|
-
throw new Error("Failed to resolve callback types", { cause: error });
|
|
3665
|
-
}
|
|
3666
|
-
const entry = response.find((item) => item.chain_id === chainId);
|
|
3667
|
-
const typeByAddress = /* @__PURE__ */ new Map();
|
|
3668
|
-
if (entry) for (const [key, list] of Object.entries(entry)) {
|
|
3669
|
-
if (key === "chain_id" || key === "not_supported") continue;
|
|
3670
|
-
if (!Array.isArray(list)) continue;
|
|
3671
|
-
for (const address of list) typeByAddress.set(address.toLowerCase(), key);
|
|
3672
|
-
}
|
|
3673
4158
|
const callbacks = [];
|
|
3674
4159
|
const positions = [];
|
|
3675
4160
|
const lots = [];
|
|
3676
4161
|
for (const { offer, blockNumber: offerBlockNumber } of offers) {
|
|
3677
|
-
if (offer.
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
try {
|
|
3682
|
-
decoded = decode$2(callbackType, offer.callback.data);
|
|
3683
|
-
} catch (err) {
|
|
3684
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
3685
|
-
throw new Error("Failed to decode callback data", { cause: error });
|
|
3686
|
-
}
|
|
3687
|
-
if (decoded.length === 0) continue;
|
|
3688
|
-
const offerHash = hash(offer);
|
|
3689
|
-
const callbackInputs = decoded.map((callback) => ({
|
|
4162
|
+
if (!offer.buy) continue;
|
|
4163
|
+
if (!isEmptyCallback(offer)) continue;
|
|
4164
|
+
const loanToken = offer.loanToken.toLowerCase();
|
|
4165
|
+
positions.push(from$12({
|
|
3690
4166
|
chainId: offer.chainId,
|
|
3691
|
-
contract:
|
|
4167
|
+
contract: loanToken,
|
|
3692
4168
|
user: offer.maker,
|
|
3693
|
-
|
|
4169
|
+
type: Type.ERC20,
|
|
4170
|
+
asset: loanToken,
|
|
4171
|
+
blockNumber: offerBlockNumber
|
|
3694
4172
|
}));
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
4173
|
+
lots.push({
|
|
4174
|
+
positionChainId: offer.chainId,
|
|
4175
|
+
positionContract: loanToken,
|
|
4176
|
+
positionUser: offer.maker,
|
|
4177
|
+
group: offer.group,
|
|
4178
|
+
size: offer.assets
|
|
3698
4179
|
});
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
const asset = callbackType === Type$1.BuyVaultV1Callback ? void 0 : contract;
|
|
3703
|
-
positions.push(from$12({
|
|
4180
|
+
callbacks.push({
|
|
4181
|
+
offerHash: hash(offer),
|
|
4182
|
+
callbacks: [{
|
|
3704
4183
|
chainId: offer.chainId,
|
|
3705
|
-
contract,
|
|
4184
|
+
contract: loanToken,
|
|
3706
4185
|
user: offer.maker,
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
}));
|
|
3711
|
-
const isLoanPosition = offer.loanToken.toLowerCase() === asset?.toLowerCase();
|
|
3712
|
-
lots.push({
|
|
3713
|
-
positionChainId: offer.chainId,
|
|
3714
|
-
positionContract: contract,
|
|
3715
|
-
positionUser: offer.maker,
|
|
3716
|
-
group: offer.group,
|
|
3717
|
-
size: isLoanPosition ? offer.assets : callback.amount
|
|
3718
|
-
});
|
|
3719
|
-
}
|
|
4186
|
+
amount: offer.assets
|
|
4187
|
+
}]
|
|
4188
|
+
});
|
|
3720
4189
|
}
|
|
3721
4190
|
return {
|
|
3722
4191
|
callbacks,
|
|
@@ -4903,8 +5372,8 @@ const offerExample = {
|
|
|
4903
5372
|
price: "2750000000000000000",
|
|
4904
5373
|
group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
|
|
4905
5374
|
session: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
4906
|
-
callback: "
|
|
4907
|
-
callback_data: "
|
|
5375
|
+
callback: "0x0000000000000000000000000000000000000000",
|
|
5376
|
+
callback_data: "0x"
|
|
4908
5377
|
},
|
|
4909
5378
|
offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
|
|
4910
5379
|
obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
|
|
@@ -4956,25 +5425,10 @@ const validateOfferExample = {
|
|
|
4956
5425
|
lltv: "860000000000000000"
|
|
4957
5426
|
}],
|
|
4958
5427
|
callback: {
|
|
4959
|
-
address: "
|
|
4960
|
-
data: "
|
|
5428
|
+
address: "0x0000000000000000000000000000000000000000",
|
|
5429
|
+
data: "0x"
|
|
4961
5430
|
}
|
|
4962
5431
|
};
|
|
4963
|
-
const callbackTypesRequestExample = { callbacks: [{
|
|
4964
|
-
chain_id: 1,
|
|
4965
|
-
addresses: [
|
|
4966
|
-
"0x1111111111111111111111111111111111111111",
|
|
4967
|
-
"0x3333333333333333333333333333333333333333",
|
|
4968
|
-
"0x9999999999999999999999999999999999999999"
|
|
4969
|
-
]
|
|
4970
|
-
}] };
|
|
4971
|
-
const callbackTypesResponseExample = [{
|
|
4972
|
-
chain_id: 1,
|
|
4973
|
-
sell_erc20_callback: ["0x1111111111111111111111111111111111111111"],
|
|
4974
|
-
buy_erc20: ["0x5555555555555555555555555555555555555555"],
|
|
4975
|
-
buy_vault_v1_callback: ["0x3333333333333333333333333333333333333333"],
|
|
4976
|
-
not_supported: ["0x9999999999999999999999999999999999999999"]
|
|
4977
|
-
}];
|
|
4978
5432
|
const routerStatusExample = {
|
|
4979
5433
|
status: "live",
|
|
4980
5434
|
initialized: true,
|
|
@@ -5043,55 +5497,6 @@ __decorate([ApiProperty({
|
|
|
5043
5497
|
type: "string",
|
|
5044
5498
|
example: validateOfferExample.callback.data
|
|
5045
5499
|
})], ValidateCallbackRequest.prototype, "data", void 0);
|
|
5046
|
-
var CallbackTypesChainRequest = class {};
|
|
5047
|
-
__decorate([ApiProperty({
|
|
5048
|
-
type: "number",
|
|
5049
|
-
example: callbackTypesRequestExample.callbacks[0].chain_id
|
|
5050
|
-
})], CallbackTypesChainRequest.prototype, "chain_id", void 0);
|
|
5051
|
-
__decorate([ApiProperty({
|
|
5052
|
-
type: () => [String],
|
|
5053
|
-
example: callbackTypesRequestExample.callbacks[0].addresses
|
|
5054
|
-
})], CallbackTypesChainRequest.prototype, "addresses", void 0);
|
|
5055
|
-
var CallbackTypesRequest = class {};
|
|
5056
|
-
__decorate([ApiProperty({
|
|
5057
|
-
type: () => [CallbackTypesChainRequest],
|
|
5058
|
-
example: callbackTypesRequestExample.callbacks
|
|
5059
|
-
})], CallbackTypesRequest.prototype, "callbacks", void 0);
|
|
5060
|
-
var CallbackTypesChainResponse = class {};
|
|
5061
|
-
__decorate([ApiProperty({
|
|
5062
|
-
type: "number",
|
|
5063
|
-
example: callbackTypesResponseExample[0].chain_id
|
|
5064
|
-
})], CallbackTypesChainResponse.prototype, "chain_id", void 0);
|
|
5065
|
-
__decorate([ApiProperty({
|
|
5066
|
-
type: () => [String],
|
|
5067
|
-
required: false,
|
|
5068
|
-
example: callbackTypesResponseExample[0].buy_vault_v1_callback
|
|
5069
|
-
})], CallbackTypesChainResponse.prototype, "buy_vault_v1_callback", void 0);
|
|
5070
|
-
__decorate([ApiProperty({
|
|
5071
|
-
type: () => [String],
|
|
5072
|
-
required: false,
|
|
5073
|
-
example: callbackTypesResponseExample[0].sell_erc20_callback
|
|
5074
|
-
})], CallbackTypesChainResponse.prototype, "sell_erc20_callback", void 0);
|
|
5075
|
-
__decorate([ApiProperty({
|
|
5076
|
-
type: () => [String],
|
|
5077
|
-
required: false,
|
|
5078
|
-
example: callbackTypesResponseExample[0].buy_erc20
|
|
5079
|
-
})], CallbackTypesChainResponse.prototype, "buy_erc20", void 0);
|
|
5080
|
-
__decorate([ApiProperty({
|
|
5081
|
-
type: () => [String],
|
|
5082
|
-
example: callbackTypesResponseExample[0].not_supported
|
|
5083
|
-
})], CallbackTypesChainResponse.prototype, "not_supported", void 0);
|
|
5084
|
-
var CallbackTypesSuccessResponse = class extends SuccessResponse {};
|
|
5085
|
-
__decorate([ApiProperty({
|
|
5086
|
-
type: "string",
|
|
5087
|
-
nullable: true,
|
|
5088
|
-
example: "maturity:1:1730415600:end_of_next_month"
|
|
5089
|
-
})], CallbackTypesSuccessResponse.prototype, "cursor", void 0);
|
|
5090
|
-
__decorate([ApiProperty({
|
|
5091
|
-
type: () => [CallbackTypesChainResponse],
|
|
5092
|
-
description: "Callback types grouped by chain.",
|
|
5093
|
-
example: callbackTypesResponseExample
|
|
5094
|
-
})], CallbackTypesSuccessResponse.prototype, "data", void 0);
|
|
5095
5500
|
var AskResponse = class {};
|
|
5096
5501
|
__decorate([ApiProperty({
|
|
5097
5502
|
type: "string",
|
|
@@ -5256,7 +5661,8 @@ var OfferListResponse = class extends SuccessResponse {};
|
|
|
5256
5661
|
__decorate([ApiProperty({
|
|
5257
5662
|
type: "string",
|
|
5258
5663
|
nullable: true,
|
|
5259
|
-
example: offerCursorExample
|
|
5664
|
+
example: offerCursorExample,
|
|
5665
|
+
description: "Pagination cursor. Offer hash (0x...) for maker queries; base64url-encoded cursor for obligation queries."
|
|
5260
5666
|
})], OfferListResponse.prototype, "cursor", void 0);
|
|
5261
5667
|
__decorate([ApiProperty({
|
|
5262
5668
|
type: () => [OfferListItemResponse],
|
|
@@ -5608,7 +6014,7 @@ __decorate([
|
|
|
5608
6014
|
methods: ["post"],
|
|
5609
6015
|
path: "/v1/validate",
|
|
5610
6016
|
summary: "Validate offers",
|
|
5611
|
-
description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
6017
|
+
description: "Validates offers against router validation rules. Only empty callbacks (zero address, 0x data) are accepted. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
5612
6018
|
}),
|
|
5613
6019
|
ApiBody({ type: ValidateOffersRequest }),
|
|
5614
6020
|
ApiResponse({
|
|
@@ -5627,28 +6033,6 @@ ValidateController = __decorate([ApiTags("Make"), ApiResponse({
|
|
|
5627
6033
|
description: "Bad Request",
|
|
5628
6034
|
type: BadRequestResponse
|
|
5629
6035
|
})], ValidateController);
|
|
5630
|
-
let CallbacksController = class CallbacksController {
|
|
5631
|
-
async resolveCallbackTypes() {}
|
|
5632
|
-
};
|
|
5633
|
-
__decorate([
|
|
5634
|
-
ApiOperation({
|
|
5635
|
-
methods: ["post"],
|
|
5636
|
-
path: "/v1/callbacks",
|
|
5637
|
-
summary: "Resolve callback types",
|
|
5638
|
-
description: "Returns callback types for callback addresses grouped by chain."
|
|
5639
|
-
}),
|
|
5640
|
-
ApiBody({ type: CallbackTypesRequest }),
|
|
5641
|
-
ApiResponse({
|
|
5642
|
-
status: 200,
|
|
5643
|
-
description: "Success",
|
|
5644
|
-
type: CallbackTypesSuccessResponse
|
|
5645
|
-
})
|
|
5646
|
-
], CallbacksController.prototype, "resolveCallbackTypes", null);
|
|
5647
|
-
CallbacksController = __decorate([ApiTags("Make"), ApiResponse({
|
|
5648
|
-
status: 400,
|
|
5649
|
-
description: "Bad Request",
|
|
5650
|
-
type: BadRequestResponse
|
|
5651
|
-
})], CallbacksController);
|
|
5652
6036
|
let OffersController = class OffersController {
|
|
5653
6037
|
async getOffers() {}
|
|
5654
6038
|
};
|
|
@@ -5798,22 +6182,21 @@ const configRulesMaturityExample = {
|
|
|
5798
6182
|
name: "end_of_next_month",
|
|
5799
6183
|
timestamp: 1730415600
|
|
5800
6184
|
};
|
|
5801
|
-
const configRulesCallbackExample = {
|
|
5802
|
-
type: "callback",
|
|
5803
|
-
chain_id: 1,
|
|
5804
|
-
address: "0x1111111111111111111111111111111111111111",
|
|
5805
|
-
callback_type: "sell_erc20_callback"
|
|
5806
|
-
};
|
|
5807
6185
|
const configRulesLoanTokenExample = {
|
|
5808
6186
|
type: "loan_token",
|
|
5809
6187
|
chain_id: 1,
|
|
5810
6188
|
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
5811
6189
|
};
|
|
6190
|
+
const configRulesOracleExample = {
|
|
6191
|
+
type: "oracle",
|
|
6192
|
+
chain_id: 1,
|
|
6193
|
+
address: "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83"
|
|
6194
|
+
};
|
|
5812
6195
|
const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
|
|
5813
6196
|
const configRulesPayloadExample = [
|
|
5814
6197
|
configRulesMaturityExample,
|
|
5815
|
-
|
|
5816
|
-
|
|
6198
|
+
configRulesLoanTokenExample,
|
|
6199
|
+
configRulesOracleExample
|
|
5817
6200
|
];
|
|
5818
6201
|
const configContractNames = [
|
|
5819
6202
|
"mempool",
|
|
@@ -5876,14 +6259,9 @@ __decorate([ApiProperty({
|
|
|
5876
6259
|
})], ConfigRulesRuleResponse.prototype, "timestamp", void 0);
|
|
5877
6260
|
__decorate([ApiProperty({
|
|
5878
6261
|
type: "string",
|
|
5879
|
-
example:
|
|
6262
|
+
example: configRulesLoanTokenExample.address,
|
|
5880
6263
|
required: false
|
|
5881
6264
|
})], ConfigRulesRuleResponse.prototype, "address", void 0);
|
|
5882
|
-
__decorate([ApiProperty({
|
|
5883
|
-
type: "string",
|
|
5884
|
-
example: configRulesCallbackExample.callback_type,
|
|
5885
|
-
required: false
|
|
5886
|
-
})], ConfigRulesRuleResponse.prototype, "callback_type", void 0);
|
|
5887
6265
|
var ConfigRulesSuccessResponse = class {};
|
|
5888
6266
|
__decorate([ApiProperty({ type: () => ConfigRulesMeta })], ConfigRulesSuccessResponse.prototype, "meta", void 0);
|
|
5889
6267
|
__decorate([ApiProperty({
|
|
@@ -5944,7 +6322,7 @@ __decorate([
|
|
|
5944
6322
|
methods: ["get"],
|
|
5945
6323
|
path: "/v1/config/rules",
|
|
5946
6324
|
summary: "Get config rules",
|
|
5947
|
-
description: "Returns configured rules for supported chains."
|
|
6325
|
+
description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
|
|
5948
6326
|
}),
|
|
5949
6327
|
ApiQuery({
|
|
5950
6328
|
name: "cursor",
|
|
@@ -5964,7 +6342,7 @@ __decorate([
|
|
|
5964
6342
|
name: "types",
|
|
5965
6343
|
type: ["string"],
|
|
5966
6344
|
required: false,
|
|
5967
|
-
example: "maturity,loan_token",
|
|
6345
|
+
example: "maturity,loan_token,oracle",
|
|
5968
6346
|
description: "Filter by rule types (comma-separated).",
|
|
5969
6347
|
style: "form",
|
|
5970
6348
|
explode: false
|
|
@@ -5979,1062 +6357,456 @@ __decorate([
|
|
|
5979
6357
|
explode: false
|
|
5980
6358
|
}),
|
|
5981
6359
|
ApiResponse({
|
|
5982
|
-
status: 200,
|
|
5983
|
-
description: "Success",
|
|
5984
|
-
type: ConfigRulesSuccessResponse
|
|
5985
|
-
})
|
|
5986
|
-
], ConfigRulesController.prototype, "getConfigRules", null);
|
|
5987
|
-
ConfigRulesController = __decorate([ApiTags("System")], ConfigRulesController);
|
|
5988
|
-
let ObligationsController = class ObligationsController {
|
|
5989
|
-
async getObligations() {}
|
|
5990
|
-
async getObligation() {}
|
|
5991
|
-
};
|
|
5992
|
-
__decorate([
|
|
5993
|
-
ApiOperation({
|
|
5994
|
-
methods: ["get"],
|
|
5995
|
-
path: "/v1/obligations",
|
|
5996
|
-
summary: "List all obligations",
|
|
5997
|
-
description: "Returns a list of obligations with their current best ask and bid. Obligations are sorted by their id in ascending order by default."
|
|
5998
|
-
}),
|
|
5999
|
-
ApiQuery({
|
|
6000
|
-
name: "cursor",
|
|
6001
|
-
type: "string",
|
|
6002
|
-
example: obligationCursorExample,
|
|
6003
|
-
description: "Obligation id cursor for pagination."
|
|
6004
|
-
}),
|
|
6005
|
-
ApiQuery({
|
|
6006
|
-
name: "limit",
|
|
6007
|
-
type: "number",
|
|
6008
|
-
example: 10,
|
|
6009
|
-
description: "Maximum number of obligations to return."
|
|
6010
|
-
}),
|
|
6011
|
-
ApiQuery({
|
|
6012
|
-
name: "chains",
|
|
6013
|
-
type: ["number"],
|
|
6014
|
-
required: false,
|
|
6015
|
-
example: "1,8453",
|
|
6016
|
-
description: "Filter by chain IDs (comma-separated).",
|
|
6017
|
-
style: "form",
|
|
6018
|
-
explode: false
|
|
6019
|
-
}),
|
|
6020
|
-
ApiQuery({
|
|
6021
|
-
name: "loan_tokens",
|
|
6022
|
-
type: ["string"],
|
|
6023
|
-
required: false,
|
|
6024
|
-
example: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078,0x34Cf890dB685FC536E05652FB41f02090c3fb751",
|
|
6025
|
-
description: "Filter by loan token addresses (comma-separated).",
|
|
6026
|
-
style: "form",
|
|
6027
|
-
explode: false
|
|
6028
|
-
}),
|
|
6029
|
-
ApiQuery({
|
|
6030
|
-
name: "collateral_tokens",
|
|
6031
|
-
type: ["string"],
|
|
6032
|
-
required: false,
|
|
6033
|
-
example: "0x34Cf890dB685FC536E05652FB41f02090c3fb751,0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
|
|
6034
|
-
description: "Filter by collateral tokens (comma-separated, matches any collateral).",
|
|
6035
|
-
style: "form",
|
|
6036
|
-
explode: false
|
|
6037
|
-
}),
|
|
6038
|
-
ApiQuery({
|
|
6039
|
-
name: "maturities",
|
|
6040
|
-
type: ["number"],
|
|
6041
|
-
required: false,
|
|
6042
|
-
example: "1761922800,1764524800",
|
|
6043
|
-
description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
|
|
6044
|
-
style: "form",
|
|
6045
|
-
explode: false
|
|
6046
|
-
}),
|
|
6047
|
-
ApiResponse({
|
|
6048
|
-
status: 200,
|
|
6049
|
-
description: "Success",
|
|
6050
|
-
type: ObligationListResponse
|
|
6051
|
-
})
|
|
6052
|
-
], ObligationsController.prototype, "getObligations", null);
|
|
6053
|
-
__decorate([
|
|
6054
|
-
ApiOperation({
|
|
6055
|
-
methods: ["get"],
|
|
6056
|
-
path: "/v1/obligations/{obligationId}",
|
|
6057
|
-
summary: "Get an obligation",
|
|
6058
|
-
description: "Returns an obligation by its id."
|
|
6059
|
-
}),
|
|
6060
|
-
ApiParam({
|
|
6061
|
-
name: "obligationId",
|
|
6062
|
-
type: "string",
|
|
6063
|
-
example: "0x12590ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9f67",
|
|
6064
|
-
description: "Obligation id."
|
|
6065
|
-
}),
|
|
6066
|
-
ApiResponse({
|
|
6067
|
-
status: 200,
|
|
6068
|
-
description: "Success",
|
|
6069
|
-
type: ObligationSingleSuccessResponse
|
|
6070
|
-
})
|
|
6071
|
-
], ObligationsController.prototype, "getObligation", null);
|
|
6072
|
-
ObligationsController = __decorate([ApiTags("Markets"), ApiResponse({
|
|
6073
|
-
status: 400,
|
|
6074
|
-
description: "Bad Request",
|
|
6075
|
-
type: BadRequestResponse
|
|
6076
|
-
})], ObligationsController);
|
|
6077
|
-
let UsersController = class UsersController {
|
|
6078
|
-
async getUserPositions() {}
|
|
6360
|
+
status: 200,
|
|
6361
|
+
description: "Success",
|
|
6362
|
+
type: ConfigRulesSuccessResponse
|
|
6363
|
+
})
|
|
6364
|
+
], ConfigRulesController.prototype, "getConfigRules", null);
|
|
6365
|
+
ConfigRulesController = __decorate([ApiTags("System")], ConfigRulesController);
|
|
6366
|
+
let ObligationsController = class ObligationsController {
|
|
6367
|
+
async getObligations() {}
|
|
6368
|
+
async getObligation() {}
|
|
6079
6369
|
};
|
|
6080
6370
|
__decorate([
|
|
6081
6371
|
ApiOperation({
|
|
6082
6372
|
methods: ["get"],
|
|
6083
|
-
path: "/v1/
|
|
6084
|
-
summary: "
|
|
6085
|
-
description: "Returns
|
|
6086
|
-
}),
|
|
6087
|
-
ApiParam({
|
|
6088
|
-
name: "userAddress",
|
|
6089
|
-
type: "string",
|
|
6090
|
-
example: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
|
|
6091
|
-
description: "User address to get positions for."
|
|
6373
|
+
path: "/v1/obligations",
|
|
6374
|
+
summary: "List all obligations",
|
|
6375
|
+
description: "Returns a list of obligations with their current best ask and bid. Obligations are sorted by their id in ascending order by default."
|
|
6092
6376
|
}),
|
|
6093
6377
|
ApiQuery({
|
|
6094
6378
|
name: "cursor",
|
|
6095
6379
|
type: "string",
|
|
6096
|
-
example:
|
|
6097
|
-
description: "
|
|
6380
|
+
example: obligationCursorExample,
|
|
6381
|
+
description: "Obligation id cursor for pagination."
|
|
6098
6382
|
}),
|
|
6099
6383
|
ApiQuery({
|
|
6100
6384
|
name: "limit",
|
|
6101
6385
|
type: "number",
|
|
6102
6386
|
example: 10,
|
|
6103
|
-
description: "Maximum number of
|
|
6104
|
-
}),
|
|
6105
|
-
ApiResponse({
|
|
6106
|
-
status: 200,
|
|
6107
|
-
description: "Success",
|
|
6108
|
-
type: PositionListResponse
|
|
6109
|
-
})
|
|
6110
|
-
], UsersController.prototype, "getUserPositions", null);
|
|
6111
|
-
UsersController = __decorate([ApiTags("Make"), ApiResponse({
|
|
6112
|
-
status: 400,
|
|
6113
|
-
description: "Bad Request",
|
|
6114
|
-
type: BadRequestResponse
|
|
6115
|
-
})], UsersController);
|
|
6116
|
-
const OpenApi = async () => {
|
|
6117
|
-
return await generateDocument({
|
|
6118
|
-
controllers: [
|
|
6119
|
-
BooksController,
|
|
6120
|
-
ConfigContractsController,
|
|
6121
|
-
ConfigRulesController,
|
|
6122
|
-
OffersController,
|
|
6123
|
-
ObligationsController,
|
|
6124
|
-
HealthController,
|
|
6125
|
-
UsersController,
|
|
6126
|
-
ValidateController,
|
|
6127
|
-
CallbacksController
|
|
6128
|
-
],
|
|
6129
|
-
document: {
|
|
6130
|
-
openapi: "3.1.0",
|
|
6131
|
-
info: {
|
|
6132
|
-
title: "Router API",
|
|
6133
|
-
version: "1.0.0",
|
|
6134
|
-
description: "API for the Morpho Router"
|
|
6135
|
-
},
|
|
6136
|
-
servers: [{
|
|
6137
|
-
url: "https://router.morpho.dev",
|
|
6138
|
-
description: "Production server"
|
|
6139
|
-
}, {
|
|
6140
|
-
url: "http://localhost:7891",
|
|
6141
|
-
description: "Local development server"
|
|
6142
|
-
}],
|
|
6143
|
-
tags: [
|
|
6144
|
-
{
|
|
6145
|
-
name: "Markets",
|
|
6146
|
-
description: "Read-only endpoints to discover markets, order books and fetch current offers."
|
|
6147
|
-
},
|
|
6148
|
-
{
|
|
6149
|
-
name: "Make",
|
|
6150
|
-
description: "Utilities to ease making offers."
|
|
6151
|
-
},
|
|
6152
|
-
{
|
|
6153
|
-
name: "System",
|
|
6154
|
-
description: "Router configuration and health monitoring."
|
|
6155
|
-
}
|
|
6156
|
-
]
|
|
6157
|
-
}
|
|
6158
|
-
});
|
|
6159
|
-
};
|
|
6160
|
-
|
|
6161
|
-
//#endregion
|
|
6162
|
-
//#region src/api/Schema/PositionResponse.ts
|
|
6163
|
-
var PositionResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$2 });
|
|
6164
|
-
/**
|
|
6165
|
-
* Creates a `PositionResponse` from a `PositionWithReserved`.
|
|
6166
|
-
* @param position - {@link PositionWithReserved}
|
|
6167
|
-
* @returns The created `PositionResponse`. {@link PositionResponse}
|
|
6168
|
-
*/
|
|
6169
|
-
function from$2(position) {
|
|
6170
|
-
return {
|
|
6171
|
-
chain_id: position.chainId,
|
|
6172
|
-
contract: position.contract,
|
|
6173
|
-
user: position.user,
|
|
6174
|
-
reserved: position.reserved.toString(),
|
|
6175
|
-
block_number: position.blockNumber
|
|
6176
|
-
};
|
|
6177
|
-
}
|
|
6178
|
-
|
|
6179
|
-
//#endregion
|
|
6180
|
-
//#region src/api/Schema/requests.ts
|
|
6181
|
-
const MAX_LIMIT = 100;
|
|
6182
|
-
const DEFAULT_LIMIT$4 = 20;
|
|
6183
|
-
const CONFIG_RULES_MAX_LIMIT = 1e3;
|
|
6184
|
-
const CONFIG_RULES_DEFAULT_LIMIT = 100;
|
|
6185
|
-
const CONFIG_CONTRACTS_MAX_LIMIT = 1e3;
|
|
6186
|
-
const CONFIG_CONTRACTS_DEFAULT_LIMIT = 1e3;
|
|
6187
|
-
/** Validate cursor is a valid base64url-encoded JSON object.
|
|
6188
|
-
* Domain layer handles semantic validation of cursor fields. */
|
|
6189
|
-
function isValidBase64urlJson(val) {
|
|
6190
|
-
try {
|
|
6191
|
-
const decoded = Buffer.from(val, "base64url").toString("utf8");
|
|
6192
|
-
JSON.parse(decoded);
|
|
6193
|
-
return true;
|
|
6194
|
-
} catch {
|
|
6195
|
-
return false;
|
|
6196
|
-
}
|
|
6197
|
-
}
|
|
6198
|
-
const csvArray = (schema) => z$1.preprocess((value) => {
|
|
6199
|
-
if (value === void 0) return void 0;
|
|
6200
|
-
if (Array.isArray(value)) {
|
|
6201
|
-
if (value.some((item) => typeof item !== "string")) return value;
|
|
6202
|
-
return value.flatMap((item) => item.split(",")).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6203
|
-
}
|
|
6204
|
-
if (typeof value === "string") return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6205
|
-
return value;
|
|
6206
|
-
}, z$1.array(schema)).optional();
|
|
6207
|
-
const PaginationQueryParams = z$1.object({
|
|
6208
|
-
cursor: z$1.string().optional().refine((val) => {
|
|
6209
|
-
if (!val) return true;
|
|
6210
|
-
return isValidBase64urlJson(val);
|
|
6211
|
-
}, { message: "Invalid cursor format. Must be a valid base64url-encoded cursor object" }).meta({
|
|
6212
|
-
description: "Pagination cursor in base64url-encoded format",
|
|
6213
|
-
example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
|
|
6214
|
-
}),
|
|
6215
|
-
limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
|
|
6216
|
-
description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
|
|
6217
|
-
example: 10
|
|
6218
|
-
})
|
|
6219
|
-
});
|
|
6220
|
-
const ConfigRuleTypes = z$1.enum([
|
|
6221
|
-
"maturity",
|
|
6222
|
-
"callback",
|
|
6223
|
-
"loan_token"
|
|
6224
|
-
]);
|
|
6225
|
-
const GetConfigRulesQueryParams = z$1.object({
|
|
6226
|
-
cursor: z$1.string().regex(/^(maturity|callback|loan_token):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
|
|
6227
|
-
description: "Pagination cursor in type:chain_id:<value> format",
|
|
6228
|
-
example: "maturity:1:1730415600:end_of_next_month"
|
|
6229
|
-
}),
|
|
6230
|
-
limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(CONFIG_RULES_MAX_LIMIT, { message: `Limit cannot exceed ${CONFIG_RULES_MAX_LIMIT}` })).optional().default(CONFIG_RULES_DEFAULT_LIMIT).meta({
|
|
6231
|
-
description: `Limit maximum: ${CONFIG_RULES_MAX_LIMIT}. Default: ${CONFIG_RULES_DEFAULT_LIMIT}`,
|
|
6232
|
-
example: 100
|
|
6233
|
-
}),
|
|
6234
|
-
types: csvArray(ConfigRuleTypes).meta({
|
|
6235
|
-
description: "Filter by rule types (comma-separated).",
|
|
6236
|
-
example: "maturity,loan_token"
|
|
6237
|
-
}),
|
|
6238
|
-
chains: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
6239
|
-
description: "Filter by chain IDs (comma-separated).",
|
|
6240
|
-
example: "1,8453"
|
|
6241
|
-
})
|
|
6242
|
-
});
|
|
6243
|
-
const GetConfigContractsQueryParams = z$1.object({
|
|
6244
|
-
cursor: z$1.string().regex(/^[1-9]\d*:0x[a-fA-F0-9]{40}$/, { message: "Cursor must be in the format chain_id:0x..." }).optional().meta({
|
|
6245
|
-
description: "Pagination cursor in chain_id:address format (lowercase address).",
|
|
6246
|
-
example: "1:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
6247
|
-
}),
|
|
6248
|
-
limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(CONFIG_CONTRACTS_MAX_LIMIT, { message: `Limit cannot exceed ${CONFIG_CONTRACTS_MAX_LIMIT}` })).optional().default(CONFIG_CONTRACTS_DEFAULT_LIMIT).meta({
|
|
6249
|
-
description: `Limit maximum: ${CONFIG_CONTRACTS_MAX_LIMIT}. Default: ${CONFIG_CONTRACTS_DEFAULT_LIMIT}`,
|
|
6250
|
-
example: 1e3
|
|
6251
|
-
}),
|
|
6252
|
-
chains: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
6253
|
-
description: "Filter by chain IDs (comma-separated).",
|
|
6254
|
-
example: "1,8453"
|
|
6255
|
-
})
|
|
6256
|
-
});
|
|
6257
|
-
const GetOffersQueryParams = z$1.object({
|
|
6258
|
-
...PaginationQueryParams.shape,
|
|
6259
|
-
side: z$1.enum(["buy", "sell"]).optional().meta({
|
|
6260
|
-
description: "Side of the offer. Required when using obligation_id.",
|
|
6261
|
-
example: "buy"
|
|
6262
|
-
}),
|
|
6263
|
-
obligation_id: z$1.string().regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).optional().meta({
|
|
6264
|
-
description: "Offers obligation id. Required when not using maker.",
|
|
6265
|
-
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6266
|
-
}),
|
|
6267
|
-
maker: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Maker must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).optional().meta({
|
|
6268
|
-
description: "Maker address to filter offers by. Alternative to obligation_id + side.",
|
|
6269
|
-
example: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
|
|
6270
|
-
})
|
|
6271
|
-
}).superRefine((val, ctx) => {
|
|
6272
|
-
const hasObligation = val.obligation_id !== void 0;
|
|
6273
|
-
const hasSide = val.side !== void 0;
|
|
6274
|
-
const hasMaker = val.maker !== void 0;
|
|
6275
|
-
if (hasMaker && (hasObligation || hasSide)) {
|
|
6276
|
-
ctx.addIssue({
|
|
6277
|
-
code: "custom",
|
|
6278
|
-
message: "Cannot use both maker and obligation_id/side parameters"
|
|
6279
|
-
});
|
|
6280
|
-
return;
|
|
6281
|
-
}
|
|
6282
|
-
if (hasMaker) return;
|
|
6283
|
-
if (!hasObligation || !hasSide) ctx.addIssue({
|
|
6284
|
-
code: "custom",
|
|
6285
|
-
message: "Must provide either maker or both obligation_id and side"
|
|
6286
|
-
});
|
|
6287
|
-
});
|
|
6288
|
-
const GetObligationsQueryParams = z$1.object({
|
|
6289
|
-
...PaginationQueryParams.shape,
|
|
6290
|
-
cursor: z$1.string().optional().meta({
|
|
6291
|
-
description: "Obligation id cursor",
|
|
6292
|
-
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6387
|
+
description: "Maximum number of obligations to return."
|
|
6293
6388
|
}),
|
|
6294
|
-
|
|
6389
|
+
ApiQuery({
|
|
6390
|
+
name: "chains",
|
|
6391
|
+
type: ["number"],
|
|
6392
|
+
required: false,
|
|
6393
|
+
example: "1,8453",
|
|
6295
6394
|
description: "Filter by chain IDs (comma-separated).",
|
|
6296
|
-
|
|
6395
|
+
style: "form",
|
|
6396
|
+
explode: false
|
|
6297
6397
|
}),
|
|
6298
|
-
|
|
6398
|
+
ApiQuery({
|
|
6399
|
+
name: "loan_tokens",
|
|
6400
|
+
type: ["string"],
|
|
6401
|
+
required: false,
|
|
6402
|
+
example: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078,0x34Cf890dB685FC536E05652FB41f02090c3fb751",
|
|
6299
6403
|
description: "Filter by loan token addresses (comma-separated).",
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
collateral_tokens: csvArray(z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Collateral token must be a valid 20-byte address" }).transform((val) => val.toLowerCase())).meta({
|
|
6303
|
-
description: "Filter by collateral tokens (comma-separated, matches any collateral).",
|
|
6304
|
-
example: "0x34Cf890dB685FC536E05652FB41f02090c3fb751,0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078"
|
|
6305
|
-
}),
|
|
6306
|
-
maturities: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Maturity must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
6307
|
-
description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
|
|
6308
|
-
example: "1761922800,1764524800"
|
|
6309
|
-
})
|
|
6310
|
-
});
|
|
6311
|
-
const GetObligationParams = z$1.object({ obligation_id: z$1.string({ error: "Obligation id is required and must be a valid 32-byte hex string" }).regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).meta({
|
|
6312
|
-
description: "Obligation id",
|
|
6313
|
-
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6314
|
-
}) });
|
|
6315
|
-
/** Validate a book cursor format: {side, lastPrice, offersCursor} */
|
|
6316
|
-
function isValidBookCursor(cursorString) {
|
|
6317
|
-
const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
|
|
6318
|
-
try {
|
|
6319
|
-
const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
|
|
6320
|
-
return (v?.side === "buy" || v?.side === "sell") && isNumericString(v?.lastPrice) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
|
|
6321
|
-
} catch {
|
|
6322
|
-
return false;
|
|
6323
|
-
}
|
|
6324
|
-
}
|
|
6325
|
-
const BookPaginationQueryParams = z$1.object({
|
|
6326
|
-
cursor: z$1.string().optional().refine((value) => {
|
|
6327
|
-
if (!value) return true;
|
|
6328
|
-
return isValidBookCursor(value);
|
|
6329
|
-
}, { message: "Invalid cursor format. Must be a valid base64url-encoded book cursor object" }).meta({
|
|
6330
|
-
description: "Pagination cursor in base64url-encoded format for book levels",
|
|
6331
|
-
example: "eyJzaWRlIjoiYnV5IiwibGFzdFJhdGUiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwib2ZmZXJzQ3Vyc29yIjpudWxsfQ"
|
|
6404
|
+
style: "form",
|
|
6405
|
+
explode: false
|
|
6332
6406
|
}),
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
"1",
|
|
6342
|
-
"0"
|
|
6343
|
-
]).transform((value) => value === "true" || value === "1").optional().meta({
|
|
6344
|
-
description: "Enable strict mode to fail health checks when initialization is incomplete.",
|
|
6345
|
-
example: "true"
|
|
6346
|
-
}) });
|
|
6347
|
-
const GetBookParams = z$1.object({
|
|
6348
|
-
...BookPaginationQueryParams.shape,
|
|
6349
|
-
obligation_id: z$1.string({ error: "Obligation id is required and must be a valid 32-byte hex string" }).regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).meta({
|
|
6350
|
-
description: "Obligation id",
|
|
6351
|
-
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6407
|
+
ApiQuery({
|
|
6408
|
+
name: "collateral_tokens",
|
|
6409
|
+
type: ["string"],
|
|
6410
|
+
required: false,
|
|
6411
|
+
example: "0x34Cf890dB685FC536E05652FB41f02090c3fb751,0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
|
|
6412
|
+
description: "Filter by collateral tokens (comma-separated, matches any collateral).",
|
|
6413
|
+
style: "form",
|
|
6414
|
+
explode: false
|
|
6352
6415
|
}),
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
description: "Chain id.",
|
|
6362
|
-
example: 1
|
|
6416
|
+
ApiQuery({
|
|
6417
|
+
name: "maturities",
|
|
6418
|
+
type: ["number"],
|
|
6419
|
+
required: false,
|
|
6420
|
+
example: "1761922800,1764524800",
|
|
6421
|
+
description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
|
|
6422
|
+
style: "form",
|
|
6423
|
+
explode: false
|
|
6363
6424
|
}),
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
}).strict()) }).strict();
|
|
6369
|
-
const GetUserPositionsParams = z$1.object({
|
|
6370
|
-
...PaginationQueryParams.shape,
|
|
6371
|
-
user_address: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
|
|
6372
|
-
description: "User address to get positions for",
|
|
6373
|
-
example: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
|
|
6425
|
+
ApiResponse({
|
|
6426
|
+
status: 200,
|
|
6427
|
+
description: "Success",
|
|
6428
|
+
type: ObligationListResponse
|
|
6374
6429
|
})
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
if (!result.success) return failure(result.error);
|
|
6403
|
-
const query = result.data;
|
|
6404
|
-
try {
|
|
6405
|
-
const { levels, nextCursor } = await db.book.get({
|
|
6406
|
-
side: query.side,
|
|
6407
|
-
obligationId: query.obligation_id,
|
|
6408
|
-
cursor: query.cursor,
|
|
6409
|
-
limit: query.limit
|
|
6410
|
-
});
|
|
6411
|
-
return success({
|
|
6412
|
-
data: levels.map(from$5),
|
|
6413
|
-
cursor: nextCursor
|
|
6414
|
-
});
|
|
6415
|
-
} catch (err) {
|
|
6416
|
-
logger.error({
|
|
6417
|
-
err,
|
|
6418
|
-
msg: "Error get book",
|
|
6419
|
-
errorMessage: err instanceof Error ? err.message : String(err),
|
|
6420
|
-
errorStack: err instanceof Error ? err.stack : void 0
|
|
6421
|
-
});
|
|
6422
|
-
return failure(err);
|
|
6423
|
-
}
|
|
6424
|
-
}
|
|
6425
|
-
|
|
6426
|
-
//#endregion
|
|
6427
|
-
//#region src/api/Controllers/getConfigContracts.ts
|
|
6428
|
-
const CONFIG_CONTRACT_NAMES = [
|
|
6429
|
-
"mempool",
|
|
6430
|
-
"multicall",
|
|
6431
|
-
"v2"
|
|
6432
|
-
];
|
|
6433
|
-
/**
|
|
6434
|
-
* Returns contract addresses used by indexers (mempool, v2) plus multicall per chain.
|
|
6435
|
-
* @param query - Raw query parameters containing optional chain filters.
|
|
6436
|
-
* @param chainRegistry - The chain registry instance. {@link ChainRegistry.ChainRegistry}
|
|
6437
|
-
* @returns The indexer contract configuration. {@link ApiPayload.Payload<ConfigContract[]>}
|
|
6438
|
-
*/
|
|
6439
|
-
async function getConfigContracts(query, chainRegistry) {
|
|
6440
|
-
const parsed = safeParse("get_config_contracts", query ?? {});
|
|
6441
|
-
if (!parsed.success) return failure(parsed.error);
|
|
6442
|
-
const { chains: chainsFilter, cursor, limit } = parsed.data;
|
|
6443
|
-
const chainFilter = chainsFilter?.length ? new Set(chainsFilter) : null;
|
|
6444
|
-
const contracts = [];
|
|
6445
|
-
const seenAddresses = /* @__PURE__ */ new Set();
|
|
6446
|
-
for (const chain of chainRegistry.list()) {
|
|
6447
|
-
if (chainFilter && !chainFilter.has(chain.id)) continue;
|
|
6448
|
-
const mempool = chain.custom?.mempool?.address;
|
|
6449
|
-
if (!mempool) return failure(new InternalServerError(`Missing mempool address for chain ${chain.id}.`));
|
|
6450
|
-
const multicall = chain.contracts?.multicall3?.address;
|
|
6451
|
-
if (!multicall) return failure(new InternalServerError(`Missing multicall3 address for chain ${chain.id}.`));
|
|
6452
|
-
const v2 = chain.custom?.morpho?.address;
|
|
6453
|
-
if (!v2) return failure(new InternalServerError(`Missing morpho address for chain ${chain.id}.`));
|
|
6454
|
-
const chainContracts = [
|
|
6455
|
-
{
|
|
6456
|
-
chain_id: chain.id,
|
|
6457
|
-
name: "mempool",
|
|
6458
|
-
address: mempool
|
|
6459
|
-
},
|
|
6460
|
-
{
|
|
6461
|
-
chain_id: chain.id,
|
|
6462
|
-
name: "multicall",
|
|
6463
|
-
address: multicall
|
|
6464
|
-
},
|
|
6465
|
-
{
|
|
6466
|
-
chain_id: chain.id,
|
|
6467
|
-
name: "v2",
|
|
6468
|
-
address: v2
|
|
6469
|
-
}
|
|
6470
|
-
];
|
|
6471
|
-
for (const contract of chainContracts) {
|
|
6472
|
-
const cursorKey = `${contract.chain_id}:${contract.address.toLowerCase()}`;
|
|
6473
|
-
if (seenAddresses.has(cursorKey)) return failure(new InternalServerError(`Duplicate contract address ${contract.address} for chain ${chain.id}.`));
|
|
6474
|
-
seenAddresses.add(cursorKey);
|
|
6475
|
-
contracts.push(contract);
|
|
6476
|
-
}
|
|
6477
|
-
}
|
|
6478
|
-
contracts.sort((a, b) => {
|
|
6479
|
-
if (a.chain_id !== b.chain_id) return a.chain_id - b.chain_id;
|
|
6480
|
-
const addressCompare = a.address.toLowerCase().localeCompare(b.address.toLowerCase());
|
|
6481
|
-
if (addressCompare !== 0) return addressCompare;
|
|
6482
|
-
return a.name.localeCompare(b.name);
|
|
6483
|
-
});
|
|
6484
|
-
let cursorContract = null;
|
|
6485
|
-
if (cursor) try {
|
|
6486
|
-
cursorContract = parseCursor$1(cursor);
|
|
6487
|
-
} catch (err) {
|
|
6488
|
-
return failure(err);
|
|
6489
|
-
}
|
|
6490
|
-
const startIndex = cursorContract ? findStartIndex$1(contracts, cursorContract) : 0;
|
|
6491
|
-
const page = contracts.slice(startIndex, startIndex + limit);
|
|
6492
|
-
const nextCursor = startIndex + limit < contracts.length && page.length > 0 ? formatCursor$1(page.at(-1)) : null;
|
|
6493
|
-
return success({
|
|
6494
|
-
data: page,
|
|
6495
|
-
cursor: nextCursor
|
|
6496
|
-
});
|
|
6497
|
-
}
|
|
6498
|
-
function parseCursor$1(cursor) {
|
|
6499
|
-
const [chain, address] = cursor.split(":", 2);
|
|
6500
|
-
if (!chain || !address) throw new BadRequestError("Cursor must be in the format chain_id:0x...");
|
|
6501
|
-
return {
|
|
6502
|
-
chain_id: Number.parseInt(chain, 10),
|
|
6503
|
-
address: address.toLowerCase()
|
|
6504
|
-
};
|
|
6505
|
-
}
|
|
6506
|
-
function formatCursor$1(contract) {
|
|
6507
|
-
return `${contract.chain_id}:${contract.address.toLowerCase()}`;
|
|
6508
|
-
}
|
|
6509
|
-
function findStartIndex$1(contracts, cursor) {
|
|
6510
|
-
let low = 0;
|
|
6511
|
-
let high = contracts.length;
|
|
6512
|
-
while (low < high) {
|
|
6513
|
-
const mid = Math.floor((low + high) / 2);
|
|
6514
|
-
const current = contracts[mid];
|
|
6515
|
-
if (compareContract(current, cursor) <= 0) low = mid + 1;
|
|
6516
|
-
else high = mid;
|
|
6517
|
-
}
|
|
6518
|
-
return low;
|
|
6519
|
-
}
|
|
6520
|
-
function compareContract(contract, cursor) {
|
|
6521
|
-
if (contract.chain_id !== cursor.chain_id) return contract.chain_id - cursor.chain_id;
|
|
6522
|
-
return contract.address.toLowerCase().localeCompare(cursor.address.toLowerCase());
|
|
6523
|
-
}
|
|
6524
|
-
|
|
6525
|
-
//#endregion
|
|
6526
|
-
//#region src/gatekeeper/GateConfig.ts
|
|
6527
|
-
/**
|
|
6528
|
-
* Returns the callback configuration for a given chain and callback type, if it exists.
|
|
6529
|
-
*
|
|
6530
|
-
* @param chain - Chain name for which to read the validation configuration
|
|
6531
|
-
* @param type - Callback type to retrieve
|
|
6532
|
-
* @returns The matching callback configuration or undefined if not configured
|
|
6533
|
-
*/
|
|
6534
|
-
function getCallback(chain, type) {
|
|
6535
|
-
return configs[chain].callbacks?.find((c) => c.type === type);
|
|
6536
|
-
}
|
|
6537
|
-
/**
|
|
6538
|
-
* Attempts to infer the configured callback type from a callback address on a chain.
|
|
6539
|
-
* Skips the empty callback type as it does not carry addresses.
|
|
6540
|
-
*
|
|
6541
|
-
* @param chain - Chain name for which to infer the callback type
|
|
6542
|
-
* @param address - Callback contract address
|
|
6543
|
-
* @returns The callback type when found, otherwise undefined
|
|
6544
|
-
*/
|
|
6545
|
-
function getCallbackType(chain, address) {
|
|
6546
|
-
return configs[chain].callbacks?.find((c) => c.type !== Type$1.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
|
|
6547
|
-
}
|
|
6548
|
-
/**
|
|
6549
|
-
* Returns the list of allowed non-empty callback addresses for a chain.
|
|
6550
|
-
*
|
|
6551
|
-
* @param chain - Chain name
|
|
6552
|
-
* @returns Array of allowed callback addresses (lowercased). Empty when none configured
|
|
6553
|
-
*/
|
|
6554
|
-
const getCallbackAddresses = (chain) => {
|
|
6555
|
-
return configs[chain].callbacks?.filter((c) => c.type !== Type$1.BuyWithEmptyCallback).flatMap((c) => c.addresses) ?? [];
|
|
6556
|
-
};
|
|
6557
|
-
const assets = {
|
|
6558
|
-
[ChainId.ETHEREUM.toString()]: [
|
|
6559
|
-
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
6560
|
-
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
6561
|
-
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
6562
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
6563
|
-
],
|
|
6564
|
-
[ChainId.BASE.toString()]: [
|
|
6565
|
-
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
6566
|
-
"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
|
6567
|
-
"0x4200000000000000000000000000000000000006",
|
|
6568
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
6569
|
-
],
|
|
6570
|
-
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
6571
|
-
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
6572
|
-
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
6573
|
-
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
6574
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
6575
|
-
"0xce79ddb3152d52ff8fe65a4c7e058b035fcb560a"
|
|
6576
|
-
],
|
|
6577
|
-
[ChainId.ANVIL.toString()]: [
|
|
6578
|
-
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
6579
|
-
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
6580
|
-
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
6581
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
6582
|
-
]
|
|
6430
|
+
], ObligationsController.prototype, "getObligations", null);
|
|
6431
|
+
__decorate([
|
|
6432
|
+
ApiOperation({
|
|
6433
|
+
methods: ["get"],
|
|
6434
|
+
path: "/v1/obligations/{obligationId}",
|
|
6435
|
+
summary: "Get an obligation",
|
|
6436
|
+
description: "Returns an obligation by its id."
|
|
6437
|
+
}),
|
|
6438
|
+
ApiParam({
|
|
6439
|
+
name: "obligationId",
|
|
6440
|
+
type: "string",
|
|
6441
|
+
example: "0x12590ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9f67",
|
|
6442
|
+
description: "Obligation id."
|
|
6443
|
+
}),
|
|
6444
|
+
ApiResponse({
|
|
6445
|
+
status: 200,
|
|
6446
|
+
description: "Success",
|
|
6447
|
+
type: ObligationSingleSuccessResponse
|
|
6448
|
+
})
|
|
6449
|
+
], ObligationsController.prototype, "getObligation", null);
|
|
6450
|
+
ObligationsController = __decorate([ApiTags("Markets"), ApiResponse({
|
|
6451
|
+
status: 400,
|
|
6452
|
+
description: "Bad Request",
|
|
6453
|
+
type: BadRequestResponse
|
|
6454
|
+
})], ObligationsController);
|
|
6455
|
+
let UsersController = class UsersController {
|
|
6456
|
+
async getUserPositions() {}
|
|
6583
6457
|
};
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6458
|
+
__decorate([
|
|
6459
|
+
ApiOperation({
|
|
6460
|
+
methods: ["get"],
|
|
6461
|
+
path: "/v1/users/{userAddress}/positions",
|
|
6462
|
+
summary: "Get user positions",
|
|
6463
|
+
description: "Returns positions for a user with reserved balance. The reserved balance is the amount locked by active offers (max lot upper - offset - consumed)."
|
|
6464
|
+
}),
|
|
6465
|
+
ApiParam({
|
|
6466
|
+
name: "userAddress",
|
|
6467
|
+
type: "string",
|
|
6468
|
+
example: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
|
|
6469
|
+
description: "User address to get positions for."
|
|
6470
|
+
}),
|
|
6471
|
+
ApiQuery({
|
|
6472
|
+
name: "cursor",
|
|
6473
|
+
type: "string",
|
|
6474
|
+
example: offerCursorExample,
|
|
6475
|
+
description: "Pagination cursor in base64url-encoded format."
|
|
6476
|
+
}),
|
|
6477
|
+
ApiQuery({
|
|
6478
|
+
name: "limit",
|
|
6479
|
+
type: "number",
|
|
6480
|
+
example: 10,
|
|
6481
|
+
description: "Maximum number of positions to return."
|
|
6482
|
+
}),
|
|
6483
|
+
ApiResponse({
|
|
6484
|
+
status: 200,
|
|
6485
|
+
description: "Success",
|
|
6486
|
+
type: PositionListResponse
|
|
6487
|
+
})
|
|
6488
|
+
], UsersController.prototype, "getUserPositions", null);
|
|
6489
|
+
UsersController = __decorate([ApiTags("Make"), ApiResponse({
|
|
6490
|
+
status: 400,
|
|
6491
|
+
description: "Bad Request",
|
|
6492
|
+
type: BadRequestResponse
|
|
6493
|
+
})], UsersController);
|
|
6494
|
+
const OpenApi = async () => {
|
|
6495
|
+
return await generateDocument({
|
|
6496
|
+
controllers: [
|
|
6497
|
+
BooksController,
|
|
6498
|
+
ConfigContractsController,
|
|
6499
|
+
ConfigRulesController,
|
|
6500
|
+
OffersController,
|
|
6501
|
+
ObligationsController,
|
|
6502
|
+
HealthController,
|
|
6503
|
+
UsersController,
|
|
6504
|
+
ValidateController
|
|
6627
6505
|
],
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
6635
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
6636
|
-
},
|
|
6637
|
-
{
|
|
6638
|
-
type: Type$1.SellERC20Callback,
|
|
6639
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
6506
|
+
document: {
|
|
6507
|
+
openapi: "3.1.0",
|
|
6508
|
+
info: {
|
|
6509
|
+
title: "Router API",
|
|
6510
|
+
version: "1.0.0",
|
|
6511
|
+
description: "API for the Morpho Router"
|
|
6640
6512
|
},
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
name: maturityName,
|
|
6663
|
-
timestamp: from$16(maturityName)
|
|
6664
|
-
});
|
|
6665
|
-
const callbacks = config.callbacks ?? [];
|
|
6666
|
-
for (const callback of callbacks) {
|
|
6667
|
-
if (callback.type === Type$1.BuyWithEmptyCallback) continue;
|
|
6668
|
-
if (!("addresses" in callback)) continue;
|
|
6669
|
-
for (const address of callback.addresses) rules.push({
|
|
6670
|
-
type: "callback",
|
|
6671
|
-
chain_id: chain.id,
|
|
6672
|
-
address: normalizeAddress(address),
|
|
6673
|
-
callback_type: callback.type
|
|
6674
|
-
});
|
|
6675
|
-
}
|
|
6676
|
-
const loanTokens = assets[chain.id.toString()] ?? [];
|
|
6677
|
-
for (const address of loanTokens) rules.push({
|
|
6678
|
-
type: "loan_token",
|
|
6679
|
-
chain_id: chain.id,
|
|
6680
|
-
address: normalizeAddress(address)
|
|
6681
|
-
});
|
|
6682
|
-
}
|
|
6683
|
-
rules.sort(compareConfigRules);
|
|
6684
|
-
return rules;
|
|
6685
|
-
}
|
|
6686
|
-
/**
|
|
6687
|
-
* Compute a stable checksum for the provided configured rules.
|
|
6688
|
-
* @param rules - Configured rules to checksum.
|
|
6689
|
-
* @returns MD5 checksum.
|
|
6690
|
-
*/
|
|
6691
|
-
function buildConfigRulesChecksum(rules) {
|
|
6692
|
-
const hash = createHash("md5");
|
|
6693
|
-
const orderedRules = [...rules].sort(compareConfigRules);
|
|
6694
|
-
for (const rule of orderedRules) {
|
|
6695
|
-
if (rule.type === "maturity") {
|
|
6696
|
-
hash.update(`maturity:${rule.chain_id}:${rule.name}:${rule.timestamp}\n`);
|
|
6697
|
-
continue;
|
|
6698
|
-
}
|
|
6699
|
-
if (rule.type === "callback") {
|
|
6700
|
-
hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
|
|
6701
|
-
continue;
|
|
6513
|
+
servers: [{
|
|
6514
|
+
url: "https://router.morpho.dev",
|
|
6515
|
+
description: "Production server"
|
|
6516
|
+
}, {
|
|
6517
|
+
url: "http://localhost:7891",
|
|
6518
|
+
description: "Local development server"
|
|
6519
|
+
}],
|
|
6520
|
+
tags: [
|
|
6521
|
+
{
|
|
6522
|
+
name: "Markets",
|
|
6523
|
+
description: "Read-only endpoints to discover markets, order books and fetch current offers."
|
|
6524
|
+
},
|
|
6525
|
+
{
|
|
6526
|
+
name: "Make",
|
|
6527
|
+
description: "Utilities to ease making offers."
|
|
6528
|
+
},
|
|
6529
|
+
{
|
|
6530
|
+
name: "System",
|
|
6531
|
+
description: "Router configuration and health monitoring."
|
|
6532
|
+
}
|
|
6533
|
+
]
|
|
6702
6534
|
}
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
return hash.digest("hex");
|
|
6706
|
-
}
|
|
6707
|
-
function normalizeAddress(address) {
|
|
6708
|
-
return address.toLowerCase();
|
|
6709
|
-
}
|
|
6710
|
-
function compareConfigRules(left, right) {
|
|
6711
|
-
if (left.chain_id !== right.chain_id) return left.chain_id - right.chain_id;
|
|
6712
|
-
if (left.type !== right.type) return left.type.localeCompare(right.type);
|
|
6713
|
-
if (left.type === "maturity" && right.type === "maturity") return left.timestamp - right.timestamp;
|
|
6714
|
-
if (left.type === "callback" && right.type === "callback") {
|
|
6715
|
-
if (left.callback_type !== right.callback_type) return left.callback_type.localeCompare(right.callback_type);
|
|
6716
|
-
return left.address.localeCompare(right.address);
|
|
6717
|
-
}
|
|
6718
|
-
if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
|
|
6719
|
-
return 0;
|
|
6720
|
-
}
|
|
6535
|
+
});
|
|
6536
|
+
};
|
|
6721
6537
|
|
|
6722
6538
|
//#endregion
|
|
6723
|
-
//#region src/api/
|
|
6539
|
+
//#region src/api/Schema/PositionResponse.ts
|
|
6540
|
+
var PositionResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$2 });
|
|
6724
6541
|
/**
|
|
6725
|
-
*
|
|
6726
|
-
* @param
|
|
6727
|
-
* @
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
async function getConfigRules(query, chains) {
|
|
6731
|
-
const parsed = safeParse("get_config_rules", query ?? {});
|
|
6732
|
-
if (!parsed.success) return failure(parsed.error);
|
|
6733
|
-
const { cursor, limit, types, chains: chainIds } = parsed.data;
|
|
6734
|
-
const typeFilter = types?.length ? new Set(types) : null;
|
|
6735
|
-
const chainFilter = chainIds?.length ? new Set(chainIds) : null;
|
|
6736
|
-
const filteredRules = buildConfigRules(chains).filter((rule) => {
|
|
6737
|
-
if (chainFilter && !chainFilter.has(rule.chain_id)) return false;
|
|
6738
|
-
if (typeFilter && !typeFilter.has(rule.type)) return false;
|
|
6739
|
-
return true;
|
|
6740
|
-
});
|
|
6741
|
-
const checksum = buildConfigRulesChecksum(filteredRules);
|
|
6742
|
-
let cursorRule = null;
|
|
6743
|
-
if (cursor) try {
|
|
6744
|
-
cursorRule = parseCursor(cursor);
|
|
6745
|
-
} catch (err) {
|
|
6746
|
-
return failure(err);
|
|
6747
|
-
}
|
|
6748
|
-
if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError("Cursor type must match requested rule types"));
|
|
6749
|
-
if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError("Cursor chain_id must match requested chains"));
|
|
6750
|
-
const startIndex = cursorRule ? findStartIndex(filteredRules, cursorRule) : 0;
|
|
6751
|
-
const page = filteredRules.slice(startIndex, startIndex + limit);
|
|
6752
|
-
const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor(page.at(-1)) : null;
|
|
6753
|
-
const response = success({
|
|
6754
|
-
data: page,
|
|
6755
|
-
cursor: nextCursor
|
|
6756
|
-
});
|
|
6757
|
-
response.body.meta.checksum = checksum;
|
|
6758
|
-
return response;
|
|
6759
|
-
}
|
|
6760
|
-
function formatCursor(rule) {
|
|
6761
|
-
if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
|
|
6762
|
-
if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
|
|
6763
|
-
return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
6764
|
-
}
|
|
6765
|
-
function parseCursor(cursor) {
|
|
6766
|
-
const [type, chain, ...rest] = cursor.split(":");
|
|
6767
|
-
if (!type || !chain || rest.length === 0) throw new BadRequestError("Cursor must be in the format type:chain_id:<value>");
|
|
6768
|
-
if (!isConfigRuleType(type)) throw new BadRequestError("Cursor has an invalid rule type");
|
|
6769
|
-
const chain_id = Number.parseInt(chain, 10);
|
|
6770
|
-
if (!Number.isFinite(chain_id)) throw new BadRequestError("Cursor has an invalid chain_id");
|
|
6771
|
-
if (type === "maturity") {
|
|
6772
|
-
const timestampValue = Number.parseInt(rest[0] ?? "", 10);
|
|
6773
|
-
const nameValue = rest.slice(1).join(":");
|
|
6774
|
-
if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError("Cursor must be in the format maturity:chain_id:timestamp:name");
|
|
6775
|
-
if (!isMaturityType(nameValue)) throw new BadRequestError("Cursor has an invalid maturity name");
|
|
6776
|
-
return {
|
|
6777
|
-
type,
|
|
6778
|
-
chain_id,
|
|
6779
|
-
timestamp: parseMaturity(timestampValue),
|
|
6780
|
-
name: nameValue
|
|
6781
|
-
};
|
|
6782
|
-
}
|
|
6783
|
-
if (type === "callback") {
|
|
6784
|
-
const callbackTypeValue = rest[0] ?? "";
|
|
6785
|
-
const addressValue = rest.slice(1).join(":");
|
|
6786
|
-
if (!callbackTypeValue || !addressValue) throw new BadRequestError("Cursor must be in the format callback:chain_id:callback_type:address");
|
|
6787
|
-
if (!isCallbackType(callbackTypeValue)) throw new BadRequestError("Cursor has an invalid callback type");
|
|
6788
|
-
return {
|
|
6789
|
-
type,
|
|
6790
|
-
chain_id,
|
|
6791
|
-
callback_type: callbackTypeValue,
|
|
6792
|
-
address: parseAddress(addressValue, "Cursor address")
|
|
6793
|
-
};
|
|
6794
|
-
}
|
|
6795
|
-
const addressValue = rest.join(":");
|
|
6796
|
-
if (!addressValue) throw new BadRequestError("Cursor must be in the format loan_token:chain_id:address");
|
|
6542
|
+
* Creates a `PositionResponse` from a `PositionWithReserved`.
|
|
6543
|
+
* @param position - {@link PositionWithReserved}
|
|
6544
|
+
* @returns The created `PositionResponse`. {@link PositionResponse}
|
|
6545
|
+
*/
|
|
6546
|
+
function from$2(position) {
|
|
6797
6547
|
return {
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6548
|
+
chain_id: position.chainId,
|
|
6549
|
+
contract: position.contract,
|
|
6550
|
+
user: position.user,
|
|
6551
|
+
reserved: position.reserved.toString(),
|
|
6552
|
+
block_number: position.blockNumber
|
|
6801
6553
|
};
|
|
6802
6554
|
}
|
|
6803
|
-
function findStartIndex(rules, cursor) {
|
|
6804
|
-
let low = 0;
|
|
6805
|
-
let high = rules.length;
|
|
6806
|
-
while (low < high) {
|
|
6807
|
-
const mid = Math.floor((low + high) / 2);
|
|
6808
|
-
const current = rules[mid];
|
|
6809
|
-
if (compareConfigRules(current, cursor) <= 0) low = mid + 1;
|
|
6810
|
-
else high = mid;
|
|
6811
|
-
}
|
|
6812
|
-
return low;
|
|
6813
|
-
}
|
|
6814
|
-
function parseAddress(address, label) {
|
|
6815
|
-
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError(`${label} must be a valid 20-byte address`);
|
|
6816
|
-
return address.toLowerCase();
|
|
6817
|
-
}
|
|
6818
|
-
function isConfigRuleType(value) {
|
|
6819
|
-
return value === "maturity" || value === "callback" || value === "loan_token";
|
|
6820
|
-
}
|
|
6821
|
-
function isMaturityType(value) {
|
|
6822
|
-
return Object.values(MaturityType).includes(value);
|
|
6823
|
-
}
|
|
6824
|
-
function parseMaturity(value) {
|
|
6825
|
-
try {
|
|
6826
|
-
return from$16(value);
|
|
6827
|
-
} catch (err) {
|
|
6828
|
-
throw new BadRequestError(err instanceof Error ? err.message : "Invalid maturity timestamp");
|
|
6829
|
-
}
|
|
6830
|
-
}
|
|
6831
|
-
function isCallbackType(value) {
|
|
6832
|
-
if (value === Type$1.BuyWithEmptyCallback) return false;
|
|
6833
|
-
return Object.values(Type$1).includes(value);
|
|
6834
|
-
}
|
|
6835
6555
|
|
|
6836
6556
|
//#endregion
|
|
6837
|
-
//#region src/api/
|
|
6838
|
-
const
|
|
6557
|
+
//#region src/api/Schema/requests.ts
|
|
6558
|
+
const MAX_LIMIT = 100;
|
|
6559
|
+
const DEFAULT_LIMIT$4 = 20;
|
|
6560
|
+
const CONFIG_RULES_MAX_LIMIT = 1e3;
|
|
6561
|
+
const CONFIG_RULES_DEFAULT_LIMIT = 100;
|
|
6562
|
+
const CONFIG_CONTRACTS_MAX_LIMIT = 1e3;
|
|
6563
|
+
const CONFIG_CONTRACTS_DEFAULT_LIMIT = 1e3;
|
|
6564
|
+
/** Validate cursor is a valid base64url-encoded JSON object.
|
|
6565
|
+
* Domain layer handles semantic validation of cursor fields. */
|
|
6566
|
+
function isValidBase64urlJson(val) {
|
|
6839
6567
|
try {
|
|
6840
|
-
|
|
6568
|
+
const decoded = Buffer.from(val, "base64url").toString("utf8");
|
|
6569
|
+
JSON.parse(decoded);
|
|
6570
|
+
return true;
|
|
6841
6571
|
} catch {
|
|
6842
|
-
return
|
|
6572
|
+
return false;
|
|
6843
6573
|
}
|
|
6844
|
-
})();
|
|
6845
|
-
/**
|
|
6846
|
-
* Build the OpenAPI document for the router.
|
|
6847
|
-
* @returns OpenAPI document. {@link OpenAPIDocument}
|
|
6848
|
-
*/
|
|
6849
|
-
async function getSwaggerJson() {
|
|
6850
|
-
return OpenApi();
|
|
6851
|
-
}
|
|
6852
|
-
/**
|
|
6853
|
-
* Render the API documentation HTML page.
|
|
6854
|
-
* @returns HTML page as string.
|
|
6855
|
-
*/
|
|
6856
|
-
async function getDocsHtml() {
|
|
6857
|
-
const spec = await OpenApi();
|
|
6858
|
-
return `<!DOCTYPE html>
|
|
6859
|
-
<html>
|
|
6860
|
-
<head>
|
|
6861
|
-
<meta charset="UTF-8">
|
|
6862
|
-
<title>Router API Docs (Scalar)</title>
|
|
6863
|
-
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
6864
|
-
<style>
|
|
6865
|
-
html, body { margin: 0; height: 100%; }
|
|
6866
|
-
api-reference { height: 100%; width: 100%; }
|
|
6867
|
-
</style>
|
|
6868
|
-
</head>
|
|
6869
|
-
<body>
|
|
6870
|
-
<div id="api-container" style="height:100%;width:100%;"></div>
|
|
6871
|
-
<script>
|
|
6872
|
-
window.addEventListener('load', function () {
|
|
6873
|
-
const spec = ${JSON.stringify(spec)};
|
|
6874
|
-
Scalar.createApiReference('#api-container', { spec: { content: spec, hideModels: true } });
|
|
6875
|
-
});
|
|
6876
|
-
<\/script>
|
|
6877
|
-
</body>
|
|
6878
|
-
</html>`;
|
|
6879
|
-
}
|
|
6880
|
-
/**
|
|
6881
|
-
* Finds the integrator.md file.
|
|
6882
|
-
* Handles source, bundled CLI, and Lambda scenarios.
|
|
6883
|
-
*/
|
|
6884
|
-
function findIntegratorMd() {
|
|
6885
|
-
const candidates = [
|
|
6886
|
-
resolve(__dirname, "../../../docs/integrator.md"),
|
|
6887
|
-
resolve(__dirname, "../docs/integrator.md"),
|
|
6888
|
-
resolve(process.cwd(), "docs/integrator.md")
|
|
6889
|
-
];
|
|
6890
|
-
for (const candidate of candidates) if (existsSync(candidate)) return candidate;
|
|
6891
|
-
throw new Error(`integrator.md not found. Tried: ${candidates.join(", ")}`);
|
|
6892
6574
|
}
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
* @returns HTML page with the rendered markdown documentation.
|
|
6896
|
-
*/
|
|
6897
|
-
async function getIntegratorDocsHtml() {
|
|
6898
|
-
return `<!DOCTYPE html>
|
|
6899
|
-
<html>
|
|
6900
|
-
<head>
|
|
6901
|
-
<meta charset="UTF-8">
|
|
6902
|
-
<title>Documentation</title>
|
|
6903
|
-
<style>
|
|
6904
|
-
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.6; }
|
|
6905
|
-
pre { background: #f4f4f4; padding: 1rem; overflow-x: auto; }
|
|
6906
|
-
code { background: #f4f4f4; padding: 0.2rem 0.4rem; }
|
|
6907
|
-
a { color: #0066cc; }
|
|
6908
|
-
</style>
|
|
6909
|
-
</head>
|
|
6910
|
-
<body>
|
|
6911
|
-
<nav><a href="/docs/api">API Reference →</a></nav>
|
|
6912
|
-
${await marked(await readFile(findIntegratorMd(), "utf-8"))}
|
|
6913
|
-
</body>
|
|
6914
|
-
</html>`;
|
|
6575
|
+
function isValidOfferHashCursor(val) {
|
|
6576
|
+
return /^0x[a-f0-9]{64}$/i.test(val);
|
|
6915
6577
|
}
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
try {
|
|
6922
|
-
const parsed = safeParse("get_health", query);
|
|
6923
|
-
if (!parsed.success) return failure(parsed.error);
|
|
6924
|
-
const snapshot = await create$16({
|
|
6925
|
-
db,
|
|
6926
|
-
chainRegistry
|
|
6927
|
-
}).getSnapshot();
|
|
6928
|
-
if (parsed.data.strict && !snapshot.initialized) return failure(new APIError(STATUS_CODE.INTERNAL_SERVER_ERROR, "Indexer block state is not initialized", "INTERNAL_SERVER_ERROR", toSnakeCase$1({
|
|
6929
|
-
missingChains: snapshot.missingChains,
|
|
6930
|
-
missingCollectors: snapshot.missingCollectors
|
|
6931
|
-
})));
|
|
6932
|
-
return success({ data: toSnakeCase$1({
|
|
6933
|
-
status: snapshot.status,
|
|
6934
|
-
initialized: snapshot.initialized,
|
|
6935
|
-
missingChains: snapshot.missingChains,
|
|
6936
|
-
missingCollectors: snapshot.missingCollectors
|
|
6937
|
-
}) });
|
|
6938
|
-
} catch (err) {
|
|
6939
|
-
logger.error({
|
|
6940
|
-
err,
|
|
6941
|
-
msg: "Error getting health status",
|
|
6942
|
-
errorMessage: err instanceof Error ? err.message : String(err),
|
|
6943
|
-
errorStack: err instanceof Error ? err.stack : void 0
|
|
6944
|
-
});
|
|
6945
|
-
return failure(err);
|
|
6578
|
+
const csvArray = (schema) => z$1.preprocess((value) => {
|
|
6579
|
+
if (value === void 0) return void 0;
|
|
6580
|
+
if (Array.isArray(value)) {
|
|
6581
|
+
if (value.some((item) => typeof item !== "string")) return value;
|
|
6582
|
+
return value.flatMap((item) => item.split(",")).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6946
6583
|
}
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
if (!
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6584
|
+
if (typeof value === "string") return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6585
|
+
return value;
|
|
6586
|
+
}, z$1.array(schema)).optional();
|
|
6587
|
+
const PaginationQueryParams = z$1.object({
|
|
6588
|
+
cursor: z$1.string().optional().refine((val) => {
|
|
6589
|
+
if (!val) return true;
|
|
6590
|
+
return isValidBase64urlJson(val);
|
|
6591
|
+
}, { message: "Invalid cursor format. Must be a valid base64url-encoded cursor object" }).meta({
|
|
6592
|
+
description: "Pagination cursor in base64url-encoded format",
|
|
6593
|
+
example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
|
|
6594
|
+
}),
|
|
6595
|
+
limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
|
|
6596
|
+
description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
|
|
6597
|
+
example: 10
|
|
6598
|
+
})
|
|
6599
|
+
});
|
|
6600
|
+
const ConfigRuleTypes = z$1.enum([
|
|
6601
|
+
"maturity",
|
|
6602
|
+
"callback",
|
|
6603
|
+
"loan_token",
|
|
6604
|
+
"oracle"
|
|
6605
|
+
]);
|
|
6606
|
+
const GetConfigRulesQueryParams = z$1.object({
|
|
6607
|
+
cursor: z$1.string().regex(/^(maturity|callback|loan_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
|
|
6608
|
+
description: "Pagination cursor in type:chain_id:<value> format",
|
|
6609
|
+
example: "maturity:1:1730415600:end_of_next_month"
|
|
6610
|
+
}),
|
|
6611
|
+
limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(CONFIG_RULES_MAX_LIMIT, { message: `Limit cannot exceed ${CONFIG_RULES_MAX_LIMIT}` })).optional().default(CONFIG_RULES_DEFAULT_LIMIT).meta({
|
|
6612
|
+
description: `Limit maximum: ${CONFIG_RULES_MAX_LIMIT}. Default: ${CONFIG_RULES_DEFAULT_LIMIT}`,
|
|
6613
|
+
example: 100
|
|
6614
|
+
}),
|
|
6615
|
+
types: csvArray(ConfigRuleTypes).meta({
|
|
6616
|
+
description: "Filter by rule types (comma-separated).",
|
|
6617
|
+
example: "maturity,loan_token,oracle"
|
|
6618
|
+
}),
|
|
6619
|
+
chains: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
6620
|
+
description: "Filter by chain IDs (comma-separated).",
|
|
6621
|
+
example: "1,8453"
|
|
6622
|
+
})
|
|
6623
|
+
});
|
|
6624
|
+
const GetConfigContractsQueryParams = z$1.object({
|
|
6625
|
+
cursor: z$1.string().regex(/^[1-9]\d*:0x[a-fA-F0-9]{40}$/, { message: "Cursor must be in the format chain_id:0x..." }).optional().meta({
|
|
6626
|
+
description: "Pagination cursor in chain_id:address format (lowercase address).",
|
|
6627
|
+
example: "1:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
6628
|
+
}),
|
|
6629
|
+
limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(CONFIG_CONTRACTS_MAX_LIMIT, { message: `Limit cannot exceed ${CONFIG_CONTRACTS_MAX_LIMIT}` })).optional().default(CONFIG_CONTRACTS_DEFAULT_LIMIT).meta({
|
|
6630
|
+
description: `Limit maximum: ${CONFIG_CONTRACTS_MAX_LIMIT}. Default: ${CONFIG_CONTRACTS_DEFAULT_LIMIT}`,
|
|
6631
|
+
example: 1e3
|
|
6632
|
+
}),
|
|
6633
|
+
chains: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
6634
|
+
description: "Filter by chain IDs (comma-separated).",
|
|
6635
|
+
example: "1,8453"
|
|
6636
|
+
})
|
|
6637
|
+
});
|
|
6638
|
+
const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend({
|
|
6639
|
+
cursor: z$1.string().optional().meta({
|
|
6640
|
+
description: "Pagination cursor. Use offer hash (0x...) for maker queries, base64url for obligation queries.",
|
|
6641
|
+
example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
|
|
6642
|
+
}),
|
|
6643
|
+
side: z$1.enum(["buy", "sell"]).optional().meta({
|
|
6644
|
+
description: "Side of the offer. Required when using obligation_id.",
|
|
6645
|
+
example: "buy"
|
|
6646
|
+
}),
|
|
6647
|
+
obligation_id: z$1.string().regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).optional().meta({
|
|
6648
|
+
description: "Offers obligation id. Required when not using maker.",
|
|
6649
|
+
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6650
|
+
}),
|
|
6651
|
+
maker: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Maker must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).optional().meta({
|
|
6652
|
+
description: "Maker address to filter offers by. Alternative to obligation_id + side.",
|
|
6653
|
+
example: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
|
|
6654
|
+
})
|
|
6655
|
+
}).superRefine((val, ctx) => {
|
|
6656
|
+
const hasObligation = val.obligation_id !== void 0;
|
|
6657
|
+
const hasSide = val.side !== void 0;
|
|
6658
|
+
const hasMaker = val.maker !== void 0;
|
|
6659
|
+
if (hasMaker && (hasObligation || hasSide)) {
|
|
6660
|
+
ctx.addIssue({
|
|
6661
|
+
code: "custom",
|
|
6662
|
+
message: "Cannot use both maker and obligation_id/side parameters"
|
|
6976
6663
|
});
|
|
6977
|
-
return
|
|
6664
|
+
return;
|
|
6978
6665
|
}
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
if (!parsed.success) return failure(parsed.error);
|
|
6985
|
-
const snapshot = await create$16({
|
|
6986
|
-
db,
|
|
6987
|
-
chainRegistry
|
|
6988
|
-
}).getSnapshot();
|
|
6989
|
-
if (parsed.data.strict && !snapshot.initialized) return failure(new APIError(STATUS_CODE.INTERNAL_SERVER_ERROR, "Indexer block state is not initialized", "INTERNAL_SERVER_ERROR", toSnakeCase$1({
|
|
6990
|
-
missingChains: snapshot.missingChains,
|
|
6991
|
-
missingCollectors: snapshot.missingCollectors
|
|
6992
|
-
})));
|
|
6993
|
-
const collectors = snapshot.collectors;
|
|
6994
|
-
return success({ data: collectors.map(({ name, chainId, blockNumber, updatedAt, lag, status, initialized }) => toSnakeCase$1({
|
|
6995
|
-
name,
|
|
6996
|
-
chainId,
|
|
6997
|
-
blockNumber,
|
|
6998
|
-
updatedAt,
|
|
6999
|
-
lag,
|
|
7000
|
-
status,
|
|
7001
|
-
initialized
|
|
7002
|
-
})) });
|
|
7003
|
-
} catch (err) {
|
|
7004
|
-
logger.error({
|
|
7005
|
-
err,
|
|
7006
|
-
msg: "Error getting health status for collectors",
|
|
7007
|
-
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7008
|
-
errorStack: err instanceof Error ? err.stack : void 0
|
|
6666
|
+
if (hasMaker) {
|
|
6667
|
+
if (val.cursor !== void 0 && !isValidOfferHashCursor(val.cursor)) ctx.addIssue({
|
|
6668
|
+
code: "custom",
|
|
6669
|
+
path: ["cursor"],
|
|
6670
|
+
message: "Cursor must be a 32-byte hex offer hash when filtering by maker"
|
|
7009
6671
|
});
|
|
7010
|
-
return
|
|
6672
|
+
return;
|
|
6673
|
+
}
|
|
6674
|
+
if (!hasObligation || !hasSide) ctx.addIssue({
|
|
6675
|
+
code: "custom",
|
|
6676
|
+
message: "Must provide either maker or both obligation_id and side"
|
|
6677
|
+
});
|
|
6678
|
+
if (val.cursor !== void 0 && !isValidBase64urlJson(val.cursor)) ctx.addIssue({
|
|
6679
|
+
code: "custom",
|
|
6680
|
+
path: ["cursor"],
|
|
6681
|
+
message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
|
|
6682
|
+
});
|
|
6683
|
+
}).transform((val) => {
|
|
6684
|
+
if (val.maker && val.cursor) return {
|
|
6685
|
+
...val,
|
|
6686
|
+
cursor: val.cursor.toLowerCase()
|
|
6687
|
+
};
|
|
6688
|
+
return val;
|
|
6689
|
+
});
|
|
6690
|
+
const GetObligationsQueryParams = z$1.object({
|
|
6691
|
+
...PaginationQueryParams.shape,
|
|
6692
|
+
cursor: z$1.string().optional().meta({
|
|
6693
|
+
description: "Obligation id cursor",
|
|
6694
|
+
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6695
|
+
}),
|
|
6696
|
+
chains: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
6697
|
+
description: "Filter by chain IDs (comma-separated).",
|
|
6698
|
+
example: "1,8453"
|
|
6699
|
+
}),
|
|
6700
|
+
loan_tokens: csvArray(z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Loan token must be a valid 20-byte address" }).transform((val) => val.toLowerCase())).meta({
|
|
6701
|
+
description: "Filter by loan token addresses (comma-separated).",
|
|
6702
|
+
example: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078,0x34Cf890dB685FC536E05652FB41f02090c3fb751"
|
|
6703
|
+
}),
|
|
6704
|
+
collateral_tokens: csvArray(z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Collateral token must be a valid 20-byte address" }).transform((val) => val.toLowerCase())).meta({
|
|
6705
|
+
description: "Filter by collateral tokens (comma-separated, matches any collateral).",
|
|
6706
|
+
example: "0x34Cf890dB685FC536E05652FB41f02090c3fb751,0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078"
|
|
6707
|
+
}),
|
|
6708
|
+
maturities: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Maturity must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
6709
|
+
description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
|
|
6710
|
+
example: "1761922800,1764524800"
|
|
6711
|
+
})
|
|
6712
|
+
});
|
|
6713
|
+
const GetObligationParams = z$1.object({ obligation_id: z$1.string({ error: "Obligation id is required and must be a valid 32-byte hex string" }).regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).meta({
|
|
6714
|
+
description: "Obligation id",
|
|
6715
|
+
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6716
|
+
}) });
|
|
6717
|
+
/** Validate a book cursor format: {side, lastPrice, offersCursor} */
|
|
6718
|
+
function isValidBookCursor(cursorString) {
|
|
6719
|
+
const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
|
|
6720
|
+
try {
|
|
6721
|
+
const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
|
|
6722
|
+
return (v?.side === "buy" || v?.side === "sell") && isNumericString(v?.lastPrice) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
|
|
6723
|
+
} catch {
|
|
6724
|
+
return false;
|
|
7011
6725
|
}
|
|
7012
6726
|
}
|
|
6727
|
+
const BookPaginationQueryParams = z$1.object({
|
|
6728
|
+
cursor: z$1.string().optional().refine((value) => {
|
|
6729
|
+
if (!value) return true;
|
|
6730
|
+
return isValidBookCursor(value);
|
|
6731
|
+
}, { message: "Invalid cursor format. Must be a valid base64url-encoded book cursor object" }).meta({
|
|
6732
|
+
description: "Pagination cursor in base64url-encoded format for book levels",
|
|
6733
|
+
example: "eyJzaWRlIjoiYnV5IiwibGFzdFJhdGUiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwib2ZmZXJzQ3Vyc29yIjpudWxsfQ"
|
|
6734
|
+
}),
|
|
6735
|
+
limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
|
|
6736
|
+
description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
|
|
6737
|
+
example: 10
|
|
6738
|
+
})
|
|
6739
|
+
});
|
|
6740
|
+
const HealthQueryParams = z$1.object({ strict: z$1.enum([
|
|
6741
|
+
"true",
|
|
6742
|
+
"false",
|
|
6743
|
+
"1",
|
|
6744
|
+
"0"
|
|
6745
|
+
]).transform((value) => value === "true" || value === "1").optional().meta({
|
|
6746
|
+
description: "Enable strict mode to fail health checks when initialization is incomplete.",
|
|
6747
|
+
example: "true"
|
|
6748
|
+
}) });
|
|
6749
|
+
const GetBookParams = z$1.object({
|
|
6750
|
+
...BookPaginationQueryParams.shape,
|
|
6751
|
+
obligation_id: z$1.string({ error: "Obligation id is required and must be a valid 32-byte hex string" }).regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).meta({
|
|
6752
|
+
description: "Obligation id",
|
|
6753
|
+
example: "0x1234567890123456789012345678901234567890123456789012345678901234"
|
|
6754
|
+
}),
|
|
6755
|
+
side: z$1.enum(["buy", "sell"]).meta({
|
|
6756
|
+
description: "Side of the book (buy or sell).",
|
|
6757
|
+
example: "buy"
|
|
6758
|
+
})
|
|
6759
|
+
});
|
|
6760
|
+
const ValidateOffersBody = z$1.object({ offers: z$1.array(z$1.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
|
|
6761
|
+
const GetUserPositionsParams = z$1.object({
|
|
6762
|
+
...PaginationQueryParams.shape,
|
|
6763
|
+
user_address: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
|
|
6764
|
+
description: "User address to get positions for",
|
|
6765
|
+
example: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
|
|
6766
|
+
})
|
|
6767
|
+
});
|
|
6768
|
+
const schemas = {
|
|
6769
|
+
get_health: HealthQueryParams,
|
|
6770
|
+
get_health_collectors: HealthQueryParams,
|
|
6771
|
+
get_health_chains: HealthQueryParams,
|
|
6772
|
+
get_config_contracts: GetConfigContractsQueryParams,
|
|
6773
|
+
get_config_rules: GetConfigRulesQueryParams,
|
|
6774
|
+
get_offers: GetOffersQueryParams,
|
|
6775
|
+
get_obligations: GetObligationsQueryParams,
|
|
6776
|
+
get_obligation: GetObligationParams,
|
|
6777
|
+
get_book: GetBookParams,
|
|
6778
|
+
validate_offers: ValidateOffersBody,
|
|
6779
|
+
get_user_positions: GetUserPositionsParams
|
|
6780
|
+
};
|
|
6781
|
+
function parse(action, query) {
|
|
6782
|
+
return schemas[action].parse(query);
|
|
6783
|
+
}
|
|
6784
|
+
function safeParse(action, query, error) {
|
|
6785
|
+
return schemas[action].safeParse(query, { error });
|
|
6786
|
+
}
|
|
7013
6787
|
|
|
7014
6788
|
//#endregion
|
|
7015
|
-
//#region src/api/Controllers/
|
|
7016
|
-
async function
|
|
6789
|
+
//#region src/api/Controllers/getBook.ts
|
|
6790
|
+
async function getBook(params, db) {
|
|
7017
6791
|
const logger = getLogger();
|
|
7018
|
-
const result = safeParse("
|
|
6792
|
+
const result = safeParse("get_book", params, (issue) => issue.message);
|
|
7019
6793
|
if (!result.success) return failure(result.error);
|
|
7020
6794
|
const query = result.data;
|
|
7021
6795
|
try {
|
|
7022
|
-
const {
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
6796
|
+
const { levels, nextCursor } = await db.book.get({
|
|
6797
|
+
side: query.side,
|
|
6798
|
+
obligationId: query.obligation_id,
|
|
6799
|
+
cursor: query.cursor,
|
|
6800
|
+
limit: query.limit
|
|
6801
|
+
});
|
|
7026
6802
|
return success({
|
|
7027
|
-
data: from$
|
|
7028
|
-
|
|
7029
|
-
ask: { price: 0n },
|
|
7030
|
-
bid: { price: 0n }
|
|
7031
|
-
}),
|
|
7032
|
-
cursor: null
|
|
6803
|
+
data: levels.map(from$5),
|
|
6804
|
+
cursor: nextCursor
|
|
7033
6805
|
});
|
|
7034
6806
|
} catch (err) {
|
|
7035
6807
|
logger.error({
|
|
7036
6808
|
err,
|
|
7037
|
-
msg: "Error get
|
|
6809
|
+
msg: "Error get book",
|
|
7038
6810
|
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7039
6811
|
errorStack: err instanceof Error ? err.stack : void 0
|
|
7040
6812
|
});
|
|
@@ -7043,463 +6815,649 @@ async function getObligation(params, db) {
|
|
|
7043
6815
|
}
|
|
7044
6816
|
|
|
7045
6817
|
//#endregion
|
|
7046
|
-
//#region src/api/Controllers/
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
6818
|
+
//#region src/api/Controllers/getConfigContracts.ts
|
|
6819
|
+
const CONFIG_CONTRACT_NAMES = [
|
|
6820
|
+
"mempool",
|
|
6821
|
+
"multicall",
|
|
6822
|
+
"v2"
|
|
6823
|
+
];
|
|
6824
|
+
/**
|
|
6825
|
+
* Returns contract addresses used by indexers (mempool, v2) plus multicall per chain.
|
|
6826
|
+
* @param query - Raw query parameters containing optional chain filters.
|
|
6827
|
+
* @param chainRegistry - The chain registry instance. {@link ChainRegistry.ChainRegistry}
|
|
6828
|
+
* @returns The indexer contract configuration. {@link ApiPayload.Payload<ConfigContract[]>}
|
|
6829
|
+
*/
|
|
6830
|
+
async function getConfigContracts(query, chainRegistry) {
|
|
6831
|
+
const parsed = safeParse("get_config_contracts", query ?? {});
|
|
6832
|
+
if (!parsed.success) return failure(parsed.error);
|
|
6833
|
+
const { chains: chainsFilter, cursor, limit } = parsed.data;
|
|
6834
|
+
const chainFilter = chainsFilter?.length ? new Set(chainsFilter) : null;
|
|
6835
|
+
const contracts = [];
|
|
6836
|
+
const seenAddresses = /* @__PURE__ */ new Set();
|
|
6837
|
+
for (const chain of chainRegistry.list()) {
|
|
6838
|
+
if (chainFilter && !chainFilter.has(chain.id)) continue;
|
|
6839
|
+
const mempool = chain.custom?.mempool?.address;
|
|
6840
|
+
if (!mempool) return failure(new InternalServerError(`Missing mempool address for chain ${chain.id}.`));
|
|
6841
|
+
const multicall = chain.contracts?.multicall3?.address;
|
|
6842
|
+
if (!multicall) return failure(new InternalServerError(`Missing multicall3 address for chain ${chain.id}.`));
|
|
6843
|
+
const v2 = chain.custom?.morpho?.address;
|
|
6844
|
+
if (!v2) return failure(new InternalServerError(`Missing morpho address for chain ${chain.id}.`));
|
|
6845
|
+
const chainContracts = [
|
|
6846
|
+
{
|
|
6847
|
+
chain_id: chain.id,
|
|
6848
|
+
name: "mempool",
|
|
6849
|
+
address: mempool
|
|
6850
|
+
},
|
|
6851
|
+
{
|
|
6852
|
+
chain_id: chain.id,
|
|
6853
|
+
name: "multicall",
|
|
6854
|
+
address: multicall
|
|
6855
|
+
},
|
|
6856
|
+
{
|
|
6857
|
+
chain_id: chain.id,
|
|
6858
|
+
name: "v2",
|
|
6859
|
+
address: v2
|
|
6860
|
+
}
|
|
6861
|
+
];
|
|
6862
|
+
for (const contract of chainContracts) {
|
|
6863
|
+
const cursorKey = `${contract.chain_id}:${contract.address.toLowerCase()}`;
|
|
6864
|
+
if (seenAddresses.has(cursorKey)) return failure(new InternalServerError(`Duplicate contract address ${contract.address} for chain ${chain.id}.`));
|
|
6865
|
+
seenAddresses.add(cursorKey);
|
|
6866
|
+
contracts.push(contract);
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
contracts.sort((a, b) => {
|
|
6870
|
+
if (a.chain_id !== b.chain_id) return a.chain_id - b.chain_id;
|
|
6871
|
+
const addressCompare = a.address.toLowerCase().localeCompare(b.address.toLowerCase());
|
|
6872
|
+
if (addressCompare !== 0) return addressCompare;
|
|
6873
|
+
return a.name.localeCompare(b.name);
|
|
6874
|
+
});
|
|
6875
|
+
let cursorContract = null;
|
|
6876
|
+
if (cursor) try {
|
|
6877
|
+
cursorContract = parseCursor$1(cursor);
|
|
7074
6878
|
} catch (err) {
|
|
7075
|
-
logger.error({
|
|
7076
|
-
err,
|
|
7077
|
-
msg: "Error get obligations",
|
|
7078
|
-
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7079
|
-
errorStack: err instanceof Error ? err.stack : void 0
|
|
7080
|
-
});
|
|
7081
6879
|
return failure(err);
|
|
7082
6880
|
}
|
|
6881
|
+
const startIndex = cursorContract ? findStartIndex$1(contracts, cursorContract) : 0;
|
|
6882
|
+
const page = contracts.slice(startIndex, startIndex + limit);
|
|
6883
|
+
const nextCursor = startIndex + limit < contracts.length && page.length > 0 ? formatCursor$1(page.at(-1)) : null;
|
|
6884
|
+
return success({
|
|
6885
|
+
data: page,
|
|
6886
|
+
cursor: nextCursor
|
|
6887
|
+
});
|
|
7083
6888
|
}
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
offersCallbacks: () => offersCallbacks,
|
|
7122
|
-
offsets: () => offsets,
|
|
7123
|
-
oracles: () => oracles,
|
|
7124
|
-
positionTypes: () => positionTypes,
|
|
7125
|
-
positions: () => positions,
|
|
7126
|
-
status: () => status,
|
|
7127
|
-
transfers: () => transfers,
|
|
7128
|
-
trees: () => trees,
|
|
7129
|
-
validations: () => validations
|
|
7130
|
-
});
|
|
7131
|
-
const s = pgSchema(VERSION);
|
|
7132
|
-
var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
|
|
7133
|
-
EnumTableName["OBLIGATIONS"] = "obligations";
|
|
7134
|
-
EnumTableName["GROUPS"] = "groups";
|
|
7135
|
-
EnumTableName["CONSUMED_EVENTS"] = "consumed_events";
|
|
7136
|
-
EnumTableName["OBLIGATION_COLLATERALS_V2"] = "obligation_collaterals_v2";
|
|
7137
|
-
EnumTableName["ORACLES"] = "oracles";
|
|
7138
|
-
EnumTableName["OFFERS"] = "offers";
|
|
7139
|
-
EnumTableName["OFFERS_CALLBACKS"] = "offers_callbacks";
|
|
7140
|
-
EnumTableName["CALLBACKS"] = "callbacks";
|
|
7141
|
-
EnumTableName["POSITIONS"] = "positions";
|
|
7142
|
-
EnumTableName["TRANSFERS"] = "transfers";
|
|
7143
|
-
EnumTableName["VALIDATIONS"] = "validations";
|
|
7144
|
-
EnumTableName["COLLECTORS"] = "collectors";
|
|
7145
|
-
EnumTableName["CHAINS"] = "chains";
|
|
7146
|
-
EnumTableName["LOTS"] = "lots";
|
|
7147
|
-
EnumTableName["OFFSETS"] = "offsets";
|
|
7148
|
-
EnumTableName["TREES"] = "trees";
|
|
7149
|
-
EnumTableName["MERKLE_PATHS"] = "merkle_paths";
|
|
7150
|
-
return EnumTableName;
|
|
7151
|
-
}(EnumTableName || {});
|
|
7152
|
-
const TABLE_NAMES = Object.values(EnumTableName);
|
|
7153
|
-
const VERSIONED_TABLE_NAMES = TABLE_NAMES.map((table) => `"${VERSION}"."${table}"`);
|
|
7154
|
-
const obligations = s.table(EnumTableName.OBLIGATIONS, {
|
|
7155
|
-
obligationId: varchar("obligation_id", { length: 66 }).primaryKey(),
|
|
7156
|
-
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
7157
|
-
loanToken: varchar("loan_token", { length: 42 }).notNull(),
|
|
7158
|
-
maturity: integer("maturity").notNull()
|
|
7159
|
-
});
|
|
7160
|
-
const groups = s.table(EnumTableName.GROUPS, {
|
|
7161
|
-
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
7162
|
-
maker: varchar("maker", { length: 42 }).notNull(),
|
|
7163
|
-
group: varchar("group", { length: 66 }).notNull(),
|
|
7164
|
-
consumed: numeric("consumed", {
|
|
7165
|
-
precision: 78,
|
|
7166
|
-
scale: 0
|
|
7167
|
-
}).notNull(),
|
|
7168
|
-
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
7169
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7170
|
-
}, (table) => [primaryKey({
|
|
7171
|
-
columns: [
|
|
7172
|
-
table.chainId,
|
|
7173
|
-
table.maker,
|
|
7174
|
-
table.group
|
|
7175
|
-
],
|
|
7176
|
-
name: "groups_pk"
|
|
7177
|
-
}), index("groups_chain_id_maker_group_consumed_idx").on(table.chainId, table.maker, table.group, table.consumed)]);
|
|
7178
|
-
const consumedEvents = s.table(EnumTableName.CONSUMED_EVENTS, {
|
|
7179
|
-
eventId: varchar("event_id", { length: 128 }).primaryKey(),
|
|
7180
|
-
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
7181
|
-
maker: varchar("maker", { length: 42 }).notNull(),
|
|
7182
|
-
group: varchar("group", { length: 66 }).notNull(),
|
|
7183
|
-
amount: numeric("amount", {
|
|
7184
|
-
precision: 78,
|
|
7185
|
-
scale: 0
|
|
7186
|
-
}).notNull(),
|
|
7187
|
-
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
7188
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7189
|
-
}, (t) => [
|
|
7190
|
-
foreignKey({
|
|
7191
|
-
columns: [
|
|
7192
|
-
t.chainId,
|
|
7193
|
-
t.maker,
|
|
7194
|
-
t.group
|
|
7195
|
-
],
|
|
7196
|
-
foreignColumns: [
|
|
7197
|
-
groups.chainId,
|
|
7198
|
-
groups.maker,
|
|
7199
|
-
groups.group
|
|
7200
|
-
],
|
|
7201
|
-
name: "consumed_events_groups_fk"
|
|
7202
|
-
}).onDelete("cascade"),
|
|
7203
|
-
index("consumed_events_group_idx").on(t.chainId, t.maker, t.group),
|
|
7204
|
-
index("consumed_events_block_number_idx").on(t.blockNumber)
|
|
7205
|
-
]);
|
|
7206
|
-
const obligationCollateralsV2 = s.table(EnumTableName.OBLIGATION_COLLATERALS_V2, {
|
|
7207
|
-
obligationId: varchar("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
|
|
7208
|
-
asset: varchar("asset", { length: 42 }).notNull(),
|
|
7209
|
-
oracleChainId: bigint("oracle_chain_id", { mode: "number" }).$type().notNull(),
|
|
7210
|
-
oracleAddress: varchar("oracle_address", { length: 42 }).notNull(),
|
|
7211
|
-
lltv: bigint("lltv", { mode: "bigint" }).notNull(),
|
|
7212
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7213
|
-
}, (table) => [
|
|
7214
|
-
primaryKey({
|
|
7215
|
-
columns: [table.obligationId, table.asset],
|
|
7216
|
-
name: "obligation_collaterals_v2_pk"
|
|
7217
|
-
}),
|
|
7218
|
-
foreignKey({
|
|
7219
|
-
columns: [table.oracleChainId, table.oracleAddress],
|
|
7220
|
-
foreignColumns: [oracles.chainId, oracles.address],
|
|
7221
|
-
name: "obligation_collaterals_v2_oracles_fk"
|
|
7222
|
-
}),
|
|
7223
|
-
index("obligation_collaterals_v2_obligation_id_idx").on(table.obligationId),
|
|
7224
|
-
index("obligation_collaterals_v2_oracle_fk_idx").on(table.oracleChainId, table.oracleAddress)
|
|
7225
|
-
]);
|
|
7226
|
-
const oracles = s.table(EnumTableName.ORACLES, {
|
|
7227
|
-
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
7228
|
-
address: varchar("address", { length: 42 }).notNull(),
|
|
7229
|
-
price: numeric("price", {
|
|
7230
|
-
precision: 78,
|
|
7231
|
-
scale: 0
|
|
7232
|
-
}),
|
|
7233
|
-
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
7234
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7235
|
-
}, (table) => [primaryKey({
|
|
7236
|
-
columns: [table.chainId, table.address],
|
|
7237
|
-
name: "oracles_pk"
|
|
7238
|
-
})]);
|
|
7239
|
-
const offers = s.table(EnumTableName.OFFERS, {
|
|
7240
|
-
hash: varchar("hash", { length: 66 }).primaryKey(),
|
|
7241
|
-
obligationId: varchar("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
|
|
7242
|
-
assets: numeric("assets", {
|
|
7243
|
-
precision: 78,
|
|
7244
|
-
scale: 0
|
|
7245
|
-
}).notNull(),
|
|
7246
|
-
obligationUnits: numeric("obligation_units", {
|
|
7247
|
-
precision: 78,
|
|
7248
|
-
scale: 0
|
|
7249
|
-
}).notNull().default("0"),
|
|
7250
|
-
obligationShares: numeric("obligation_shares", {
|
|
7251
|
-
precision: 78,
|
|
7252
|
-
scale: 0
|
|
7253
|
-
}).notNull().default("0"),
|
|
7254
|
-
price: numeric("price", {
|
|
7255
|
-
precision: 78,
|
|
7256
|
-
scale: 0
|
|
7257
|
-
}).notNull(),
|
|
7258
|
-
maturity: integer("maturity").notNull(),
|
|
7259
|
-
expiry: integer("expiry").notNull(),
|
|
7260
|
-
start: integer("start").notNull(),
|
|
7261
|
-
groupChainId: bigint("group_chain_id", { mode: "number" }).$type().notNull(),
|
|
7262
|
-
groupMaker: varchar("group_maker", { length: 42 }).notNull(),
|
|
7263
|
-
group: varchar("group_group", { length: 66 }).notNull(),
|
|
7264
|
-
session: varchar("session", { length: 66 }).notNull(),
|
|
7265
|
-
buy: boolean("buy").notNull(),
|
|
7266
|
-
callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
|
|
7267
|
-
callbackData: text("callback_data").notNull(),
|
|
7268
|
-
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
7269
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7270
|
-
}, (table) => [
|
|
7271
|
-
foreignKey({
|
|
7272
|
-
columns: [
|
|
7273
|
-
table.groupChainId,
|
|
7274
|
-
table.groupMaker,
|
|
7275
|
-
table.group
|
|
7276
|
-
],
|
|
7277
|
-
foreignColumns: [
|
|
7278
|
-
groups.chainId,
|
|
7279
|
-
groups.maker,
|
|
7280
|
-
groups.group
|
|
7281
|
-
],
|
|
7282
|
-
name: "offers_groups_fk"
|
|
7283
|
-
}).onDelete("cascade"),
|
|
7284
|
-
index("offers_group_fk_idx").on(table.groupChainId, table.groupMaker, table.group),
|
|
7285
|
-
index("offers_group_and_hash_idx").on(table.groupChainId, table.groupMaker, table.group, table.hash),
|
|
7286
|
-
index("offers_obligation_id_side_idx").on(table.obligationId, table.buy)
|
|
7287
|
-
]);
|
|
7288
|
-
const offersCallbacks = s.table(EnumTableName.OFFERS_CALLBACKS, {
|
|
7289
|
-
offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
|
|
7290
|
-
callbackId: varchar("callback_id", { length: 66 })
|
|
7291
|
-
}, (table) => [primaryKey({
|
|
7292
|
-
columns: [table.offerHash, table.callbackId],
|
|
7293
|
-
name: "offers_callbacks_pk"
|
|
7294
|
-
})]);
|
|
7295
|
-
const callbacks = s.table(EnumTableName.CALLBACKS, {
|
|
7296
|
-
id: varchar("id", { length: 66 }).primaryKey(),
|
|
7297
|
-
positionChainId: bigint("position_chain_id", { mode: "number" }).$type().notNull(),
|
|
7298
|
-
positionContract: varchar("position_contract", { length: 42 }).notNull(),
|
|
7299
|
-
positionUser: varchar("position_user", { length: 42 }).notNull(),
|
|
7300
|
-
amount: numeric("amount", {
|
|
7301
|
-
precision: 78,
|
|
7302
|
-
scale: 0
|
|
7303
|
-
})
|
|
7304
|
-
}, (table) => [foreignKey({
|
|
7305
|
-
columns: [
|
|
7306
|
-
table.positionChainId,
|
|
7307
|
-
table.positionContract,
|
|
7308
|
-
table.positionUser
|
|
6889
|
+
function parseCursor$1(cursor) {
|
|
6890
|
+
const [chain, address] = cursor.split(":", 2);
|
|
6891
|
+
if (!chain || !address) throw new BadRequestError("Cursor must be in the format chain_id:0x...");
|
|
6892
|
+
return {
|
|
6893
|
+
chain_id: Number.parseInt(chain, 10),
|
|
6894
|
+
address: address.toLowerCase()
|
|
6895
|
+
};
|
|
6896
|
+
}
|
|
6897
|
+
function formatCursor$1(contract) {
|
|
6898
|
+
return `${contract.chain_id}:${contract.address.toLowerCase()}`;
|
|
6899
|
+
}
|
|
6900
|
+
function findStartIndex$1(contracts, cursor) {
|
|
6901
|
+
let low = 0;
|
|
6902
|
+
let high = contracts.length;
|
|
6903
|
+
while (low < high) {
|
|
6904
|
+
const mid = Math.floor((low + high) / 2);
|
|
6905
|
+
const current = contracts[mid];
|
|
6906
|
+
if (compareContract(current, cursor) <= 0) low = mid + 1;
|
|
6907
|
+
else high = mid;
|
|
6908
|
+
}
|
|
6909
|
+
return low;
|
|
6910
|
+
}
|
|
6911
|
+
function compareContract(contract, cursor) {
|
|
6912
|
+
if (contract.chain_id !== cursor.chain_id) return contract.chain_id - cursor.chain_id;
|
|
6913
|
+
return contract.address.toLowerCase().localeCompare(cursor.address.toLowerCase());
|
|
6914
|
+
}
|
|
6915
|
+
|
|
6916
|
+
//#endregion
|
|
6917
|
+
//#region src/gatekeeper/GateConfig.ts
|
|
6918
|
+
const assets = {
|
|
6919
|
+
[ChainId.ETHEREUM.toString()]: [
|
|
6920
|
+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
6921
|
+
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
6922
|
+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
6923
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
6924
|
+
"0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
|
|
6925
|
+
"0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
|
|
7309
6926
|
],
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
6927
|
+
[ChainId.BASE.toString()]: [
|
|
6928
|
+
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
6929
|
+
"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
|
6930
|
+
"0x4200000000000000000000000000000000000006",
|
|
6931
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
6932
|
+
"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
|
6933
|
+
"0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
|
|
6934
|
+
"0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
|
|
7314
6935
|
],
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
group: varchar("group", { length: 66 }).notNull(),
|
|
7322
|
-
lower: numeric("lower", {
|
|
7323
|
-
precision: 78,
|
|
7324
|
-
scale: 0
|
|
7325
|
-
}).notNull(),
|
|
7326
|
-
upper: numeric("upper", {
|
|
7327
|
-
precision: 78,
|
|
7328
|
-
scale: 0
|
|
7329
|
-
}).notNull()
|
|
7330
|
-
}, (table) => [
|
|
7331
|
-
primaryKey({
|
|
7332
|
-
columns: [
|
|
7333
|
-
table.chainId,
|
|
7334
|
-
table.user,
|
|
7335
|
-
table.contract,
|
|
7336
|
-
table.group
|
|
7337
|
-
],
|
|
7338
|
-
name: "lots_pk"
|
|
7339
|
-
}),
|
|
7340
|
-
foreignKey({
|
|
7341
|
-
columns: [
|
|
7342
|
-
table.chainId,
|
|
7343
|
-
table.contract,
|
|
7344
|
-
table.user
|
|
7345
|
-
],
|
|
7346
|
-
foreignColumns: [
|
|
7347
|
-
positions.chainId,
|
|
7348
|
-
positions.contract,
|
|
7349
|
-
positions.user
|
|
7350
|
-
],
|
|
7351
|
-
name: "lots_positions_fk"
|
|
7352
|
-
}).onDelete("cascade"),
|
|
7353
|
-
foreignKey({
|
|
7354
|
-
columns: [
|
|
7355
|
-
table.chainId,
|
|
7356
|
-
table.user,
|
|
7357
|
-
table.group
|
|
7358
|
-
],
|
|
7359
|
-
foreignColumns: [
|
|
7360
|
-
groups.chainId,
|
|
7361
|
-
groups.maker,
|
|
7362
|
-
groups.group
|
|
7363
|
-
],
|
|
7364
|
-
name: "lots_groups_fk"
|
|
7365
|
-
}).onDelete("cascade")
|
|
7366
|
-
]);
|
|
7367
|
-
const offsets = s.table(EnumTableName.OFFSETS, {
|
|
7368
|
-
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
7369
|
-
user: varchar("user", { length: 42 }).notNull(),
|
|
7370
|
-
contract: varchar("contract", { length: 42 }).notNull(),
|
|
7371
|
-
group: varchar("group", { length: 66 }).notNull(),
|
|
7372
|
-
value: numeric("value", {
|
|
7373
|
-
precision: 78,
|
|
7374
|
-
scale: 0
|
|
7375
|
-
}).notNull()
|
|
7376
|
-
}, (table) => [primaryKey({
|
|
7377
|
-
columns: [
|
|
7378
|
-
table.chainId,
|
|
7379
|
-
table.user,
|
|
7380
|
-
table.contract,
|
|
7381
|
-
table.group
|
|
6936
|
+
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
6937
|
+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
6938
|
+
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
6939
|
+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
6940
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
6941
|
+
"0xce79ddb3152d52ff8fe65a4c7e058b035fcb560a"
|
|
7382
6942
|
],
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
6943
|
+
[ChainId.ANVIL.toString()]: [
|
|
6944
|
+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
6945
|
+
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
6946
|
+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
6947
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
6948
|
+
]
|
|
6949
|
+
};
|
|
6950
|
+
const oracles = {
|
|
6951
|
+
[ChainId.ETHEREUM.toString()]: [
|
|
6952
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
6953
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
6954
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
6955
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
6956
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
6957
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
7389
6958
|
],
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
6959
|
+
[ChainId.BASE.toString()]: [
|
|
6960
|
+
"0xD09048c8B568Dbf5f189302beA26c9edABFC4858",
|
|
6961
|
+
"0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4",
|
|
6962
|
+
"0x05D2618404668D725B66c0f32B39e4EC15B393dC",
|
|
6963
|
+
"0xE1bb8E5b4930eC9FeC7f7943FCF6227649F14B37",
|
|
6964
|
+
"0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9",
|
|
6965
|
+
"0x10b95702a0ce895972C91e432C4f7E19811D320E",
|
|
6966
|
+
"0x8C87DbD7A0c647A4291592Bc2994dbF95880fE2F",
|
|
6967
|
+
"0x4A11590e5326138B514E08A9B52202D42077Ca65",
|
|
6968
|
+
"0xa54122f0E0766258377Ffe732e454A3248f454F4"
|
|
7394
6969
|
],
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
const positions = s.table(EnumTableName.POSITIONS, {
|
|
7403
|
-
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
7404
|
-
contract: varchar("contract", { length: 42 }).notNull(),
|
|
7405
|
-
user: varchar("user", { length: 42 }).notNull(),
|
|
7406
|
-
positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
|
|
7407
|
-
balance: numeric("balance", {
|
|
7408
|
-
precision: 78,
|
|
7409
|
-
scale: 0
|
|
7410
|
-
}),
|
|
7411
|
-
asset: varchar("asset", { length: 42 }),
|
|
7412
|
-
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
7413
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7414
|
-
}, (table) => [primaryKey({
|
|
7415
|
-
columns: [
|
|
7416
|
-
table.chainId,
|
|
7417
|
-
table.contract,
|
|
7418
|
-
table.user
|
|
6970
|
+
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
6971
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
6972
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
6973
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
6974
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
6975
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
6976
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
7419
6977
|
],
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
]
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
]
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
6978
|
+
[ChainId.ANVIL.toString()]: [
|
|
6979
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
6980
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
6981
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
6982
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
6983
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
6984
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
6985
|
+
]
|
|
6986
|
+
};
|
|
6987
|
+
const configs = {
|
|
6988
|
+
ethereum: {
|
|
6989
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
6990
|
+
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
6991
|
+
},
|
|
6992
|
+
base: {
|
|
6993
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
6994
|
+
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
6995
|
+
},
|
|
6996
|
+
"ethereum-virtual-testnet": {
|
|
6997
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
6998
|
+
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
6999
|
+
},
|
|
7000
|
+
anvil: {
|
|
7001
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
7002
|
+
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
7003
|
+
}
|
|
7004
|
+
};
|
|
7005
|
+
|
|
7006
|
+
//#endregion
|
|
7007
|
+
//#region src/gatekeeper/ConfigRules.ts
|
|
7008
|
+
/**
|
|
7009
|
+
* Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
|
|
7010
|
+
* @param chains - Chains to include in the configured rules.
|
|
7011
|
+
* @returns Sorted list of config rules.
|
|
7012
|
+
*/
|
|
7013
|
+
function buildConfigRules(chains) {
|
|
7014
|
+
const rules = [];
|
|
7015
|
+
for (const chain of chains) {
|
|
7016
|
+
const maturities = configs[chain.name].maturities ?? [];
|
|
7017
|
+
for (const maturityName of maturities) rules.push({
|
|
7018
|
+
type: "maturity",
|
|
7019
|
+
chain_id: chain.id,
|
|
7020
|
+
name: maturityName,
|
|
7021
|
+
timestamp: from$16(maturityName)
|
|
7022
|
+
});
|
|
7023
|
+
const loanTokens = assets[chain.id.toString()] ?? [];
|
|
7024
|
+
for (const address of loanTokens) rules.push({
|
|
7025
|
+
type: "loan_token",
|
|
7026
|
+
chain_id: chain.id,
|
|
7027
|
+
address: normalizeAddress(address)
|
|
7028
|
+
});
|
|
7029
|
+
const oracles$2 = oracles[chain.id.toString()] ?? [];
|
|
7030
|
+
for (const address of oracles$2) rules.push({
|
|
7031
|
+
type: "oracle",
|
|
7032
|
+
chain_id: chain.id,
|
|
7033
|
+
address: normalizeAddress(address)
|
|
7034
|
+
});
|
|
7035
|
+
}
|
|
7036
|
+
rules.sort(compareConfigRules);
|
|
7037
|
+
return rules;
|
|
7038
|
+
}
|
|
7039
|
+
/**
|
|
7040
|
+
* Compute a stable checksum for the provided configured rules.
|
|
7041
|
+
* @param rules - Configured rules to checksum.
|
|
7042
|
+
* @returns MD5 checksum.
|
|
7043
|
+
*/
|
|
7044
|
+
function buildConfigRulesChecksum(rules) {
|
|
7045
|
+
const hash = createHash("md5");
|
|
7046
|
+
const orderedRules = [...rules].sort(compareConfigRules);
|
|
7047
|
+
for (const rule of orderedRules) {
|
|
7048
|
+
if (rule.type === "maturity") {
|
|
7049
|
+
hash.update(`maturity:${rule.chain_id}:${rule.name}:${rule.timestamp}\n`);
|
|
7050
|
+
continue;
|
|
7051
|
+
}
|
|
7052
|
+
if (rule.type === "callback") {
|
|
7053
|
+
hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
|
|
7054
|
+
continue;
|
|
7055
|
+
}
|
|
7056
|
+
if (rule.type === "oracle") {
|
|
7057
|
+
hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
|
|
7058
|
+
continue;
|
|
7059
|
+
}
|
|
7060
|
+
hash.update(`loan_token:${rule.chain_id}:${rule.address}\n`);
|
|
7061
|
+
}
|
|
7062
|
+
return hash.digest("hex");
|
|
7063
|
+
}
|
|
7064
|
+
function normalizeAddress(address) {
|
|
7065
|
+
return address.toLowerCase();
|
|
7066
|
+
}
|
|
7067
|
+
function compareConfigRules(left, right) {
|
|
7068
|
+
if (left.chain_id !== right.chain_id) return left.chain_id - right.chain_id;
|
|
7069
|
+
if (left.type !== right.type) return left.type.localeCompare(right.type);
|
|
7070
|
+
if (left.type === "maturity" && right.type === "maturity") return left.timestamp - right.timestamp;
|
|
7071
|
+
if (left.type === "callback" && right.type === "callback") {
|
|
7072
|
+
if (left.callback_type !== right.callback_type) return left.callback_type.localeCompare(right.callback_type);
|
|
7073
|
+
return left.address.localeCompare(right.address);
|
|
7074
|
+
}
|
|
7075
|
+
if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
|
|
7076
|
+
if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
|
|
7077
|
+
return 0;
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
//#endregion
|
|
7081
|
+
//#region src/api/Controllers/getConfigRules.ts
|
|
7082
|
+
/**
|
|
7083
|
+
* Returns configured rules for the configured chains.
|
|
7084
|
+
* @param query - Raw query parameters containing filters/cursor/limit.
|
|
7085
|
+
* @param chains - Chains to include in the configured rules.
|
|
7086
|
+
* @returns Config rules response payload. {@link ApiPayload.Payload}
|
|
7087
|
+
*/
|
|
7088
|
+
async function getConfigRules(query, chains) {
|
|
7089
|
+
const parsed = safeParse("get_config_rules", query ?? {});
|
|
7090
|
+
if (!parsed.success) return failure(parsed.error);
|
|
7091
|
+
const { cursor, limit, types, chains: chainIds } = parsed.data;
|
|
7092
|
+
const typeFilter = types?.length ? new Set(types) : null;
|
|
7093
|
+
const chainFilter = chainIds?.length ? new Set(chainIds) : null;
|
|
7094
|
+
const filteredRules = buildConfigRules(chains).filter((rule) => {
|
|
7095
|
+
if (chainFilter && !chainFilter.has(rule.chain_id)) return false;
|
|
7096
|
+
if (typeFilter && !typeFilter.has(rule.type)) return false;
|
|
7097
|
+
return true;
|
|
7098
|
+
});
|
|
7099
|
+
const checksum = buildConfigRulesChecksum(filteredRules);
|
|
7100
|
+
let cursorRule = null;
|
|
7101
|
+
if (cursor) try {
|
|
7102
|
+
cursorRule = parseCursor(cursor);
|
|
7103
|
+
} catch (err) {
|
|
7104
|
+
return failure(err);
|
|
7105
|
+
}
|
|
7106
|
+
if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError("Cursor type must match requested rule types"));
|
|
7107
|
+
if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError("Cursor chain_id must match requested chains"));
|
|
7108
|
+
const startIndex = cursorRule ? findStartIndex(filteredRules, cursorRule) : 0;
|
|
7109
|
+
const page = filteredRules.slice(startIndex, startIndex + limit);
|
|
7110
|
+
const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor(page.at(-1)) : null;
|
|
7111
|
+
const response = success({
|
|
7112
|
+
data: page,
|
|
7113
|
+
cursor: nextCursor
|
|
7114
|
+
});
|
|
7115
|
+
response.body.meta.checksum = checksum;
|
|
7116
|
+
return response;
|
|
7117
|
+
}
|
|
7118
|
+
function formatCursor(rule) {
|
|
7119
|
+
if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
|
|
7120
|
+
if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
|
|
7121
|
+
if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
7122
|
+
return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
7123
|
+
}
|
|
7124
|
+
function parseCursor(cursor) {
|
|
7125
|
+
const [type, chain, ...rest] = cursor.split(":");
|
|
7126
|
+
if (!type || !chain || rest.length === 0) throw new BadRequestError("Cursor must be in the format type:chain_id:<value>");
|
|
7127
|
+
if (!isConfigRuleType(type)) throw new BadRequestError("Cursor has an invalid rule type");
|
|
7128
|
+
const chain_id = Number.parseInt(chain, 10);
|
|
7129
|
+
if (!Number.isFinite(chain_id)) throw new BadRequestError("Cursor has an invalid chain_id");
|
|
7130
|
+
if (type === "maturity") {
|
|
7131
|
+
const timestampValue = Number.parseInt(rest[0] ?? "", 10);
|
|
7132
|
+
const nameValue = rest.slice(1).join(":");
|
|
7133
|
+
if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError("Cursor must be in the format maturity:chain_id:timestamp:name");
|
|
7134
|
+
if (!isMaturityType(nameValue)) throw new BadRequestError("Cursor has an invalid maturity name");
|
|
7135
|
+
return {
|
|
7136
|
+
type,
|
|
7137
|
+
chain_id,
|
|
7138
|
+
timestamp: parseMaturity(timestampValue),
|
|
7139
|
+
name: nameValue
|
|
7140
|
+
};
|
|
7141
|
+
}
|
|
7142
|
+
if (type === "callback") {
|
|
7143
|
+
const callbackTypeValue = rest[0] ?? "";
|
|
7144
|
+
const addressValue = rest.slice(1).join(":");
|
|
7145
|
+
if (!callbackTypeValue || !addressValue) throw new BadRequestError("Cursor must be in the format callback:chain_id:callback_type:address");
|
|
7146
|
+
if (!isCallbackType(callbackTypeValue)) throw new BadRequestError("Cursor has an invalid callback type");
|
|
7147
|
+
return {
|
|
7148
|
+
type,
|
|
7149
|
+
chain_id,
|
|
7150
|
+
callback_type: callbackTypeValue,
|
|
7151
|
+
address: parseAddress(addressValue, "Cursor address")
|
|
7152
|
+
};
|
|
7153
|
+
}
|
|
7154
|
+
if (type === "loan_token" || type === "oracle") {
|
|
7155
|
+
const addressValue = rest.join(":");
|
|
7156
|
+
if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
|
|
7157
|
+
return {
|
|
7158
|
+
type,
|
|
7159
|
+
chain_id,
|
|
7160
|
+
address: parseAddress(addressValue, "Cursor address")
|
|
7161
|
+
};
|
|
7162
|
+
}
|
|
7163
|
+
throw new BadRequestError("Cursor has an invalid rule type");
|
|
7164
|
+
}
|
|
7165
|
+
function findStartIndex(rules, cursor) {
|
|
7166
|
+
let low = 0;
|
|
7167
|
+
let high = rules.length;
|
|
7168
|
+
while (low < high) {
|
|
7169
|
+
const mid = Math.floor((low + high) / 2);
|
|
7170
|
+
const current = rules[mid];
|
|
7171
|
+
if (compareConfigRules(current, cursor) <= 0) low = mid + 1;
|
|
7172
|
+
else high = mid;
|
|
7173
|
+
}
|
|
7174
|
+
return low;
|
|
7175
|
+
}
|
|
7176
|
+
function parseAddress(address, label) {
|
|
7177
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError(`${label} must be a valid 20-byte address`);
|
|
7178
|
+
return address.toLowerCase();
|
|
7179
|
+
}
|
|
7180
|
+
function isConfigRuleType(value) {
|
|
7181
|
+
return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
|
|
7182
|
+
}
|
|
7183
|
+
function isMaturityType(value) {
|
|
7184
|
+
return Object.values(MaturityType).includes(value);
|
|
7185
|
+
}
|
|
7186
|
+
function parseMaturity(value) {
|
|
7187
|
+
try {
|
|
7188
|
+
return from$16(value);
|
|
7189
|
+
} catch (err) {
|
|
7190
|
+
throw new BadRequestError(err instanceof Error ? err.message : "Invalid maturity timestamp");
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
function isCallbackType(value) {
|
|
7194
|
+
if (value === Type$1.BuyWithEmptyCallback) return false;
|
|
7195
|
+
return Object.values(Type$1).includes(value);
|
|
7196
|
+
}
|
|
7197
|
+
|
|
7198
|
+
//#endregion
|
|
7199
|
+
//#region src/api/Controllers/getDocs.ts
|
|
7200
|
+
const __dirname = (() => {
|
|
7201
|
+
try {
|
|
7202
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
7203
|
+
} catch {
|
|
7204
|
+
return process.cwd();
|
|
7205
|
+
}
|
|
7206
|
+
})();
|
|
7207
|
+
/**
|
|
7208
|
+
* Build the OpenAPI document for the router.
|
|
7209
|
+
* @returns OpenAPI document. {@link OpenAPIDocument}
|
|
7210
|
+
*/
|
|
7211
|
+
async function getSwaggerJson() {
|
|
7212
|
+
return OpenApi();
|
|
7213
|
+
}
|
|
7214
|
+
/**
|
|
7215
|
+
* Render the API documentation HTML page.
|
|
7216
|
+
* @returns HTML page as string.
|
|
7217
|
+
*/
|
|
7218
|
+
async function getDocsHtml() {
|
|
7219
|
+
const spec = await OpenApi();
|
|
7220
|
+
return `<!DOCTYPE html>
|
|
7221
|
+
<html>
|
|
7222
|
+
<head>
|
|
7223
|
+
<meta charset="UTF-8">
|
|
7224
|
+
<title>Router API Docs (Scalar)</title>
|
|
7225
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
7226
|
+
<style>
|
|
7227
|
+
html, body { margin: 0; height: 100%; }
|
|
7228
|
+
api-reference { height: 100%; width: 100%; }
|
|
7229
|
+
</style>
|
|
7230
|
+
</head>
|
|
7231
|
+
<body>
|
|
7232
|
+
<div id="api-container" style="height:100%;width:100%;"></div>
|
|
7233
|
+
<script>
|
|
7234
|
+
window.addEventListener('load', function () {
|
|
7235
|
+
const spec = ${JSON.stringify(spec)};
|
|
7236
|
+
Scalar.createApiReference('#api-container', { spec: { content: spec, hideModels: true } });
|
|
7237
|
+
});
|
|
7238
|
+
<\/script>
|
|
7239
|
+
</body>
|
|
7240
|
+
</html>`;
|
|
7241
|
+
}
|
|
7242
|
+
/**
|
|
7243
|
+
* Finds the integrator.md file.
|
|
7244
|
+
* Handles source, bundled CLI, and Lambda scenarios.
|
|
7245
|
+
*/
|
|
7246
|
+
function findIntegratorMd() {
|
|
7247
|
+
const candidates = [
|
|
7248
|
+
resolve(__dirname, "../../../docs/integrator.md"),
|
|
7249
|
+
resolve(__dirname, "../docs/integrator.md"),
|
|
7250
|
+
resolve(process.cwd(), "docs/integrator.md")
|
|
7251
|
+
];
|
|
7252
|
+
for (const candidate of candidates) if (existsSync(candidate)) return candidate;
|
|
7253
|
+
throw new Error(`integrator.md not found. Tried: ${candidates.join(", ")}`);
|
|
7254
|
+
}
|
|
7255
|
+
/**
|
|
7256
|
+
* Renders the integrator documentation as HTML.
|
|
7257
|
+
* @returns HTML page with the rendered markdown documentation.
|
|
7258
|
+
*/
|
|
7259
|
+
async function getIntegratorDocsHtml() {
|
|
7260
|
+
return `<!DOCTYPE html>
|
|
7261
|
+
<html>
|
|
7262
|
+
<head>
|
|
7263
|
+
<meta charset="UTF-8">
|
|
7264
|
+
<title>Documentation</title>
|
|
7265
|
+
<style>
|
|
7266
|
+
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.6; }
|
|
7267
|
+
pre { background: #f4f4f4; padding: 1rem; overflow-x: auto; }
|
|
7268
|
+
code { background: #f4f4f4; padding: 0.2rem 0.4rem; }
|
|
7269
|
+
a { color: #0066cc; }
|
|
7270
|
+
</style>
|
|
7271
|
+
</head>
|
|
7272
|
+
<body>
|
|
7273
|
+
<nav><a href="/docs/api">API Reference →</a></nav>
|
|
7274
|
+
${await marked(await readFile(findIntegratorMd(), "utf-8"))}
|
|
7275
|
+
</body>
|
|
7276
|
+
</html>`;
|
|
7277
|
+
}
|
|
7278
|
+
|
|
7279
|
+
//#endregion
|
|
7280
|
+
//#region src/api/Controllers/getHealth.ts
|
|
7281
|
+
async function getHealth(query, db, chainRegistry) {
|
|
7282
|
+
const logger = getLogger();
|
|
7283
|
+
try {
|
|
7284
|
+
const parsed = safeParse("get_health", query);
|
|
7285
|
+
if (!parsed.success) return failure(parsed.error);
|
|
7286
|
+
const snapshot = await create$16({
|
|
7287
|
+
db,
|
|
7288
|
+
chainRegistry
|
|
7289
|
+
}).getSnapshot();
|
|
7290
|
+
if (parsed.data.strict && !snapshot.initialized) return failure(new APIError(STATUS_CODE.INTERNAL_SERVER_ERROR, "Indexer block state is not initialized", "INTERNAL_SERVER_ERROR", toSnakeCase$1({
|
|
7291
|
+
missingChains: snapshot.missingChains,
|
|
7292
|
+
missingCollectors: snapshot.missingCollectors
|
|
7293
|
+
})));
|
|
7294
|
+
return success({ data: toSnakeCase$1({
|
|
7295
|
+
status: snapshot.status,
|
|
7296
|
+
initialized: snapshot.initialized,
|
|
7297
|
+
missingChains: snapshot.missingChains,
|
|
7298
|
+
missingCollectors: snapshot.missingCollectors
|
|
7299
|
+
}) });
|
|
7300
|
+
} catch (err) {
|
|
7301
|
+
logger.error({
|
|
7302
|
+
err,
|
|
7303
|
+
msg: "Error getting health status",
|
|
7304
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7305
|
+
errorStack: err instanceof Error ? err.stack : void 0
|
|
7306
|
+
});
|
|
7307
|
+
return failure(err);
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7310
|
+
async function getHealthChains(query, db, healthClients, chainRegistry) {
|
|
7311
|
+
const logger = getLogger();
|
|
7312
|
+
try {
|
|
7313
|
+
const parsed = safeParse("get_health_chains", query);
|
|
7314
|
+
if (!parsed.success) return failure(parsed.error);
|
|
7315
|
+
const snapshot = await create$16({
|
|
7316
|
+
db,
|
|
7317
|
+
healthClients,
|
|
7318
|
+
chainRegistry
|
|
7319
|
+
}).getSnapshot();
|
|
7320
|
+
if (parsed.data.strict && !snapshot.initialized) return failure(new APIError(STATUS_CODE.INTERNAL_SERVER_ERROR, "Indexer block state is not initialized", "INTERNAL_SERVER_ERROR", toSnakeCase$1({
|
|
7321
|
+
missingChains: snapshot.missingChains,
|
|
7322
|
+
missingCollectors: snapshot.missingCollectors
|
|
7323
|
+
})));
|
|
7324
|
+
const chains = snapshot.chains;
|
|
7325
|
+
return success({ data: chains.map(({ chainId, localBlockNumber, remoteBlockNumber, updatedAt, initialized }) => toSnakeCase$1({
|
|
7326
|
+
chainId,
|
|
7327
|
+
localBlockNumber,
|
|
7328
|
+
remoteBlockNumber,
|
|
7329
|
+
updatedAt,
|
|
7330
|
+
initialized
|
|
7331
|
+
})) });
|
|
7332
|
+
} catch (err) {
|
|
7333
|
+
logger.error({
|
|
7334
|
+
err,
|
|
7335
|
+
msg: "Error getting health status for chains",
|
|
7336
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7337
|
+
errorStack: err instanceof Error ? err.stack : void 0
|
|
7338
|
+
});
|
|
7339
|
+
return failure(err);
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
async function getHealthCollectors(query, db, chainRegistry) {
|
|
7343
|
+
const logger = getLogger();
|
|
7344
|
+
try {
|
|
7345
|
+
const parsed = safeParse("get_health_collectors", query);
|
|
7346
|
+
if (!parsed.success) return failure(parsed.error);
|
|
7347
|
+
const snapshot = await create$16({
|
|
7348
|
+
db,
|
|
7349
|
+
chainRegistry
|
|
7350
|
+
}).getSnapshot();
|
|
7351
|
+
if (parsed.data.strict && !snapshot.initialized) return failure(new APIError(STATUS_CODE.INTERNAL_SERVER_ERROR, "Indexer block state is not initialized", "INTERNAL_SERVER_ERROR", toSnakeCase$1({
|
|
7352
|
+
missingChains: snapshot.missingChains,
|
|
7353
|
+
missingCollectors: snapshot.missingCollectors
|
|
7354
|
+
})));
|
|
7355
|
+
const collectors = snapshot.collectors;
|
|
7356
|
+
return success({ data: collectors.map(({ name, chainId, blockNumber, updatedAt, lag, status, initialized }) => toSnakeCase$1({
|
|
7357
|
+
name,
|
|
7358
|
+
chainId,
|
|
7359
|
+
blockNumber,
|
|
7360
|
+
updatedAt,
|
|
7361
|
+
lag,
|
|
7362
|
+
status,
|
|
7363
|
+
initialized
|
|
7364
|
+
})) });
|
|
7365
|
+
} catch (err) {
|
|
7366
|
+
logger.error({
|
|
7367
|
+
err,
|
|
7368
|
+
msg: "Error getting health status for collectors",
|
|
7369
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7370
|
+
errorStack: err instanceof Error ? err.stack : void 0
|
|
7371
|
+
});
|
|
7372
|
+
return failure(err);
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7375
|
+
|
|
7376
|
+
//#endregion
|
|
7377
|
+
//#region src/api/Controllers/getObligation.ts
|
|
7378
|
+
async function getObligation(params, db) {
|
|
7379
|
+
const logger = getLogger();
|
|
7380
|
+
const result = safeParse("get_obligation", params, (issue) => issue.message);
|
|
7381
|
+
if (!result.success) return failure(result.error);
|
|
7382
|
+
const query = result.data;
|
|
7383
|
+
try {
|
|
7384
|
+
const { obligations } = await db.offers.getObligations({ ids: [query.obligation_id] });
|
|
7385
|
+
if (obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
|
|
7386
|
+
const obligation = obligations[0];
|
|
7387
|
+
const [quote] = await db.offers.getQuotes({ obligationIds: [id(obligation)] });
|
|
7388
|
+
return success({
|
|
7389
|
+
data: from$4(obligation, quote ?? {
|
|
7390
|
+
obligationId: id(obligation),
|
|
7391
|
+
ask: { price: 0n },
|
|
7392
|
+
bid: { price: 0n }
|
|
7393
|
+
}),
|
|
7394
|
+
cursor: null
|
|
7395
|
+
});
|
|
7396
|
+
} catch (err) {
|
|
7397
|
+
logger.error({
|
|
7398
|
+
err,
|
|
7399
|
+
msg: "Error get obligation",
|
|
7400
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7401
|
+
errorStack: err instanceof Error ? err.stack : void 0
|
|
7402
|
+
});
|
|
7403
|
+
return failure(err);
|
|
7404
|
+
}
|
|
7405
|
+
}
|
|
7406
|
+
|
|
7407
|
+
//#endregion
|
|
7408
|
+
//#region src/api/Controllers/getObligations.ts
|
|
7409
|
+
async function getObligations$1(queryParameters, db) {
|
|
7410
|
+
const logger = getLogger();
|
|
7411
|
+
const result = safeParse("get_obligations", queryParameters, (issue) => issue.message);
|
|
7412
|
+
if (!result.success) return failure(result.error);
|
|
7413
|
+
const query = result.data;
|
|
7414
|
+
try {
|
|
7415
|
+
const chainIds = query.chains?.length ? query.chains : void 0;
|
|
7416
|
+
const loanTokens = query.loan_tokens?.length ? query.loan_tokens : void 0;
|
|
7417
|
+
const collateralTokens = query.collateral_tokens?.length ? query.collateral_tokens : void 0;
|
|
7418
|
+
const maturities = query.maturities?.length ? query.maturities : void 0;
|
|
7419
|
+
const { obligations, nextCursor } = await db.offers.getObligations({
|
|
7420
|
+
cursor: query.cursor,
|
|
7421
|
+
limit: query.limit,
|
|
7422
|
+
chainId: chainIds,
|
|
7423
|
+
loanToken: loanTokens,
|
|
7424
|
+
collateralToken: collateralTokens,
|
|
7425
|
+
maturity: maturities
|
|
7426
|
+
});
|
|
7427
|
+
const quotes = await db.offers.getQuotes({ obligationIds: obligations.map((o) => id(o)) });
|
|
7428
|
+
return success({
|
|
7429
|
+
data: obligations.map((o) => from$4(o, quotes.find((q) => q.obligationId === id(o)) ?? {
|
|
7430
|
+
obligationId: id(o),
|
|
7431
|
+
ask: { price: 0n },
|
|
7432
|
+
bid: { price: 0n }
|
|
7433
|
+
})),
|
|
7434
|
+
cursor: nextCursor ?? null
|
|
7435
|
+
});
|
|
7436
|
+
} catch (err) {
|
|
7437
|
+
logger.error({
|
|
7438
|
+
err,
|
|
7439
|
+
msg: "Error get obligations",
|
|
7440
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
7441
|
+
errorStack: err instanceof Error ? err.stack : void 0
|
|
7442
|
+
});
|
|
7443
|
+
return failure(err);
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
|
|
7447
|
+
//#endregion
|
|
7448
|
+
//#region src/database/constants.ts
|
|
7449
|
+
/**
|
|
7450
|
+
* Default batch size for bulk database inserts.
|
|
7451
|
+
*
|
|
7452
|
+
* PostgreSQL limits a single query to at most 65,535 parameters
|
|
7453
|
+
* (e.g. $1, $2, ...). In bulk inserts, each row consumes one
|
|
7454
|
+
* parameter per column, so inserting too many rows at once can
|
|
7455
|
+
* exceed this limit.
|
|
7456
|
+
*
|
|
7457
|
+
* Our largest batched insert is into the `offers` table with 15 columns.
|
|
7458
|
+
* 15 cols × 4,000 rows = 60,000 parameters, safely under 65,535.
|
|
7459
|
+
*/
|
|
7460
|
+
const DEFAULT_BATCH_SIZE$1 = 4e3;
|
|
7503
7461
|
|
|
7504
7462
|
//#endregion
|
|
7505
7463
|
//#region src/database/domains/Offers.ts
|
|
@@ -7554,13 +7512,13 @@ function create$15(config) {
|
|
|
7554
7512
|
jsonb_agg(
|
|
7555
7513
|
jsonb_build_object(
|
|
7556
7514
|
'asset', ${obligationCollateralsV2.asset},
|
|
7557
|
-
'oracle', ${oracles.address},
|
|
7515
|
+
'oracle', ${oracles$1.address},
|
|
7558
7516
|
'lltv', ${obligationCollateralsV2.lltv}
|
|
7559
7517
|
)
|
|
7560
7518
|
),
|
|
7561
7519
|
'[]'::jsonb
|
|
7562
|
-
)`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
|
|
7563
|
-
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
7520
|
+
)`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
|
|
7521
|
+
AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
7564
7522
|
const rows = (await db.select({
|
|
7565
7523
|
hash: offers.hash,
|
|
7566
7524
|
maker: offers.groupMaker,
|
|
@@ -7642,10 +7600,10 @@ function create$15(config) {
|
|
|
7642
7600
|
obligationId: obligations.obligationId,
|
|
7643
7601
|
chainId: obligations.chainId,
|
|
7644
7602
|
loanToken: obligations.loanToken,
|
|
7645
|
-
collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
|
|
7603
|
+
collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
|
|
7646
7604
|
maturity: obligations.maturity
|
|
7647
|
-
}).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
|
|
7648
|
-
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).groupBy(obligations.obligationId).where(and(cursor !== null && cursor !== void 0 ? gt(obligations.obligationId, cursor) : sql`true`, ids !== void 0 && ids.length > 0 ? inArray(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) : gte(obligations.maturity, now$1), collateralFilter)).orderBy(asc(obligations.obligationId)).limit(limit);
|
|
7605
|
+
}).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
|
|
7606
|
+
AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).groupBy(obligations.obligationId).where(and(cursor !== null && cursor !== void 0 ? gt(obligations.obligationId, cursor) : sql`true`, ids !== void 0 && ids.length > 0 ? inArray(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) : gte(obligations.maturity, now$1), collateralFilter)).orderBy(asc(obligations.obligationId)).limit(limit);
|
|
7649
7607
|
const items = [];
|
|
7650
7608
|
for (const row of result) items.push(from$15({
|
|
7651
7609
|
chainId: row.chainId,
|
|
@@ -7716,40 +7674,28 @@ async function getOffersQuery(db, parameters) {
|
|
|
7716
7674
|
if (cursor !== null && cursor !== void 0) {
|
|
7717
7675
|
if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
|
|
7718
7676
|
}
|
|
7677
|
+
const now = Math.floor((Date.now() - 1) / 1e3);
|
|
7719
7678
|
const collateralsLateral = db.select({ collaterals: sql`COALESCE(
|
|
7720
7679
|
jsonb_agg(
|
|
7721
7680
|
jsonb_build_object(
|
|
7722
7681
|
'asset', ${obligationCollateralsV2.asset},
|
|
7723
|
-
'oracle', ${oracles.address},
|
|
7682
|
+
'oracle', ${oracles$1.address},
|
|
7724
7683
|
'lltv', ${obligationCollateralsV2.lltv}
|
|
7725
7684
|
)
|
|
7726
7685
|
),
|
|
7727
7686
|
'[]'::jsonb
|
|
7728
|
-
)`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
|
|
7729
|
-
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
7687
|
+
)`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
|
|
7688
|
+
AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
7730
7689
|
const availableLateral = db.select({ available: sql`COALESCE(SUM(
|
|
7731
7690
|
CASE
|
|
7732
|
-
-- If asset is null, position available is 0
|
|
7733
7691
|
WHEN ${positions.asset} IS NULL THEN 0
|
|
7734
|
-
|
|
7735
|
-
-- Position asset matches loan token: no conversion needed
|
|
7736
|
-
WHEN ${positions.asset} = ${obligations.loanToken} THEN
|
|
7692
|
+
ELSE
|
|
7737
7693
|
CASE
|
|
7738
7694
|
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
7739
7695
|
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
7740
7696
|
END
|
|
7741
|
-
|
|
7742
|
-
-- Position asset is collateral: apply oracle price * lltv
|
|
7743
|
-
-- Formula: balance * price / 1e36 * lltv / 1e18
|
|
7744
|
-
ELSE
|
|
7745
|
-
(CASE
|
|
7746
|
-
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
7747
|
-
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
7748
|
-
END)
|
|
7749
|
-
* COALESCE(${oracles.price}, 0)::numeric / 1e36
|
|
7750
|
-
* COALESCE(${obligationCollateralsV2.lltv}, 0)::numeric / 1e18
|
|
7751
7697
|
END
|
|
7752
|
-
), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, eq(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, and(eq(callbacks.positionChainId, positions.chainId), eq(callbacks.positionContract, positions.contract), eq(callbacks.positionUser, positions.user))).
|
|
7698
|
+
), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, eq(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, and(eq(callbacks.positionChainId, positions.chainId), eq(callbacks.positionContract, positions.contract), eq(callbacks.positionUser, positions.user))).where(eq(offersCallbacks.offerHash, offers.hash)).as("available_lateral");
|
|
7753
7699
|
const rows = (await db.select({
|
|
7754
7700
|
hash: offers.hash,
|
|
7755
7701
|
maker: offers.groupMaker,
|
|
@@ -7771,17 +7717,24 @@ async function getOffersQuery(db, parameters) {
|
|
|
7771
7717
|
collaterals: collateralsLateral.collaterals,
|
|
7772
7718
|
blockNumber: offers.blockNumber,
|
|
7773
7719
|
available: sql`COALESCE(${availableLateral.available}::numeric, 0)`.as("available"),
|
|
7774
|
-
takeable: sql`FLOOR(GREATEST(
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7720
|
+
takeable: sql`FLOOR(GREATEST(0,
|
|
7721
|
+
CASE WHEN ${offers.buy} = false
|
|
7722
|
+
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
7723
|
+
ELSE LEAST(
|
|
7724
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
7725
|
+
COALESCE(${availableLateral.available}::numeric, 0)
|
|
7726
|
+
)
|
|
7727
|
+
END
|
|
7780
7728
|
))`.as("takeable")
|
|
7781
|
-
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).leftJoinLateral(availableLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, maker === void 0 ? sql`GREATEST(0,
|
|
7782
|
-
${offers.
|
|
7783
|
-
|
|
7784
|
-
|
|
7729
|
+
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).leftJoinLateral(availableLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, gte(offers.expiry, now), gte(offers.maturity, now), maker === void 0 ? sql`GREATEST(0,
|
|
7730
|
+
CASE WHEN ${offers.buy} = false
|
|
7731
|
+
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
7732
|
+
ELSE LEAST(
|
|
7733
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
7734
|
+
COALESCE(${availableLateral.available}::numeric, 0)
|
|
7735
|
+
)
|
|
7736
|
+
END
|
|
7737
|
+
) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
|
|
7785
7738
|
return {
|
|
7786
7739
|
hash: row.hash,
|
|
7787
7740
|
maker: row.maker,
|
|
@@ -7891,64 +7844,6 @@ async function getUserPositions(queryParameters, db) {
|
|
|
7891
7844
|
}
|
|
7892
7845
|
}
|
|
7893
7846
|
|
|
7894
|
-
//#endregion
|
|
7895
|
-
//#region src/gatekeeper/CallbackTypes.ts
|
|
7896
|
-
/**
|
|
7897
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
7898
|
-
* @param parameters - Resolve parameters. {@link resolveCallbackTypes.Parameters}
|
|
7899
|
-
* @returns Callback types grouped by chain. {@link resolveCallbackTypes.ReturnType}
|
|
7900
|
-
* @throws If a chain id is unknown.
|
|
7901
|
-
*/
|
|
7902
|
-
function resolveCallbackTypes$1(parameters) {
|
|
7903
|
-
const { chains, request } = parameters;
|
|
7904
|
-
const chainsById = new Map(chains.map((chain) => [chain.id, chain]));
|
|
7905
|
-
return request.callbacks.map(({ chain_id, addresses }) => {
|
|
7906
|
-
const chain = chainsById.get(chain_id);
|
|
7907
|
-
if (!chain) throw new Error(`Unknown chain id ${chain_id}`);
|
|
7908
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
7909
|
-
const uniqueAddresses = new Set(addresses.map((address) => address.toLowerCase()));
|
|
7910
|
-
for (const address of uniqueAddresses) {
|
|
7911
|
-
const bucketKey = getCallbackType(chain.name, address) ?? "not_supported";
|
|
7912
|
-
const list = buckets.get(bucketKey) ?? [];
|
|
7913
|
-
list.push(address);
|
|
7914
|
-
buckets.set(bucketKey, list);
|
|
7915
|
-
}
|
|
7916
|
-
const response = { chain_id };
|
|
7917
|
-
for (const [type, list] of buckets.entries()) response[type] = list;
|
|
7918
|
-
if (!response.not_supported) response.not_supported = [];
|
|
7919
|
-
return response;
|
|
7920
|
-
});
|
|
7921
|
-
}
|
|
7922
|
-
|
|
7923
|
-
//#endregion
|
|
7924
|
-
//#region src/api/Controllers/resolveCallbackTypes.ts
|
|
7925
|
-
/**
|
|
7926
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
7927
|
-
* @param body - Request body with callback addresses. {@link CallbackTypesRequest}
|
|
7928
|
-
* @param chains - Chains to resolve callback types against. {@link Chain.Chain}
|
|
7929
|
-
* @returns Callback types grouped by chain. {@link CallbackTypesPayload}
|
|
7930
|
-
*/
|
|
7931
|
-
async function resolveCallbackTypes(body, chains) {
|
|
7932
|
-
const result = safeParse("callback_types", body, (issue) => issue.message);
|
|
7933
|
-
if (!result.success) return failure(result.error);
|
|
7934
|
-
const request = result.data;
|
|
7935
|
-
const chainIds = new Set(chains.map((chain) => chain.id));
|
|
7936
|
-
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
7937
|
-
if (unknown) return failure(new BadRequestError(`Unknown chain id ${unknown.chain_id}`));
|
|
7938
|
-
try {
|
|
7939
|
-
const data = resolveCallbackTypes$1({
|
|
7940
|
-
chains,
|
|
7941
|
-
request
|
|
7942
|
-
});
|
|
7943
|
-
return success({
|
|
7944
|
-
data,
|
|
7945
|
-
cursor: null
|
|
7946
|
-
});
|
|
7947
|
-
} catch (err) {
|
|
7948
|
-
return failure(err);
|
|
7949
|
-
}
|
|
7950
|
-
}
|
|
7951
|
-
|
|
7952
7847
|
//#endregion
|
|
7953
7848
|
//#region src/api/Controllers/validateOffers.ts
|
|
7954
7849
|
async function validateOffers(body, gatekeeper) {
|
|
@@ -8028,7 +7923,6 @@ var Controllers_exports = /* @__PURE__ */ __exportAll({
|
|
|
8028
7923
|
getOffersQuery: () => getOffersQuery,
|
|
8029
7924
|
getSwaggerJson: () => getSwaggerJson,
|
|
8030
7925
|
getUserPositions: () => getUserPositions,
|
|
8031
|
-
resolveCallbackTypes: () => resolveCallbackTypes,
|
|
8032
7926
|
validateOffers: () => validateOffers
|
|
8033
7927
|
});
|
|
8034
7928
|
|
|
@@ -8101,24 +7995,9 @@ function serve$1(parameters) {
|
|
|
8101
7995
|
const { statusCode, body } = await gatekeeper.validate(reqBody);
|
|
8102
7996
|
return c.json(body, statusCode);
|
|
8103
7997
|
} catch (err) {
|
|
8104
|
-
const failure$
|
|
8105
|
-
return c.json(failure$1.body, failure$1.statusCode);
|
|
8106
|
-
}
|
|
8107
|
-
});
|
|
8108
|
-
app.post("/v1/callbacks", async (c) => {
|
|
8109
|
-
let body;
|
|
8110
|
-
try {
|
|
8111
|
-
body = await c.req.json();
|
|
8112
|
-
} catch (err) {
|
|
8113
|
-
const failure$3 = failure(err);
|
|
8114
|
-
return c.json(failure$3.body, failure$3.statusCode);
|
|
8115
|
-
}
|
|
8116
|
-
if (body === null || typeof body !== "object") {
|
|
8117
|
-
const failure$2 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
7998
|
+
const failure$2 = failure(err);
|
|
8118
7999
|
return c.json(failure$2.body, failure$2.statusCode);
|
|
8119
8000
|
}
|
|
8120
|
-
const { statusCode, body: responseBody } = await resolveCallbackTypes(body, chainRegistry.list());
|
|
8121
|
-
return c.json(responseBody, statusCode);
|
|
8122
8001
|
});
|
|
8123
8002
|
app.get("/v1/users/:userAddress/positions", async (c) => {
|
|
8124
8003
|
const query = c.req.query();
|
|
@@ -8150,8 +8029,8 @@ function serve$1(parameters) {
|
|
|
8150
8029
|
const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
|
|
8151
8030
|
return c.json(body, statusCode);
|
|
8152
8031
|
} catch (err) {
|
|
8153
|
-
const failure$
|
|
8154
|
-
return c.json(failure$
|
|
8032
|
+
const failure$1 = failure(err);
|
|
8033
|
+
return c.json(failure$1.body, failure$1.statusCode);
|
|
8155
8034
|
}
|
|
8156
8035
|
});
|
|
8157
8036
|
app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
|
|
@@ -8168,7 +8047,6 @@ function serve$1(parameters) {
|
|
|
8168
8047
|
var RouterApi_exports = /* @__PURE__ */ __exportAll({
|
|
8169
8048
|
BookResponse: () => BookResponse_exports,
|
|
8170
8049
|
BooksController: () => BooksController,
|
|
8171
|
-
CallbacksController: () => CallbacksController,
|
|
8172
8050
|
ChainHealth: () => ChainHealth,
|
|
8173
8051
|
ChainsHealthResponse: () => ChainsHealthResponse,
|
|
8174
8052
|
CollectorHealth: () => CollectorHealth,
|
|
@@ -8390,7 +8268,7 @@ var drizzle_exports = /* @__PURE__ */ __exportAll({
|
|
|
8390
8268
|
offers: () => offers,
|
|
8391
8269
|
offersCallbacks: () => offersCallbacks,
|
|
8392
8270
|
offsets: () => offsets,
|
|
8393
|
-
oracles: () => oracles,
|
|
8271
|
+
oracles: () => oracles$1,
|
|
8394
8272
|
positionTypes: () => positionTypes,
|
|
8395
8273
|
positions: () => positions,
|
|
8396
8274
|
status: () => status,
|
|
@@ -8675,7 +8553,7 @@ async function _getOffers(db, params) {
|
|
|
8675
8553
|
'lltv', oc.lltv
|
|
8676
8554
|
) ORDER BY oc.asset), '[]'::jsonb) AS collaterals
|
|
8677
8555
|
FROM ${obligationCollateralsV2} oc
|
|
8678
|
-
JOIN ${oracles} oracle
|
|
8556
|
+
JOIN ${oracles$1} oracle
|
|
8679
8557
|
ON oracle.chain_id = oc.oracle_chain_id
|
|
8680
8558
|
AND oracle.address = oc.oracle_address
|
|
8681
8559
|
WHERE oc.obligation_id = ${obligationId}
|
|
@@ -8830,35 +8708,15 @@ async function _getOffers(db, params) {
|
|
|
8830
8708
|
AND LOWER(pc.contract) = LOWER(c.position_contract)
|
|
8831
8709
|
AND LOWER(pc."user") = LOWER(c.position_user)
|
|
8832
8710
|
),
|
|
8833
|
-
-- Compute contribution per callback in loan terms (
|
|
8711
|
+
-- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
|
|
8834
8712
|
callback_loan_contribution AS (
|
|
8835
8713
|
SELECT
|
|
8836
8714
|
cc.*,
|
|
8837
8715
|
CASE
|
|
8838
|
-
-- No lot exists: contribution is 0
|
|
8839
8716
|
WHEN cc.lot_lower IS NULL THEN 0
|
|
8840
|
-
|
|
8841
|
-
WHEN LOWER(cc.position_asset) = LOWER(cc.loan_token) THEN
|
|
8842
|
-
LEAST(
|
|
8843
|
-
cc.lot_balance,
|
|
8844
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
8845
|
-
)
|
|
8846
|
-
-- Collateral position: convert to loan using (amount * price / 10^36) * lltv / 10^18
|
|
8847
|
-
ELSE
|
|
8848
|
-
(
|
|
8849
|
-
LEAST(
|
|
8850
|
-
cc.lot_balance,
|
|
8851
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
8852
|
-
) * COALESCE(collat_oracle.price::numeric, 0) / 1e36
|
|
8853
|
-
) * COALESCE(collat_info.lltv::numeric, 0) / 1e18
|
|
8717
|
+
ELSE LEAST(cc.lot_balance, COALESCE(cc.callback_amount::numeric, cc.lot_balance))
|
|
8854
8718
|
END AS contribution_in_loan
|
|
8855
8719
|
FROM callback_contributions cc
|
|
8856
|
-
LEFT JOIN ${obligationCollateralsV2} collat_info
|
|
8857
|
-
ON collat_info.obligation_id = cc.obligation_id
|
|
8858
|
-
AND LOWER(collat_info.asset) = LOWER(cc.position_asset)
|
|
8859
|
-
LEFT JOIN ${oracles} collat_oracle
|
|
8860
|
-
ON collat_oracle.chain_id = collat_info.oracle_chain_id
|
|
8861
|
-
AND LOWER(collat_oracle.address) = LOWER(collat_info.oracle_address)
|
|
8862
8720
|
),
|
|
8863
8721
|
-- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
|
|
8864
8722
|
offer_contributions AS (
|
|
@@ -8895,6 +8753,22 @@ async function _getOffers(db, params) {
|
|
|
8895
8753
|
GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
|
|
8896
8754
|
callback_address, callback_data, block_number, group_chain_id, group_maker,
|
|
8897
8755
|
consumed, chain_id, loan_token, session
|
|
8756
|
+
UNION ALL
|
|
8757
|
+
-- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
|
|
8758
|
+
SELECT
|
|
8759
|
+
p.hash, p.obligation_id, p.assets, p.price,
|
|
8760
|
+
p.obligation_units, p.obligation_shares,
|
|
8761
|
+
p.maturity, p.expiry, p.start, p.group_group,
|
|
8762
|
+
p.buy, p.callback_address, p.callback_data,
|
|
8763
|
+
p.block_number, p.group_chain_id, p.group_maker,
|
|
8764
|
+
p.consumed, p.chain_id, p.loan_token, p.session,
|
|
8765
|
+
0 AS total_available
|
|
8766
|
+
FROM paged p
|
|
8767
|
+
WHERE p.buy = false
|
|
8768
|
+
AND NOT EXISTS (
|
|
8769
|
+
SELECT 1 FROM ${offersCallbacks} oc2
|
|
8770
|
+
WHERE oc2.offer_hash = p.hash
|
|
8771
|
+
)
|
|
8898
8772
|
)
|
|
8899
8773
|
-- Final SELECT with inline takeable computation
|
|
8900
8774
|
SELECT
|
|
@@ -8917,18 +8791,24 @@ async function _getOffers(db, params) {
|
|
|
8917
8791
|
oc.block_number,
|
|
8918
8792
|
oc.session,
|
|
8919
8793
|
COALESCE(oc.total_available, 0) AS available,
|
|
8920
|
-
-- takeable
|
|
8921
|
-
|
|
8922
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
8923
|
-
|
|
8924
|
-
|
|
8794
|
+
-- takeable: sell offers use assets - consumed directly (collateral positions not indexed yet)
|
|
8795
|
+
CASE WHEN oc.buy = false
|
|
8796
|
+
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
8797
|
+
ELSE GREATEST(0, LEAST(
|
|
8798
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
8799
|
+
COALESCE(oc.total_available, 0)
|
|
8800
|
+
))
|
|
8801
|
+
END AS takeable,
|
|
8925
8802
|
c.collaterals
|
|
8926
8803
|
FROM offer_contributions oc
|
|
8927
8804
|
LEFT JOIN collats c ON c.obligation_id = oc.obligation_id
|
|
8928
|
-
WHERE
|
|
8929
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
8930
|
-
|
|
8931
|
-
|
|
8805
|
+
WHERE CASE WHEN oc.buy = false
|
|
8806
|
+
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
8807
|
+
ELSE GREATEST(0, LEAST(
|
|
8808
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
8809
|
+
COALESCE(oc.total_available, 0)
|
|
8810
|
+
))
|
|
8811
|
+
END > 0
|
|
8932
8812
|
ORDER BY
|
|
8933
8813
|
oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
|
|
8934
8814
|
oc.block_number ASC,
|
|
@@ -9269,32 +9149,32 @@ function create$5(db) {
|
|
|
9269
9149
|
return {
|
|
9270
9150
|
get: async ({ chainId }) => {
|
|
9271
9151
|
return (await db.select({
|
|
9272
|
-
address: oracles.address,
|
|
9273
|
-
price: oracles.price,
|
|
9274
|
-
blockNumber: oracles.blockNumber,
|
|
9275
|
-
chainId: oracles.chainId
|
|
9276
|
-
}).from(oracles).where(eq(oracles.chainId, chainId))).map((r) => from$13({
|
|
9152
|
+
address: oracles$1.address,
|
|
9153
|
+
price: oracles$1.price,
|
|
9154
|
+
blockNumber: oracles$1.blockNumber,
|
|
9155
|
+
chainId: oracles$1.chainId
|
|
9156
|
+
}).from(oracles$1).where(eq(oracles$1.chainId, chainId))).map((r) => from$13({
|
|
9277
9157
|
chainId: r.chainId,
|
|
9278
9158
|
address: r.address,
|
|
9279
9159
|
price: r.price,
|
|
9280
9160
|
blockNumber: r.blockNumber
|
|
9281
9161
|
}));
|
|
9282
9162
|
},
|
|
9283
|
-
upsert: async (oracles
|
|
9284
|
-
if (oracles
|
|
9285
|
-
const rows = oracles
|
|
9163
|
+
upsert: async (oracles) => {
|
|
9164
|
+
if (oracles.length === 0) return;
|
|
9165
|
+
const rows = oracles.map((o) => ({
|
|
9286
9166
|
chainId: o.chainId,
|
|
9287
9167
|
address: o.address.toLowerCase(),
|
|
9288
9168
|
price: o.price !== null ? o.price.toString() : null,
|
|
9289
9169
|
blockNumber: o.blockNumber
|
|
9290
9170
|
}));
|
|
9291
9171
|
await db.transaction(async (dbTx) => {
|
|
9292
|
-
for (const batch of batch$1(rows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(oracles).values(batch).onConflictDoUpdate({
|
|
9293
|
-
target: [oracles.chainId, oracles.address],
|
|
9172
|
+
for (const batch of batch$1(rows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(oracles$1).values(batch).onConflictDoUpdate({
|
|
9173
|
+
target: [oracles$1.chainId, oracles$1.address],
|
|
9294
9174
|
set: {
|
|
9295
|
-
price: sql`COALESCE(EXCLUDED.price, ${oracles.price})`,
|
|
9175
|
+
price: sql`COALESCE(EXCLUDED.price, ${oracles$1.price})`,
|
|
9296
9176
|
blockNumber: sql`CASE
|
|
9297
|
-
WHEN EXCLUDED.price IS NULL THEN ${oracles.blockNumber}
|
|
9177
|
+
WHEN EXCLUDED.price IS NULL THEN ${oracles$1.blockNumber}
|
|
9298
9178
|
ELSE EXCLUDED.block_number
|
|
9299
9179
|
END`,
|
|
9300
9180
|
updatedAt: sql`NOW()`
|
|
@@ -10333,23 +10213,11 @@ function createHttpClient(config) {
|
|
|
10333
10213
|
issues: []
|
|
10334
10214
|
};
|
|
10335
10215
|
};
|
|
10336
|
-
const getCallbackTypes = async (requestPayload) => {
|
|
10337
|
-
const response = await request("/v1/callbacks", {
|
|
10338
|
-
method: "POST",
|
|
10339
|
-
headers: { "content-type": "application/json" },
|
|
10340
|
-
body: JSON.stringify(requestPayload)
|
|
10341
|
-
});
|
|
10342
|
-
const json = await response.json();
|
|
10343
|
-
if (!response.ok) throw new Error(`Gatekeeper callbacks request failed: ${extractErrorMessage(json) ?? response.statusText}`);
|
|
10344
|
-
if (!("data" in json) || !Array.isArray(json.data)) throw new Error("Gatekeeper callbacks response is invalid.");
|
|
10345
|
-
return json.data;
|
|
10346
|
-
};
|
|
10347
10216
|
return {
|
|
10348
10217
|
baseUrl,
|
|
10349
10218
|
validate,
|
|
10350
10219
|
getConfigRules,
|
|
10351
|
-
isAllowed
|
|
10352
|
-
getCallbackTypes
|
|
10220
|
+
isAllowed
|
|
10353
10221
|
};
|
|
10354
10222
|
}
|
|
10355
10223
|
function mergeHeaders(base, extra) {
|
|
@@ -10478,6 +10346,7 @@ var Rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
10478
10346
|
callback: () => callback,
|
|
10479
10347
|
chains: () => chains,
|
|
10480
10348
|
maturity: () => maturity,
|
|
10349
|
+
oracle: () => oracle,
|
|
10481
10350
|
sameMaker: () => sameMaker,
|
|
10482
10351
|
token: () => token,
|
|
10483
10352
|
validity: () => validity
|
|
@@ -10485,109 +10354,13 @@ var Rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
10485
10354
|
/**
|
|
10486
10355
|
* set of rules to validate offers.
|
|
10487
10356
|
*
|
|
10488
|
-
* @param
|
|
10357
|
+
* @param _parameters - Validity parameters with chain and client
|
|
10489
10358
|
* @returns Array of validation rules to evaluate against offers
|
|
10490
10359
|
*/
|
|
10491
|
-
function validity(
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
if (callbackType !== Type$1.SellERC20Callback) return;
|
|
10496
|
-
const decoded = decode$2(callbackType, offer.callback.data);
|
|
10497
|
-
if (decoded.length === 0) return { message: "Callback data cannot be decoded or is empty." };
|
|
10498
|
-
if (callbackType === Type$1.SellERC20Callback) {
|
|
10499
|
-
const offerCollaterals = new Set(offer.collaterals.map((c) => c.asset.toLowerCase()));
|
|
10500
|
-
if (decoded.length !== offer.collaterals.length) return { message: `Sell callback collateral length mismatch. Expected ${offer.collaterals.length}, got ${decoded.length}.` };
|
|
10501
|
-
for (const { contract } of decoded) if (!offerCollaterals.has(contract.toLowerCase())) return { message: "Sell callback collateral is not part of offer collaterals." };
|
|
10502
|
-
}
|
|
10503
|
-
});
|
|
10504
|
-
const buyCallbackVaultInvalid = batch("buy_offers_callback_vault_invalid", "Validates that buy offers have valid vault callbacks registered in allowed factories with matching assets", async (offers) => {
|
|
10505
|
-
const validationIssues = /* @__PURE__ */ new Map();
|
|
10506
|
-
const offersByVaultAddress = /* @__PURE__ */ new Map();
|
|
10507
|
-
for (let i = 0; i < offers.length; i++) {
|
|
10508
|
-
const offer = offers[i];
|
|
10509
|
-
if (getCallbackType(client.chain.name, offer.callback.address) !== Type$1.BuyVaultV1Callback) continue;
|
|
10510
|
-
try {
|
|
10511
|
-
const callbackVaults = decodeBuyVaultV1Callback(offer.callback.data);
|
|
10512
|
-
for (const { contract } of callbackVaults) {
|
|
10513
|
-
const normalizedVaultAddress = contract.toLowerCase();
|
|
10514
|
-
if (!offersByVaultAddress.has(normalizedVaultAddress)) offersByVaultAddress.set(normalizedVaultAddress, []);
|
|
10515
|
-
offersByVaultAddress.get(normalizedVaultAddress).push({
|
|
10516
|
-
index: i,
|
|
10517
|
-
offer
|
|
10518
|
-
});
|
|
10519
|
-
}
|
|
10520
|
-
} catch (_) {}
|
|
10521
|
-
}
|
|
10522
|
-
const uniqueVaultAddresses = Array.from(offersByVaultAddress.keys());
|
|
10523
|
-
if (uniqueVaultAddresses.length === 0) return validationIssues;
|
|
10524
|
-
const allowedFactories = getCallback(client.chain.name, Type$1.BuyVaultV1Callback)?.vaultFactories.map((f) => f.toLowerCase());
|
|
10525
|
-
if (!allowedFactories) return validationIssues;
|
|
10526
|
-
const multicallContracts = [];
|
|
10527
|
-
for (const vaultAddress of uniqueVaultAddresses) {
|
|
10528
|
-
multicallContracts.push({
|
|
10529
|
-
address: vaultAddress,
|
|
10530
|
-
abi: ERC4626,
|
|
10531
|
-
functionName: "asset"
|
|
10532
|
-
});
|
|
10533
|
-
for (const factoryAddress of allowedFactories) multicallContracts.push({
|
|
10534
|
-
address: factoryAddress,
|
|
10535
|
-
abi: MetaMorphoFactory,
|
|
10536
|
-
functionName: "isMetaMorpho",
|
|
10537
|
-
args: [vaultAddress]
|
|
10538
|
-
});
|
|
10539
|
-
}
|
|
10540
|
-
const multicallResults = await multicall(client, {
|
|
10541
|
-
contracts: multicallContracts,
|
|
10542
|
-
allowFailure: true
|
|
10543
|
-
});
|
|
10544
|
-
const vaultAssetByAddress = /* @__PURE__ */ new Map();
|
|
10545
|
-
const registeredVaults = /* @__PURE__ */ new Set();
|
|
10546
|
-
const numberOfFactories = allowedFactories.length;
|
|
10547
|
-
let resultIndex = 0;
|
|
10548
|
-
for (const vaultAddress of uniqueVaultAddresses) {
|
|
10549
|
-
const assetCallResult = multicallResults[resultIndex++];
|
|
10550
|
-
const assetAddress = assetCallResult.status === "success" ? assetCallResult.result : null;
|
|
10551
|
-
vaultAssetByAddress.set(vaultAddress, assetAddress);
|
|
10552
|
-
let isRegisteredInFactory = false;
|
|
10553
|
-
for (let factoryIndex = 0; factoryIndex < numberOfFactories; factoryIndex++) {
|
|
10554
|
-
const factoryCallResult = multicallResults[resultIndex++];
|
|
10555
|
-
if (factoryCallResult.status === "success" && factoryCallResult.result === true) isRegisteredInFactory = true;
|
|
10556
|
-
}
|
|
10557
|
-
if (isRegisteredInFactory) registeredVaults.add(vaultAddress);
|
|
10558
|
-
}
|
|
10559
|
-
const uniqueOffers = /* @__PURE__ */ new Map();
|
|
10560
|
-
for (const offersArray of offersByVaultAddress.values()) for (const { index, offer } of offersArray) uniqueOffers.set(index, offer);
|
|
10561
|
-
for (const [index, offer] of uniqueOffers) try {
|
|
10562
|
-
const callbackVaults = decodeBuyVaultV1Callback(offer.callback.data);
|
|
10563
|
-
const vaultsWithIssues = [];
|
|
10564
|
-
for (const { contract } of callbackVaults) {
|
|
10565
|
-
const normalizedVaultAddress = contract.toLowerCase();
|
|
10566
|
-
const assetAddress = vaultAssetByAddress.get(normalizedVaultAddress);
|
|
10567
|
-
const isRegistered = registeredVaults.has(normalizedVaultAddress);
|
|
10568
|
-
const failureReasons = [];
|
|
10569
|
-
if (assetAddress === null) failureReasons.push("asset call failed");
|
|
10570
|
-
else if (assetAddress && assetAddress.toLowerCase() !== offer.loanToken.toLowerCase()) failureReasons.push("asset mismatch");
|
|
10571
|
-
if (!isRegistered) failureReasons.push("not registered in factory");
|
|
10572
|
-
if (failureReasons.length > 0) vaultsWithIssues.push({
|
|
10573
|
-
vaultAddress: contract,
|
|
10574
|
-
failureReasons: failureReasons.join(", ")
|
|
10575
|
-
});
|
|
10576
|
-
}
|
|
10577
|
-
if (vaultsWithIssues.length > 0) {
|
|
10578
|
-
const failureDetails = vaultsWithIssues.map((v) => `${v.vaultAddress} (${v.failureReasons})`).join("; ");
|
|
10579
|
-
validationIssues.set(index, { message: `Buy offer callback vaults are invalid: ${failureDetails}` });
|
|
10580
|
-
}
|
|
10581
|
-
} catch (_) {}
|
|
10582
|
-
return validationIssues;
|
|
10583
|
-
});
|
|
10584
|
-
return [
|
|
10585
|
-
single("expiry", "Validates that offer has not expired", (offer) => {
|
|
10586
|
-
if (offer.expiry < Math.floor(Date.now() / 1e3)) return { message: "Expiry mismatch" };
|
|
10587
|
-
}),
|
|
10588
|
-
sellErc20CallbackInvalid,
|
|
10589
|
-
buyCallbackVaultInvalid
|
|
10590
|
-
];
|
|
10360
|
+
function validity(_parameters) {
|
|
10361
|
+
return [single("expiry", "Validates that offer has not expired", (offer) => {
|
|
10362
|
+
if (offer.expiry < Math.floor(Date.now() / 1e3)) return { message: "Expiry mismatch" };
|
|
10363
|
+
})];
|
|
10591
10364
|
}
|
|
10592
10365
|
const chains = ({ chains }) => single("chain_ids", `Validates that offer chain is one of: [${chains.map((c) => c.id).join(", ")}]`, (offer) => {
|
|
10593
10366
|
const allowedChainIds = chains.map((c) => c.id);
|
|
@@ -10597,12 +10370,10 @@ const maturity = ({ maturities }) => single("maturity", `Validates that offer ma
|
|
|
10597
10370
|
const allowedMaturities = maturities.map((m) => from$16(m));
|
|
10598
10371
|
if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}` };
|
|
10599
10372
|
});
|
|
10600
|
-
const callback = ({ callbacks
|
|
10601
|
-
if (isEmptyCallback(offer)
|
|
10602
|
-
if (isEmptyCallback(offer) &&
|
|
10603
|
-
if (
|
|
10604
|
-
if (!allowedAddresses.includes(offer.callback.address?.toLowerCase())) return { message: `Callback address ${offer.callback.address} is not allowed.` };
|
|
10605
|
-
}
|
|
10373
|
+
const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy empty callback is ${callbacks.includes(Type$1.BuyWithEmptyCallback) ? "allowed" : "not allowed"}; sell empty callback is ${callbacks.includes(Type$1.SellWithEmptyCallback) ? "allowed" : "not allowed"}; non-empty callbacks are rejected`, (offer) => {
|
|
10374
|
+
if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
|
|
10375
|
+
if (isEmptyCallback(offer) && offer.buy && !callbacks.includes(Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
|
|
10376
|
+
if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
|
|
10606
10377
|
});
|
|
10607
10378
|
/**
|
|
10608
10379
|
* A validation rule that checks if the offer's tokens are allowed for its chain.
|
|
@@ -10616,6 +10387,16 @@ const token = ({ assetsByChainId }) => single("token", "Validates that offer loa
|
|
|
10616
10387
|
if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
|
|
10617
10388
|
});
|
|
10618
10389
|
/**
|
|
10390
|
+
* A validation rule that checks if the offer's oracle addresses are allowed for its chain.
|
|
10391
|
+
* @param oraclesByChainId - Allowed oracles indexed by chain id.
|
|
10392
|
+
* @returns The issue that was found. If the offer is valid, this will be undefined.
|
|
10393
|
+
*/
|
|
10394
|
+
const oracle = ({ oraclesByChainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the offer chain", (offer) => {
|
|
10395
|
+
const allowedOracles = oraclesByChainId[offer.chainId]?.map((oracle) => oracle.toLowerCase());
|
|
10396
|
+
if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${offer.chainId}` };
|
|
10397
|
+
if (offer.collaterals.some((collateral) => !allowedOracles.includes(collateral.oracle.toLowerCase()))) return { message: "Oracle is not allowed" };
|
|
10398
|
+
});
|
|
10399
|
+
/**
|
|
10619
10400
|
* A batch validation rule that ensures all offers in a tree have the same maker address.
|
|
10620
10401
|
* Returns an issue only for the first non-conforming offer.
|
|
10621
10402
|
* This rule is signing-agnostic; signer verification is handled at the collector level.
|
|
@@ -10646,21 +10427,22 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
|
|
|
10646
10427
|
//#region src/gatekeeper/morphoRules.ts
|
|
10647
10428
|
const morphoRules = (chains$3) => {
|
|
10648
10429
|
const assetsByChainId = {};
|
|
10649
|
-
|
|
10430
|
+
const oraclesByChainId = {};
|
|
10431
|
+
for (const chain of chains$3) {
|
|
10432
|
+
assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
|
|
10433
|
+
oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
|
|
10434
|
+
}
|
|
10650
10435
|
return [
|
|
10651
10436
|
sameMaker(),
|
|
10652
10437
|
amountMutualExclusivity(),
|
|
10653
10438
|
chains({ chains: chains$3 }),
|
|
10654
10439
|
maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
|
|
10655
10440
|
callback({
|
|
10656
|
-
callbacks: [
|
|
10657
|
-
|
|
10658
|
-
Type$1.BuyVaultV1Callback,
|
|
10659
|
-
Type$1.SellERC20Callback
|
|
10660
|
-
],
|
|
10661
|
-
allowedAddresses: chains$3.flatMap((c) => getCallbackAddresses(c.name))
|
|
10441
|
+
callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
|
|
10442
|
+
allowedAddresses: []
|
|
10662
10443
|
}),
|
|
10663
|
-
token({ assetsByChainId })
|
|
10444
|
+
token({ assetsByChainId }),
|
|
10445
|
+
oracle({ oraclesByChainId })
|
|
10664
10446
|
];
|
|
10665
10447
|
};
|
|
10666
10448
|
|
|
@@ -10842,5 +10624,5 @@ var mempool_exports = /* @__PURE__ */ __exportAll({
|
|
|
10842
10624
|
});
|
|
10843
10625
|
|
|
10844
10626
|
//#endregion
|
|
10845
|
-
export { Abi_exports as Abi, BookResponse_exports as BookResponse, BooksController, BrandTypeId, Callback_exports as Callback,
|
|
10627
|
+
export { Abi_exports as Abi, BookResponse_exports as BookResponse, BooksController, BrandTypeId, Callback_exports as Callback, Chain_exports as Chain, ChainHealth, ChainRegistry_exports as ChainRegistry, ChainsHealthResponse, Collateral_exports as Collateral, CollectorHealth, CollectorsHealthResponse, ConfigContractsController, ConfigRulesController, Database_exports as Database, ERC4626_exports as ERC4626, Errors_exports as Errors, Format_exports as Format, Gatekeeper_exports as Gatekeeper, Client_exports as GatekeeperClient, Health_exports as Health, HealthController, Indexer_exports as Indexer, LLTV_exports as LLTV, Liquidity_exports as Liquidity, Logger_exports as Logger, Maturity_exports as Maturity, mempool_exports as Mempool, Obligation_exports as Obligation, ObligationResponse_exports as ObligationResponse, ObligationsController, Offer_exports as Offer, OfferResponse_exports as OfferResponse, OffersController, drizzle_exports as OffersSchema, OpenApi, Oracle_exports as Oracle, Position_exports as Position, PositionResponse_exports as PositionResponse, Quote_exports as Quote, RouterApi_exports as RouterApi, Client_exports$1 as RouterClient, RouterStatusResponse, Rules_exports as Rules, time_exports as Time, TradingFee_exports as TradingFee, Transfer_exports as Transfer, Tree_exports as Tree, UsersController, utils_exports as Utils, ValidateController, Gate_exports as Validation, morphoRules, parse, safeParse };
|
|
10846
10628
|
//# sourceMappingURL=index.node.mjs.map
|