@morpho-dev/router 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1200 -559
- package/dist/drizzle/migrations/0015_add-lots-table.sql +12 -0
- package/dist/drizzle/migrations/0016_merkle-metadata.sql +26 -0
- package/dist/drizzle/migrations/meta/0015_snapshot.json +1365 -0
- package/dist/drizzle/migrations/meta/0016_snapshot.json +1531 -0
- package/dist/drizzle/migrations/meta/_journal.json +14 -0
- package/dist/index.browser.d.mts +443 -229
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.d.ts +439 -229
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +533 -202
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +534 -203
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +737 -67
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.d.ts +737 -67
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +1240 -513
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +1241 -514
- package/dist/index.node.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.node.js
CHANGED
|
@@ -43,7 +43,6 @@ require("@opentelemetry/resources");
|
|
|
43
43
|
require("@opentelemetry/sdk-trace-node");
|
|
44
44
|
require("@opentelemetry/semantic-conventions");
|
|
45
45
|
let viem_chains = require("viem/chains");
|
|
46
|
-
let viem_accounts = require("viem/accounts");
|
|
47
46
|
let zod = require("zod");
|
|
48
47
|
zod = __toESM(zod);
|
|
49
48
|
let __openzeppelin_merkle_tree = require("@openzeppelin/merkle-tree");
|
|
@@ -440,6 +439,96 @@ function poll(fn, { interval }) {
|
|
|
440
439
|
return unwatch;
|
|
441
440
|
}
|
|
442
441
|
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/utils/Random.ts
|
|
444
|
+
var Random_exports = /* @__PURE__ */ __export({
|
|
445
|
+
address: () => address,
|
|
446
|
+
bool: () => bool,
|
|
447
|
+
bytes: () => bytes,
|
|
448
|
+
float: () => float,
|
|
449
|
+
hex: () => hex,
|
|
450
|
+
int: () => int,
|
|
451
|
+
seed: () => seed,
|
|
452
|
+
withSeed: () => withSeed
|
|
453
|
+
});
|
|
454
|
+
let currentRng = Math.random;
|
|
455
|
+
const FNV_OFFSET_BASIS = 2166136261;
|
|
456
|
+
const FNV_PRIME = 16777619;
|
|
457
|
+
const hashSeed = (seed$1) => {
|
|
458
|
+
let hash$1 = FNV_OFFSET_BASIS;
|
|
459
|
+
for (let i = 0; i < seed$1.length; i += 1) {
|
|
460
|
+
hash$1 ^= seed$1.charCodeAt(i);
|
|
461
|
+
hash$1 = Math.imul(hash$1, FNV_PRIME);
|
|
462
|
+
}
|
|
463
|
+
return hash$1 >>> 0;
|
|
464
|
+
};
|
|
465
|
+
const createSeededRng = (seed$1) => {
|
|
466
|
+
let state = hashSeed(seed$1);
|
|
467
|
+
return () => {
|
|
468
|
+
state += 1831565813;
|
|
469
|
+
let t = Math.imul(state ^ state >>> 15, state | 1);
|
|
470
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
471
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
/**
|
|
475
|
+
* Runs a function with a deterministic RNG derived from the given seed.
|
|
476
|
+
*/
|
|
477
|
+
function withSeed(seed$1, fn) {
|
|
478
|
+
const previous = currentRng;
|
|
479
|
+
currentRng = createSeededRng(seed$1);
|
|
480
|
+
try {
|
|
481
|
+
return fn();
|
|
482
|
+
} finally {
|
|
483
|
+
currentRng = previous;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Seeds the global RNG for deterministic test runs.
|
|
488
|
+
*/
|
|
489
|
+
function seed(seed$1) {
|
|
490
|
+
currentRng = createSeededRng(seed$1);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Returns a deterministic random float in [0, 1).
|
|
494
|
+
*/
|
|
495
|
+
function float() {
|
|
496
|
+
return currentRng();
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Returns a deterministic random integer in [min, maxExclusive).
|
|
500
|
+
*/
|
|
501
|
+
function int(maxExclusive, min$1 = 0) {
|
|
502
|
+
return Math.floor(float() * (maxExclusive - min$1)) + min$1;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Returns a deterministic random boolean.
|
|
506
|
+
*/
|
|
507
|
+
function bool(probability = .5) {
|
|
508
|
+
return float() < probability;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Returns deterministic random bytes.
|
|
512
|
+
*/
|
|
513
|
+
function bytes(length) {
|
|
514
|
+
const output = new Uint8Array(length);
|
|
515
|
+
for (let i = 0; i < length; i += 1) output[i] = int(256);
|
|
516
|
+
return output;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Returns a deterministic random hex string for the given byte length.
|
|
520
|
+
*/
|
|
521
|
+
function hex(byteLength) {
|
|
522
|
+
const output = bytes(byteLength);
|
|
523
|
+
return `0x${Array.from(output, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Returns a deterministic random address.
|
|
527
|
+
*/
|
|
528
|
+
function address() {
|
|
529
|
+
return hex(20);
|
|
530
|
+
}
|
|
531
|
+
|
|
443
532
|
//#endregion
|
|
444
533
|
//#region src/utils/time.ts
|
|
445
534
|
var time_exports = /* @__PURE__ */ __export({
|
|
@@ -457,6 +546,7 @@ function max() {
|
|
|
457
546
|
//#region src/utils/index.ts
|
|
458
547
|
var utils_exports = /* @__PURE__ */ __export({
|
|
459
548
|
BaseError: () => BaseError,
|
|
549
|
+
Random: () => Random_exports,
|
|
460
550
|
ReorgError: () => ReorgError,
|
|
461
551
|
Time: () => time_exports,
|
|
462
552
|
batch: () => batch$1,
|
|
@@ -474,7 +564,7 @@ var utils_exports = /* @__PURE__ */ __export({
|
|
|
474
564
|
|
|
475
565
|
//#endregion
|
|
476
566
|
//#region src/indexer/collectors/Admin.ts
|
|
477
|
-
function create$
|
|
567
|
+
function create$17(parameters) {
|
|
478
568
|
const collector = "admin";
|
|
479
569
|
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
480
570
|
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
@@ -723,8 +813,8 @@ const names = [
|
|
|
723
813
|
"positions",
|
|
724
814
|
"prices"
|
|
725
815
|
];
|
|
726
|
-
function create$
|
|
727
|
-
const admin = create$
|
|
816
|
+
function create$16({ name, collect, client, db, options }) {
|
|
817
|
+
const admin = create$17({
|
|
728
818
|
client,
|
|
729
819
|
db,
|
|
730
820
|
options
|
|
@@ -774,7 +864,10 @@ function create$13({ name, collect, client, db, options }) {
|
|
|
774
864
|
};
|
|
775
865
|
});
|
|
776
866
|
if (done) iterator = null;
|
|
777
|
-
else
|
|
867
|
+
else {
|
|
868
|
+
lastBlockNumber = blockNumber;
|
|
869
|
+
yield blockNumber;
|
|
870
|
+
}
|
|
778
871
|
} catch (err) {
|
|
779
872
|
const isError = err instanceof Error;
|
|
780
873
|
logger.error({
|
|
@@ -986,8 +1079,12 @@ function decode$3(type, data) {
|
|
|
986
1079
|
}
|
|
987
1080
|
function encode$3(type, data) {
|
|
988
1081
|
switch (type) {
|
|
989
|
-
case CallbackType.BuyVaultV1Callback:
|
|
990
|
-
|
|
1082
|
+
case CallbackType.BuyVaultV1Callback:
|
|
1083
|
+
if (!("vaults" in data)) throw new Error("Invalid callback data");
|
|
1084
|
+
return encodeBuyVaultV1Callback(data);
|
|
1085
|
+
case CallbackType.SellERC20Callback:
|
|
1086
|
+
if (!("collaterals" in data)) throw new Error("Invalid callback data");
|
|
1087
|
+
return encodeSellERC20Callback(data);
|
|
991
1088
|
default: throw new Error("Invalid callback type");
|
|
992
1089
|
}
|
|
993
1090
|
}
|
|
@@ -1184,22 +1281,22 @@ const DEFAULT_BATCH_SIZE$2 = 2500;
|
|
|
1184
1281
|
const MAX_BLOCK_WINDOW = 1e4;
|
|
1185
1282
|
const DEFAULT_BLOCK_WINDOW = 8e3;
|
|
1186
1283
|
async function* streamLogs(parameters) {
|
|
1187
|
-
const { client, contractAddress, event, blockNumberGte, blockNumberLte, order
|
|
1284
|
+
const { client, contractAddress, event, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$2, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
|
|
1188
1285
|
if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
|
|
1189
1286
|
if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
|
|
1190
|
-
if (order
|
|
1287
|
+
if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
|
|
1191
1288
|
const latestBlock = (await (0, viem_actions.getBlock)(client, {
|
|
1192
1289
|
blockTag: "latest",
|
|
1193
1290
|
includeTransactions: false
|
|
1194
1291
|
})).number;
|
|
1195
1292
|
let toBlock = 0n;
|
|
1196
|
-
if (order
|
|
1197
|
-
if (order
|
|
1293
|
+
if (order === "asc") toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), blockNumberLte ? BigInt(blockNumberLte) : latestBlock);
|
|
1294
|
+
if (order === "desc") toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
|
|
1198
1295
|
let fromBlock = 0n;
|
|
1199
|
-
if (order
|
|
1200
|
-
if (order
|
|
1201
|
-
if (order
|
|
1202
|
-
if (order
|
|
1296
|
+
if (order === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
|
|
1297
|
+
if (order === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
|
|
1298
|
+
if (order === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
|
|
1299
|
+
if (order === "desc") fromBlock = max$1(fromBlock, toBlock - BigInt(blockWindow));
|
|
1203
1300
|
if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
|
|
1204
1301
|
let streaming = true;
|
|
1205
1302
|
while (streaming) {
|
|
@@ -1209,29 +1306,29 @@ async function* streamLogs(parameters) {
|
|
|
1209
1306
|
fromBlock,
|
|
1210
1307
|
toBlock
|
|
1211
1308
|
});
|
|
1212
|
-
streaming = order
|
|
1309
|
+
streaming = order === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
|
|
1213
1310
|
if (logs.length === 0 && !streaming) break;
|
|
1214
1311
|
if (logs.length === 0 && streaming) yield {
|
|
1215
1312
|
logs: [],
|
|
1216
|
-
blockNumber: order
|
|
1313
|
+
blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1217
1314
|
};
|
|
1218
1315
|
logs.sort((a, b) => {
|
|
1219
|
-
if (a.blockNumber !== b.blockNumber) return order
|
|
1220
|
-
if (a.transactionIndex !== b.transactionIndex) return order
|
|
1221
|
-
return order
|
|
1316
|
+
if (a.blockNumber !== b.blockNumber) return order === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
|
|
1317
|
+
if (a.transactionIndex !== b.transactionIndex) return order === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
|
|
1318
|
+
return order === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
|
|
1222
1319
|
});
|
|
1223
1320
|
for (const logBatch of batch$1(logs, maxBatchSize)) yield {
|
|
1224
1321
|
logs: logBatch,
|
|
1225
|
-
blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order
|
|
1322
|
+
blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1226
1323
|
};
|
|
1227
|
-
if (order
|
|
1324
|
+
if (order === "asc") {
|
|
1228
1325
|
const upperBound = BigInt(blockNumberLte || latestBlock);
|
|
1229
1326
|
const nextFromBlock = min(BigInt(toBlock) + 1n, upperBound);
|
|
1230
1327
|
const nextToBlock = min(toBlock + BigInt(blockWindow) + 1n, upperBound);
|
|
1231
1328
|
fromBlock = nextFromBlock;
|
|
1232
1329
|
toBlock = nextToBlock;
|
|
1233
1330
|
}
|
|
1234
|
-
if (order
|
|
1331
|
+
if (order === "desc") {
|
|
1235
1332
|
const lowerBound = BigInt(blockNumberGte || 0);
|
|
1236
1333
|
const nextToBlock = max$1(fromBlock - 1n, lowerBound);
|
|
1237
1334
|
const nextFromBlock = max$1(fromBlock - BigInt(blockWindow) - 1n, lowerBound);
|
|
@@ -1241,7 +1338,7 @@ async function* streamLogs(parameters) {
|
|
|
1241
1338
|
}
|
|
1242
1339
|
yield {
|
|
1243
1340
|
logs: [],
|
|
1244
|
-
blockNumber: order
|
|
1341
|
+
blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1245
1342
|
};
|
|
1246
1343
|
}
|
|
1247
1344
|
var InvalidBlockRangeError = class extends BaseError {
|
|
@@ -1390,8 +1487,8 @@ const from$15 = (parameters) => {
|
|
|
1390
1487
|
*/
|
|
1391
1488
|
function random$3() {
|
|
1392
1489
|
return from$15({
|
|
1393
|
-
asset: (
|
|
1394
|
-
oracle: (
|
|
1490
|
+
asset: address(),
|
|
1491
|
+
oracle: address(),
|
|
1395
1492
|
lltv: .965
|
|
1396
1493
|
});
|
|
1397
1494
|
}
|
|
@@ -1806,12 +1903,8 @@ function id(obligation) {
|
|
|
1806
1903
|
function random$2() {
|
|
1807
1904
|
return from$13({
|
|
1808
1905
|
chainId: 1,
|
|
1809
|
-
loanToken: (
|
|
1810
|
-
collaterals: [
|
|
1811
|
-
asset: (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
|
|
1812
|
-
oracle: (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
|
|
1813
|
-
lltv: .965
|
|
1814
|
-
})],
|
|
1906
|
+
loanToken: address(),
|
|
1907
|
+
collaterals: [random$3()],
|
|
1815
1908
|
maturity: from$14("end_of_next_quarter")
|
|
1816
1909
|
});
|
|
1817
1910
|
}
|
|
@@ -1831,101 +1924,249 @@ var CollateralsAreNotSortedError = class extends BaseError {
|
|
|
1831
1924
|
//#endregion
|
|
1832
1925
|
//#region src/core/Tree.ts
|
|
1833
1926
|
var Tree_exports = /* @__PURE__ */ __export({
|
|
1927
|
+
DecodeError: () => DecodeError,
|
|
1928
|
+
EncodeError: () => EncodeError,
|
|
1929
|
+
TreeError: () => TreeError,
|
|
1834
1930
|
VERSION: () => VERSION$1,
|
|
1835
1931
|
decode: () => decode$2,
|
|
1836
1932
|
encode: () => encode$2,
|
|
1837
|
-
|
|
1933
|
+
encodeUnsigned: () => encodeUnsigned,
|
|
1934
|
+
from: () => from$12,
|
|
1935
|
+
proofs: () => proofs
|
|
1838
1936
|
});
|
|
1839
1937
|
const VERSION$1 = 1;
|
|
1938
|
+
const normalizeHash = (hash$1) => hash$1.toLowerCase();
|
|
1840
1939
|
/**
|
|
1841
1940
|
* Builds a Merkle tree from a list of offers.
|
|
1842
1941
|
*
|
|
1843
1942
|
* Leaves are the offer `hash` values as `bytes32` and are deterministically
|
|
1844
|
-
* ordered
|
|
1845
|
-
* regardless of the input order.
|
|
1943
|
+
* ordered following the StandardMerkleTree leaf ordering so that the resulting
|
|
1944
|
+
* root is stable regardless of the input order.
|
|
1846
1945
|
*
|
|
1847
1946
|
* @param offers - Offers to include in the tree.
|
|
1848
1947
|
* @returns A `StandardMerkleTree` of `bytes32` leaves representing the offers.
|
|
1948
|
+
* @throws {TreeError} If tree building fails due to offer inconsistencies.
|
|
1849
1949
|
*/
|
|
1850
1950
|
const from$12 = (offers$1) => {
|
|
1851
|
-
const leaves =
|
|
1852
|
-
return [offer.hash];
|
|
1853
|
-
});
|
|
1951
|
+
const leaves = offers$1.map((offer) => [offer.hash]);
|
|
1854
1952
|
const tree = __openzeppelin_merkle_tree.StandardMerkleTree.of(leaves, ["bytes32"]);
|
|
1855
|
-
|
|
1953
|
+
const orderedOffers = orderOffers(tree, offers$1);
|
|
1954
|
+
return Object.assign(tree, { offers: orderedOffers });
|
|
1856
1955
|
};
|
|
1857
|
-
const
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1956
|
+
const orderOffers = (tree, offers$1) => {
|
|
1957
|
+
const offerByHash = /* @__PURE__ */ new Map();
|
|
1958
|
+
for (const offer of offers$1) offerByHash.set(normalizeHash(offer.hash), offer);
|
|
1959
|
+
const entries = tree.dump().values.map((value) => {
|
|
1960
|
+
const hash$1 = normalizeHash(value.value[0]);
|
|
1961
|
+
const offer = offerByHash.get(hash$1);
|
|
1962
|
+
if (!offer) throw new TreeError(`missing offer for leaf ${hash$1}`);
|
|
1963
|
+
return {
|
|
1964
|
+
offer,
|
|
1965
|
+
treeIndex: value.treeIndex
|
|
1966
|
+
};
|
|
1967
|
+
});
|
|
1968
|
+
entries.sort((a, b) => b.treeIndex - a.treeIndex);
|
|
1969
|
+
return entries.map((item) => item.offer);
|
|
1860
1970
|
};
|
|
1861
1971
|
/**
|
|
1862
|
-
*
|
|
1972
|
+
* Generates merkle proofs for all offers in a tree.
|
|
1863
1973
|
*
|
|
1864
|
-
*
|
|
1865
|
-
*
|
|
1974
|
+
* Each proof allows independent verification that an offer is included in the tree
|
|
1975
|
+
* without requiring the full tree. Proofs are ordered by StandardMerkleTree leaf ordering.
|
|
1866
1976
|
*
|
|
1867
|
-
* @param tree - The
|
|
1868
|
-
* @returns
|
|
1869
|
-
* @throws Error if the given `root` does not match the offers.
|
|
1977
|
+
* @param tree - The {@link Tree} to generate proofs for.
|
|
1978
|
+
* @returns Array of proofs - {@link Proof}
|
|
1870
1979
|
*/
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1980
|
+
const proofs = (tree) => {
|
|
1981
|
+
return tree.offers.map((offer) => {
|
|
1982
|
+
return {
|
|
1983
|
+
offer,
|
|
1984
|
+
path: tree.getProof([offer.hash])
|
|
1985
|
+
};
|
|
1986
|
+
});
|
|
1987
|
+
};
|
|
1988
|
+
const assertHex = (value, expectedBytes, name) => {
|
|
1989
|
+
if (typeof value !== "string" || !(0, viem.isHex)(value)) throw new DecodeError(`${name} is not a valid hex string`);
|
|
1990
|
+
if ((0, viem.hexToBytes)(value).length !== expectedBytes) throw new DecodeError(`${name}: expected ${expectedBytes} bytes`);
|
|
1991
|
+
};
|
|
1992
|
+
const verifySignatureAndRecoverAddress = async (params) => {
|
|
1993
|
+
const { root, signature } = params;
|
|
1994
|
+
assertHex(signature, 65, "signature");
|
|
1995
|
+
const hash$1 = (0, viem.hashMessage)({ raw: root });
|
|
1996
|
+
try {
|
|
1997
|
+
return await (0, viem.recoverAddress)({
|
|
1998
|
+
hash: hash$1,
|
|
1999
|
+
signature
|
|
2000
|
+
});
|
|
2001
|
+
} catch {
|
|
2002
|
+
throw new DecodeError("signature recovery failed");
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
/**
|
|
2006
|
+
* Encodes a merkle tree with signature into hex calldata for onchain broadcast.
|
|
2007
|
+
*
|
|
2008
|
+
* Layout: `0x{vv}{gzip([...offers])}{root}{signature}` where:
|
|
2009
|
+
* - `{vv}`: 1-byte version (currently 0x01)
|
|
2010
|
+
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
2011
|
+
* - `{root}`: 32-byte merkle root
|
|
2012
|
+
* - `{signature}`: 65-byte EIP-191 signature over raw root bytes
|
|
2013
|
+
*
|
|
2014
|
+
* Validates signature authenticity and root integrity before encoding.
|
|
2015
|
+
*
|
|
2016
|
+
* @example
|
|
2017
|
+
* ```typescript
|
|
2018
|
+
* const tree = Tree.from(offers);
|
|
2019
|
+
* const signature = await wallet.signMessage({ message: { raw: tree.root } });
|
|
2020
|
+
* const calldata = await Tree.encode(tree, signature);
|
|
2021
|
+
* await broadcast(calldata);
|
|
2022
|
+
* ```
|
|
2023
|
+
*
|
|
2024
|
+
* @example
|
|
2025
|
+
* Manual construction (for advanced users):
|
|
2026
|
+
* ```typescript
|
|
2027
|
+
* const tree = Tree.from(offers);
|
|
2028
|
+
* const compressed = gzip(JSON.stringify(tree.offers.map(Offer.serialize)));
|
|
2029
|
+
* const partial = `0x01${bytesToHex(compressed)}${tree.root.slice(2)}`;
|
|
2030
|
+
* const signature = await wallet.signMessage({ message: { raw: tree.root } });
|
|
2031
|
+
* const calldata = `${partial}${signature.slice(2)}`;
|
|
2032
|
+
* ```
|
|
2033
|
+
*
|
|
2034
|
+
* @param tree - Merkle tree of offers
|
|
2035
|
+
* @param signature - EIP-191 signature over raw root bytes
|
|
2036
|
+
* @returns Hex-encoded calldata ready for onchain broadcast
|
|
2037
|
+
* @throws {EncodeError} If signature verification fails or root mismatch
|
|
2038
|
+
*/
|
|
2039
|
+
const encode$2 = async (tree, signature) => {
|
|
2040
|
+
validateTreeForEncoding(tree);
|
|
2041
|
+
await verifySignatureAndRecoverAddress({
|
|
2042
|
+
root: tree.root,
|
|
2043
|
+
signature
|
|
2044
|
+
});
|
|
2045
|
+
const unsigned = encodeUnsignedBytes(tree);
|
|
2046
|
+
const sigBytes = (0, viem.hexToBytes)(signature);
|
|
2047
|
+
const encoded = new Uint8Array(unsigned.length + sigBytes.length);
|
|
2048
|
+
encoded.set(unsigned, 0);
|
|
2049
|
+
encoded.set(sigBytes, unsigned.length);
|
|
1902
2050
|
return (0, viem.bytesToHex)(encoded);
|
|
1903
2051
|
};
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2052
|
+
/**
|
|
2053
|
+
* Encodes a merkle tree without a signature into hex payload for client-side signing.
|
|
2054
|
+
*
|
|
2055
|
+
* Layout: `0x{vv}{gzip([...offers])}{root}` where:
|
|
2056
|
+
* - `{vv}`: 1-byte version (currently 0x01)
|
|
2057
|
+
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
2058
|
+
* - `{root}`: 32-byte merkle root
|
|
2059
|
+
*
|
|
2060
|
+
* Validates root integrity before encoding.
|
|
2061
|
+
*
|
|
2062
|
+
* @param tree - Merkle tree of offers
|
|
2063
|
+
* @returns Hex-encoded unsigned payload
|
|
2064
|
+
* @throws {EncodeError} If root mismatch
|
|
2065
|
+
*/
|
|
2066
|
+
const encodeUnsigned = (tree) => {
|
|
2067
|
+
validateTreeForEncoding(tree);
|
|
2068
|
+
return (0, viem.bytesToHex)(encodeUnsignedBytes(tree));
|
|
2069
|
+
};
|
|
2070
|
+
const validateTreeForEncoding = (tree) => {
|
|
2071
|
+
if (VERSION$1 > 255) throw new EncodeError(`version overflow: ${VERSION$1} exceeds 255`);
|
|
2072
|
+
const computed = from$12(tree.offers);
|
|
2073
|
+
if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
|
|
2074
|
+
};
|
|
2075
|
+
const encodeUnsignedBytes = (tree) => {
|
|
2076
|
+
const offersPayload = tree.offers.map(serialize);
|
|
2077
|
+
const compressed = (0, pako.gzip)(JSON.stringify(offersPayload));
|
|
2078
|
+
const rootBytes = (0, viem.hexToBytes)(tree.root);
|
|
2079
|
+
const encoded = new Uint8Array(1 + compressed.length + 32);
|
|
2080
|
+
encoded[0] = VERSION$1;
|
|
2081
|
+
encoded.set(compressed, 1);
|
|
2082
|
+
encoded.set(rootBytes, 1 + compressed.length);
|
|
2083
|
+
return encoded;
|
|
1907
2084
|
};
|
|
1908
2085
|
/**
|
|
1909
|
-
* Decodes
|
|
2086
|
+
* Decodes hex calldata into a validated merkle tree.
|
|
1910
2087
|
*
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
2088
|
+
* Validates signature before decompression for fail-fast rejection of invalid payloads.
|
|
2089
|
+
* Returns the tree with separately validated signature and recovered signer address.
|
|
1913
2090
|
*
|
|
1914
|
-
*
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
2091
|
+
* Validation order:
|
|
2092
|
+
* 1. Version check
|
|
2093
|
+
* 2. Signature verification (fail-fast, before decompression)
|
|
2094
|
+
* 3. Decompression (only if signature valid)
|
|
2095
|
+
* 4. Root verification (computed from offers vs embedded root)
|
|
2096
|
+
*
|
|
2097
|
+
* @example
|
|
2098
|
+
* ```typescript
|
|
2099
|
+
* const { tree, signature, signer } = await Tree.decode(calldata);
|
|
2100
|
+
* console.log(`Tree signed by ${signer} with ${tree.offers.length} offers`);
|
|
2101
|
+
* ```
|
|
2102
|
+
*
|
|
2103
|
+
* @param encoded - Hex calldata in format `0x{vv}{gzip}{root}{signature}`
|
|
2104
|
+
* @returns Validated tree, signature, and recovered signer address
|
|
2105
|
+
* @throws {DecodeError} If version invalid, signature invalid, or root mismatch
|
|
2106
|
+
*/
|
|
2107
|
+
const decode$2 = async (encoded) => {
|
|
2108
|
+
const bytes$1 = (0, viem.hexToBytes)(encoded);
|
|
2109
|
+
if (bytes$1.length < 98) throw new DecodeError("payload too short");
|
|
2110
|
+
const version = bytes$1[0];
|
|
2111
|
+
if (version !== (VERSION$1 & 255)) throw new DecodeError(`invalid version: expected ${VERSION$1}, got ${version ?? 0}`);
|
|
2112
|
+
const signature = (0, viem.bytesToHex)(bytes$1.slice(-65));
|
|
2113
|
+
const root = (0, viem.bytesToHex)(bytes$1.slice(-97, -65));
|
|
2114
|
+
assertHex(root, 32, "root");
|
|
2115
|
+
assertHex(signature, 65, "signature");
|
|
2116
|
+
const signer = await verifySignatureAndRecoverAddress({
|
|
2117
|
+
root,
|
|
2118
|
+
signature
|
|
2119
|
+
});
|
|
2120
|
+
const compressed = bytes$1.slice(1, -97);
|
|
2121
|
+
let decoded;
|
|
2122
|
+
try {
|
|
2123
|
+
decoded = (0, pako.ungzip)(compressed, { to: "string" });
|
|
2124
|
+
} catch {
|
|
2125
|
+
throw new DecodeError("decompression failed");
|
|
2126
|
+
}
|
|
2127
|
+
let rawOffers;
|
|
2128
|
+
try {
|
|
2129
|
+
rawOffers = JSON.parse(decoded);
|
|
2130
|
+
} catch {
|
|
2131
|
+
throw new DecodeError("JSON parse failed");
|
|
2132
|
+
}
|
|
2133
|
+
const tree = from$12(rawOffers.map((o) => OfferSchema().parse(o)));
|
|
2134
|
+
if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
|
|
2135
|
+
return {
|
|
2136
|
+
tree,
|
|
2137
|
+
signature,
|
|
2138
|
+
signer
|
|
2139
|
+
};
|
|
2140
|
+
};
|
|
2141
|
+
/**
|
|
2142
|
+
* Error thrown during tree building operations.
|
|
2143
|
+
* Indicates structural issues with the tree (missing offers, inconsistent state).
|
|
2144
|
+
*/
|
|
2145
|
+
var TreeError = class extends BaseError {
|
|
2146
|
+
name = "Tree.TreeError";
|
|
2147
|
+
constructor(reason) {
|
|
2148
|
+
super(`Tree error: ${reason}`);
|
|
2149
|
+
}
|
|
2150
|
+
};
|
|
2151
|
+
/**
|
|
2152
|
+
* Error thrown during tree encoding.
|
|
2153
|
+
* Indicates validation failures (signature, root mismatch, mixed makers).
|
|
2154
|
+
*/
|
|
2155
|
+
var EncodeError = class extends BaseError {
|
|
2156
|
+
name = "Tree.EncodeError";
|
|
2157
|
+
constructor(reason) {
|
|
2158
|
+
super(`Failed to encode tree: ${reason}`);
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
2161
|
+
/**
|
|
2162
|
+
* Error thrown during tree decoding.
|
|
2163
|
+
* Indicates payload corruption, version mismatch, or validation failures.
|
|
1917
2164
|
*/
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const decoded = (0, pako.ungzip)(bytes.slice(1), { to: "string" });
|
|
1924
|
-
const data = JSON.parse(decoded);
|
|
1925
|
-
const root = data[0];
|
|
1926
|
-
const tree = from$12(data.slice(1).map((o) => OfferSchema().parse(o)));
|
|
1927
|
-
if (root !== tree.root) throw new Error(`Invalid root: expected ${tree.root}, got ${root}`);
|
|
1928
|
-
return tree;
|
|
2165
|
+
var DecodeError = class extends BaseError {
|
|
2166
|
+
name = "Tree.DecodeError";
|
|
2167
|
+
constructor(reason) {
|
|
2168
|
+
super(`Failed to decode tree: ${reason}`);
|
|
2169
|
+
}
|
|
1929
2170
|
};
|
|
1930
2171
|
|
|
1931
2172
|
//#endregion
|
|
@@ -1945,6 +2186,7 @@ var Offer_exports = /* @__PURE__ */ __export({
|
|
|
1945
2186
|
hash: () => hash,
|
|
1946
2187
|
obligationId: () => obligationId,
|
|
1947
2188
|
random: () => random$1,
|
|
2189
|
+
serialize: () => serialize,
|
|
1948
2190
|
sign: () => sign,
|
|
1949
2191
|
signatureMsg: () => signatureMsg,
|
|
1950
2192
|
toSnakeCase: () => toSnakeCase,
|
|
@@ -2025,16 +2267,47 @@ function toSnakeCase(offer) {
|
|
|
2025
2267
|
return toSnakeCase$1(offer);
|
|
2026
2268
|
}
|
|
2027
2269
|
/**
|
|
2270
|
+
* Serializes an offer for merkle tree encoding.
|
|
2271
|
+
* Converts BigInt fields to strings for JSON compatibility.
|
|
2272
|
+
*
|
|
2273
|
+
* @param offer - Offer to serialize
|
|
2274
|
+
* @returns JSON-serializable offer object
|
|
2275
|
+
*/
|
|
2276
|
+
const serialize = (offer) => ({
|
|
2277
|
+
offering: offer.offering,
|
|
2278
|
+
assets: offer.assets.toString(),
|
|
2279
|
+
rate: offer.rate.toString(),
|
|
2280
|
+
maturity: Number(offer.maturity),
|
|
2281
|
+
expiry: Number(offer.expiry),
|
|
2282
|
+
start: Number(offer.start),
|
|
2283
|
+
nonce: offer.nonce.toString(),
|
|
2284
|
+
buy: offer.buy,
|
|
2285
|
+
chainId: offer.chainId,
|
|
2286
|
+
loanToken: offer.loanToken,
|
|
2287
|
+
collaterals: offer.collaterals.map((c) => ({
|
|
2288
|
+
asset: c.asset,
|
|
2289
|
+
oracle: c.oracle,
|
|
2290
|
+
lltv: c.lltv.toString()
|
|
2291
|
+
})),
|
|
2292
|
+
callback: {
|
|
2293
|
+
address: offer.callback.address,
|
|
2294
|
+
data: offer.callback.data,
|
|
2295
|
+
gasLimit: offer.callback.gasLimit.toString()
|
|
2296
|
+
},
|
|
2297
|
+
signature: offer.signature,
|
|
2298
|
+
hash: offer.hash
|
|
2299
|
+
});
|
|
2300
|
+
/**
|
|
2028
2301
|
* Generates a random Offer.
|
|
2029
2302
|
* The returned Offer contains randomly generated values.
|
|
2030
2303
|
* @warning The generated Offer should not be used for production usage.
|
|
2031
2304
|
* @returns {Offer} A randomly generated Offer object.
|
|
2032
2305
|
*/
|
|
2033
2306
|
function random$1(config) {
|
|
2034
|
-
const chain = config?.chains ? config.chains[
|
|
2035
|
-
const loanToken = config?.loanTokens ? config.loanTokens[
|
|
2036
|
-
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [(
|
|
2037
|
-
const collateralAsset = collateralCandidates[
|
|
2307
|
+
const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
|
|
2308
|
+
const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
|
|
2309
|
+
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
|
|
2310
|
+
const collateralAsset = collateralCandidates[int(collateralCandidates.length)];
|
|
2038
2311
|
const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
|
|
2039
2312
|
const maturity$1 = config?.maturity ?? from$14(maturityOption);
|
|
2040
2313
|
const lltv = from$16(weightedChoice([
|
|
@@ -2048,7 +2321,7 @@ function random$1(config) {
|
|
|
2048
2321
|
[.965, 4],
|
|
2049
2322
|
[.98, 2]
|
|
2050
2323
|
]));
|
|
2051
|
-
const buy = config?.buy !== void 0 ? config.buy :
|
|
2324
|
+
const buy = config?.buy !== void 0 ? config.buy : bool();
|
|
2052
2325
|
const ONE = 1000000000000000000n;
|
|
2053
2326
|
const qMin = buy ? 16 : 4;
|
|
2054
2327
|
const len = (buy ? 32 : 16) - qMin + 1;
|
|
@@ -2059,9 +2332,9 @@ function random$1(config) {
|
|
|
2059
2332
|
const rate = config?.rate ?? weightedChoice(ratePairs);
|
|
2060
2333
|
const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
|
|
2061
2334
|
const unit = BigInt(10) ** BigInt(loanTokenDecimals);
|
|
2062
|
-
const amountBase = BigInt(100 +
|
|
2335
|
+
const amountBase = BigInt(100 + int(999901));
|
|
2063
2336
|
const assetsScaled = config?.assets ?? amountBase * unit;
|
|
2064
|
-
const consumed = config?.consumed !== void 0 ? config.consumed :
|
|
2337
|
+
const consumed = config?.consumed !== void 0 ? config.consumed : float() < .8 ? 0n : assetsScaled * BigInt(1 + int(900)) / 1000n;
|
|
2065
2338
|
const callbackBySide = (() => {
|
|
2066
2339
|
if (buy) return {
|
|
2067
2340
|
address: viem.zeroAddress,
|
|
@@ -2080,29 +2353,29 @@ function random$1(config) {
|
|
|
2080
2353
|
};
|
|
2081
2354
|
})();
|
|
2082
2355
|
return from$11({
|
|
2083
|
-
offering: config?.offering ?? (
|
|
2356
|
+
offering: config?.offering ?? address(),
|
|
2084
2357
|
assets: assetsScaled,
|
|
2085
2358
|
rate,
|
|
2086
2359
|
maturity: maturity$1,
|
|
2087
2360
|
expiry: config?.expiry ?? maturity$1 - 1,
|
|
2088
2361
|
start: config?.start ?? maturity$1 - 10,
|
|
2089
|
-
nonce: BigInt(
|
|
2362
|
+
nonce: BigInt(int(1e6)),
|
|
2090
2363
|
buy,
|
|
2091
2364
|
chainId: chain.id,
|
|
2092
2365
|
loanToken,
|
|
2093
|
-
collaterals: config?.collaterals ?? Array.from({ length:
|
|
2366
|
+
collaterals: config?.collaterals ?? Array.from({ length: int(3) + 1 }, () => ({
|
|
2094
2367
|
...random$3(),
|
|
2095
2368
|
lltv
|
|
2096
2369
|
})).sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
2097
2370
|
callback: config?.callback ?? callbackBySide,
|
|
2098
2371
|
consumed,
|
|
2099
2372
|
takeable: config?.takeable ?? assetsScaled - consumed,
|
|
2100
|
-
blockNumber: config?.blockNumber ??
|
|
2373
|
+
blockNumber: config?.blockNumber ?? int(Number.MAX_SAFE_INTEGER)
|
|
2101
2374
|
});
|
|
2102
2375
|
}
|
|
2103
2376
|
const weightedChoice = (pairs) => {
|
|
2104
2377
|
const total = pairs.reduce((sum, [, weight]) => sum + weight, 0);
|
|
2105
|
-
let roll =
|
|
2378
|
+
let roll = float() * total;
|
|
2106
2379
|
for (const [value, weight] of pairs) {
|
|
2107
2380
|
roll -= weight;
|
|
2108
2381
|
if (roll < 0) return value;
|
|
@@ -2570,8 +2843,8 @@ function fromSnakeCase(snake) {
|
|
|
2570
2843
|
function random() {
|
|
2571
2844
|
return from$8({
|
|
2572
2845
|
obligationId: id(random$2()),
|
|
2573
|
-
ask: { rate: BigInt(
|
|
2574
|
-
bid: { rate: BigInt(
|
|
2846
|
+
ask: { rate: BigInt(int(1e6)) },
|
|
2847
|
+
bid: { rate: BigInt(int(1e6)) }
|
|
2575
2848
|
});
|
|
2576
2849
|
}
|
|
2577
2850
|
var InvalidQuoteError = class extends BaseError {
|
|
@@ -2755,30 +3028,79 @@ async function* collectOffersV2(parameters) {
|
|
|
2755
3028
|
});
|
|
2756
3029
|
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
2757
3030
|
blockNumber = lastStreamBlockNumber;
|
|
2758
|
-
const
|
|
3031
|
+
const decodedTrees = [];
|
|
2759
3032
|
for (const log of logs) {
|
|
2760
3033
|
if (!log) continue;
|
|
2761
3034
|
const [payload] = (0, viem.decodeAbiParameters)([{ type: "bytes" }], log.data);
|
|
2762
3035
|
try {
|
|
2763
|
-
const tree = decode$2(payload);
|
|
2764
|
-
|
|
3036
|
+
const { tree, signature, signer } = await decode$2(payload);
|
|
3037
|
+
const signerMismatch = tree.offers.find((offer) => offer.offering.toLowerCase() !== signer.toLowerCase());
|
|
3038
|
+
if (signerMismatch) {
|
|
3039
|
+
logger.debug({
|
|
3040
|
+
msg: "Tree rejected: signer mismatch",
|
|
3041
|
+
reason: "signer_mismatch",
|
|
3042
|
+
signer,
|
|
3043
|
+
offering: signerMismatch.offering,
|
|
3044
|
+
chain_id: client.chain.id
|
|
3045
|
+
});
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
const offersWithBlock = tree.offers.map((offer) => ({
|
|
2765
3049
|
...offer,
|
|
2766
3050
|
blockNumber: Number(log.blockNumber)
|
|
3051
|
+
}));
|
|
3052
|
+
const treeWithBlock = Object.assign(Object.create(Object.getPrototypeOf(tree)), tree, { offers: offersWithBlock });
|
|
3053
|
+
decodedTrees.push({
|
|
3054
|
+
tree: treeWithBlock,
|
|
3055
|
+
signature,
|
|
3056
|
+
signer,
|
|
3057
|
+
blockNumber: Number(log.blockNumber)
|
|
2767
3058
|
});
|
|
2768
|
-
} catch (
|
|
3059
|
+
} catch (err) {
|
|
3060
|
+
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
3061
|
+
logger.debug({
|
|
3062
|
+
msg: "Tree decode failed",
|
|
3063
|
+
reason,
|
|
3064
|
+
chain_id: client.chain.id,
|
|
3065
|
+
err: err instanceof Error ? err.message : String(err)
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
2769
3068
|
}
|
|
2770
3069
|
await db.transaction(async (dbTx) => {
|
|
2771
3070
|
const { epoch, blockNumber: latestBlockNumber } = await dbTx.chains.getBlockNumber(client.chain.id);
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
3071
|
+
const treesToInsert = [];
|
|
3072
|
+
let totalValidOffers = 0;
|
|
3073
|
+
for (const { tree, signature } of decodedTrees) try {
|
|
3074
|
+
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
3075
|
+
const hasBlockWindowViolation = tree.offers.some((offer) => offer.blockNumber > latestBlockNumber);
|
|
3076
|
+
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
3077
|
+
if (allowedResults.issues.length > 0) {
|
|
3078
|
+
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
3079
|
+
logger.debug({
|
|
3080
|
+
msg: "Tree offers rejected by gatekeeper",
|
|
3081
|
+
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
3082
|
+
chain_id: client.chain.id,
|
|
3083
|
+
issues_count: allowedResults.issues.length
|
|
3084
|
+
});
|
|
3085
|
+
} else if (hasBlockWindowViolation) logger.debug({
|
|
3086
|
+
msg: "Tree rejected: offers outside block window",
|
|
3087
|
+
reason: "block_window",
|
|
3088
|
+
chain_id: client.chain.id
|
|
3089
|
+
});
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3092
|
+
treesToInsert.push({
|
|
3093
|
+
tree,
|
|
3094
|
+
signature
|
|
3095
|
+
});
|
|
3096
|
+
totalValidOffers += tree.offers.length;
|
|
2775
3097
|
} catch (err) {
|
|
2776
3098
|
logger.error({
|
|
2777
3099
|
err,
|
|
2778
|
-
msg: "Failed to validate offers"
|
|
3100
|
+
msg: "Failed to validate offers for tree"
|
|
2779
3101
|
});
|
|
2780
3102
|
}
|
|
2781
|
-
await dbTx.
|
|
3103
|
+
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
2782
3104
|
try {
|
|
2783
3105
|
await dbTx.collectors.saveBlockNumber({
|
|
2784
3106
|
collectorName: collector,
|
|
@@ -2786,10 +3108,11 @@ async function* collectOffersV2(parameters) {
|
|
|
2786
3108
|
blockNumber,
|
|
2787
3109
|
epoch
|
|
2788
3110
|
});
|
|
2789
|
-
if (
|
|
3111
|
+
if (totalValidOffers > 0) logger.info({
|
|
2790
3112
|
msg: `New offers`,
|
|
2791
3113
|
collector,
|
|
2792
|
-
count:
|
|
3114
|
+
count: totalValidOffers,
|
|
3115
|
+
trees_count: treesToInsert.length,
|
|
2793
3116
|
chain_id: client.chain.id,
|
|
2794
3117
|
block_range: [startBlock, lastStreamBlockNumber]
|
|
2795
3118
|
});
|
|
@@ -3432,7 +3755,7 @@ async function* collectPrices(parameters) {
|
|
|
3432
3755
|
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
3433
3756
|
function createBuilder(parameters) {
|
|
3434
3757
|
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow } = {} } = parameters;
|
|
3435
|
-
const createCollector = (name, collect) => create$
|
|
3758
|
+
const createCollector = (name, collect) => create$16({
|
|
3436
3759
|
name,
|
|
3437
3760
|
collect,
|
|
3438
3761
|
client,
|
|
@@ -3520,7 +3843,7 @@ const from$6 = (parameters) => {
|
|
|
3520
3843
|
//#endregion
|
|
3521
3844
|
//#region src/indexer/Indexer.ts
|
|
3522
3845
|
var Indexer_exports = /* @__PURE__ */ __export({
|
|
3523
|
-
create: () => create$
|
|
3846
|
+
create: () => create$15,
|
|
3524
3847
|
from: () => from$5
|
|
3525
3848
|
});
|
|
3526
3849
|
function from$5(config) {
|
|
@@ -3535,7 +3858,7 @@ function from$5(config) {
|
|
|
3535
3858
|
retryAttempts,
|
|
3536
3859
|
retryDelayMs
|
|
3537
3860
|
});
|
|
3538
|
-
return create$
|
|
3861
|
+
return create$15({
|
|
3539
3862
|
db,
|
|
3540
3863
|
client,
|
|
3541
3864
|
collectors: [
|
|
@@ -3547,7 +3870,7 @@ function from$5(config) {
|
|
|
3547
3870
|
interval
|
|
3548
3871
|
});
|
|
3549
3872
|
}
|
|
3550
|
-
function create$
|
|
3873
|
+
function create$15(params) {
|
|
3551
3874
|
const { db, collectors: collectors$1, interval, client } = params;
|
|
3552
3875
|
const logger = getLogger();
|
|
3553
3876
|
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
@@ -3604,12 +3927,12 @@ function create$12(params) {
|
|
|
3604
3927
|
|
|
3605
3928
|
//#endregion
|
|
3606
3929
|
//#region src/api/Health.ts
|
|
3607
|
-
var Health_exports = /* @__PURE__ */ __export({ create: () => create$
|
|
3930
|
+
var Health_exports = /* @__PURE__ */ __export({ create: () => create$14 });
|
|
3608
3931
|
const DEFAULT_MAX_ALLOWED_LAG = 5;
|
|
3609
3932
|
/**
|
|
3610
3933
|
* Create a health service that exposes collector and chain block numbers.
|
|
3611
3934
|
*/
|
|
3612
|
-
function create$
|
|
3935
|
+
function create$14(parameters) {
|
|
3613
3936
|
const { db, maxAllowedLag = DEFAULT_MAX_ALLOWED_LAG, healthClients } = parameters;
|
|
3614
3937
|
const loadSnapshot = async () => {
|
|
3615
3938
|
const [collectorRows, chainRows, remoteBlockByChainId] = await Promise.all([
|
|
@@ -3756,13 +4079,16 @@ var OfferResponse_exports = /* @__PURE__ */ __export({ from: () => from$2 });
|
|
|
3756
4079
|
* Creates an `OfferResponse` from an `Offer`.
|
|
3757
4080
|
* @constructor
|
|
3758
4081
|
* @param offer - {@link Offer}
|
|
4082
|
+
* @param attestation - {@link Attestation}
|
|
3759
4083
|
* @returns The created `OfferResponse`. {@link OfferResponse}
|
|
3760
4084
|
*/
|
|
3761
|
-
function from$2(offer) {
|
|
3762
|
-
const
|
|
4085
|
+
function from$2(offer, attestation) {
|
|
4086
|
+
const { signature: _, ...rest } = toSnakeCase$1(offer);
|
|
3763
4087
|
return {
|
|
3764
|
-
...
|
|
3765
|
-
|
|
4088
|
+
...rest,
|
|
4089
|
+
root: attestation?.root.toLowerCase() ?? null,
|
|
4090
|
+
proof: attestation?.proof.map((p) => p.toLowerCase()) ?? null,
|
|
4091
|
+
signature: attestation?.signature.toLowerCase() ?? null
|
|
3766
4092
|
};
|
|
3767
4093
|
}
|
|
3768
4094
|
|
|
@@ -3900,10 +4226,12 @@ const offerExample = {
|
|
|
3900
4226
|
data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000",
|
|
3901
4227
|
gas_limit: "500000"
|
|
3902
4228
|
},
|
|
3903
|
-
signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400",
|
|
3904
4229
|
consumed: "0",
|
|
3905
4230
|
takeable: "369216000000000000000000",
|
|
3906
|
-
block_number: 0xa7495128adfb1
|
|
4231
|
+
block_number: 0xa7495128adfb1,
|
|
4232
|
+
root: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
|
4233
|
+
proof: ["0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"],
|
|
4234
|
+
signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400"
|
|
3907
4235
|
};
|
|
3908
4236
|
const collectorsHealthExample = {
|
|
3909
4237
|
name: "offers",
|
|
@@ -4048,6 +4376,16 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
|
4048
4376
|
type: "number",
|
|
4049
4377
|
example: offerExample.block_number
|
|
4050
4378
|
})], OfferListItemResponse.prototype, "block_number", void 0);
|
|
4379
|
+
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4380
|
+
type: "string",
|
|
4381
|
+
nullable: true,
|
|
4382
|
+
example: offerExample.root
|
|
4383
|
+
})], OfferListItemResponse.prototype, "root", void 0);
|
|
4384
|
+
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4385
|
+
type: [String],
|
|
4386
|
+
nullable: true,
|
|
4387
|
+
example: offerExample.proof
|
|
4388
|
+
})], OfferListItemResponse.prototype, "proof", void 0);
|
|
4051
4389
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4052
4390
|
type: "string",
|
|
4053
4391
|
nullable: true,
|
|
@@ -4231,44 +4569,61 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
|
4231
4569
|
var ValidateOffersRequest = class {};
|
|
4232
4570
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4233
4571
|
type: () => [ValidateOfferRequest],
|
|
4234
|
-
description: "Array of offers in snake_case format.
|
|
4235
|
-
required:
|
|
4572
|
+
description: "Array of offers in snake_case format. Required, non-empty.",
|
|
4573
|
+
required: true
|
|
4236
4574
|
})], ValidateOffersRequest.prototype, "offers", void 0);
|
|
4575
|
+
var ValidationSuccessDataResponse = class {};
|
|
4237
4576
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4238
4577
|
type: "string",
|
|
4239
|
-
description: "
|
|
4240
|
-
example: "
|
|
4241
|
-
|
|
4242
|
-
})], ValidateOffersRequest.prototype, "calldata", void 0);
|
|
4243
|
-
var ValidateOfferResultResponse = class {};
|
|
4578
|
+
description: "Unsigned payload: version (1B) + gzip(offers) + root (32B).",
|
|
4579
|
+
example: "0x01789c..."
|
|
4580
|
+
})], ValidationSuccessDataResponse.prototype, "payload", void 0);
|
|
4244
4581
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4245
4582
|
type: "string",
|
|
4246
|
-
|
|
4247
|
-
|
|
4583
|
+
description: "Merkle tree root to sign with EIP-191.",
|
|
4584
|
+
example: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427"
|
|
4585
|
+
})], ValidationSuccessDataResponse.prototype, "root", void 0);
|
|
4586
|
+
var ValidationSuccessResponse = class extends SuccessResponse {};
|
|
4248
4587
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4249
|
-
type: "
|
|
4250
|
-
|
|
4251
|
-
|
|
4588
|
+
type: "string",
|
|
4589
|
+
nullable: true,
|
|
4590
|
+
example: null
|
|
4591
|
+
})], ValidationSuccessResponse.prototype, "cursor", void 0);
|
|
4592
|
+
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4593
|
+
type: () => ValidationSuccessDataResponse,
|
|
4594
|
+
description: "Payload and root for client-side signing."
|
|
4595
|
+
})], ValidationSuccessResponse.prototype, "data", void 0);
|
|
4596
|
+
var ValidationIssueResponse = class {};
|
|
4597
|
+
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4598
|
+
type: "number",
|
|
4599
|
+
description: "0-indexed position of the failed offer in the request array.",
|
|
4600
|
+
example: 0
|
|
4601
|
+
})], ValidationIssueResponse.prototype, "index", void 0);
|
|
4252
4602
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4253
4603
|
type: "string",
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
})],
|
|
4604
|
+
description: "Gatekeeper rule name that rejected the offer.",
|
|
4605
|
+
example: "no_buy"
|
|
4606
|
+
})], ValidationIssueResponse.prototype, "rule", void 0);
|
|
4257
4607
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4258
4608
|
type: "string",
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
})],
|
|
4262
|
-
var
|
|
4609
|
+
description: "Human-readable rejection reason.",
|
|
4610
|
+
example: "Buy offers are not supported"
|
|
4611
|
+
})], ValidationIssueResponse.prototype, "message", void 0);
|
|
4612
|
+
var ValidationFailureDataResponse = class {};
|
|
4613
|
+
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4614
|
+
type: () => [ValidationIssueResponse],
|
|
4615
|
+
description: "List of validation issues. Returned when any offer fails validation."
|
|
4616
|
+
})], ValidationFailureDataResponse.prototype, "issues", void 0);
|
|
4617
|
+
var ValidationFailureResponse = class extends SuccessResponse {};
|
|
4263
4618
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4264
4619
|
type: "string",
|
|
4265
4620
|
nullable: true,
|
|
4266
4621
|
example: null
|
|
4267
|
-
})],
|
|
4622
|
+
})], ValidationFailureResponse.prototype, "cursor", void 0);
|
|
4268
4623
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4269
|
-
type: () =>
|
|
4270
|
-
description: "
|
|
4271
|
-
})],
|
|
4624
|
+
type: () => ValidationFailureDataResponse,
|
|
4625
|
+
description: "List of validation issues. Returned when any offer fails validation."
|
|
4626
|
+
})], ValidationFailureResponse.prototype, "data", void 0);
|
|
4272
4627
|
var BookLevelResponse = class {};
|
|
4273
4628
|
__decorate([(0, openapi_metadata_decorators.ApiProperty)({
|
|
4274
4629
|
type: "string",
|
|
@@ -4333,13 +4688,18 @@ __decorate([
|
|
|
4333
4688
|
methods: ["post"],
|
|
4334
4689
|
path: "/v1/validate",
|
|
4335
4690
|
summary: "Validate offers",
|
|
4336
|
-
description: "Validates offers against router validation rules. Returns
|
|
4691
|
+
description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
4337
4692
|
}),
|
|
4338
4693
|
(0, openapi_metadata_decorators.ApiBody)({ type: ValidateOffersRequest }),
|
|
4339
4694
|
(0, openapi_metadata_decorators.ApiResponse)({
|
|
4340
4695
|
status: 200,
|
|
4341
4696
|
description: "Success",
|
|
4342
|
-
type:
|
|
4697
|
+
type: ValidationSuccessResponse
|
|
4698
|
+
}),
|
|
4699
|
+
(0, openapi_metadata_decorators.ApiResponse)({
|
|
4700
|
+
status: 200,
|
|
4701
|
+
description: "Validation issues",
|
|
4702
|
+
type: ValidationFailureResponse
|
|
4343
4703
|
})
|
|
4344
4704
|
], ValidateController.prototype, "validateOffers", null);
|
|
4345
4705
|
ValidateController = __decorate([(0, openapi_metadata_decorators.ApiTags)("Validate"), (0, openapi_metadata_decorators.ApiResponse)({
|
|
@@ -4508,7 +4868,7 @@ const OpenApi = async (options = {}) => {
|
|
|
4508
4868
|
if (options.rules && options.rules.length > 0) {
|
|
4509
4869
|
const rulesDescription = options.rules.map((rule) => `- **${rule.name}**: ${rule.description}`).join("\n");
|
|
4510
4870
|
const validatePath = document.paths?.["/v1/validate"];
|
|
4511
|
-
if (validatePath && "post" in validatePath && validatePath.post) validatePath.post.description = `Validates offers against router validation rules. Returns
|
|
4871
|
+
if (validatePath && "post" in validatePath && validatePath.post) validatePath.post.description = `Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure.\n\n**Available validation rules:**\n${rulesDescription}`;
|
|
4512
4872
|
}
|
|
4513
4873
|
return document;
|
|
4514
4874
|
};
|
|
@@ -4520,17 +4880,23 @@ var Cursor_exports = /* @__PURE__ */ __export({
|
|
|
4520
4880
|
encode: () => encode,
|
|
4521
4881
|
validate: () => validate
|
|
4522
4882
|
});
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
const c = cursor;
|
|
4526
|
-
if (![
|
|
4883
|
+
const isSort = (value) => {
|
|
4884
|
+
return [
|
|
4527
4885
|
"rate",
|
|
4528
4886
|
"maturity",
|
|
4529
4887
|
"expiry",
|
|
4530
4888
|
"amount"
|
|
4531
|
-
].includes(
|
|
4532
|
-
|
|
4533
|
-
|
|
4889
|
+
].includes(value);
|
|
4890
|
+
};
|
|
4891
|
+
function validate(cursor) {
|
|
4892
|
+
if (!cursor || typeof cursor !== "object") throw new Error("Cursor must be an object");
|
|
4893
|
+
const c = cursor;
|
|
4894
|
+
const sort = c.sort;
|
|
4895
|
+
const dir = c.dir;
|
|
4896
|
+
const hash$1 = c.hash;
|
|
4897
|
+
if (typeof sort !== "string" || !isSort(sort)) throw new Error(`Invalid sort field: ${String(sort)}. Must be one of: rate, maturity, expiry, amount`);
|
|
4898
|
+
if (typeof dir !== "string" || !["asc", "desc"].includes(dir)) throw new Error(`Invalid direction: ${String(dir)}. Must be one of: asc, desc`);
|
|
4899
|
+
if (typeof hash$1 !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash$1)) throw new Error(`Invalid hash format: ${String(hash$1)}. Must be a 64-character hex string starting with 0x`);
|
|
4534
4900
|
const validation = {
|
|
4535
4901
|
rate: {
|
|
4536
4902
|
field: "rate",
|
|
@@ -4547,24 +4913,30 @@ function validate(cursor) {
|
|
|
4547
4913
|
maturity: {
|
|
4548
4914
|
field: "maturity",
|
|
4549
4915
|
type: "number",
|
|
4550
|
-
|
|
4916
|
+
min: 1,
|
|
4551
4917
|
error: "positive number"
|
|
4552
4918
|
},
|
|
4553
4919
|
expiry: {
|
|
4554
4920
|
field: "expiry",
|
|
4555
4921
|
type: "number",
|
|
4556
|
-
|
|
4922
|
+
min: 1,
|
|
4557
4923
|
error: "positive number"
|
|
4558
4924
|
}
|
|
4559
|
-
}[
|
|
4560
|
-
if (!validation) throw new Error(`Invalid sort field: ${
|
|
4925
|
+
}[sort];
|
|
4926
|
+
if (!validation) throw new Error(`Invalid sort field: ${sort}`);
|
|
4561
4927
|
const fieldValue = c[validation.field];
|
|
4562
|
-
if (
|
|
4563
|
-
if (
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4928
|
+
if (fieldValue === void 0 || fieldValue === null) throw new Error(`${sort} sort requires '${validation.field}' field to be present`);
|
|
4929
|
+
if (validation.type === "string") {
|
|
4930
|
+
if (typeof fieldValue !== "string") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
|
|
4931
|
+
if (!validation.pattern.test(fieldValue)) throw new Error(`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`);
|
|
4932
|
+
}
|
|
4933
|
+
if (validation.type === "number") {
|
|
4934
|
+
if (typeof fieldValue !== "number") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
|
|
4935
|
+
if (fieldValue < validation.min) throw new Error(`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`);
|
|
4936
|
+
}
|
|
4937
|
+
const page = c.page;
|
|
4938
|
+
if (page !== void 0) {
|
|
4939
|
+
if (typeof page !== "number" || !Number.isInteger(page) || page < 1) throw new Error("Invalid page: must be a positive integer");
|
|
4568
4940
|
}
|
|
4569
4941
|
return true;
|
|
4570
4942
|
}
|
|
@@ -4680,21 +5052,7 @@ const schemas = {
|
|
|
4680
5052
|
get_obligations: GetObligationsQueryParams,
|
|
4681
5053
|
get_obligation: GetObligationParams,
|
|
4682
5054
|
get_book: GetBookParams,
|
|
4683
|
-
validate_offers: zod.object({
|
|
4684
|
-
offers: zod.any().refine((val) => val === void 0 || Array.isArray(val), { message: "'offers' must be an array" }),
|
|
4685
|
-
calldata: zod.string().regex(/^0x[a-fA-F0-9]*$/, { message: "'calldata' must be a hex string starting with '0x'" }).optional()
|
|
4686
|
-
}).superRefine((val, ctx) => {
|
|
4687
|
-
const hasOffers = val.offers !== void 0;
|
|
4688
|
-
const hasCalldata = val.calldata !== void 0;
|
|
4689
|
-
if (hasOffers && hasCalldata) ctx.addIssue({
|
|
4690
|
-
code: "custom",
|
|
4691
|
-
message: "Request body must contain either 'offers' or 'calldata', not both"
|
|
4692
|
-
});
|
|
4693
|
-
if (!hasOffers && !hasCalldata) ctx.addIssue({
|
|
4694
|
-
code: "custom",
|
|
4695
|
-
message: "Request body must contain either 'offers' array or 'calldata' hex string"
|
|
4696
|
-
});
|
|
4697
|
-
})
|
|
5055
|
+
validate_offers: zod.object({ offers: zod.array(zod.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict()
|
|
4698
5056
|
};
|
|
4699
5057
|
function parse(action, query) {
|
|
4700
5058
|
return schemas[action].parse(query);
|
|
@@ -4777,7 +5135,7 @@ async function getDocsHtml({ gatekeeper }) {
|
|
|
4777
5135
|
async function getHealth(db) {
|
|
4778
5136
|
const logger = getLogger();
|
|
4779
5137
|
try {
|
|
4780
|
-
const status$1 = await create$
|
|
5138
|
+
const status$1 = await create$14({ db }).getStatus();
|
|
4781
5139
|
return success({ data: toSnakeCase$1({ status: status$1 }) });
|
|
4782
5140
|
} catch (err) {
|
|
4783
5141
|
logger.error({
|
|
@@ -4792,7 +5150,7 @@ async function getHealth(db) {
|
|
|
4792
5150
|
async function getHealthChains(db, healthClients) {
|
|
4793
5151
|
const logger = getLogger();
|
|
4794
5152
|
try {
|
|
4795
|
-
const chains$3 = await create$
|
|
5153
|
+
const chains$3 = await create$14({
|
|
4796
5154
|
db,
|
|
4797
5155
|
healthClients
|
|
4798
5156
|
}).getChains();
|
|
@@ -4815,7 +5173,7 @@ async function getHealthChains(db, healthClients) {
|
|
|
4815
5173
|
async function getHealthCollectors(db) {
|
|
4816
5174
|
const logger = getLogger();
|
|
4817
5175
|
try {
|
|
4818
|
-
const collectors$1 = await create$
|
|
5176
|
+
const collectors$1 = await create$14({ db }).getCollectors();
|
|
4819
5177
|
return success({ data: collectors$1.map(({ name, chainId, blockNumber, updatedAt, lag, status: status$1 }) => toSnakeCase$1({
|
|
4820
5178
|
name,
|
|
4821
5179
|
chainId,
|
|
@@ -4916,8 +5274,10 @@ async function getOffers$1(queryParameters, db) {
|
|
|
4916
5274
|
cursor: query.cursor,
|
|
4917
5275
|
limit: query.limit
|
|
4918
5276
|
});
|
|
5277
|
+
const hashes = offers$1.map((offer) => offer.hash);
|
|
5278
|
+
const attestationMap = await db.trees.getAttestations(hashes);
|
|
4919
5279
|
return success({
|
|
4920
|
-
data: offers$1.map(from$2),
|
|
5280
|
+
data: offers$1.map((offer) => from$2(offer, attestationMap.get(offer.hash.toLowerCase()))),
|
|
4921
5281
|
cursor: nextCursor ?? null
|
|
4922
5282
|
});
|
|
4923
5283
|
} catch (err) {
|
|
@@ -4937,56 +5297,49 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4937
5297
|
const logger = getLogger();
|
|
4938
5298
|
const result = safeParse("validate_offers", body, (issue) => issue.message);
|
|
4939
5299
|
if (!result.success) return failure(new BadRequestError(result.error.issues[0]?.message ?? "Invalid request body"));
|
|
4940
|
-
const { offers: rawOffers
|
|
4941
|
-
const results = [];
|
|
5300
|
+
const { offers: rawOffers } = result.data;
|
|
4942
5301
|
const parsedOffers = [];
|
|
4943
|
-
const
|
|
4944
|
-
|
|
4945
|
-
if (calldata !== void 0) try {
|
|
4946
|
-
const tree = decode$2(calldata);
|
|
4947
|
-
for (const [i, offer] of tree.offers.entries()) {
|
|
4948
|
-
parsedOffers.push(offer);
|
|
4949
|
-
parsedOfferIndices.push(i);
|
|
4950
|
-
}
|
|
4951
|
-
} catch (err) {
|
|
4952
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4953
|
-
return failure(new BadRequestError(`Failed to decode calldata: ${message}`));
|
|
4954
|
-
}
|
|
4955
|
-
if (hasOffers) for (let i = 0; i < rawOffers.length; i++) {
|
|
5302
|
+
const offerIndexByHash = /* @__PURE__ */ new Map();
|
|
5303
|
+
for (let i = 0; i < rawOffers.length; i++) {
|
|
4956
5304
|
const rawOffer = rawOffers[i];
|
|
4957
5305
|
try {
|
|
4958
5306
|
const offer = fromSnakeCase$1(rawOffer);
|
|
4959
|
-
|
|
4960
|
-
|
|
5307
|
+
if (!offerIndexByHash.has(offer.hash)) {
|
|
5308
|
+
offerIndexByHash.set(offer.hash, i);
|
|
5309
|
+
parsedOffers.push(offer);
|
|
5310
|
+
}
|
|
4961
5311
|
} catch (err) {
|
|
4962
5312
|
let message = err instanceof Error ? err.message : String(err);
|
|
4963
5313
|
if (err instanceof InvalidOfferError) message = err.formattedMessage;
|
|
4964
|
-
|
|
4965
|
-
offer_hash: rawOffer?.hash ?? "unknown",
|
|
4966
|
-
valid: false,
|
|
4967
|
-
rule: "parse_error",
|
|
4968
|
-
message
|
|
4969
|
-
};
|
|
5314
|
+
return failure(new BadRequestError(`Offer at index ${i} failed to parse: ${message}`));
|
|
4970
5315
|
}
|
|
4971
5316
|
}
|
|
4972
|
-
|
|
4973
|
-
const {
|
|
4974
|
-
|
|
4975
|
-
const
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
};
|
|
5317
|
+
try {
|
|
5318
|
+
const { issues } = await gatekeeper.isAllowed(parsedOffers);
|
|
5319
|
+
if (issues.length > 0) {
|
|
5320
|
+
const mappedIssues = issues.map((issue) => {
|
|
5321
|
+
const index$1 = offerIndexByHash.get(issue.item.hash);
|
|
5322
|
+
if (index$1 === void 0) return null;
|
|
5323
|
+
return {
|
|
5324
|
+
index: index$1,
|
|
5325
|
+
rule: issue.ruleName,
|
|
5326
|
+
message: issue.message
|
|
5327
|
+
};
|
|
5328
|
+
}).filter((issue) => issue !== null);
|
|
5329
|
+
return success({
|
|
5330
|
+
data: { issues: mappedIssues },
|
|
5331
|
+
cursor: null
|
|
5332
|
+
});
|
|
4989
5333
|
}
|
|
5334
|
+
const tree = from$12(parsedOffers);
|
|
5335
|
+
const payload = encodeUnsigned(tree);
|
|
5336
|
+
return success({
|
|
5337
|
+
data: {
|
|
5338
|
+
payload,
|
|
5339
|
+
root: tree.root
|
|
5340
|
+
},
|
|
5341
|
+
cursor: null
|
|
5342
|
+
});
|
|
4990
5343
|
} catch (err) {
|
|
4991
5344
|
logger.error({
|
|
4992
5345
|
err,
|
|
@@ -4996,11 +5349,6 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4996
5349
|
});
|
|
4997
5350
|
return failure(err);
|
|
4998
5351
|
}
|
|
4999
|
-
const orderedResults = results.filter((r) => r !== void 0);
|
|
5000
|
-
return success({
|
|
5001
|
-
data: orderedResults,
|
|
5002
|
-
cursor: null
|
|
5003
|
-
});
|
|
5004
5352
|
}
|
|
5005
5353
|
|
|
5006
5354
|
//#endregion
|
|
@@ -5022,13 +5370,13 @@ var Controllers_exports = /* @__PURE__ */ __export({
|
|
|
5022
5370
|
//#region src/api/Api.ts
|
|
5023
5371
|
function from$1(config) {
|
|
5024
5372
|
const { db, gatekeeper, port } = config;
|
|
5025
|
-
return create$
|
|
5373
|
+
return create$13({
|
|
5026
5374
|
port,
|
|
5027
5375
|
db,
|
|
5028
5376
|
gatekeeper
|
|
5029
5377
|
});
|
|
5030
5378
|
}
|
|
5031
|
-
function create$
|
|
5379
|
+
function create$13(params) {
|
|
5032
5380
|
return { serve: () => serve(params) };
|
|
5033
5381
|
}
|
|
5034
5382
|
/**
|
|
@@ -5123,7 +5471,7 @@ var RouterApi_exports = /* @__PURE__ */ __export({
|
|
|
5123
5471
|
OpenApi: () => OpenApi,
|
|
5124
5472
|
RouterStatusResponse: () => RouterStatusResponse,
|
|
5125
5473
|
ValidateController: () => ValidateController,
|
|
5126
|
-
create: () => create$
|
|
5474
|
+
create: () => create$13,
|
|
5127
5475
|
from: () => from$1,
|
|
5128
5476
|
parse: () => parse,
|
|
5129
5477
|
safeParse: () => safeParse
|
|
@@ -5188,24 +5536,28 @@ async function getOffers(apiClient, parameters) {
|
|
|
5188
5536
|
throw new HttpGetApiFailedError(`GET request returned ${response.status}`, { details: JSON.stringify(error) });
|
|
5189
5537
|
}
|
|
5190
5538
|
const offers$1 = data?.data.map((item) => {
|
|
5191
|
-
const { signature, ...rest } = item;
|
|
5192
|
-
return
|
|
5193
|
-
...
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5539
|
+
const { root, proof, signature, ...rest } = item;
|
|
5540
|
+
return {
|
|
5541
|
+
...fromSnakeCase$1({
|
|
5542
|
+
...rest,
|
|
5543
|
+
offering: item.offering,
|
|
5544
|
+
maturity: from$14(item.maturity),
|
|
5545
|
+
loan_token: item.loan_token,
|
|
5546
|
+
collaterals: item.collaterals.map((collateral) => ({
|
|
5547
|
+
asset: collateral.asset,
|
|
5548
|
+
oracle: collateral.oracle,
|
|
5549
|
+
lltv: collateral.lltv
|
|
5550
|
+
})),
|
|
5551
|
+
callback: {
|
|
5552
|
+
...item.callback,
|
|
5553
|
+
address: item.callback.address,
|
|
5554
|
+
data: item.callback.data
|
|
5555
|
+
},
|
|
5556
|
+
signature: signature?.toLowerCase()
|
|
5557
|
+
}),
|
|
5558
|
+
root: root?.toLowerCase() || void 0,
|
|
5559
|
+
proof: proof?.map((p) => p.toLowerCase()) || void 0
|
|
5560
|
+
};
|
|
5209
5561
|
}) ?? [];
|
|
5210
5562
|
return {
|
|
5211
5563
|
cursor: data?.cursor ?? null,
|
|
@@ -5299,15 +5651,19 @@ var schema_exports = /* @__PURE__ */ __export({
|
|
|
5299
5651
|
collectors: () => collectors,
|
|
5300
5652
|
consumedEvents: () => consumedEvents,
|
|
5301
5653
|
groups: () => groups,
|
|
5654
|
+
lots: () => lots,
|
|
5655
|
+
merklePaths: () => merklePaths,
|
|
5302
5656
|
obligationCollateralsV2: () => obligationCollateralsV2,
|
|
5303
5657
|
obligations: () => obligations,
|
|
5304
5658
|
offers: () => offers,
|
|
5305
5659
|
offersCallbacks: () => offersCallbacks,
|
|
5660
|
+
offsets: () => offsets,
|
|
5306
5661
|
oracles: () => oracles,
|
|
5307
5662
|
positionTypes: () => positionTypes,
|
|
5308
5663
|
positions: () => positions,
|
|
5309
5664
|
status: () => status,
|
|
5310
5665
|
transfers: () => transfers,
|
|
5666
|
+
trees: () => trees,
|
|
5311
5667
|
validations: () => validations
|
|
5312
5668
|
});
|
|
5313
5669
|
const s = (0, drizzle_orm_pg_core.pgSchema)(VERSION);
|
|
@@ -5325,6 +5681,10 @@ var EnumTableName = /* @__PURE__ */ function(EnumTableName$1) {
|
|
|
5325
5681
|
EnumTableName$1["VALIDATIONS"] = "validations";
|
|
5326
5682
|
EnumTableName$1["COLLECTORS"] = "collectors";
|
|
5327
5683
|
EnumTableName$1["CHAINS"] = "chains";
|
|
5684
|
+
EnumTableName$1["LOTS"] = "lots";
|
|
5685
|
+
EnumTableName$1["OFFSETS"] = "offsets";
|
|
5686
|
+
EnumTableName$1["TREES"] = "trees";
|
|
5687
|
+
EnumTableName$1["MERKLE_PATHS"] = "merkle_paths";
|
|
5328
5688
|
return EnumTableName$1;
|
|
5329
5689
|
}(EnumTableName || {});
|
|
5330
5690
|
const TABLE_NAMES = Object.values(EnumTableName);
|
|
@@ -5487,6 +5847,86 @@ const callbacks = s.table(EnumTableName.CALLBACKS, {
|
|
|
5487
5847
|
],
|
|
5488
5848
|
name: "callbacks_positions_fk"
|
|
5489
5849
|
}).onDelete("cascade")]);
|
|
5850
|
+
const lots = s.table(EnumTableName.LOTS, {
|
|
5851
|
+
chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
|
|
5852
|
+
user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
|
|
5853
|
+
contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
|
|
5854
|
+
group: (0, drizzle_orm_pg_core.varchar)("group", { length: 66 }).notNull(),
|
|
5855
|
+
lower: (0, drizzle_orm_pg_core.numeric)("lower", {
|
|
5856
|
+
precision: 78,
|
|
5857
|
+
scale: 0
|
|
5858
|
+
}).notNull(),
|
|
5859
|
+
upper: (0, drizzle_orm_pg_core.numeric)("upper", {
|
|
5860
|
+
precision: 78,
|
|
5861
|
+
scale: 0
|
|
5862
|
+
}).notNull()
|
|
5863
|
+
}, (table) => [
|
|
5864
|
+
(0, drizzle_orm_pg_core.primaryKey)({
|
|
5865
|
+
columns: [
|
|
5866
|
+
table.chainId,
|
|
5867
|
+
table.user,
|
|
5868
|
+
table.contract,
|
|
5869
|
+
table.group
|
|
5870
|
+
],
|
|
5871
|
+
name: "lots_pk"
|
|
5872
|
+
}),
|
|
5873
|
+
(0, drizzle_orm_pg_core.foreignKey)({
|
|
5874
|
+
columns: [
|
|
5875
|
+
table.chainId,
|
|
5876
|
+
table.contract,
|
|
5877
|
+
table.user
|
|
5878
|
+
],
|
|
5879
|
+
foreignColumns: [
|
|
5880
|
+
positions.chainId,
|
|
5881
|
+
positions.contract,
|
|
5882
|
+
positions.user
|
|
5883
|
+
],
|
|
5884
|
+
name: "lots_positions_fk"
|
|
5885
|
+
}).onDelete("cascade"),
|
|
5886
|
+
(0, drizzle_orm_pg_core.foreignKey)({
|
|
5887
|
+
columns: [
|
|
5888
|
+
table.chainId,
|
|
5889
|
+
table.user,
|
|
5890
|
+
table.group
|
|
5891
|
+
],
|
|
5892
|
+
foreignColumns: [
|
|
5893
|
+
groups.chainId,
|
|
5894
|
+
groups.maker,
|
|
5895
|
+
groups.group
|
|
5896
|
+
],
|
|
5897
|
+
name: "lots_groups_fk"
|
|
5898
|
+
}).onDelete("cascade")
|
|
5899
|
+
]);
|
|
5900
|
+
const offsets = s.table(EnumTableName.OFFSETS, {
|
|
5901
|
+
chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
|
|
5902
|
+
user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
|
|
5903
|
+
contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
|
|
5904
|
+
group: (0, drizzle_orm_pg_core.varchar)("group", { length: 66 }).notNull(),
|
|
5905
|
+
value: (0, drizzle_orm_pg_core.numeric)("value", {
|
|
5906
|
+
precision: 78,
|
|
5907
|
+
scale: 0
|
|
5908
|
+
}).notNull()
|
|
5909
|
+
}, (table) => [(0, drizzle_orm_pg_core.primaryKey)({
|
|
5910
|
+
columns: [
|
|
5911
|
+
table.chainId,
|
|
5912
|
+
table.user,
|
|
5913
|
+
table.contract,
|
|
5914
|
+
table.group
|
|
5915
|
+
],
|
|
5916
|
+
name: "offsets_pk"
|
|
5917
|
+
}), (0, drizzle_orm_pg_core.foreignKey)({
|
|
5918
|
+
columns: [
|
|
5919
|
+
table.chainId,
|
|
5920
|
+
table.contract,
|
|
5921
|
+
table.user
|
|
5922
|
+
],
|
|
5923
|
+
foreignColumns: [
|
|
5924
|
+
positions.chainId,
|
|
5925
|
+
positions.contract,
|
|
5926
|
+
positions.user
|
|
5927
|
+
],
|
|
5928
|
+
name: "offsets_positions_fk"
|
|
5929
|
+
}).onDelete("cascade")]);
|
|
5490
5930
|
const PositionTypes = s.enum("position_type", Object.values(Type));
|
|
5491
5931
|
const positionTypes = s.table("position_types", {
|
|
5492
5932
|
id: (0, drizzle_orm_pg_core.serial)("id").primaryKey(),
|
|
@@ -5582,6 +6022,17 @@ const chains$1 = s.table(EnumTableName.CHAINS, {
|
|
|
5582
6022
|
}).default("0").notNull(),
|
|
5583
6023
|
updatedAt: (0, drizzle_orm_pg_core.timestamp)("updated_at").defaultNow().notNull()
|
|
5584
6024
|
}, (table) => [(0, drizzle_orm_pg_core.uniqueIndex)("chains_id_epoch_idx").on(table.chainId, table.epoch)]);
|
|
6025
|
+
const trees = s.table(EnumTableName.TREES, {
|
|
6026
|
+
root: (0, drizzle_orm_pg_core.varchar)("root", { length: 66 }).primaryKey(),
|
|
6027
|
+
rootSignature: (0, drizzle_orm_pg_core.varchar)("root_signature", { length: 132 }).notNull(),
|
|
6028
|
+
createdAt: (0, drizzle_orm_pg_core.timestamp)("created_at").defaultNow().notNull()
|
|
6029
|
+
});
|
|
6030
|
+
const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
6031
|
+
offerHash: (0, drizzle_orm_pg_core.varchar)("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
|
|
6032
|
+
treeRoot: (0, drizzle_orm_pg_core.varchar)("tree_root", { length: 66 }).notNull().references(() => trees.root, { onDelete: "cascade" }),
|
|
6033
|
+
proofNodes: (0, drizzle_orm_pg_core.text)("proof_nodes").notNull(),
|
|
6034
|
+
createdAt: (0, drizzle_orm_pg_core.timestamp)("created_at").defaultNow().notNull()
|
|
6035
|
+
}, (table) => [(0, drizzle_orm_pg_core.index)("merkle_paths_tree_root_idx").on(table.treeRoot)]);
|
|
5585
6036
|
|
|
5586
6037
|
//#endregion
|
|
5587
6038
|
//#region src/database/drizzle/index.ts
|
|
@@ -5596,15 +6047,19 @@ var drizzle_exports = /* @__PURE__ */ __export({
|
|
|
5596
6047
|
collectors: () => collectors,
|
|
5597
6048
|
consumedEvents: () => consumedEvents,
|
|
5598
6049
|
groups: () => groups,
|
|
6050
|
+
lots: () => lots,
|
|
6051
|
+
merklePaths: () => merklePaths,
|
|
5599
6052
|
obligationCollateralsV2: () => obligationCollateralsV2,
|
|
5600
6053
|
obligations: () => obligations,
|
|
5601
6054
|
offers: () => offers,
|
|
5602
6055
|
offersCallbacks: () => offersCallbacks,
|
|
6056
|
+
offsets: () => offsets,
|
|
5603
6057
|
oracles: () => oracles,
|
|
5604
6058
|
positionTypes: () => positionTypes,
|
|
5605
6059
|
positions: () => positions,
|
|
5606
6060
|
status: () => status,
|
|
5607
6061
|
transfers: () => transfers,
|
|
6062
|
+
trees: () => trees,
|
|
5608
6063
|
validations: () => validations
|
|
5609
6064
|
});
|
|
5610
6065
|
|
|
@@ -5612,7 +6067,7 @@ var drizzle_exports = /* @__PURE__ */ __export({
|
|
|
5612
6067
|
//#region src/database/domains/Book.ts
|
|
5613
6068
|
const DEFAULT_LIMIT$3 = 100;
|
|
5614
6069
|
const MAX_TOTAL_OFFERS = 500;
|
|
5615
|
-
function create$
|
|
6070
|
+
function create$12(config) {
|
|
5616
6071
|
const db = config.db;
|
|
5617
6072
|
const logger = getLogger();
|
|
5618
6073
|
const getOffers$2 = async (parameters) => {
|
|
@@ -5631,52 +6086,20 @@ function create$9(config) {
|
|
|
5631
6086
|
offers: [],
|
|
5632
6087
|
nextCursor: null
|
|
5633
6088
|
};
|
|
5634
|
-
const
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
obligationId: obligationId$1,
|
|
5645
|
-
side,
|
|
5646
|
-
now: now$1,
|
|
5647
|
-
rateSortDirection,
|
|
5648
|
-
cursor: offerCursor,
|
|
5649
|
-
limit: batchSize
|
|
5650
|
-
});
|
|
5651
|
-
if (rawOffers.length === 0) break;
|
|
5652
|
-
const newCallbackIds = rawOffers.flatMap((o) => o.callbackIds).filter((id$1) => !callbackState.has(id$1));
|
|
5653
|
-
await _updateCallbacksByIds(callbackState, db, newCallbackIds);
|
|
5654
|
-
await _updatePositionsByKeys(positionState, db, [...new Set(newCallbackIds.map((id$1) => callbackState.get(id$1)?.positionKey).filter((k) => k !== void 0 && !positionState.has(k)))]);
|
|
5655
|
-
await _updatePrices(prices, db, _collectNewOracleAddresses(rawOffers, callbackState, positionState, prices));
|
|
5656
|
-
const validOffers = _computeCrossInvalidation(rawOffers, callbackState, positionState, prices);
|
|
5657
|
-
let isOfferInPreviousPages = inputCursor === null;
|
|
5658
|
-
const cursorRate = inputCursor ? BigInt(inputCursor.rate) : 0n;
|
|
5659
|
-
for (const offer of validOffers) {
|
|
5660
|
-
if (!isOfferInPreviousPages) if (rateSortDirection === "asc" ? offer.rate > cursorRate : offer.rate < cursorRate) isOfferInPreviousPages = true;
|
|
5661
|
-
else if (offer.hash === inputCursor.hash) {
|
|
5662
|
-
isOfferInPreviousPages = true;
|
|
5663
|
-
continue;
|
|
5664
|
-
} else continue;
|
|
5665
|
-
book.push(offer);
|
|
5666
|
-
if (book.length >= effectiveLimit) {
|
|
5667
|
-
hasMoreOffers = true;
|
|
5668
|
-
break;
|
|
5669
|
-
}
|
|
5670
|
-
}
|
|
5671
|
-
offerCursor = rawNextCursor;
|
|
5672
|
-
if (!offerCursor) break;
|
|
5673
|
-
}
|
|
5674
|
-
const lastReturnedOffer = book[book.length - 1];
|
|
5675
|
-
const newTotalReturned = previouslyReturned + book.length;
|
|
6089
|
+
const { offers: offers$1, hasMore } = await _getOffers(db, {
|
|
6090
|
+
obligationId: obligationId$1,
|
|
6091
|
+
side,
|
|
6092
|
+
now: now$1,
|
|
6093
|
+
rateSortDirection,
|
|
6094
|
+
cursor: inputCursor,
|
|
6095
|
+
limit: Math.min(requestedLimit, MAX_TOTAL_OFFERS - previouslyReturned)
|
|
6096
|
+
});
|
|
6097
|
+
const lastReturnedOffer = offers$1[offers$1.length - 1];
|
|
6098
|
+
const newTotalReturned = previouslyReturned + offers$1.length;
|
|
5676
6099
|
const hasHitHardLimit = newTotalReturned >= MAX_TOTAL_OFFERS;
|
|
5677
6100
|
return {
|
|
5678
|
-
offers:
|
|
5679
|
-
nextCursor:
|
|
6101
|
+
offers: offers$1,
|
|
6102
|
+
nextCursor: offers$1.length > 0 && lastReturnedOffer && !hasHitHardLimit && hasMore ? Cursor.encode(lastReturnedOffer, newTotalReturned, now$1, side) : null
|
|
5680
6103
|
};
|
|
5681
6104
|
};
|
|
5682
6105
|
return {
|
|
@@ -5723,8 +6146,8 @@ function create$9(config) {
|
|
|
5723
6146
|
getOffers: getOffers$2
|
|
5724
6147
|
};
|
|
5725
6148
|
}
|
|
5726
|
-
/** Get offers with
|
|
5727
|
-
async function
|
|
6149
|
+
/** Get offers with computed takeable based on lot balance. */
|
|
6150
|
+
async function _getOffers(db, params) {
|
|
5728
6151
|
const { obligationId: obligationId$1, side, now: now$1, rateSortDirection, cursor, limit } = params;
|
|
5729
6152
|
const raw = await db.execute(drizzle_orm.sql`
|
|
5730
6153
|
WITH collats AS MATERIALIZED (
|
|
@@ -5792,32 +6215,202 @@ async function _getOffersWithCallbackIds(db, params) {
|
|
|
5792
6215
|
ORDER BY e.rate ${rateSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
|
|
5793
6216
|
LIMIT ${limit}
|
|
5794
6217
|
),
|
|
5795
|
-
|
|
6218
|
+
-- Compute sum of offsets per position
|
|
6219
|
+
position_offsets AS (
|
|
6220
|
+
SELECT
|
|
6221
|
+
chain_id,
|
|
6222
|
+
"user",
|
|
6223
|
+
contract,
|
|
6224
|
+
SUM(value::numeric) AS total_offset
|
|
6225
|
+
FROM ${offsets}
|
|
6226
|
+
GROUP BY chain_id, "user", contract
|
|
6227
|
+
),
|
|
6228
|
+
-- Compute position_consumed: sum of consumed from all groups with lots on each position (converted to lot terms)
|
|
6229
|
+
position_consumed AS (
|
|
5796
6230
|
SELECT
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
6231
|
+
l.chain_id,
|
|
6232
|
+
l.contract,
|
|
6233
|
+
l."user",
|
|
6234
|
+
SUM(
|
|
6235
|
+
CASE
|
|
6236
|
+
WHEN wo.assets::numeric > 0
|
|
6237
|
+
THEN COALESCE(g.consumed::numeric, 0) * (l.upper::numeric - l.lower::numeric) / wo.assets::numeric
|
|
6238
|
+
ELSE 0
|
|
6239
|
+
END
|
|
6240
|
+
) AS consumed
|
|
6241
|
+
FROM ${lots} l
|
|
6242
|
+
JOIN ${groups} g
|
|
6243
|
+
ON g.chain_id = l.chain_id
|
|
6244
|
+
AND LOWER(g.maker) = LOWER(l."user")
|
|
6245
|
+
AND g."group" = l."group"
|
|
6246
|
+
JOIN winners wo
|
|
6247
|
+
ON wo.group_chain_id = g.chain_id
|
|
6248
|
+
AND LOWER(wo.group_maker) = LOWER(g.maker)
|
|
6249
|
+
AND wo.group_group = g."group"
|
|
6250
|
+
GROUP BY l.chain_id, l.contract, l."user"
|
|
6251
|
+
),
|
|
6252
|
+
-- Compute callback contributions with lot balance
|
|
6253
|
+
callback_contributions AS (
|
|
6254
|
+
SELECT
|
|
6255
|
+
p.hash,
|
|
6256
|
+
p.obligation_id,
|
|
6257
|
+
p.assets,
|
|
6258
|
+
p.rate,
|
|
6259
|
+
p.maturity,
|
|
6260
|
+
p.expiry,
|
|
6261
|
+
p.start,
|
|
6262
|
+
p.nonce,
|
|
6263
|
+
p.buy,
|
|
6264
|
+
p.callback_address,
|
|
6265
|
+
p.callback_data,
|
|
6266
|
+
p.block_number,
|
|
6267
|
+
p.group_chain_id,
|
|
6268
|
+
p.group_maker,
|
|
6269
|
+
p.group_group,
|
|
6270
|
+
p.consumed,
|
|
6271
|
+
p.chain_id,
|
|
6272
|
+
p.loan_token,
|
|
6273
|
+
c.id AS callback_id,
|
|
6274
|
+
c.position_chain_id,
|
|
6275
|
+
c.position_contract,
|
|
6276
|
+
c.position_user,
|
|
6277
|
+
c.amount AS callback_amount,
|
|
6278
|
+
pos.balance AS position_balance,
|
|
6279
|
+
pos.asset AS position_asset,
|
|
6280
|
+
l.lower AS lot_lower,
|
|
6281
|
+
l.upper AS lot_upper,
|
|
6282
|
+
-- Compute lot_balance: min(position_balance + offset + position_consumed - lot.lower, lot.size - lot_consumed)
|
|
6283
|
+
-- lot_consumed is converted from loan token to lot terms: consumed * lot_size / assets
|
|
6284
|
+
GREATEST(0, LEAST(
|
|
6285
|
+
COALESCE(pos.balance::numeric, 0) + COALESCE(pos_offsets.total_offset, 0) + COALESCE(pc.consumed, 0) - COALESCE(l.lower::numeric, 0),
|
|
6286
|
+
(COALESCE(l.upper::numeric, 0) - COALESCE(l.lower::numeric, 0)) -
|
|
6287
|
+
CASE
|
|
6288
|
+
WHEN p.assets::numeric > 0
|
|
6289
|
+
THEN COALESCE(p.consumed::numeric, 0) * (COALESCE(l.upper::numeric, 0) - COALESCE(l.lower::numeric, 0)) / p.assets::numeric
|
|
6290
|
+
ELSE 0
|
|
6291
|
+
END
|
|
6292
|
+
)) AS lot_balance
|
|
5801
6293
|
FROM paged p
|
|
5802
6294
|
LEFT JOIN ${offersCallbacks} oc ON oc.offer_hash = p.hash
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
6295
|
+
LEFT JOIN ${callbacks} c ON c.id = oc.callback_id
|
|
6296
|
+
LEFT JOIN ${lots} l
|
|
6297
|
+
ON l.chain_id = c.position_chain_id
|
|
6298
|
+
AND LOWER(l.contract) = LOWER(c.position_contract)
|
|
6299
|
+
AND LOWER(l."user") = LOWER(c.position_user)
|
|
6300
|
+
AND l."group" = p.group_group
|
|
6301
|
+
LEFT JOIN ${positions} pos
|
|
6302
|
+
ON pos.chain_id = c.position_chain_id
|
|
6303
|
+
AND LOWER(pos.contract) = LOWER(c.position_contract)
|
|
6304
|
+
AND LOWER(pos."user") = LOWER(c.position_user)
|
|
6305
|
+
LEFT JOIN position_offsets pos_offsets
|
|
6306
|
+
ON pos_offsets.chain_id = c.position_chain_id
|
|
6307
|
+
AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
|
|
6308
|
+
AND LOWER(pos_offsets."user") = LOWER(c.position_user)
|
|
6309
|
+
LEFT JOIN position_consumed pc
|
|
6310
|
+
ON pc.chain_id = c.position_chain_id
|
|
6311
|
+
AND LOWER(pc.contract) = LOWER(c.position_contract)
|
|
6312
|
+
AND LOWER(pc."user") = LOWER(c.position_user)
|
|
6313
|
+
),
|
|
6314
|
+
-- Compute contribution per callback in loan terms (with oracle price via LEFT JOIN)
|
|
6315
|
+
callback_loan_contribution AS (
|
|
6316
|
+
SELECT
|
|
6317
|
+
cc.*,
|
|
6318
|
+
CASE
|
|
6319
|
+
-- No lot exists: contribution is 0
|
|
6320
|
+
WHEN cc.lot_lower IS NULL THEN 0
|
|
6321
|
+
-- Loan token position: use lot_balance directly, apply callback limit
|
|
6322
|
+
WHEN LOWER(cc.position_asset) = LOWER(cc.loan_token) THEN
|
|
6323
|
+
LEAST(
|
|
6324
|
+
cc.lot_balance,
|
|
6325
|
+
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
6326
|
+
)
|
|
6327
|
+
-- Collateral position: convert to loan using (amount * price / 10^36) * lltv / 10^18
|
|
6328
|
+
ELSE
|
|
6329
|
+
(
|
|
6330
|
+
LEAST(
|
|
6331
|
+
cc.lot_balance,
|
|
6332
|
+
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
6333
|
+
) * COALESCE(collat_oracle.price::numeric, 0) / 1e36
|
|
6334
|
+
) * COALESCE(collat_info.lltv::numeric, 0) / 1e18
|
|
6335
|
+
END AS contribution_in_loan
|
|
6336
|
+
FROM callback_contributions cc
|
|
6337
|
+
LEFT JOIN ${obligationCollateralsV2} collat_info
|
|
6338
|
+
ON collat_info.obligation_id = cc.obligation_id
|
|
6339
|
+
AND LOWER(collat_info.asset) = LOWER(cc.position_asset)
|
|
6340
|
+
LEFT JOIN ${oracles} collat_oracle
|
|
6341
|
+
ON collat_oracle.chain_id = collat_info.oracle_chain_id
|
|
6342
|
+
AND LOWER(collat_oracle.address) = LOWER(collat_info.oracle_address)
|
|
6343
|
+
),
|
|
6344
|
+
-- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
|
|
6345
|
+
offer_contributions AS (
|
|
6346
|
+
SELECT
|
|
6347
|
+
hash,
|
|
6348
|
+
obligation_id,
|
|
6349
|
+
assets,
|
|
6350
|
+
rate,
|
|
6351
|
+
maturity,
|
|
6352
|
+
expiry,
|
|
6353
|
+
start,
|
|
6354
|
+
nonce,
|
|
6355
|
+
buy,
|
|
6356
|
+
callback_address,
|
|
6357
|
+
callback_data,
|
|
6358
|
+
block_number,
|
|
6359
|
+
group_chain_id,
|
|
6360
|
+
group_maker,
|
|
6361
|
+
consumed,
|
|
6362
|
+
chain_id,
|
|
6363
|
+
loan_token,
|
|
6364
|
+
SUM(contribution_in_loan) AS total_available
|
|
6365
|
+
FROM (
|
|
6366
|
+
-- Take max contribution per position using DISTINCT ON (idiomatic PostgreSQL)
|
|
6367
|
+
SELECT DISTINCT ON (clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user)
|
|
6368
|
+
clc.*
|
|
6369
|
+
FROM callback_loan_contribution clc
|
|
6370
|
+
WHERE clc.callback_id IS NOT NULL
|
|
6371
|
+
ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
|
|
6372
|
+
) deduped
|
|
6373
|
+
GROUP BY hash, obligation_id, assets, rate, maturity, expiry, start, nonce, buy,
|
|
6374
|
+
callback_address, callback_data, block_number, group_chain_id, group_maker,
|
|
6375
|
+
consumed, chain_id, loan_token
|
|
5806
6376
|
)
|
|
6377
|
+
-- Final SELECT with inline takeable computation
|
|
5807
6378
|
SELECT
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
6379
|
+
oc.hash,
|
|
6380
|
+
oc.group_maker,
|
|
6381
|
+
oc.assets,
|
|
6382
|
+
oc.consumed,
|
|
6383
|
+
oc.rate,
|
|
6384
|
+
oc.maturity,
|
|
6385
|
+
oc.expiry,
|
|
6386
|
+
oc.start,
|
|
6387
|
+
oc.nonce,
|
|
6388
|
+
oc.buy,
|
|
6389
|
+
oc.chain_id,
|
|
6390
|
+
oc.loan_token,
|
|
6391
|
+
oc.callback_address,
|
|
6392
|
+
oc.callback_data,
|
|
6393
|
+
oc.block_number,
|
|
6394
|
+
-- takeable = min(assets - consumed, total_available)
|
|
6395
|
+
GREATEST(0, LEAST(
|
|
6396
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
6397
|
+
COALESCE(oc.total_available, 0)
|
|
6398
|
+
)) AS takeable,
|
|
6399
|
+
c.collaterals
|
|
6400
|
+
FROM offer_contributions oc
|
|
6401
|
+
LEFT JOIN collats c ON c.obligation_id = oc.obligation_id
|
|
6402
|
+
WHERE GREATEST(0, LEAST(
|
|
6403
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
6404
|
+
COALESCE(oc.total_available, 0)
|
|
6405
|
+
)) > 0
|
|
5813
6406
|
ORDER BY
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6407
|
+
oc.rate ${rateSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`},
|
|
6408
|
+
oc.block_number ASC,
|
|
6409
|
+
oc.assets DESC,
|
|
6410
|
+
oc.hash ASC;
|
|
5818
6411
|
`);
|
|
5819
|
-
|
|
5820
|
-
|
|
6412
|
+
return {
|
|
6413
|
+
offers: raw.rows.map((row) => from$11({
|
|
5821
6414
|
offering: row.group_maker,
|
|
5822
6415
|
assets: BigInt(row.assets),
|
|
5823
6416
|
rate: BigInt(row.rate),
|
|
@@ -5839,165 +6432,12 @@ async function _getOffersWithCallbackIds(db, params) {
|
|
|
5839
6432
|
gasLimit: 0n
|
|
5840
6433
|
},
|
|
5841
6434
|
consumed: BigInt(row.consumed),
|
|
5842
|
-
takeable:
|
|
6435
|
+
takeable: BigInt(row.takeable.split(".")[0] ?? "0"),
|
|
5843
6436
|
blockNumber: row.block_number
|
|
5844
|
-
}),
|
|
5845
|
-
|
|
5846
|
-
}));
|
|
5847
|
-
let nextCursor = null;
|
|
5848
|
-
if (raw.rows.length === limit) {
|
|
5849
|
-
const last = raw.rows[raw.rows.length - 1];
|
|
5850
|
-
nextCursor = {
|
|
5851
|
-
rate: last.rate,
|
|
5852
|
-
blockNumber: last.block_number,
|
|
5853
|
-
assets: last.assets,
|
|
5854
|
-
hash: last.hash
|
|
5855
|
-
};
|
|
5856
|
-
}
|
|
5857
|
-
return {
|
|
5858
|
-
offers: offers$1,
|
|
5859
|
-
nextCursor
|
|
6437
|
+
})),
|
|
6438
|
+
hasMore: raw.rows.length === limit
|
|
5860
6439
|
};
|
|
5861
6440
|
}
|
|
5862
|
-
/** Get callbacks by their IDs. */
|
|
5863
|
-
async function _updateCallbacksByIds(state, db, ids) {
|
|
5864
|
-
if (ids.length === 0) return;
|
|
5865
|
-
const raw = await db.execute(drizzle_orm.sql`
|
|
5866
|
-
SELECT c.id, c.position_chain_id, c.position_contract, c.position_user, c.amount
|
|
5867
|
-
FROM ${callbacks} c
|
|
5868
|
-
WHERE c.id IN (${drizzle_orm.sql.join(ids.map((id$1) => drizzle_orm.sql`${id$1}`), drizzle_orm.sql`, `)})
|
|
5869
|
-
`);
|
|
5870
|
-
for (const row of raw.rows) if (!state.has(row.id)) state.set(row.id, {
|
|
5871
|
-
positionKey: _buildPositionKey(row.position_chain_id, row.position_contract, row.position_user),
|
|
5872
|
-
amount: row.amount != null ? BigInt(row.amount) : null
|
|
5873
|
-
});
|
|
5874
|
-
}
|
|
5875
|
-
/** Get positions by their composite keys. */
|
|
5876
|
-
async function _updatePositionsByKeys(state, db, keys) {
|
|
5877
|
-
if (keys.length === 0) return;
|
|
5878
|
-
const parsedKeys = keys.map((key) => {
|
|
5879
|
-
const parts = key.split(":");
|
|
5880
|
-
return {
|
|
5881
|
-
chainId: BigInt(parts[0]),
|
|
5882
|
-
contract: parts[1],
|
|
5883
|
-
user: parts[2]
|
|
5884
|
-
};
|
|
5885
|
-
});
|
|
5886
|
-
const raw = await db.execute(drizzle_orm.sql`
|
|
5887
|
-
SELECT p.chain_id, p.contract, p."user", p.balance, p.asset
|
|
5888
|
-
FROM ${positions} p
|
|
5889
|
-
WHERE (p.chain_id, LOWER(p.contract), LOWER(p."user")) IN (
|
|
5890
|
-
${drizzle_orm.sql.join(parsedKeys.map((k) => drizzle_orm.sql`(${k.chainId}, ${k.contract.toLowerCase()}, ${k.user.toLowerCase()})`), drizzle_orm.sql`, `)}
|
|
5891
|
-
)
|
|
5892
|
-
`);
|
|
5893
|
-
for (const row of raw.rows) {
|
|
5894
|
-
const key = _buildPositionKey(row.chain_id, row.contract, row.user);
|
|
5895
|
-
if (!state.has(key)) state.set(key, {
|
|
5896
|
-
balance: row.balance ? BigInt(row.balance) : 0n,
|
|
5897
|
-
remaining: row.balance ? BigInt(row.balance) : 0n,
|
|
5898
|
-
asset: row.asset ?? "0x0000000000000000000000000000000000000000"
|
|
5899
|
-
});
|
|
5900
|
-
}
|
|
5901
|
-
}
|
|
5902
|
-
/** Get oracle prices by chain_id and address. */
|
|
5903
|
-
async function _updatePrices(state, db, oracles$1) {
|
|
5904
|
-
if (oracles$1.length === 0) return;
|
|
5905
|
-
const raw = await db.execute(drizzle_orm.sql`
|
|
5906
|
-
SELECT o.chain_id, o.address, o.price
|
|
5907
|
-
FROM ${oracles} o
|
|
5908
|
-
WHERE (o.chain_id, LOWER(o.address)) IN (${drizzle_orm.sql.join(oracles$1.map((o) => drizzle_orm.sql`(${o.chainId}, ${o.address.toLowerCase()})`), drizzle_orm.sql`, `)})
|
|
5909
|
-
`);
|
|
5910
|
-
for (const row of raw.rows) {
|
|
5911
|
-
const key = `${row.chain_id}:${row.address.toLowerCase()}`;
|
|
5912
|
-
if (!state.has(key)) state.set(key, row.price ? BigInt(row.price) : 0n);
|
|
5913
|
-
}
|
|
5914
|
-
}
|
|
5915
|
-
/** Build a composite position key from its components. */
|
|
5916
|
-
function _buildPositionKey(chainId, contract, user) {
|
|
5917
|
-
return `${chainId}:${contract.toLowerCase()}:${user.toLowerCase()}`;
|
|
5918
|
-
}
|
|
5919
|
-
/** Collect oracle addresses that need to be Geted for collateral positions. */
|
|
5920
|
-
function _collectNewOracleAddresses(offers$1, callbackState, positionState, prices) {
|
|
5921
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5922
|
-
const result = [];
|
|
5923
|
-
for (const offer of offers$1) for (const callbackId of offer.callbackIds) {
|
|
5924
|
-
const callback$1 = callbackState.get(callbackId);
|
|
5925
|
-
if (!callback$1) continue;
|
|
5926
|
-
const position = positionState.get(callback$1.positionKey);
|
|
5927
|
-
if (!position) continue;
|
|
5928
|
-
if (position.asset.toLowerCase() === offer.loanToken.toLowerCase()) continue;
|
|
5929
|
-
const collateral = offer.collaterals.find((c) => c.asset.toLowerCase() === position.asset.toLowerCase());
|
|
5930
|
-
if (collateral) {
|
|
5931
|
-
const key = `${offer.chainId}:${collateral.oracle.toLowerCase()}`;
|
|
5932
|
-
if (!prices.has(key) && !seen.has(key)) {
|
|
5933
|
-
seen.add(key);
|
|
5934
|
-
result.push({
|
|
5935
|
-
chainId: offer.chainId,
|
|
5936
|
-
address: collateral.oracle.toLowerCase()
|
|
5937
|
-
});
|
|
5938
|
-
}
|
|
5939
|
-
}
|
|
5940
|
-
}
|
|
5941
|
-
return result;
|
|
5942
|
-
}
|
|
5943
|
-
/**
|
|
5944
|
-
* Compute cross-invalidation for a batch of offers.
|
|
5945
|
-
* Deducts consumed liquidity from shared positions and returns offers with takeable amounts.
|
|
5946
|
-
*/
|
|
5947
|
-
function _computeCrossInvalidation(offers$1, callbackState, positionState, prices) {
|
|
5948
|
-
const result = [];
|
|
5949
|
-
for (const offer of offers$1) {
|
|
5950
|
-
const contributions = /* @__PURE__ */ new Map();
|
|
5951
|
-
for (const callbackId of offer.callbackIds) {
|
|
5952
|
-
const callback$1 = callbackState.get(callbackId);
|
|
5953
|
-
if (!callback$1) continue;
|
|
5954
|
-
const position = positionState.get(callback$1.positionKey);
|
|
5955
|
-
if (!position) continue;
|
|
5956
|
-
let conversion;
|
|
5957
|
-
if (position.asset.toLowerCase() === offer.loanToken.toLowerCase()) conversion = null;
|
|
5958
|
-
else {
|
|
5959
|
-
const collateral = offer.collaterals.find((c) => c.asset.toLowerCase() === position.asset.toLowerCase());
|
|
5960
|
-
if (!collateral) conversion = {
|
|
5961
|
-
price: 0n,
|
|
5962
|
-
lltv: 0n
|
|
5963
|
-
};
|
|
5964
|
-
else {
|
|
5965
|
-
const key = `${offer.chainId}:${collateral.oracle.toLowerCase()}`;
|
|
5966
|
-
conversion = {
|
|
5967
|
-
price: prices.get(key) ?? 0n,
|
|
5968
|
-
lltv: collateral.lltv
|
|
5969
|
-
};
|
|
5970
|
-
}
|
|
5971
|
-
}
|
|
5972
|
-
const availableFromPosition = conversion === null ? position.remaining : Conversion.collateralToLoan(position.remaining, conversion);
|
|
5973
|
-
const callbackLimitInLoanTerms = conversion === null || callback$1.amount === null ? callback$1.amount : Conversion.collateralToLoan(callback$1.amount, conversion);
|
|
5974
|
-
const callbackAvailable = callbackLimitInLoanTerms === null ? availableFromPosition : min(availableFromPosition, callbackLimitInLoanTerms);
|
|
5975
|
-
const existing = contributions.get(callback$1.positionKey);
|
|
5976
|
-
if (existing) existing.available = min(availableFromPosition, max$1(existing.available, callbackAvailable));
|
|
5977
|
-
else contributions.set(callback$1.positionKey, {
|
|
5978
|
-
available: callbackAvailable,
|
|
5979
|
-
conversion
|
|
5980
|
-
});
|
|
5981
|
-
}
|
|
5982
|
-
let totalAvailable = 0n;
|
|
5983
|
-
for (const [, contrib] of contributions) totalAvailable += contrib.available;
|
|
5984
|
-
const takeable = min(offer.assets - offer.consumed, totalAvailable);
|
|
5985
|
-
if (takeable <= 0n) continue;
|
|
5986
|
-
for (const [key, contrib] of contributions) {
|
|
5987
|
-
const position = positionState.get(key);
|
|
5988
|
-
const proportionalTakeable = totalAvailable > 0n ? contrib.available * takeable / totalAvailable : 0n;
|
|
5989
|
-
const toDeduct = contrib.conversion === null ? proportionalTakeable : Conversion.loanToCollateral(proportionalTakeable, contrib.conversion);
|
|
5990
|
-
position.remaining = position.remaining - toDeduct;
|
|
5991
|
-
if (position.remaining < 0n) position.remaining = 0n;
|
|
5992
|
-
}
|
|
5993
|
-
const { callbackIds: _, ...cleanOffer } = offer;
|
|
5994
|
-
result.push(from$11({
|
|
5995
|
-
...cleanOffer,
|
|
5996
|
-
takeable
|
|
5997
|
-
}));
|
|
5998
|
-
}
|
|
5999
|
-
return result;
|
|
6000
|
-
}
|
|
6001
6441
|
let Cursor;
|
|
6002
6442
|
(function(_Cursor) {
|
|
6003
6443
|
function encode$4(offer, totalReturned, now$1, side) {
|
|
@@ -6062,7 +6502,7 @@ let LevelCursor;
|
|
|
6062
6502
|
//#endregion
|
|
6063
6503
|
//#region src/database/domains/Chains.ts
|
|
6064
6504
|
/** Postgres implementation. */
|
|
6065
|
-
const create$
|
|
6505
|
+
const create$11 = (config) => {
|
|
6066
6506
|
const db = config.db;
|
|
6067
6507
|
const logger = getLogger();
|
|
6068
6508
|
return {
|
|
@@ -6118,7 +6558,7 @@ const create$8 = (config) => {
|
|
|
6118
6558
|
//#endregion
|
|
6119
6559
|
//#region src/database/domains/Collectors.ts
|
|
6120
6560
|
/** Postgres implementation. */
|
|
6121
|
-
const create$
|
|
6561
|
+
const create$10 = (config) => {
|
|
6122
6562
|
const db = config.db;
|
|
6123
6563
|
const logger = getLogger();
|
|
6124
6564
|
return {
|
|
@@ -6211,7 +6651,7 @@ const DEFAULT_BATCH_SIZE$1 = 4e3;
|
|
|
6211
6651
|
|
|
6212
6652
|
//#endregion
|
|
6213
6653
|
//#region src/database/domains/Consumed.ts
|
|
6214
|
-
function create$
|
|
6654
|
+
function create$9(db) {
|
|
6215
6655
|
return {
|
|
6216
6656
|
create: async (events) => {
|
|
6217
6657
|
if (events.length === 0) return;
|
|
@@ -6252,6 +6692,51 @@ function create$6(db) {
|
|
|
6252
6692
|
};
|
|
6253
6693
|
}
|
|
6254
6694
|
|
|
6695
|
+
//#endregion
|
|
6696
|
+
//#region src/database/domains/Lots.ts
|
|
6697
|
+
function create$8(db) {
|
|
6698
|
+
return {
|
|
6699
|
+
get: async (parameters) => {
|
|
6700
|
+
const { chainId, user, contract, group } = parameters ?? {};
|
|
6701
|
+
const conditions = [];
|
|
6702
|
+
if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(lots.chainId, chainId));
|
|
6703
|
+
if (user !== void 0) conditions.push((0, drizzle_orm.eq)(lots.user, user.toLowerCase()));
|
|
6704
|
+
if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(lots.contract, contract.toLowerCase()));
|
|
6705
|
+
if (group !== void 0) conditions.push((0, drizzle_orm.eq)(lots.group, group));
|
|
6706
|
+
return (await db.select().from(lots).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
|
|
6707
|
+
chainId: row.chainId,
|
|
6708
|
+
user: row.user,
|
|
6709
|
+
contract: row.contract,
|
|
6710
|
+
group: row.group,
|
|
6711
|
+
lower: BigInt(row.lower),
|
|
6712
|
+
upper: BigInt(row.upper)
|
|
6713
|
+
}));
|
|
6714
|
+
},
|
|
6715
|
+
create: async (parameters) => {
|
|
6716
|
+
if (parameters.length === 0) return;
|
|
6717
|
+
const lotsByPositionGroup = /* @__PURE__ */ new Map();
|
|
6718
|
+
for (const offer of parameters) {
|
|
6719
|
+
const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}`.toLowerCase();
|
|
6720
|
+
const existing = lotsByPositionGroup.get(key);
|
|
6721
|
+
if (!existing || offer.size > existing.size) lotsByPositionGroup.set(key, offer);
|
|
6722
|
+
}
|
|
6723
|
+
for (const offer of lotsByPositionGroup.values()) if ((await db.select().from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase()), (0, drizzle_orm.eq)(lots.group, offer.group))).limit(1)).length === 0) {
|
|
6724
|
+
const maxUpperResult = await db.select({ maxUpper: drizzle_orm.sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase())));
|
|
6725
|
+
const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
|
|
6726
|
+
const newUpper = newLower + offer.size;
|
|
6727
|
+
await db.insert(lots).values({
|
|
6728
|
+
chainId: offer.positionChainId,
|
|
6729
|
+
user: offer.positionUser.toLowerCase(),
|
|
6730
|
+
contract: offer.positionContract.toLowerCase(),
|
|
6731
|
+
group: offer.group,
|
|
6732
|
+
lower: newLower.toString(),
|
|
6733
|
+
upper: newUpper.toString()
|
|
6734
|
+
});
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
};
|
|
6738
|
+
}
|
|
6739
|
+
|
|
6255
6740
|
//#endregion
|
|
6256
6741
|
//#region src/gatekeeper/Gate.ts
|
|
6257
6742
|
var Gate_exports = /* @__PURE__ */ __export({
|
|
@@ -6365,8 +6850,8 @@ function getCallback(chain, type) {
|
|
|
6365
6850
|
* @param address - Callback contract address
|
|
6366
6851
|
* @returns The callback type when found, otherwise undefined
|
|
6367
6852
|
*/
|
|
6368
|
-
function getCallbackType(chain, address) {
|
|
6369
|
-
return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
|
|
6853
|
+
function getCallbackType(chain, address$1) {
|
|
6854
|
+
return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address$1?.toLowerCase()))?.type;
|
|
6370
6855
|
}
|
|
6371
6856
|
/**
|
|
6372
6857
|
* Returns the callback addresses for a given chain and callback type, if it exists.
|
|
@@ -6480,8 +6965,8 @@ const configs = {
|
|
|
6480
6965
|
|
|
6481
6966
|
//#endregion
|
|
6482
6967
|
//#region src/gatekeeper/Gatekeeper.ts
|
|
6483
|
-
var Gatekeeper_exports = /* @__PURE__ */ __export({ create: () => create$
|
|
6484
|
-
function create$
|
|
6968
|
+
var Gatekeeper_exports = /* @__PURE__ */ __export({ create: () => create$7 });
|
|
6969
|
+
function create$7(parameters) {
|
|
6485
6970
|
return {
|
|
6486
6971
|
rules: parameters.rules,
|
|
6487
6972
|
isAllowed: async (offers$1) => {
|
|
@@ -6499,6 +6984,7 @@ var Rules_exports = /* @__PURE__ */ __export({
|
|
|
6499
6984
|
callback: () => callback,
|
|
6500
6985
|
chains: () => chains,
|
|
6501
6986
|
maturity: () => maturity,
|
|
6987
|
+
sameMaker: () => sameMaker,
|
|
6502
6988
|
token: () => token,
|
|
6503
6989
|
validity: () => validity
|
|
6504
6990
|
});
|
|
@@ -6635,10 +7121,29 @@ const token = ({ assets: assets$1 }) => single("token", "Validates that offer lo
|
|
|
6635
7121
|
if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
|
|
6636
7122
|
if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
|
|
6637
7123
|
});
|
|
7124
|
+
/**
|
|
7125
|
+
* A batch validation rule that ensures all offers in a tree have the same maker (offering address).
|
|
7126
|
+
* Returns an issue only for the first non-conforming offer.
|
|
7127
|
+
* This rule is signing-agnostic; signer verification is handled at the collector level.
|
|
7128
|
+
*/
|
|
7129
|
+
const sameMaker = () => batch("mixed_maker", "Validates that all offers in a batch have the same maker (offering address)", (offers$1) => {
|
|
7130
|
+
const issues = /* @__PURE__ */ new Map();
|
|
7131
|
+
if (offers$1.length === 0) return issues;
|
|
7132
|
+
const firstMaker = offers$1[0].offering.toLowerCase();
|
|
7133
|
+
for (let i = 1; i < offers$1.length; i++) {
|
|
7134
|
+
const offer = offers$1[i];
|
|
7135
|
+
if (offer.offering.toLowerCase() !== firstMaker) {
|
|
7136
|
+
issues.set(i, { message: `Offer has different maker ${offer.offering} than first offer ${offers$1[0].offering}` });
|
|
7137
|
+
return issues;
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
return issues;
|
|
7141
|
+
});
|
|
6638
7142
|
|
|
6639
7143
|
//#endregion
|
|
6640
7144
|
//#region src/gatekeeper/morphoRules.ts
|
|
6641
7145
|
const morphoRules = (chains$3) => [
|
|
7146
|
+
sameMaker(),
|
|
6642
7147
|
chains({ chains: chains$3 }),
|
|
6643
7148
|
maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
|
|
6644
7149
|
callback({
|
|
@@ -6655,7 +7160,7 @@ const morphoRules = (chains$3) => [
|
|
|
6655
7160
|
//#endregion
|
|
6656
7161
|
//#region src/database/domains/Offers.ts
|
|
6657
7162
|
const DEFAULT_LIMIT$2 = 100;
|
|
6658
|
-
function create$
|
|
7163
|
+
function create$6(config) {
|
|
6659
7164
|
const db = config.db;
|
|
6660
7165
|
return {
|
|
6661
7166
|
create: async (offers$1) => {
|
|
@@ -6814,6 +7319,22 @@ function create$4(config) {
|
|
|
6814
7319
|
amount: callback$1.amount
|
|
6815
7320
|
})));
|
|
6816
7321
|
for (const batch$2 of batch$1(callbacksRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(callbacks).values(batch$2).onConflictDoNothing();
|
|
7322
|
+
const lotInfos = [];
|
|
7323
|
+
for (const [offerHash, callbacks$1] of offersCallbacksMap.entries()) {
|
|
7324
|
+
const offer = inserted.find((o) => o.hash === offerHash);
|
|
7325
|
+
if (!offer) continue;
|
|
7326
|
+
for (const callback$1 of callbacks$1) {
|
|
7327
|
+
const isLoanPosition = obligationsMap.get(offer.obligationId)?.loanToken.toLowerCase() === callback$1.asset?.toLowerCase();
|
|
7328
|
+
lotInfos.push({
|
|
7329
|
+
positionChainId: callback$1.chainId,
|
|
7330
|
+
positionContract: callback$1.contract,
|
|
7331
|
+
positionUser: callback$1.user,
|
|
7332
|
+
group: offer.group,
|
|
7333
|
+
size: isLoanPosition ? BigInt(offer.assets) : BigInt(callback$1.amount)
|
|
7334
|
+
});
|
|
7335
|
+
}
|
|
7336
|
+
}
|
|
7337
|
+
if (lotInfos.length > 0) await dbTx.lots.create(lotInfos);
|
|
6817
7338
|
obligationsMap.clear();
|
|
6818
7339
|
collateralsMap.clear();
|
|
6819
7340
|
oraclesMap.clear();
|
|
@@ -6996,9 +7517,29 @@ function create$4(config) {
|
|
|
6996
7517
|
};
|
|
6997
7518
|
}
|
|
6998
7519
|
|
|
7520
|
+
//#endregion
|
|
7521
|
+
//#region src/database/domains/Offsets.ts
|
|
7522
|
+
function create$5(db) {
|
|
7523
|
+
return { get: async (parameters) => {
|
|
7524
|
+
const { chainId, user, contract, group } = parameters ?? {};
|
|
7525
|
+
const conditions = [];
|
|
7526
|
+
if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.chainId, chainId));
|
|
7527
|
+
if (user !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.user, user.toLowerCase()));
|
|
7528
|
+
if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.contract, contract.toLowerCase()));
|
|
7529
|
+
if (group !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.group, group));
|
|
7530
|
+
return (await db.select().from(offsets).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
|
|
7531
|
+
chainId: row.chainId,
|
|
7532
|
+
user: row.user,
|
|
7533
|
+
contract: row.contract,
|
|
7534
|
+
group: row.group,
|
|
7535
|
+
value: BigInt(row.value)
|
|
7536
|
+
}));
|
|
7537
|
+
} };
|
|
7538
|
+
}
|
|
7539
|
+
|
|
6999
7540
|
//#endregion
|
|
7000
7541
|
//#region src/database/domains/Oracles.ts
|
|
7001
|
-
function create$
|
|
7542
|
+
function create$4(db) {
|
|
7002
7543
|
return {
|
|
7003
7544
|
get: async ({ chainId }) => {
|
|
7004
7545
|
return (await db.select({
|
|
@@ -7038,7 +7579,7 @@ function create$3(db) {
|
|
|
7038
7579
|
//#endregion
|
|
7039
7580
|
//#region src/database/domains/Positions.ts
|
|
7040
7581
|
const DEFAULT_LIMIT$1 = 100;
|
|
7041
|
-
const create$
|
|
7582
|
+
const create$3 = (db) => {
|
|
7042
7583
|
return {
|
|
7043
7584
|
upsert: async (positions$1) => {
|
|
7044
7585
|
const positionsMap = /* @__PURE__ */ new Map();
|
|
@@ -7154,7 +7695,7 @@ const create$2 = (db) => {
|
|
|
7154
7695
|
|
|
7155
7696
|
//#endregion
|
|
7156
7697
|
//#region src/database/domains/Transfers.ts
|
|
7157
|
-
const create$
|
|
7698
|
+
const create$2 = (db) => ({ create: async (transfers$1) => {
|
|
7158
7699
|
if (transfers$1.length === 0) return 0;
|
|
7159
7700
|
return await db.transaction(async (dbTx) => {
|
|
7160
7701
|
let totalInserted = 0;
|
|
@@ -7251,6 +7792,91 @@ const create$1 = (db) => ({ create: async (transfers$1) => {
|
|
|
7251
7792
|
});
|
|
7252
7793
|
} });
|
|
7253
7794
|
|
|
7795
|
+
//#endregion
|
|
7796
|
+
//#region src/database/domains/Trees.ts
|
|
7797
|
+
/**
|
|
7798
|
+
* Creates a Trees domain instance for managing merkle tree metadata.
|
|
7799
|
+
*
|
|
7800
|
+
* @param config - Configuration with database instance
|
|
7801
|
+
* @returns TreesDomain instance
|
|
7802
|
+
*/
|
|
7803
|
+
function create$1(config) {
|
|
7804
|
+
const db = config.db;
|
|
7805
|
+
return {
|
|
7806
|
+
create: async (trees$1) => {
|
|
7807
|
+
if (trees$1.length === 0) return [];
|
|
7808
|
+
return await db.transaction(async (dbTx) => {
|
|
7809
|
+
const roots = [];
|
|
7810
|
+
for (const { tree, signature } of trees$1) {
|
|
7811
|
+
const root = tree.root.toLowerCase();
|
|
7812
|
+
roots.push(root);
|
|
7813
|
+
await dbTx.insert(trees).values({
|
|
7814
|
+
root,
|
|
7815
|
+
rootSignature: signature.toLowerCase()
|
|
7816
|
+
}).onConflictDoUpdate({
|
|
7817
|
+
target: [trees.root],
|
|
7818
|
+
set: {
|
|
7819
|
+
rootSignature: signature.toLowerCase(),
|
|
7820
|
+
createdAt: drizzle_orm.sql`NOW()`
|
|
7821
|
+
}
|
|
7822
|
+
});
|
|
7823
|
+
await dbTx.offers.create(tree.offers);
|
|
7824
|
+
const pathRows = proofs(tree).map((proof) => ({
|
|
7825
|
+
offerHash: proof.offer.hash.toLowerCase(),
|
|
7826
|
+
treeRoot: root,
|
|
7827
|
+
proofNodes: concatenateProofs(proof.path)
|
|
7828
|
+
}));
|
|
7829
|
+
for (const batch$2 of batch$1(pathRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(merklePaths).values(batch$2).onConflictDoUpdate({
|
|
7830
|
+
target: [merklePaths.offerHash],
|
|
7831
|
+
set: {
|
|
7832
|
+
treeRoot: drizzle_orm.sql`excluded.tree_root`,
|
|
7833
|
+
proofNodes: drizzle_orm.sql`excluded.proof_nodes`,
|
|
7834
|
+
createdAt: drizzle_orm.sql`NOW()`
|
|
7835
|
+
}
|
|
7836
|
+
});
|
|
7837
|
+
}
|
|
7838
|
+
return roots;
|
|
7839
|
+
});
|
|
7840
|
+
},
|
|
7841
|
+
getAttestations: async (hashes) => {
|
|
7842
|
+
if (hashes.length === 0) return /* @__PURE__ */ new Map();
|
|
7843
|
+
const normalizedHashes = hashes.map((h) => h.toLowerCase());
|
|
7844
|
+
const results = await db.select({
|
|
7845
|
+
offerHash: merklePaths.offerHash,
|
|
7846
|
+
treeRoot: merklePaths.treeRoot,
|
|
7847
|
+
proofNodes: merklePaths.proofNodes,
|
|
7848
|
+
rootSignature: trees.rootSignature
|
|
7849
|
+
}).from(merklePaths).innerJoin(trees, (0, drizzle_orm.eq)(merklePaths.treeRoot, trees.root)).where((0, drizzle_orm.inArray)(merklePaths.offerHash, normalizedHashes));
|
|
7850
|
+
const attestationMap = /* @__PURE__ */ new Map();
|
|
7851
|
+
for (const row of results) attestationMap.set(row.offerHash, {
|
|
7852
|
+
root: row.treeRoot,
|
|
7853
|
+
signature: row.rootSignature,
|
|
7854
|
+
proof: splitProofs(row.proofNodes)
|
|
7855
|
+
});
|
|
7856
|
+
return attestationMap;
|
|
7857
|
+
}
|
|
7858
|
+
};
|
|
7859
|
+
}
|
|
7860
|
+
/**
|
|
7861
|
+
* Concatenates an array of 32-byte hex hashes into a single hex string.
|
|
7862
|
+
* Empty arrays return "0x".
|
|
7863
|
+
*/
|
|
7864
|
+
function concatenateProofs(proofs$1) {
|
|
7865
|
+
if (proofs$1.length === 0) return "0x";
|
|
7866
|
+
return `0x${proofs$1.map((p) => p.slice(2)).join("")}`;
|
|
7867
|
+
}
|
|
7868
|
+
/**
|
|
7869
|
+
* Splits a concatenated hex string back into an array of 32-byte hex hashes.
|
|
7870
|
+
* Returns empty array for "0x" or empty string.
|
|
7871
|
+
*/
|
|
7872
|
+
function splitProofs(concatenated) {
|
|
7873
|
+
if (!concatenated || concatenated === "0x" || concatenated.length <= 2) return [];
|
|
7874
|
+
const hex$1 = concatenated.slice(2);
|
|
7875
|
+
const proofs$1 = [];
|
|
7876
|
+
for (let i = 0; i < hex$1.length; i += 64) proofs$1.push(`0x${hex$1.slice(i, i + 64)}`);
|
|
7877
|
+
return proofs$1;
|
|
7878
|
+
}
|
|
7879
|
+
|
|
7254
7880
|
//#endregion
|
|
7255
7881
|
//#region src/database/domains/Validations.ts
|
|
7256
7882
|
const DEFAULT_LIMIT = 100;
|
|
@@ -7316,15 +7942,18 @@ function create(db) {
|
|
|
7316
7942
|
var Database_exports = /* @__PURE__ */ __export({ connect: () => connect$1 });
|
|
7317
7943
|
function createDomains(core) {
|
|
7318
7944
|
return {
|
|
7319
|
-
book: create$
|
|
7320
|
-
collectors: create$
|
|
7321
|
-
offers: create$
|
|
7322
|
-
chains: create$
|
|
7323
|
-
consumed: create$
|
|
7324
|
-
|
|
7945
|
+
book: create$12({ db: core }),
|
|
7946
|
+
collectors: create$10({ db: core }),
|
|
7947
|
+
offers: create$6({ db: core }),
|
|
7948
|
+
chains: create$11({ db: core }),
|
|
7949
|
+
consumed: create$9(core),
|
|
7950
|
+
lots: create$8(core),
|
|
7951
|
+
offsets: create$5(core),
|
|
7952
|
+
oracles: create$4(core),
|
|
7953
|
+
trees: create$1({ db: core }),
|
|
7325
7954
|
validations: create(core),
|
|
7326
|
-
positions: create$
|
|
7327
|
-
transfers: create$
|
|
7955
|
+
positions: create$3(core),
|
|
7956
|
+
transfers: create$2(core)
|
|
7328
7957
|
};
|
|
7329
7958
|
}
|
|
7330
7959
|
const AUGMENT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -7359,10 +7988,22 @@ function augmentWithDomains(base) {
|
|
|
7359
7988
|
value: dms.consumed,
|
|
7360
7989
|
enumerable: true
|
|
7361
7990
|
},
|
|
7991
|
+
lots: {
|
|
7992
|
+
value: dms.lots,
|
|
7993
|
+
enumerable: true
|
|
7994
|
+
},
|
|
7995
|
+
offsets: {
|
|
7996
|
+
value: dms.offsets,
|
|
7997
|
+
enumerable: true
|
|
7998
|
+
},
|
|
7362
7999
|
oracles: {
|
|
7363
8000
|
value: dms.oracles,
|
|
7364
8001
|
enumerable: true
|
|
7365
8002
|
},
|
|
8003
|
+
trees: {
|
|
8004
|
+
value: dms.trees,
|
|
8005
|
+
enumerable: true
|
|
8006
|
+
},
|
|
7366
8007
|
validations: {
|
|
7367
8008
|
value: dms.validations,
|
|
7368
8009
|
enumerable: true
|
|
@@ -7379,6 +8020,7 @@ function augmentWithDomains(base) {
|
|
|
7379
8020
|
AUGMENT_CACHE.set(base, wrapped);
|
|
7380
8021
|
return wrapped;
|
|
7381
8022
|
}
|
|
8023
|
+
let cachedInMemoryDatabase;
|
|
7382
8024
|
/**
|
|
7383
8025
|
* Connect to the database.
|
|
7384
8026
|
* @notice If no connection string is provided, an in-process PGLite database is created.
|
|
@@ -7401,15 +8043,17 @@ function connect$1(connectionString) {
|
|
|
7401
8043
|
clean: async () => await clean(driver$1)
|
|
7402
8044
|
});
|
|
7403
8045
|
}
|
|
8046
|
+
if (cachedInMemoryDatabase) return cachedInMemoryDatabase;
|
|
7404
8047
|
const pool = new __electric_sql_pglite.PGlite();
|
|
7405
8048
|
const driver = (0, drizzle_orm_pglite.drizzle)(pool, { schema: schema_exports });
|
|
7406
8049
|
const core = augmentWithDomains(driver);
|
|
7407
|
-
|
|
8050
|
+
cachedInMemoryDatabase = Object.assign(core, {
|
|
7408
8051
|
name: "pglite",
|
|
7409
8052
|
pool,
|
|
7410
8053
|
applyMigrations: applyMigrations("pglite", driver),
|
|
7411
8054
|
clean: async () => await clean(driver)
|
|
7412
8055
|
});
|
|
8056
|
+
return cachedInMemoryDatabase;
|
|
7413
8057
|
}
|
|
7414
8058
|
const MIGRATED_DRIVERS = /* @__PURE__ */ new WeakSet();
|
|
7415
8059
|
function applyMigrations(kind, driver) {
|
|
@@ -7613,6 +8257,35 @@ async function postMigrate(driver) {
|
|
|
7613
8257
|
REFERENCING OLD TABLE AS deleted_rows
|
|
7614
8258
|
FOR EACH STATEMENT
|
|
7615
8259
|
EXECUTE FUNCTION cleanup_orphan_positions();
|
|
8260
|
+
`);
|
|
8261
|
+
await driver.execute(`
|
|
8262
|
+
CREATE OR REPLACE FUNCTION cleanup_orphan_groups()
|
|
8263
|
+
RETURNS TRIGGER AS $$
|
|
8264
|
+
BEGIN
|
|
8265
|
+
DELETE FROM "${VERSION}"."groups" g
|
|
8266
|
+
USING (
|
|
8267
|
+
SELECT DISTINCT group_chain_id, group_maker, group_group
|
|
8268
|
+
FROM deleted_rows
|
|
8269
|
+
) AS affected
|
|
8270
|
+
WHERE g.chain_id = affected.group_chain_id
|
|
8271
|
+
AND g.maker = affected.group_maker
|
|
8272
|
+
AND g."group" = affected.group_group
|
|
8273
|
+
AND NOT EXISTS (
|
|
8274
|
+
SELECT 1 FROM "${VERSION}"."offers" o
|
|
8275
|
+
WHERE o.group_chain_id = g.chain_id
|
|
8276
|
+
AND o.group_maker = g.maker
|
|
8277
|
+
AND o.group_group = g."group"
|
|
8278
|
+
);
|
|
8279
|
+
RETURN NULL;
|
|
8280
|
+
END;
|
|
8281
|
+
$$ LANGUAGE plpgsql;
|
|
8282
|
+
`);
|
|
8283
|
+
await driver.execute(`
|
|
8284
|
+
CREATE OR REPLACE TRIGGER trg_cleanup_orphan_groups
|
|
8285
|
+
AFTER DELETE ON "${VERSION}"."offers"
|
|
8286
|
+
REFERENCING OLD TABLE AS deleted_rows
|
|
8287
|
+
FOR EACH STATEMENT
|
|
8288
|
+
EXECUTE FUNCTION cleanup_orphan_groups();
|
|
7616
8289
|
`);
|
|
7617
8290
|
await driver.execute(`
|
|
7618
8291
|
CREATE OR REPLACE FUNCTION cleanup_orphan_obligations_and_oracles()
|
|
@@ -7665,6 +8338,58 @@ async function postMigrate(driver) {
|
|
|
7665
8338
|
REFERENCING OLD TABLE AS deleted_rows
|
|
7666
8339
|
FOR EACH STATEMENT
|
|
7667
8340
|
EXECUTE FUNCTION cleanup_orphan_obligations_and_oracles();
|
|
8341
|
+
`);
|
|
8342
|
+
await driver.execute(`
|
|
8343
|
+
CREATE OR REPLACE FUNCTION create_offset_on_lot_delete()
|
|
8344
|
+
RETURNS trigger
|
|
8345
|
+
LANGUAGE plpgsql AS $$
|
|
8346
|
+
BEGIN
|
|
8347
|
+
INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", value)
|
|
8348
|
+
VALUES (
|
|
8349
|
+
OLD.chain_id,
|
|
8350
|
+
OLD."user",
|
|
8351
|
+
OLD.contract,
|
|
8352
|
+
OLD."group",
|
|
8353
|
+
OLD.upper::numeric - OLD.lower::numeric
|
|
8354
|
+
)
|
|
8355
|
+
ON CONFLICT (chain_id, "user", contract, "group") DO NOTHING;
|
|
8356
|
+
RETURN OLD;
|
|
8357
|
+
END;
|
|
8358
|
+
$$;
|
|
8359
|
+
`);
|
|
8360
|
+
await driver.execute(`
|
|
8361
|
+
CREATE OR REPLACE TRIGGER trg_lots_create_offset_before_delete
|
|
8362
|
+
BEFORE DELETE ON "${VERSION}"."lots"
|
|
8363
|
+
FOR EACH ROW
|
|
8364
|
+
EXECUTE FUNCTION create_offset_on_lot_delete();
|
|
8365
|
+
`);
|
|
8366
|
+
await driver.execute(`
|
|
8367
|
+
CREATE OR REPLACE FUNCTION delete_position_if_no_lots()
|
|
8368
|
+
RETURNS trigger
|
|
8369
|
+
LANGUAGE plpgsql AS $$
|
|
8370
|
+
BEGIN
|
|
8371
|
+
-- Check if any lots remain on this position
|
|
8372
|
+
IF NOT EXISTS (
|
|
8373
|
+
SELECT 1 FROM "${VERSION}"."lots" l
|
|
8374
|
+
WHERE l.chain_id = OLD.chain_id
|
|
8375
|
+
AND l.contract = OLD.contract
|
|
8376
|
+
AND l."user" = OLD."user"
|
|
8377
|
+
) THEN
|
|
8378
|
+
-- No lots remain, delete the position (cascades to offsets)
|
|
8379
|
+
DELETE FROM "${VERSION}"."positions" p
|
|
8380
|
+
WHERE p.chain_id = OLD.chain_id
|
|
8381
|
+
AND p.contract = OLD.contract
|
|
8382
|
+
AND p."user" = OLD."user";
|
|
8383
|
+
END IF;
|
|
8384
|
+
RETURN NULL;
|
|
8385
|
+
END;
|
|
8386
|
+
$$;
|
|
8387
|
+
`);
|
|
8388
|
+
await driver.execute(`
|
|
8389
|
+
CREATE OR REPLACE TRIGGER trg_lots_delete_position_if_empty
|
|
8390
|
+
AFTER DELETE ON "${VERSION}"."lots"
|
|
8391
|
+
FOR EACH ROW
|
|
8392
|
+
EXECUTE FUNCTION delete_position_if_no_lots();
|
|
7668
8393
|
`);
|
|
7669
8394
|
});
|
|
7670
8395
|
}
|
|
@@ -7696,22 +8421,24 @@ async function add(config, offers$1) {
|
|
|
7696
8421
|
const tree = from$12(offers$1.map((o) => from$11(o)));
|
|
7697
8422
|
const chainId = await getChainId(config.client);
|
|
7698
8423
|
for (const offer of tree.offers) if (chainId !== offer.chainId) throw new ChainIdMismatchError(offer.chainId, chainId);
|
|
8424
|
+
const signature = await sign(tree.offers, config.client);
|
|
8425
|
+
const encoded = await encode$2(tree, signature);
|
|
7699
8426
|
try {
|
|
7700
8427
|
return await config.client.sendTransaction({
|
|
7701
8428
|
chain: config.client.chain,
|
|
7702
8429
|
account: config.client.account,
|
|
7703
8430
|
to: config.mempoolAddress,
|
|
7704
|
-
data:
|
|
8431
|
+
data: encoded
|
|
7705
8432
|
});
|
|
7706
8433
|
} catch (error) {
|
|
7707
8434
|
throw new ViemClientError(error instanceof Error ? error.message : "Unknown error");
|
|
7708
8435
|
}
|
|
7709
8436
|
}
|
|
7710
8437
|
async function* get(config, parameters) {
|
|
7711
|
-
const { loanToken, blockNumberGte, blockNumberLte, order
|
|
8438
|
+
const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE } = {} } = parameters || {};
|
|
7712
8439
|
yield* streamOffers(config, {
|
|
7713
8440
|
loanToken,
|
|
7714
|
-
order
|
|
8441
|
+
order,
|
|
7715
8442
|
blockNumberGte,
|
|
7716
8443
|
blockNumberLte,
|
|
7717
8444
|
options: {
|
|
@@ -7733,7 +8460,7 @@ const getChainId = async (client) => {
|
|
|
7733
8460
|
return chainId;
|
|
7734
8461
|
};
|
|
7735
8462
|
async function* streamOffers(config, parameters) {
|
|
7736
|
-
const { loanToken, blockNumberGte, blockNumberLte, order
|
|
8463
|
+
const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = config.blockWindow } = {} } = parameters;
|
|
7737
8464
|
const stream = streamLogs({
|
|
7738
8465
|
client: config.client.extend(viem.publicActions),
|
|
7739
8466
|
contractAddress: config.mempoolAddress,
|
|
@@ -7750,13 +8477,13 @@ async function* streamOffers(config, parameters) {
|
|
|
7750
8477
|
},
|
|
7751
8478
|
blockNumberGte,
|
|
7752
8479
|
blockNumberLte,
|
|
7753
|
-
order
|
|
8480
|
+
order,
|
|
7754
8481
|
options: {
|
|
7755
8482
|
maxBatchSize,
|
|
7756
8483
|
blockWindow
|
|
7757
8484
|
}
|
|
7758
8485
|
});
|
|
7759
|
-
let blockNumber = order
|
|
8486
|
+
let blockNumber = order === "asc" ? blockNumberGte : blockNumberLte;
|
|
7760
8487
|
for await (const { logs, blockNumber: newBlockNumber } of stream) {
|
|
7761
8488
|
blockNumber = newBlockNumber;
|
|
7762
8489
|
if (logs.length === 0) continue;
|
|
@@ -7765,7 +8492,7 @@ async function* streamOffers(config, parameters) {
|
|
|
7765
8492
|
if (!log) continue;
|
|
7766
8493
|
const [payload] = (0, viem.decodeAbiParameters)([{ type: "bytes" }], log.data);
|
|
7767
8494
|
try {
|
|
7768
|
-
const tree = decode$2(payload);
|
|
8495
|
+
const { tree } = await decode$2(payload);
|
|
7769
8496
|
for (const offer of tree.offers) {
|
|
7770
8497
|
if (loanToken && offer.loanToken.toLowerCase() !== loanToken.toLowerCase()) continue;
|
|
7771
8498
|
offers$1.push({
|