@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.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-jass6xSI.mjs";
|
|
2
2
|
import { getBlock, getBlockNumber, getLogs, multicall } from "viem/actions";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
-
import { bytesToHex, decodeAbiParameters, encodeAbiParameters, erc20Abi, getAddress, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, parseAbi, parseEventLogs, publicActions, stringify, zeroAddress } from "viem";
|
|
4
|
+
import { bytesToHex, decodeAbiParameters, encodeAbiParameters, erc20Abi, getAddress, hashMessage, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, parseAbi, parseEventLogs, publicActions, recoverAddress, stringify, zeroAddress } from "viem";
|
|
5
5
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
6
6
|
import "@opentelemetry/exporter-trace-otlp-proto";
|
|
7
7
|
import "@opentelemetry/id-generator-aws-xray";
|
|
@@ -13,7 +13,6 @@ import "@opentelemetry/resources";
|
|
|
13
13
|
import "@opentelemetry/sdk-trace-node";
|
|
14
14
|
import "@opentelemetry/semantic-conventions";
|
|
15
15
|
import { anvil, base, mainnet } from "viem/chains";
|
|
16
|
-
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
17
16
|
import * as z$1 from "zod";
|
|
18
17
|
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
|
|
19
18
|
import { gzip, ungzip } from "pako";
|
|
@@ -408,6 +407,96 @@ function poll(fn, { interval }) {
|
|
|
408
407
|
return unwatch;
|
|
409
408
|
}
|
|
410
409
|
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/utils/Random.ts
|
|
412
|
+
var Random_exports = /* @__PURE__ */ __export({
|
|
413
|
+
address: () => address,
|
|
414
|
+
bool: () => bool,
|
|
415
|
+
bytes: () => bytes,
|
|
416
|
+
float: () => float,
|
|
417
|
+
hex: () => hex,
|
|
418
|
+
int: () => int,
|
|
419
|
+
seed: () => seed,
|
|
420
|
+
withSeed: () => withSeed
|
|
421
|
+
});
|
|
422
|
+
let currentRng = Math.random;
|
|
423
|
+
const FNV_OFFSET_BASIS = 2166136261;
|
|
424
|
+
const FNV_PRIME = 16777619;
|
|
425
|
+
const hashSeed = (seed$1) => {
|
|
426
|
+
let hash$1 = FNV_OFFSET_BASIS;
|
|
427
|
+
for (let i = 0; i < seed$1.length; i += 1) {
|
|
428
|
+
hash$1 ^= seed$1.charCodeAt(i);
|
|
429
|
+
hash$1 = Math.imul(hash$1, FNV_PRIME);
|
|
430
|
+
}
|
|
431
|
+
return hash$1 >>> 0;
|
|
432
|
+
};
|
|
433
|
+
const createSeededRng = (seed$1) => {
|
|
434
|
+
let state = hashSeed(seed$1);
|
|
435
|
+
return () => {
|
|
436
|
+
state += 1831565813;
|
|
437
|
+
let t = Math.imul(state ^ state >>> 15, state | 1);
|
|
438
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
439
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
/**
|
|
443
|
+
* Runs a function with a deterministic RNG derived from the given seed.
|
|
444
|
+
*/
|
|
445
|
+
function withSeed(seed$1, fn) {
|
|
446
|
+
const previous = currentRng;
|
|
447
|
+
currentRng = createSeededRng(seed$1);
|
|
448
|
+
try {
|
|
449
|
+
return fn();
|
|
450
|
+
} finally {
|
|
451
|
+
currentRng = previous;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Seeds the global RNG for deterministic test runs.
|
|
456
|
+
*/
|
|
457
|
+
function seed(seed$1) {
|
|
458
|
+
currentRng = createSeededRng(seed$1);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Returns a deterministic random float in [0, 1).
|
|
462
|
+
*/
|
|
463
|
+
function float() {
|
|
464
|
+
return currentRng();
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Returns a deterministic random integer in [min, maxExclusive).
|
|
468
|
+
*/
|
|
469
|
+
function int(maxExclusive, min$1 = 0) {
|
|
470
|
+
return Math.floor(float() * (maxExclusive - min$1)) + min$1;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Returns a deterministic random boolean.
|
|
474
|
+
*/
|
|
475
|
+
function bool(probability = .5) {
|
|
476
|
+
return float() < probability;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Returns deterministic random bytes.
|
|
480
|
+
*/
|
|
481
|
+
function bytes(length) {
|
|
482
|
+
const output = new Uint8Array(length);
|
|
483
|
+
for (let i = 0; i < length; i += 1) output[i] = int(256);
|
|
484
|
+
return output;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Returns a deterministic random hex string for the given byte length.
|
|
488
|
+
*/
|
|
489
|
+
function hex(byteLength) {
|
|
490
|
+
const output = bytes(byteLength);
|
|
491
|
+
return `0x${Array.from(output, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Returns a deterministic random address.
|
|
495
|
+
*/
|
|
496
|
+
function address() {
|
|
497
|
+
return hex(20);
|
|
498
|
+
}
|
|
499
|
+
|
|
411
500
|
//#endregion
|
|
412
501
|
//#region src/utils/time.ts
|
|
413
502
|
var time_exports = /* @__PURE__ */ __export({
|
|
@@ -425,6 +514,7 @@ function max() {
|
|
|
425
514
|
//#region src/utils/index.ts
|
|
426
515
|
var utils_exports = /* @__PURE__ */ __export({
|
|
427
516
|
BaseError: () => BaseError,
|
|
517
|
+
Random: () => Random_exports,
|
|
428
518
|
ReorgError: () => ReorgError,
|
|
429
519
|
Time: () => time_exports,
|
|
430
520
|
batch: () => batch$1,
|
|
@@ -442,7 +532,7 @@ var utils_exports = /* @__PURE__ */ __export({
|
|
|
442
532
|
|
|
443
533
|
//#endregion
|
|
444
534
|
//#region src/indexer/collectors/Admin.ts
|
|
445
|
-
function create$
|
|
535
|
+
function create$17(parameters) {
|
|
446
536
|
const collector = "admin";
|
|
447
537
|
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
448
538
|
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
@@ -691,8 +781,8 @@ const names = [
|
|
|
691
781
|
"positions",
|
|
692
782
|
"prices"
|
|
693
783
|
];
|
|
694
|
-
function create$
|
|
695
|
-
const admin = create$
|
|
784
|
+
function create$16({ name, collect, client, db, options }) {
|
|
785
|
+
const admin = create$17({
|
|
696
786
|
client,
|
|
697
787
|
db,
|
|
698
788
|
options
|
|
@@ -742,7 +832,10 @@ function create$13({ name, collect, client, db, options }) {
|
|
|
742
832
|
};
|
|
743
833
|
});
|
|
744
834
|
if (done) iterator = null;
|
|
745
|
-
else
|
|
835
|
+
else {
|
|
836
|
+
lastBlockNumber = blockNumber;
|
|
837
|
+
yield blockNumber;
|
|
838
|
+
}
|
|
746
839
|
} catch (err) {
|
|
747
840
|
const isError = err instanceof Error;
|
|
748
841
|
logger.error({
|
|
@@ -954,8 +1047,12 @@ function decode$3(type, data) {
|
|
|
954
1047
|
}
|
|
955
1048
|
function encode$3(type, data) {
|
|
956
1049
|
switch (type) {
|
|
957
|
-
case CallbackType.BuyVaultV1Callback:
|
|
958
|
-
|
|
1050
|
+
case CallbackType.BuyVaultV1Callback:
|
|
1051
|
+
if (!("vaults" in data)) throw new Error("Invalid callback data");
|
|
1052
|
+
return encodeBuyVaultV1Callback(data);
|
|
1053
|
+
case CallbackType.SellERC20Callback:
|
|
1054
|
+
if (!("collaterals" in data)) throw new Error("Invalid callback data");
|
|
1055
|
+
return encodeSellERC20Callback(data);
|
|
959
1056
|
default: throw new Error("Invalid callback type");
|
|
960
1057
|
}
|
|
961
1058
|
}
|
|
@@ -1152,22 +1249,22 @@ const DEFAULT_BATCH_SIZE$2 = 2500;
|
|
|
1152
1249
|
const MAX_BLOCK_WINDOW = 1e4;
|
|
1153
1250
|
const DEFAULT_BLOCK_WINDOW = 8e3;
|
|
1154
1251
|
async function* streamLogs(parameters) {
|
|
1155
|
-
const { client, contractAddress, event, blockNumberGte, blockNumberLte, order
|
|
1252
|
+
const { client, contractAddress, event, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$2, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
|
|
1156
1253
|
if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
|
|
1157
1254
|
if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
|
|
1158
|
-
if (order
|
|
1255
|
+
if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
|
|
1159
1256
|
const latestBlock = (await getBlock(client, {
|
|
1160
1257
|
blockTag: "latest",
|
|
1161
1258
|
includeTransactions: false
|
|
1162
1259
|
})).number;
|
|
1163
1260
|
let toBlock = 0n;
|
|
1164
|
-
if (order
|
|
1165
|
-
if (order
|
|
1261
|
+
if (order === "asc") toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), blockNumberLte ? BigInt(blockNumberLte) : latestBlock);
|
|
1262
|
+
if (order === "desc") toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
|
|
1166
1263
|
let fromBlock = 0n;
|
|
1167
|
-
if (order
|
|
1168
|
-
if (order
|
|
1169
|
-
if (order
|
|
1170
|
-
if (order
|
|
1264
|
+
if (order === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
|
|
1265
|
+
if (order === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
|
|
1266
|
+
if (order === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
|
|
1267
|
+
if (order === "desc") fromBlock = max$1(fromBlock, toBlock - BigInt(blockWindow));
|
|
1171
1268
|
if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
|
|
1172
1269
|
let streaming = true;
|
|
1173
1270
|
while (streaming) {
|
|
@@ -1177,29 +1274,29 @@ async function* streamLogs(parameters) {
|
|
|
1177
1274
|
fromBlock,
|
|
1178
1275
|
toBlock
|
|
1179
1276
|
});
|
|
1180
|
-
streaming = order
|
|
1277
|
+
streaming = order === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
|
|
1181
1278
|
if (logs.length === 0 && !streaming) break;
|
|
1182
1279
|
if (logs.length === 0 && streaming) yield {
|
|
1183
1280
|
logs: [],
|
|
1184
|
-
blockNumber: order
|
|
1281
|
+
blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1185
1282
|
};
|
|
1186
1283
|
logs.sort((a, b) => {
|
|
1187
|
-
if (a.blockNumber !== b.blockNumber) return order
|
|
1188
|
-
if (a.transactionIndex !== b.transactionIndex) return order
|
|
1189
|
-
return order
|
|
1284
|
+
if (a.blockNumber !== b.blockNumber) return order === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
|
|
1285
|
+
if (a.transactionIndex !== b.transactionIndex) return order === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
|
|
1286
|
+
return order === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
|
|
1190
1287
|
});
|
|
1191
1288
|
for (const logBatch of batch$1(logs, maxBatchSize)) yield {
|
|
1192
1289
|
logs: logBatch,
|
|
1193
|
-
blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order
|
|
1290
|
+
blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1194
1291
|
};
|
|
1195
|
-
if (order
|
|
1292
|
+
if (order === "asc") {
|
|
1196
1293
|
const upperBound = BigInt(blockNumberLte || latestBlock);
|
|
1197
1294
|
const nextFromBlock = min(BigInt(toBlock) + 1n, upperBound);
|
|
1198
1295
|
const nextToBlock = min(toBlock + BigInt(blockWindow) + 1n, upperBound);
|
|
1199
1296
|
fromBlock = nextFromBlock;
|
|
1200
1297
|
toBlock = nextToBlock;
|
|
1201
1298
|
}
|
|
1202
|
-
if (order
|
|
1299
|
+
if (order === "desc") {
|
|
1203
1300
|
const lowerBound = BigInt(blockNumberGte || 0);
|
|
1204
1301
|
const nextToBlock = max$1(fromBlock - 1n, lowerBound);
|
|
1205
1302
|
const nextFromBlock = max$1(fromBlock - BigInt(blockWindow) - 1n, lowerBound);
|
|
@@ -1209,7 +1306,7 @@ async function* streamLogs(parameters) {
|
|
|
1209
1306
|
}
|
|
1210
1307
|
yield {
|
|
1211
1308
|
logs: [],
|
|
1212
|
-
blockNumber: order
|
|
1309
|
+
blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1213
1310
|
};
|
|
1214
1311
|
}
|
|
1215
1312
|
var InvalidBlockRangeError = class extends BaseError {
|
|
@@ -1358,8 +1455,8 @@ const from$15 = (parameters) => {
|
|
|
1358
1455
|
*/
|
|
1359
1456
|
function random$3() {
|
|
1360
1457
|
return from$15({
|
|
1361
|
-
asset:
|
|
1362
|
-
oracle:
|
|
1458
|
+
asset: address(),
|
|
1459
|
+
oracle: address(),
|
|
1363
1460
|
lltv: .965
|
|
1364
1461
|
});
|
|
1365
1462
|
}
|
|
@@ -1774,12 +1871,8 @@ function id(obligation) {
|
|
|
1774
1871
|
function random$2() {
|
|
1775
1872
|
return from$13({
|
|
1776
1873
|
chainId: 1,
|
|
1777
|
-
loanToken:
|
|
1778
|
-
collaterals: [
|
|
1779
|
-
asset: privateKeyToAccount(generatePrivateKey()).address,
|
|
1780
|
-
oracle: privateKeyToAccount(generatePrivateKey()).address,
|
|
1781
|
-
lltv: .965
|
|
1782
|
-
})],
|
|
1874
|
+
loanToken: address(),
|
|
1875
|
+
collaterals: [random$3()],
|
|
1783
1876
|
maturity: from$14("end_of_next_quarter")
|
|
1784
1877
|
});
|
|
1785
1878
|
}
|
|
@@ -1799,101 +1892,249 @@ var CollateralsAreNotSortedError = class extends BaseError {
|
|
|
1799
1892
|
//#endregion
|
|
1800
1893
|
//#region src/core/Tree.ts
|
|
1801
1894
|
var Tree_exports = /* @__PURE__ */ __export({
|
|
1895
|
+
DecodeError: () => DecodeError,
|
|
1896
|
+
EncodeError: () => EncodeError,
|
|
1897
|
+
TreeError: () => TreeError,
|
|
1802
1898
|
VERSION: () => VERSION$1,
|
|
1803
1899
|
decode: () => decode$2,
|
|
1804
1900
|
encode: () => encode$2,
|
|
1805
|
-
|
|
1901
|
+
encodeUnsigned: () => encodeUnsigned,
|
|
1902
|
+
from: () => from$12,
|
|
1903
|
+
proofs: () => proofs
|
|
1806
1904
|
});
|
|
1807
1905
|
const VERSION$1 = 1;
|
|
1906
|
+
const normalizeHash = (hash$1) => hash$1.toLowerCase();
|
|
1808
1907
|
/**
|
|
1809
1908
|
* Builds a Merkle tree from a list of offers.
|
|
1810
1909
|
*
|
|
1811
1910
|
* Leaves are the offer `hash` values as `bytes32` and are deterministically
|
|
1812
|
-
* ordered
|
|
1813
|
-
* regardless of the input order.
|
|
1911
|
+
* ordered following the StandardMerkleTree leaf ordering so that the resulting
|
|
1912
|
+
* root is stable regardless of the input order.
|
|
1814
1913
|
*
|
|
1815
1914
|
* @param offers - Offers to include in the tree.
|
|
1816
1915
|
* @returns A `StandardMerkleTree` of `bytes32` leaves representing the offers.
|
|
1916
|
+
* @throws {TreeError} If tree building fails due to offer inconsistencies.
|
|
1817
1917
|
*/
|
|
1818
1918
|
const from$12 = (offers$1) => {
|
|
1819
|
-
const leaves =
|
|
1820
|
-
return [offer.hash];
|
|
1821
|
-
});
|
|
1919
|
+
const leaves = offers$1.map((offer) => [offer.hash]);
|
|
1822
1920
|
const tree = StandardMerkleTree.of(leaves, ["bytes32"]);
|
|
1823
|
-
|
|
1921
|
+
const orderedOffers = orderOffers(tree, offers$1);
|
|
1922
|
+
return Object.assign(tree, { offers: orderedOffers });
|
|
1824
1923
|
};
|
|
1825
|
-
const
|
|
1826
|
-
const
|
|
1827
|
-
|
|
1924
|
+
const orderOffers = (tree, offers$1) => {
|
|
1925
|
+
const offerByHash = /* @__PURE__ */ new Map();
|
|
1926
|
+
for (const offer of offers$1) offerByHash.set(normalizeHash(offer.hash), offer);
|
|
1927
|
+
const entries = tree.dump().values.map((value) => {
|
|
1928
|
+
const hash$1 = normalizeHash(value.value[0]);
|
|
1929
|
+
const offer = offerByHash.get(hash$1);
|
|
1930
|
+
if (!offer) throw new TreeError(`missing offer for leaf ${hash$1}`);
|
|
1931
|
+
return {
|
|
1932
|
+
offer,
|
|
1933
|
+
treeIndex: value.treeIndex
|
|
1934
|
+
};
|
|
1935
|
+
});
|
|
1936
|
+
entries.sort((a, b) => b.treeIndex - a.treeIndex);
|
|
1937
|
+
return entries.map((item) => item.offer);
|
|
1828
1938
|
};
|
|
1829
1939
|
/**
|
|
1830
|
-
*
|
|
1940
|
+
* Generates merkle proofs for all offers in a tree.
|
|
1831
1941
|
*
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1942
|
+
* Each proof allows independent verification that an offer is included in the tree
|
|
1943
|
+
* without requiring the full tree. Proofs are ordered by StandardMerkleTree leaf ordering.
|
|
1834
1944
|
*
|
|
1835
|
-
* @param tree - The
|
|
1836
|
-
* @returns
|
|
1837
|
-
* @throws Error if the given `root` does not match the offers.
|
|
1945
|
+
* @param tree - The {@link Tree} to generate proofs for.
|
|
1946
|
+
* @returns Array of proofs - {@link Proof}
|
|
1838
1947
|
*/
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1948
|
+
const proofs = (tree) => {
|
|
1949
|
+
return tree.offers.map((offer) => {
|
|
1950
|
+
return {
|
|
1951
|
+
offer,
|
|
1952
|
+
path: tree.getProof([offer.hash])
|
|
1953
|
+
};
|
|
1954
|
+
});
|
|
1955
|
+
};
|
|
1956
|
+
const assertHex = (value, expectedBytes, name) => {
|
|
1957
|
+
if (typeof value !== "string" || !isHex(value)) throw new DecodeError(`${name} is not a valid hex string`);
|
|
1958
|
+
if (hexToBytes(value).length !== expectedBytes) throw new DecodeError(`${name}: expected ${expectedBytes} bytes`);
|
|
1959
|
+
};
|
|
1960
|
+
const verifySignatureAndRecoverAddress = async (params) => {
|
|
1961
|
+
const { root, signature } = params;
|
|
1962
|
+
assertHex(signature, 65, "signature");
|
|
1963
|
+
const hash$1 = hashMessage({ raw: root });
|
|
1964
|
+
try {
|
|
1965
|
+
return await recoverAddress({
|
|
1966
|
+
hash: hash$1,
|
|
1967
|
+
signature
|
|
1968
|
+
});
|
|
1969
|
+
} catch {
|
|
1970
|
+
throw new DecodeError("signature recovery failed");
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
/**
|
|
1974
|
+
* Encodes a merkle tree with signature into hex calldata for onchain broadcast.
|
|
1975
|
+
*
|
|
1976
|
+
* Layout: `0x{vv}{gzip([...offers])}{root}{signature}` where:
|
|
1977
|
+
* - `{vv}`: 1-byte version (currently 0x01)
|
|
1978
|
+
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
1979
|
+
* - `{root}`: 32-byte merkle root
|
|
1980
|
+
* - `{signature}`: 65-byte EIP-191 signature over raw root bytes
|
|
1981
|
+
*
|
|
1982
|
+
* Validates signature authenticity and root integrity before encoding.
|
|
1983
|
+
*
|
|
1984
|
+
* @example
|
|
1985
|
+
* ```typescript
|
|
1986
|
+
* const tree = Tree.from(offers);
|
|
1987
|
+
* const signature = await wallet.signMessage({ message: { raw: tree.root } });
|
|
1988
|
+
* const calldata = await Tree.encode(tree, signature);
|
|
1989
|
+
* await broadcast(calldata);
|
|
1990
|
+
* ```
|
|
1991
|
+
*
|
|
1992
|
+
* @example
|
|
1993
|
+
* Manual construction (for advanced users):
|
|
1994
|
+
* ```typescript
|
|
1995
|
+
* const tree = Tree.from(offers);
|
|
1996
|
+
* const compressed = gzip(JSON.stringify(tree.offers.map(Offer.serialize)));
|
|
1997
|
+
* const partial = `0x01${bytesToHex(compressed)}${tree.root.slice(2)}`;
|
|
1998
|
+
* const signature = await wallet.signMessage({ message: { raw: tree.root } });
|
|
1999
|
+
* const calldata = `${partial}${signature.slice(2)}`;
|
|
2000
|
+
* ```
|
|
2001
|
+
*
|
|
2002
|
+
* @param tree - Merkle tree of offers
|
|
2003
|
+
* @param signature - EIP-191 signature over raw root bytes
|
|
2004
|
+
* @returns Hex-encoded calldata ready for onchain broadcast
|
|
2005
|
+
* @throws {EncodeError} If signature verification fails or root mismatch
|
|
2006
|
+
*/
|
|
2007
|
+
const encode$2 = async (tree, signature) => {
|
|
2008
|
+
validateTreeForEncoding(tree);
|
|
2009
|
+
await verifySignatureAndRecoverAddress({
|
|
2010
|
+
root: tree.root,
|
|
2011
|
+
signature
|
|
2012
|
+
});
|
|
2013
|
+
const unsigned = encodeUnsignedBytes(tree);
|
|
2014
|
+
const sigBytes = hexToBytes(signature);
|
|
2015
|
+
const encoded = new Uint8Array(unsigned.length + sigBytes.length);
|
|
2016
|
+
encoded.set(unsigned, 0);
|
|
2017
|
+
encoded.set(sigBytes, unsigned.length);
|
|
1870
2018
|
return bytesToHex(encoded);
|
|
1871
2019
|
};
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2020
|
+
/**
|
|
2021
|
+
* Encodes a merkle tree without a signature into hex payload for client-side signing.
|
|
2022
|
+
*
|
|
2023
|
+
* Layout: `0x{vv}{gzip([...offers])}{root}` where:
|
|
2024
|
+
* - `{vv}`: 1-byte version (currently 0x01)
|
|
2025
|
+
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
2026
|
+
* - `{root}`: 32-byte merkle root
|
|
2027
|
+
*
|
|
2028
|
+
* Validates root integrity before encoding.
|
|
2029
|
+
*
|
|
2030
|
+
* @param tree - Merkle tree of offers
|
|
2031
|
+
* @returns Hex-encoded unsigned payload
|
|
2032
|
+
* @throws {EncodeError} If root mismatch
|
|
2033
|
+
*/
|
|
2034
|
+
const encodeUnsigned = (tree) => {
|
|
2035
|
+
validateTreeForEncoding(tree);
|
|
2036
|
+
return bytesToHex(encodeUnsignedBytes(tree));
|
|
2037
|
+
};
|
|
2038
|
+
const validateTreeForEncoding = (tree) => {
|
|
2039
|
+
if (VERSION$1 > 255) throw new EncodeError(`version overflow: ${VERSION$1} exceeds 255`);
|
|
2040
|
+
const computed = from$12(tree.offers);
|
|
2041
|
+
if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
|
|
2042
|
+
};
|
|
2043
|
+
const encodeUnsignedBytes = (tree) => {
|
|
2044
|
+
const offersPayload = tree.offers.map(serialize);
|
|
2045
|
+
const compressed = gzip(JSON.stringify(offersPayload));
|
|
2046
|
+
const rootBytes = hexToBytes(tree.root);
|
|
2047
|
+
const encoded = new Uint8Array(1 + compressed.length + 32);
|
|
2048
|
+
encoded[0] = VERSION$1;
|
|
2049
|
+
encoded.set(compressed, 1);
|
|
2050
|
+
encoded.set(rootBytes, 1 + compressed.length);
|
|
2051
|
+
return encoded;
|
|
1875
2052
|
};
|
|
1876
2053
|
/**
|
|
1877
|
-
* Decodes
|
|
2054
|
+
* Decodes hex calldata into a validated merkle tree.
|
|
1878
2055
|
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
2056
|
+
* Validates signature before decompression for fail-fast rejection of invalid payloads.
|
|
2057
|
+
* Returns the tree with separately validated signature and recovered signer address.
|
|
1881
2058
|
*
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
2059
|
+
* Validation order:
|
|
2060
|
+
* 1. Version check
|
|
2061
|
+
* 2. Signature verification (fail-fast, before decompression)
|
|
2062
|
+
* 3. Decompression (only if signature valid)
|
|
2063
|
+
* 4. Root verification (computed from offers vs embedded root)
|
|
2064
|
+
*
|
|
2065
|
+
* @example
|
|
2066
|
+
* ```typescript
|
|
2067
|
+
* const { tree, signature, signer } = await Tree.decode(calldata);
|
|
2068
|
+
* console.log(`Tree signed by ${signer} with ${tree.offers.length} offers`);
|
|
2069
|
+
* ```
|
|
2070
|
+
*
|
|
2071
|
+
* @param encoded - Hex calldata in format `0x{vv}{gzip}{root}{signature}`
|
|
2072
|
+
* @returns Validated tree, signature, and recovered signer address
|
|
2073
|
+
* @throws {DecodeError} If version invalid, signature invalid, or root mismatch
|
|
2074
|
+
*/
|
|
2075
|
+
const decode$2 = async (encoded) => {
|
|
2076
|
+
const bytes$1 = hexToBytes(encoded);
|
|
2077
|
+
if (bytes$1.length < 98) throw new DecodeError("payload too short");
|
|
2078
|
+
const version = bytes$1[0];
|
|
2079
|
+
if (version !== (VERSION$1 & 255)) throw new DecodeError(`invalid version: expected ${VERSION$1}, got ${version ?? 0}`);
|
|
2080
|
+
const signature = bytesToHex(bytes$1.slice(-65));
|
|
2081
|
+
const root = bytesToHex(bytes$1.slice(-97, -65));
|
|
2082
|
+
assertHex(root, 32, "root");
|
|
2083
|
+
assertHex(signature, 65, "signature");
|
|
2084
|
+
const signer = await verifySignatureAndRecoverAddress({
|
|
2085
|
+
root,
|
|
2086
|
+
signature
|
|
2087
|
+
});
|
|
2088
|
+
const compressed = bytes$1.slice(1, -97);
|
|
2089
|
+
let decoded;
|
|
2090
|
+
try {
|
|
2091
|
+
decoded = ungzip(compressed, { to: "string" });
|
|
2092
|
+
} catch {
|
|
2093
|
+
throw new DecodeError("decompression failed");
|
|
2094
|
+
}
|
|
2095
|
+
let rawOffers;
|
|
2096
|
+
try {
|
|
2097
|
+
rawOffers = JSON.parse(decoded);
|
|
2098
|
+
} catch {
|
|
2099
|
+
throw new DecodeError("JSON parse failed");
|
|
2100
|
+
}
|
|
2101
|
+
const tree = from$12(rawOffers.map((o) => OfferSchema().parse(o)));
|
|
2102
|
+
if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
|
|
2103
|
+
return {
|
|
2104
|
+
tree,
|
|
2105
|
+
signature,
|
|
2106
|
+
signer
|
|
2107
|
+
};
|
|
2108
|
+
};
|
|
2109
|
+
/**
|
|
2110
|
+
* Error thrown during tree building operations.
|
|
2111
|
+
* Indicates structural issues with the tree (missing offers, inconsistent state).
|
|
2112
|
+
*/
|
|
2113
|
+
var TreeError = class extends BaseError {
|
|
2114
|
+
name = "Tree.TreeError";
|
|
2115
|
+
constructor(reason) {
|
|
2116
|
+
super(`Tree error: ${reason}`);
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2119
|
+
/**
|
|
2120
|
+
* Error thrown during tree encoding.
|
|
2121
|
+
* Indicates validation failures (signature, root mismatch, mixed makers).
|
|
2122
|
+
*/
|
|
2123
|
+
var EncodeError = class extends BaseError {
|
|
2124
|
+
name = "Tree.EncodeError";
|
|
2125
|
+
constructor(reason) {
|
|
2126
|
+
super(`Failed to encode tree: ${reason}`);
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2129
|
+
/**
|
|
2130
|
+
* Error thrown during tree decoding.
|
|
2131
|
+
* Indicates payload corruption, version mismatch, or validation failures.
|
|
1885
2132
|
*/
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
const decoded = ungzip(bytes.slice(1), { to: "string" });
|
|
1892
|
-
const data = JSON.parse(decoded);
|
|
1893
|
-
const root = data[0];
|
|
1894
|
-
const tree = from$12(data.slice(1).map((o) => OfferSchema().parse(o)));
|
|
1895
|
-
if (root !== tree.root) throw new Error(`Invalid root: expected ${tree.root}, got ${root}`);
|
|
1896
|
-
return tree;
|
|
2133
|
+
var DecodeError = class extends BaseError {
|
|
2134
|
+
name = "Tree.DecodeError";
|
|
2135
|
+
constructor(reason) {
|
|
2136
|
+
super(`Failed to decode tree: ${reason}`);
|
|
2137
|
+
}
|
|
1897
2138
|
};
|
|
1898
2139
|
|
|
1899
2140
|
//#endregion
|
|
@@ -1913,6 +2154,7 @@ var Offer_exports = /* @__PURE__ */ __export({
|
|
|
1913
2154
|
hash: () => hash,
|
|
1914
2155
|
obligationId: () => obligationId,
|
|
1915
2156
|
random: () => random$1,
|
|
2157
|
+
serialize: () => serialize,
|
|
1916
2158
|
sign: () => sign,
|
|
1917
2159
|
signatureMsg: () => signatureMsg,
|
|
1918
2160
|
toSnakeCase: () => toSnakeCase,
|
|
@@ -1993,16 +2235,47 @@ function toSnakeCase(offer) {
|
|
|
1993
2235
|
return toSnakeCase$1(offer);
|
|
1994
2236
|
}
|
|
1995
2237
|
/**
|
|
2238
|
+
* Serializes an offer for merkle tree encoding.
|
|
2239
|
+
* Converts BigInt fields to strings for JSON compatibility.
|
|
2240
|
+
*
|
|
2241
|
+
* @param offer - Offer to serialize
|
|
2242
|
+
* @returns JSON-serializable offer object
|
|
2243
|
+
*/
|
|
2244
|
+
const serialize = (offer) => ({
|
|
2245
|
+
offering: offer.offering,
|
|
2246
|
+
assets: offer.assets.toString(),
|
|
2247
|
+
rate: offer.rate.toString(),
|
|
2248
|
+
maturity: Number(offer.maturity),
|
|
2249
|
+
expiry: Number(offer.expiry),
|
|
2250
|
+
start: Number(offer.start),
|
|
2251
|
+
nonce: offer.nonce.toString(),
|
|
2252
|
+
buy: offer.buy,
|
|
2253
|
+
chainId: offer.chainId,
|
|
2254
|
+
loanToken: offer.loanToken,
|
|
2255
|
+
collaterals: offer.collaterals.map((c) => ({
|
|
2256
|
+
asset: c.asset,
|
|
2257
|
+
oracle: c.oracle,
|
|
2258
|
+
lltv: c.lltv.toString()
|
|
2259
|
+
})),
|
|
2260
|
+
callback: {
|
|
2261
|
+
address: offer.callback.address,
|
|
2262
|
+
data: offer.callback.data,
|
|
2263
|
+
gasLimit: offer.callback.gasLimit.toString()
|
|
2264
|
+
},
|
|
2265
|
+
signature: offer.signature,
|
|
2266
|
+
hash: offer.hash
|
|
2267
|
+
});
|
|
2268
|
+
/**
|
|
1996
2269
|
* Generates a random Offer.
|
|
1997
2270
|
* The returned Offer contains randomly generated values.
|
|
1998
2271
|
* @warning The generated Offer should not be used for production usage.
|
|
1999
2272
|
* @returns {Offer} A randomly generated Offer object.
|
|
2000
2273
|
*/
|
|
2001
2274
|
function random$1(config) {
|
|
2002
|
-
const chain = config?.chains ? config.chains[
|
|
2003
|
-
const loanToken = config?.loanTokens ? config.loanTokens[
|
|
2004
|
-
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [
|
|
2005
|
-
const collateralAsset = collateralCandidates[
|
|
2275
|
+
const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
|
|
2276
|
+
const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
|
|
2277
|
+
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
|
|
2278
|
+
const collateralAsset = collateralCandidates[int(collateralCandidates.length)];
|
|
2006
2279
|
const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
|
|
2007
2280
|
const maturity$1 = config?.maturity ?? from$14(maturityOption);
|
|
2008
2281
|
const lltv = from$16(weightedChoice([
|
|
@@ -2016,7 +2289,7 @@ function random$1(config) {
|
|
|
2016
2289
|
[.965, 4],
|
|
2017
2290
|
[.98, 2]
|
|
2018
2291
|
]));
|
|
2019
|
-
const buy = config?.buy !== void 0 ? config.buy :
|
|
2292
|
+
const buy = config?.buy !== void 0 ? config.buy : bool();
|
|
2020
2293
|
const ONE = 1000000000000000000n;
|
|
2021
2294
|
const qMin = buy ? 16 : 4;
|
|
2022
2295
|
const len = (buy ? 32 : 16) - qMin + 1;
|
|
@@ -2027,9 +2300,9 @@ function random$1(config) {
|
|
|
2027
2300
|
const rate = config?.rate ?? weightedChoice(ratePairs);
|
|
2028
2301
|
const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
|
|
2029
2302
|
const unit = BigInt(10) ** BigInt(loanTokenDecimals);
|
|
2030
|
-
const amountBase = BigInt(100 +
|
|
2303
|
+
const amountBase = BigInt(100 + int(999901));
|
|
2031
2304
|
const assetsScaled = config?.assets ?? amountBase * unit;
|
|
2032
|
-
const consumed = config?.consumed !== void 0 ? config.consumed :
|
|
2305
|
+
const consumed = config?.consumed !== void 0 ? config.consumed : float() < .8 ? 0n : assetsScaled * BigInt(1 + int(900)) / 1000n;
|
|
2033
2306
|
const callbackBySide = (() => {
|
|
2034
2307
|
if (buy) return {
|
|
2035
2308
|
address: zeroAddress,
|
|
@@ -2048,29 +2321,29 @@ function random$1(config) {
|
|
|
2048
2321
|
};
|
|
2049
2322
|
})();
|
|
2050
2323
|
return from$11({
|
|
2051
|
-
offering: config?.offering ??
|
|
2324
|
+
offering: config?.offering ?? address(),
|
|
2052
2325
|
assets: assetsScaled,
|
|
2053
2326
|
rate,
|
|
2054
2327
|
maturity: maturity$1,
|
|
2055
2328
|
expiry: config?.expiry ?? maturity$1 - 1,
|
|
2056
2329
|
start: config?.start ?? maturity$1 - 10,
|
|
2057
|
-
nonce: BigInt(
|
|
2330
|
+
nonce: BigInt(int(1e6)),
|
|
2058
2331
|
buy,
|
|
2059
2332
|
chainId: chain.id,
|
|
2060
2333
|
loanToken,
|
|
2061
|
-
collaterals: config?.collaterals ?? Array.from({ length:
|
|
2334
|
+
collaterals: config?.collaterals ?? Array.from({ length: int(3) + 1 }, () => ({
|
|
2062
2335
|
...random$3(),
|
|
2063
2336
|
lltv
|
|
2064
2337
|
})).sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
2065
2338
|
callback: config?.callback ?? callbackBySide,
|
|
2066
2339
|
consumed,
|
|
2067
2340
|
takeable: config?.takeable ?? assetsScaled - consumed,
|
|
2068
|
-
blockNumber: config?.blockNumber ??
|
|
2341
|
+
blockNumber: config?.blockNumber ?? int(Number.MAX_SAFE_INTEGER)
|
|
2069
2342
|
});
|
|
2070
2343
|
}
|
|
2071
2344
|
const weightedChoice = (pairs) => {
|
|
2072
2345
|
const total = pairs.reduce((sum, [, weight]) => sum + weight, 0);
|
|
2073
|
-
let roll =
|
|
2346
|
+
let roll = float() * total;
|
|
2074
2347
|
for (const [value, weight] of pairs) {
|
|
2075
2348
|
roll -= weight;
|
|
2076
2349
|
if (roll < 0) return value;
|
|
@@ -2538,8 +2811,8 @@ function fromSnakeCase(snake) {
|
|
|
2538
2811
|
function random() {
|
|
2539
2812
|
return from$8({
|
|
2540
2813
|
obligationId: id(random$2()),
|
|
2541
|
-
ask: { rate: BigInt(
|
|
2542
|
-
bid: { rate: BigInt(
|
|
2814
|
+
ask: { rate: BigInt(int(1e6)) },
|
|
2815
|
+
bid: { rate: BigInt(int(1e6)) }
|
|
2543
2816
|
});
|
|
2544
2817
|
}
|
|
2545
2818
|
var InvalidQuoteError = class extends BaseError {
|
|
@@ -2723,30 +2996,79 @@ async function* collectOffersV2(parameters) {
|
|
|
2723
2996
|
});
|
|
2724
2997
|
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
2725
2998
|
blockNumber = lastStreamBlockNumber;
|
|
2726
|
-
const
|
|
2999
|
+
const decodedTrees = [];
|
|
2727
3000
|
for (const log of logs) {
|
|
2728
3001
|
if (!log) continue;
|
|
2729
3002
|
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
2730
3003
|
try {
|
|
2731
|
-
const tree = decode$2(payload);
|
|
2732
|
-
|
|
3004
|
+
const { tree, signature, signer } = await decode$2(payload);
|
|
3005
|
+
const signerMismatch = tree.offers.find((offer) => offer.offering.toLowerCase() !== signer.toLowerCase());
|
|
3006
|
+
if (signerMismatch) {
|
|
3007
|
+
logger.debug({
|
|
3008
|
+
msg: "Tree rejected: signer mismatch",
|
|
3009
|
+
reason: "signer_mismatch",
|
|
3010
|
+
signer,
|
|
3011
|
+
offering: signerMismatch.offering,
|
|
3012
|
+
chain_id: client.chain.id
|
|
3013
|
+
});
|
|
3014
|
+
continue;
|
|
3015
|
+
}
|
|
3016
|
+
const offersWithBlock = tree.offers.map((offer) => ({
|
|
2733
3017
|
...offer,
|
|
2734
3018
|
blockNumber: Number(log.blockNumber)
|
|
3019
|
+
}));
|
|
3020
|
+
const treeWithBlock = Object.assign(Object.create(Object.getPrototypeOf(tree)), tree, { offers: offersWithBlock });
|
|
3021
|
+
decodedTrees.push({
|
|
3022
|
+
tree: treeWithBlock,
|
|
3023
|
+
signature,
|
|
3024
|
+
signer,
|
|
3025
|
+
blockNumber: Number(log.blockNumber)
|
|
2735
3026
|
});
|
|
2736
|
-
} catch (
|
|
3027
|
+
} catch (err) {
|
|
3028
|
+
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
3029
|
+
logger.debug({
|
|
3030
|
+
msg: "Tree decode failed",
|
|
3031
|
+
reason,
|
|
3032
|
+
chain_id: client.chain.id,
|
|
3033
|
+
err: err instanceof Error ? err.message : String(err)
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
2737
3036
|
}
|
|
2738
3037
|
await db.transaction(async (dbTx) => {
|
|
2739
3038
|
const { epoch, blockNumber: latestBlockNumber } = await dbTx.chains.getBlockNumber(client.chain.id);
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
3039
|
+
const treesToInsert = [];
|
|
3040
|
+
let totalValidOffers = 0;
|
|
3041
|
+
for (const { tree, signature } of decodedTrees) try {
|
|
3042
|
+
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
3043
|
+
const hasBlockWindowViolation = tree.offers.some((offer) => offer.blockNumber > latestBlockNumber);
|
|
3044
|
+
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
3045
|
+
if (allowedResults.issues.length > 0) {
|
|
3046
|
+
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
3047
|
+
logger.debug({
|
|
3048
|
+
msg: "Tree offers rejected by gatekeeper",
|
|
3049
|
+
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
3050
|
+
chain_id: client.chain.id,
|
|
3051
|
+
issues_count: allowedResults.issues.length
|
|
3052
|
+
});
|
|
3053
|
+
} else if (hasBlockWindowViolation) logger.debug({
|
|
3054
|
+
msg: "Tree rejected: offers outside block window",
|
|
3055
|
+
reason: "block_window",
|
|
3056
|
+
chain_id: client.chain.id
|
|
3057
|
+
});
|
|
3058
|
+
continue;
|
|
3059
|
+
}
|
|
3060
|
+
treesToInsert.push({
|
|
3061
|
+
tree,
|
|
3062
|
+
signature
|
|
3063
|
+
});
|
|
3064
|
+
totalValidOffers += tree.offers.length;
|
|
2743
3065
|
} catch (err) {
|
|
2744
3066
|
logger.error({
|
|
2745
3067
|
err,
|
|
2746
|
-
msg: "Failed to validate offers"
|
|
3068
|
+
msg: "Failed to validate offers for tree"
|
|
2747
3069
|
});
|
|
2748
3070
|
}
|
|
2749
|
-
await dbTx.
|
|
3071
|
+
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
2750
3072
|
try {
|
|
2751
3073
|
await dbTx.collectors.saveBlockNumber({
|
|
2752
3074
|
collectorName: collector,
|
|
@@ -2754,10 +3076,11 @@ async function* collectOffersV2(parameters) {
|
|
|
2754
3076
|
blockNumber,
|
|
2755
3077
|
epoch
|
|
2756
3078
|
});
|
|
2757
|
-
if (
|
|
3079
|
+
if (totalValidOffers > 0) logger.info({
|
|
2758
3080
|
msg: `New offers`,
|
|
2759
3081
|
collector,
|
|
2760
|
-
count:
|
|
3082
|
+
count: totalValidOffers,
|
|
3083
|
+
trees_count: treesToInsert.length,
|
|
2761
3084
|
chain_id: client.chain.id,
|
|
2762
3085
|
block_range: [startBlock, lastStreamBlockNumber]
|
|
2763
3086
|
});
|
|
@@ -3400,7 +3723,7 @@ async function* collectPrices(parameters) {
|
|
|
3400
3723
|
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
3401
3724
|
function createBuilder(parameters) {
|
|
3402
3725
|
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow } = {} } = parameters;
|
|
3403
|
-
const createCollector = (name, collect) => create$
|
|
3726
|
+
const createCollector = (name, collect) => create$16({
|
|
3404
3727
|
name,
|
|
3405
3728
|
collect,
|
|
3406
3729
|
client,
|
|
@@ -3488,7 +3811,7 @@ const from$6 = (parameters) => {
|
|
|
3488
3811
|
//#endregion
|
|
3489
3812
|
//#region src/indexer/Indexer.ts
|
|
3490
3813
|
var Indexer_exports = /* @__PURE__ */ __export({
|
|
3491
|
-
create: () => create$
|
|
3814
|
+
create: () => create$15,
|
|
3492
3815
|
from: () => from$5
|
|
3493
3816
|
});
|
|
3494
3817
|
function from$5(config) {
|
|
@@ -3503,7 +3826,7 @@ function from$5(config) {
|
|
|
3503
3826
|
retryAttempts,
|
|
3504
3827
|
retryDelayMs
|
|
3505
3828
|
});
|
|
3506
|
-
return create$
|
|
3829
|
+
return create$15({
|
|
3507
3830
|
db,
|
|
3508
3831
|
client,
|
|
3509
3832
|
collectors: [
|
|
@@ -3515,7 +3838,7 @@ function from$5(config) {
|
|
|
3515
3838
|
interval
|
|
3516
3839
|
});
|
|
3517
3840
|
}
|
|
3518
|
-
function create$
|
|
3841
|
+
function create$15(params) {
|
|
3519
3842
|
const { db, collectors: collectors$1, interval, client } = params;
|
|
3520
3843
|
const logger = getLogger();
|
|
3521
3844
|
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
@@ -3572,12 +3895,12 @@ function create$12(params) {
|
|
|
3572
3895
|
|
|
3573
3896
|
//#endregion
|
|
3574
3897
|
//#region src/api/Health.ts
|
|
3575
|
-
var Health_exports = /* @__PURE__ */ __export({ create: () => create$
|
|
3898
|
+
var Health_exports = /* @__PURE__ */ __export({ create: () => create$14 });
|
|
3576
3899
|
const DEFAULT_MAX_ALLOWED_LAG = 5;
|
|
3577
3900
|
/**
|
|
3578
3901
|
* Create a health service that exposes collector and chain block numbers.
|
|
3579
3902
|
*/
|
|
3580
|
-
function create$
|
|
3903
|
+
function create$14(parameters) {
|
|
3581
3904
|
const { db, maxAllowedLag = DEFAULT_MAX_ALLOWED_LAG, healthClients } = parameters;
|
|
3582
3905
|
const loadSnapshot = async () => {
|
|
3583
3906
|
const [collectorRows, chainRows, remoteBlockByChainId] = await Promise.all([
|
|
@@ -3724,13 +4047,16 @@ var OfferResponse_exports = /* @__PURE__ */ __export({ from: () => from$2 });
|
|
|
3724
4047
|
* Creates an `OfferResponse` from an `Offer`.
|
|
3725
4048
|
* @constructor
|
|
3726
4049
|
* @param offer - {@link Offer}
|
|
4050
|
+
* @param attestation - {@link Attestation}
|
|
3727
4051
|
* @returns The created `OfferResponse`. {@link OfferResponse}
|
|
3728
4052
|
*/
|
|
3729
|
-
function from$2(offer) {
|
|
3730
|
-
const
|
|
4053
|
+
function from$2(offer, attestation) {
|
|
4054
|
+
const { signature: _, ...rest } = toSnakeCase$1(offer);
|
|
3731
4055
|
return {
|
|
3732
|
-
...
|
|
3733
|
-
|
|
4056
|
+
...rest,
|
|
4057
|
+
root: attestation?.root.toLowerCase() ?? null,
|
|
4058
|
+
proof: attestation?.proof.map((p) => p.toLowerCase()) ?? null,
|
|
4059
|
+
signature: attestation?.signature.toLowerCase() ?? null
|
|
3734
4060
|
};
|
|
3735
4061
|
}
|
|
3736
4062
|
|
|
@@ -3868,10 +4194,12 @@ const offerExample = {
|
|
|
3868
4194
|
data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000",
|
|
3869
4195
|
gas_limit: "500000"
|
|
3870
4196
|
},
|
|
3871
|
-
signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400",
|
|
3872
4197
|
consumed: "0",
|
|
3873
4198
|
takeable: "369216000000000000000000",
|
|
3874
|
-
block_number: 0xa7495128adfb1
|
|
4199
|
+
block_number: 0xa7495128adfb1,
|
|
4200
|
+
root: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
|
4201
|
+
proof: ["0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"],
|
|
4202
|
+
signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400"
|
|
3875
4203
|
};
|
|
3876
4204
|
const collectorsHealthExample = {
|
|
3877
4205
|
name: "offers",
|
|
@@ -4016,6 +4344,16 @@ __decorate([ApiProperty({
|
|
|
4016
4344
|
type: "number",
|
|
4017
4345
|
example: offerExample.block_number
|
|
4018
4346
|
})], OfferListItemResponse.prototype, "block_number", void 0);
|
|
4347
|
+
__decorate([ApiProperty({
|
|
4348
|
+
type: "string",
|
|
4349
|
+
nullable: true,
|
|
4350
|
+
example: offerExample.root
|
|
4351
|
+
})], OfferListItemResponse.prototype, "root", void 0);
|
|
4352
|
+
__decorate([ApiProperty({
|
|
4353
|
+
type: [String],
|
|
4354
|
+
nullable: true,
|
|
4355
|
+
example: offerExample.proof
|
|
4356
|
+
})], OfferListItemResponse.prototype, "proof", void 0);
|
|
4019
4357
|
__decorate([ApiProperty({
|
|
4020
4358
|
type: "string",
|
|
4021
4359
|
nullable: true,
|
|
@@ -4199,44 +4537,61 @@ __decorate([ApiProperty({
|
|
|
4199
4537
|
var ValidateOffersRequest = class {};
|
|
4200
4538
|
__decorate([ApiProperty({
|
|
4201
4539
|
type: () => [ValidateOfferRequest],
|
|
4202
|
-
description: "Array of offers in snake_case format.
|
|
4203
|
-
required:
|
|
4540
|
+
description: "Array of offers in snake_case format. Required, non-empty.",
|
|
4541
|
+
required: true
|
|
4204
4542
|
})], ValidateOffersRequest.prototype, "offers", void 0);
|
|
4543
|
+
var ValidationSuccessDataResponse = class {};
|
|
4205
4544
|
__decorate([ApiProperty({
|
|
4206
4545
|
type: "string",
|
|
4207
|
-
description: "
|
|
4208
|
-
example: "
|
|
4209
|
-
|
|
4210
|
-
})], ValidateOffersRequest.prototype, "calldata", void 0);
|
|
4211
|
-
var ValidateOfferResultResponse = class {};
|
|
4546
|
+
description: "Unsigned payload: version (1B) + gzip(offers) + root (32B).",
|
|
4547
|
+
example: "0x01789c..."
|
|
4548
|
+
})], ValidationSuccessDataResponse.prototype, "payload", void 0);
|
|
4212
4549
|
__decorate([ApiProperty({
|
|
4213
4550
|
type: "string",
|
|
4214
|
-
|
|
4215
|
-
|
|
4551
|
+
description: "Merkle tree root to sign with EIP-191.",
|
|
4552
|
+
example: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427"
|
|
4553
|
+
})], ValidationSuccessDataResponse.prototype, "root", void 0);
|
|
4554
|
+
var ValidationSuccessResponse = class extends SuccessResponse {};
|
|
4216
4555
|
__decorate([ApiProperty({
|
|
4217
|
-
type: "
|
|
4218
|
-
|
|
4219
|
-
|
|
4556
|
+
type: "string",
|
|
4557
|
+
nullable: true,
|
|
4558
|
+
example: null
|
|
4559
|
+
})], ValidationSuccessResponse.prototype, "cursor", void 0);
|
|
4560
|
+
__decorate([ApiProperty({
|
|
4561
|
+
type: () => ValidationSuccessDataResponse,
|
|
4562
|
+
description: "Payload and root for client-side signing."
|
|
4563
|
+
})], ValidationSuccessResponse.prototype, "data", void 0);
|
|
4564
|
+
var ValidationIssueResponse = class {};
|
|
4565
|
+
__decorate([ApiProperty({
|
|
4566
|
+
type: "number",
|
|
4567
|
+
description: "0-indexed position of the failed offer in the request array.",
|
|
4568
|
+
example: 0
|
|
4569
|
+
})], ValidationIssueResponse.prototype, "index", void 0);
|
|
4220
4570
|
__decorate([ApiProperty({
|
|
4221
4571
|
type: "string",
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
})],
|
|
4572
|
+
description: "Gatekeeper rule name that rejected the offer.",
|
|
4573
|
+
example: "no_buy"
|
|
4574
|
+
})], ValidationIssueResponse.prototype, "rule", void 0);
|
|
4225
4575
|
__decorate([ApiProperty({
|
|
4226
4576
|
type: "string",
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
})],
|
|
4230
|
-
var
|
|
4577
|
+
description: "Human-readable rejection reason.",
|
|
4578
|
+
example: "Buy offers are not supported"
|
|
4579
|
+
})], ValidationIssueResponse.prototype, "message", void 0);
|
|
4580
|
+
var ValidationFailureDataResponse = class {};
|
|
4581
|
+
__decorate([ApiProperty({
|
|
4582
|
+
type: () => [ValidationIssueResponse],
|
|
4583
|
+
description: "List of validation issues. Returned when any offer fails validation."
|
|
4584
|
+
})], ValidationFailureDataResponse.prototype, "issues", void 0);
|
|
4585
|
+
var ValidationFailureResponse = class extends SuccessResponse {};
|
|
4231
4586
|
__decorate([ApiProperty({
|
|
4232
4587
|
type: "string",
|
|
4233
4588
|
nullable: true,
|
|
4234
4589
|
example: null
|
|
4235
|
-
})],
|
|
4590
|
+
})], ValidationFailureResponse.prototype, "cursor", void 0);
|
|
4236
4591
|
__decorate([ApiProperty({
|
|
4237
|
-
type: () =>
|
|
4238
|
-
description: "
|
|
4239
|
-
})],
|
|
4592
|
+
type: () => ValidationFailureDataResponse,
|
|
4593
|
+
description: "List of validation issues. Returned when any offer fails validation."
|
|
4594
|
+
})], ValidationFailureResponse.prototype, "data", void 0);
|
|
4240
4595
|
var BookLevelResponse = class {};
|
|
4241
4596
|
__decorate([ApiProperty({
|
|
4242
4597
|
type: "string",
|
|
@@ -4301,13 +4656,18 @@ __decorate([
|
|
|
4301
4656
|
methods: ["post"],
|
|
4302
4657
|
path: "/v1/validate",
|
|
4303
4658
|
summary: "Validate offers",
|
|
4304
|
-
description: "Validates offers against router validation rules. Returns
|
|
4659
|
+
description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
4305
4660
|
}),
|
|
4306
4661
|
ApiBody({ type: ValidateOffersRequest }),
|
|
4307
4662
|
ApiResponse({
|
|
4308
4663
|
status: 200,
|
|
4309
4664
|
description: "Success",
|
|
4310
|
-
type:
|
|
4665
|
+
type: ValidationSuccessResponse
|
|
4666
|
+
}),
|
|
4667
|
+
ApiResponse({
|
|
4668
|
+
status: 200,
|
|
4669
|
+
description: "Validation issues",
|
|
4670
|
+
type: ValidationFailureResponse
|
|
4311
4671
|
})
|
|
4312
4672
|
], ValidateController.prototype, "validateOffers", null);
|
|
4313
4673
|
ValidateController = __decorate([ApiTags("Validate"), ApiResponse({
|
|
@@ -4476,7 +4836,7 @@ const OpenApi = async (options = {}) => {
|
|
|
4476
4836
|
if (options.rules && options.rules.length > 0) {
|
|
4477
4837
|
const rulesDescription = options.rules.map((rule) => `- **${rule.name}**: ${rule.description}`).join("\n");
|
|
4478
4838
|
const validatePath = document.paths?.["/v1/validate"];
|
|
4479
|
-
if (validatePath && "post" in validatePath && validatePath.post) validatePath.post.description = `Validates offers against router validation rules. Returns
|
|
4839
|
+
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}`;
|
|
4480
4840
|
}
|
|
4481
4841
|
return document;
|
|
4482
4842
|
};
|
|
@@ -4488,17 +4848,23 @@ var Cursor_exports = /* @__PURE__ */ __export({
|
|
|
4488
4848
|
encode: () => encode,
|
|
4489
4849
|
validate: () => validate
|
|
4490
4850
|
});
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
const c = cursor;
|
|
4494
|
-
if (![
|
|
4851
|
+
const isSort = (value) => {
|
|
4852
|
+
return [
|
|
4495
4853
|
"rate",
|
|
4496
4854
|
"maturity",
|
|
4497
4855
|
"expiry",
|
|
4498
4856
|
"amount"
|
|
4499
|
-
].includes(
|
|
4500
|
-
|
|
4501
|
-
|
|
4857
|
+
].includes(value);
|
|
4858
|
+
};
|
|
4859
|
+
function validate(cursor) {
|
|
4860
|
+
if (!cursor || typeof cursor !== "object") throw new Error("Cursor must be an object");
|
|
4861
|
+
const c = cursor;
|
|
4862
|
+
const sort = c.sort;
|
|
4863
|
+
const dir = c.dir;
|
|
4864
|
+
const hash$1 = c.hash;
|
|
4865
|
+
if (typeof sort !== "string" || !isSort(sort)) throw new Error(`Invalid sort field: ${String(sort)}. Must be one of: rate, maturity, expiry, amount`);
|
|
4866
|
+
if (typeof dir !== "string" || !["asc", "desc"].includes(dir)) throw new Error(`Invalid direction: ${String(dir)}. Must be one of: asc, desc`);
|
|
4867
|
+
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`);
|
|
4502
4868
|
const validation = {
|
|
4503
4869
|
rate: {
|
|
4504
4870
|
field: "rate",
|
|
@@ -4515,24 +4881,30 @@ function validate(cursor) {
|
|
|
4515
4881
|
maturity: {
|
|
4516
4882
|
field: "maturity",
|
|
4517
4883
|
type: "number",
|
|
4518
|
-
|
|
4884
|
+
min: 1,
|
|
4519
4885
|
error: "positive number"
|
|
4520
4886
|
},
|
|
4521
4887
|
expiry: {
|
|
4522
4888
|
field: "expiry",
|
|
4523
4889
|
type: "number",
|
|
4524
|
-
|
|
4890
|
+
min: 1,
|
|
4525
4891
|
error: "positive number"
|
|
4526
4892
|
}
|
|
4527
|
-
}[
|
|
4528
|
-
if (!validation) throw new Error(`Invalid sort field: ${
|
|
4893
|
+
}[sort];
|
|
4894
|
+
if (!validation) throw new Error(`Invalid sort field: ${sort}`);
|
|
4529
4895
|
const fieldValue = c[validation.field];
|
|
4530
|
-
if (
|
|
4531
|
-
if (
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4896
|
+
if (fieldValue === void 0 || fieldValue === null) throw new Error(`${sort} sort requires '${validation.field}' field to be present`);
|
|
4897
|
+
if (validation.type === "string") {
|
|
4898
|
+
if (typeof fieldValue !== "string") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
|
|
4899
|
+
if (!validation.pattern.test(fieldValue)) throw new Error(`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`);
|
|
4900
|
+
}
|
|
4901
|
+
if (validation.type === "number") {
|
|
4902
|
+
if (typeof fieldValue !== "number") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
|
|
4903
|
+
if (fieldValue < validation.min) throw new Error(`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`);
|
|
4904
|
+
}
|
|
4905
|
+
const page = c.page;
|
|
4906
|
+
if (page !== void 0) {
|
|
4907
|
+
if (typeof page !== "number" || !Number.isInteger(page) || page < 1) throw new Error("Invalid page: must be a positive integer");
|
|
4536
4908
|
}
|
|
4537
4909
|
return true;
|
|
4538
4910
|
}
|
|
@@ -4648,21 +5020,7 @@ const schemas = {
|
|
|
4648
5020
|
get_obligations: GetObligationsQueryParams,
|
|
4649
5021
|
get_obligation: GetObligationParams,
|
|
4650
5022
|
get_book: GetBookParams,
|
|
4651
|
-
validate_offers: z$1.object({
|
|
4652
|
-
offers: z$1.any().refine((val) => val === void 0 || Array.isArray(val), { message: "'offers' must be an array" }),
|
|
4653
|
-
calldata: z$1.string().regex(/^0x[a-fA-F0-9]*$/, { message: "'calldata' must be a hex string starting with '0x'" }).optional()
|
|
4654
|
-
}).superRefine((val, ctx) => {
|
|
4655
|
-
const hasOffers = val.offers !== void 0;
|
|
4656
|
-
const hasCalldata = val.calldata !== void 0;
|
|
4657
|
-
if (hasOffers && hasCalldata) ctx.addIssue({
|
|
4658
|
-
code: "custom",
|
|
4659
|
-
message: "Request body must contain either 'offers' or 'calldata', not both"
|
|
4660
|
-
});
|
|
4661
|
-
if (!hasOffers && !hasCalldata) ctx.addIssue({
|
|
4662
|
-
code: "custom",
|
|
4663
|
-
message: "Request body must contain either 'offers' array or 'calldata' hex string"
|
|
4664
|
-
});
|
|
4665
|
-
})
|
|
5023
|
+
validate_offers: z$1.object({ offers: z$1.array(z$1.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict()
|
|
4666
5024
|
};
|
|
4667
5025
|
function parse(action, query) {
|
|
4668
5026
|
return schemas[action].parse(query);
|
|
@@ -4745,7 +5103,7 @@ async function getDocsHtml({ gatekeeper }) {
|
|
|
4745
5103
|
async function getHealth(db) {
|
|
4746
5104
|
const logger = getLogger();
|
|
4747
5105
|
try {
|
|
4748
|
-
const status$1 = await create$
|
|
5106
|
+
const status$1 = await create$14({ db }).getStatus();
|
|
4749
5107
|
return success({ data: toSnakeCase$1({ status: status$1 }) });
|
|
4750
5108
|
} catch (err) {
|
|
4751
5109
|
logger.error({
|
|
@@ -4760,7 +5118,7 @@ async function getHealth(db) {
|
|
|
4760
5118
|
async function getHealthChains(db, healthClients) {
|
|
4761
5119
|
const logger = getLogger();
|
|
4762
5120
|
try {
|
|
4763
|
-
const chains$3 = await create$
|
|
5121
|
+
const chains$3 = await create$14({
|
|
4764
5122
|
db,
|
|
4765
5123
|
healthClients
|
|
4766
5124
|
}).getChains();
|
|
@@ -4783,7 +5141,7 @@ async function getHealthChains(db, healthClients) {
|
|
|
4783
5141
|
async function getHealthCollectors(db) {
|
|
4784
5142
|
const logger = getLogger();
|
|
4785
5143
|
try {
|
|
4786
|
-
const collectors$1 = await create$
|
|
5144
|
+
const collectors$1 = await create$14({ db }).getCollectors();
|
|
4787
5145
|
return success({ data: collectors$1.map(({ name, chainId, blockNumber, updatedAt, lag, status: status$1 }) => toSnakeCase$1({
|
|
4788
5146
|
name,
|
|
4789
5147
|
chainId,
|
|
@@ -4884,8 +5242,10 @@ async function getOffers$1(queryParameters, db) {
|
|
|
4884
5242
|
cursor: query.cursor,
|
|
4885
5243
|
limit: query.limit
|
|
4886
5244
|
});
|
|
5245
|
+
const hashes = offers$1.map((offer) => offer.hash);
|
|
5246
|
+
const attestationMap = await db.trees.getAttestations(hashes);
|
|
4887
5247
|
return success({
|
|
4888
|
-
data: offers$1.map(from$2),
|
|
5248
|
+
data: offers$1.map((offer) => from$2(offer, attestationMap.get(offer.hash.toLowerCase()))),
|
|
4889
5249
|
cursor: nextCursor ?? null
|
|
4890
5250
|
});
|
|
4891
5251
|
} catch (err) {
|
|
@@ -4905,56 +5265,49 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4905
5265
|
const logger = getLogger();
|
|
4906
5266
|
const result = safeParse("validate_offers", body, (issue) => issue.message);
|
|
4907
5267
|
if (!result.success) return failure(new BadRequestError(result.error.issues[0]?.message ?? "Invalid request body"));
|
|
4908
|
-
const { offers: rawOffers
|
|
4909
|
-
const results = [];
|
|
5268
|
+
const { offers: rawOffers } = result.data;
|
|
4910
5269
|
const parsedOffers = [];
|
|
4911
|
-
const
|
|
4912
|
-
|
|
4913
|
-
if (calldata !== void 0) try {
|
|
4914
|
-
const tree = decode$2(calldata);
|
|
4915
|
-
for (const [i, offer] of tree.offers.entries()) {
|
|
4916
|
-
parsedOffers.push(offer);
|
|
4917
|
-
parsedOfferIndices.push(i);
|
|
4918
|
-
}
|
|
4919
|
-
} catch (err) {
|
|
4920
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4921
|
-
return failure(new BadRequestError(`Failed to decode calldata: ${message}`));
|
|
4922
|
-
}
|
|
4923
|
-
if (hasOffers) for (let i = 0; i < rawOffers.length; i++) {
|
|
5270
|
+
const offerIndexByHash = /* @__PURE__ */ new Map();
|
|
5271
|
+
for (let i = 0; i < rawOffers.length; i++) {
|
|
4924
5272
|
const rawOffer = rawOffers[i];
|
|
4925
5273
|
try {
|
|
4926
5274
|
const offer = fromSnakeCase$1(rawOffer);
|
|
4927
|
-
|
|
4928
|
-
|
|
5275
|
+
if (!offerIndexByHash.has(offer.hash)) {
|
|
5276
|
+
offerIndexByHash.set(offer.hash, i);
|
|
5277
|
+
parsedOffers.push(offer);
|
|
5278
|
+
}
|
|
4929
5279
|
} catch (err) {
|
|
4930
5280
|
let message = err instanceof Error ? err.message : String(err);
|
|
4931
5281
|
if (err instanceof InvalidOfferError) message = err.formattedMessage;
|
|
4932
|
-
|
|
4933
|
-
offer_hash: rawOffer?.hash ?? "unknown",
|
|
4934
|
-
valid: false,
|
|
4935
|
-
rule: "parse_error",
|
|
4936
|
-
message
|
|
4937
|
-
};
|
|
5282
|
+
return failure(new BadRequestError(`Offer at index ${i} failed to parse: ${message}`));
|
|
4938
5283
|
}
|
|
4939
5284
|
}
|
|
4940
|
-
|
|
4941
|
-
const {
|
|
4942
|
-
|
|
4943
|
-
const
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
};
|
|
5285
|
+
try {
|
|
5286
|
+
const { issues } = await gatekeeper.isAllowed(parsedOffers);
|
|
5287
|
+
if (issues.length > 0) {
|
|
5288
|
+
const mappedIssues = issues.map((issue) => {
|
|
5289
|
+
const index$1 = offerIndexByHash.get(issue.item.hash);
|
|
5290
|
+
if (index$1 === void 0) return null;
|
|
5291
|
+
return {
|
|
5292
|
+
index: index$1,
|
|
5293
|
+
rule: issue.ruleName,
|
|
5294
|
+
message: issue.message
|
|
5295
|
+
};
|
|
5296
|
+
}).filter((issue) => issue !== null);
|
|
5297
|
+
return success({
|
|
5298
|
+
data: { issues: mappedIssues },
|
|
5299
|
+
cursor: null
|
|
5300
|
+
});
|
|
4957
5301
|
}
|
|
5302
|
+
const tree = from$12(parsedOffers);
|
|
5303
|
+
const payload = encodeUnsigned(tree);
|
|
5304
|
+
return success({
|
|
5305
|
+
data: {
|
|
5306
|
+
payload,
|
|
5307
|
+
root: tree.root
|
|
5308
|
+
},
|
|
5309
|
+
cursor: null
|
|
5310
|
+
});
|
|
4958
5311
|
} catch (err) {
|
|
4959
5312
|
logger.error({
|
|
4960
5313
|
err,
|
|
@@ -4964,11 +5317,6 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4964
5317
|
});
|
|
4965
5318
|
return failure(err);
|
|
4966
5319
|
}
|
|
4967
|
-
const orderedResults = results.filter((r) => r !== void 0);
|
|
4968
|
-
return success({
|
|
4969
|
-
data: orderedResults,
|
|
4970
|
-
cursor: null
|
|
4971
|
-
});
|
|
4972
5320
|
}
|
|
4973
5321
|
|
|
4974
5322
|
//#endregion
|
|
@@ -4990,13 +5338,13 @@ var Controllers_exports = /* @__PURE__ */ __export({
|
|
|
4990
5338
|
//#region src/api/Api.ts
|
|
4991
5339
|
function from$1(config) {
|
|
4992
5340
|
const { db, gatekeeper, port } = config;
|
|
4993
|
-
return create$
|
|
5341
|
+
return create$13({
|
|
4994
5342
|
port,
|
|
4995
5343
|
db,
|
|
4996
5344
|
gatekeeper
|
|
4997
5345
|
});
|
|
4998
5346
|
}
|
|
4999
|
-
function create$
|
|
5347
|
+
function create$13(params) {
|
|
5000
5348
|
return { serve: () => serve$1(params) };
|
|
5001
5349
|
}
|
|
5002
5350
|
/**
|
|
@@ -5091,7 +5439,7 @@ var RouterApi_exports = /* @__PURE__ */ __export({
|
|
|
5091
5439
|
OpenApi: () => OpenApi,
|
|
5092
5440
|
RouterStatusResponse: () => RouterStatusResponse,
|
|
5093
5441
|
ValidateController: () => ValidateController,
|
|
5094
|
-
create: () => create$
|
|
5442
|
+
create: () => create$13,
|
|
5095
5443
|
from: () => from$1,
|
|
5096
5444
|
parse: () => parse,
|
|
5097
5445
|
safeParse: () => safeParse
|
|
@@ -5156,24 +5504,28 @@ async function getOffers(apiClient, parameters) {
|
|
|
5156
5504
|
throw new HttpGetApiFailedError(`GET request returned ${response.status}`, { details: JSON.stringify(error) });
|
|
5157
5505
|
}
|
|
5158
5506
|
const offers$1 = data?.data.map((item) => {
|
|
5159
|
-
const { signature, ...rest } = item;
|
|
5160
|
-
return
|
|
5161
|
-
...
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5507
|
+
const { root, proof, signature, ...rest } = item;
|
|
5508
|
+
return {
|
|
5509
|
+
...fromSnakeCase$1({
|
|
5510
|
+
...rest,
|
|
5511
|
+
offering: item.offering,
|
|
5512
|
+
maturity: from$14(item.maturity),
|
|
5513
|
+
loan_token: item.loan_token,
|
|
5514
|
+
collaterals: item.collaterals.map((collateral) => ({
|
|
5515
|
+
asset: collateral.asset,
|
|
5516
|
+
oracle: collateral.oracle,
|
|
5517
|
+
lltv: collateral.lltv
|
|
5518
|
+
})),
|
|
5519
|
+
callback: {
|
|
5520
|
+
...item.callback,
|
|
5521
|
+
address: item.callback.address,
|
|
5522
|
+
data: item.callback.data
|
|
5523
|
+
},
|
|
5524
|
+
signature: signature?.toLowerCase()
|
|
5525
|
+
}),
|
|
5526
|
+
root: root?.toLowerCase() || void 0,
|
|
5527
|
+
proof: proof?.map((p) => p.toLowerCase()) || void 0
|
|
5528
|
+
};
|
|
5177
5529
|
}) ?? [];
|
|
5178
5530
|
return {
|
|
5179
5531
|
cursor: data?.cursor ?? null,
|
|
@@ -5267,15 +5619,19 @@ var schema_exports = /* @__PURE__ */ __export({
|
|
|
5267
5619
|
collectors: () => collectors,
|
|
5268
5620
|
consumedEvents: () => consumedEvents,
|
|
5269
5621
|
groups: () => groups,
|
|
5622
|
+
lots: () => lots,
|
|
5623
|
+
merklePaths: () => merklePaths,
|
|
5270
5624
|
obligationCollateralsV2: () => obligationCollateralsV2,
|
|
5271
5625
|
obligations: () => obligations,
|
|
5272
5626
|
offers: () => offers,
|
|
5273
5627
|
offersCallbacks: () => offersCallbacks,
|
|
5628
|
+
offsets: () => offsets,
|
|
5274
5629
|
oracles: () => oracles,
|
|
5275
5630
|
positionTypes: () => positionTypes,
|
|
5276
5631
|
positions: () => positions,
|
|
5277
5632
|
status: () => status,
|
|
5278
5633
|
transfers: () => transfers,
|
|
5634
|
+
trees: () => trees,
|
|
5279
5635
|
validations: () => validations
|
|
5280
5636
|
});
|
|
5281
5637
|
const s = pgSchema(VERSION);
|
|
@@ -5293,6 +5649,10 @@ var EnumTableName = /* @__PURE__ */ function(EnumTableName$1) {
|
|
|
5293
5649
|
EnumTableName$1["VALIDATIONS"] = "validations";
|
|
5294
5650
|
EnumTableName$1["COLLECTORS"] = "collectors";
|
|
5295
5651
|
EnumTableName$1["CHAINS"] = "chains";
|
|
5652
|
+
EnumTableName$1["LOTS"] = "lots";
|
|
5653
|
+
EnumTableName$1["OFFSETS"] = "offsets";
|
|
5654
|
+
EnumTableName$1["TREES"] = "trees";
|
|
5655
|
+
EnumTableName$1["MERKLE_PATHS"] = "merkle_paths";
|
|
5296
5656
|
return EnumTableName$1;
|
|
5297
5657
|
}(EnumTableName || {});
|
|
5298
5658
|
const TABLE_NAMES = Object.values(EnumTableName);
|
|
@@ -5455,6 +5815,86 @@ const callbacks = s.table(EnumTableName.CALLBACKS, {
|
|
|
5455
5815
|
],
|
|
5456
5816
|
name: "callbacks_positions_fk"
|
|
5457
5817
|
}).onDelete("cascade")]);
|
|
5818
|
+
const lots = s.table(EnumTableName.LOTS, {
|
|
5819
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
5820
|
+
user: varchar("user", { length: 42 }).notNull(),
|
|
5821
|
+
contract: varchar("contract", { length: 42 }).notNull(),
|
|
5822
|
+
group: varchar("group", { length: 66 }).notNull(),
|
|
5823
|
+
lower: numeric("lower", {
|
|
5824
|
+
precision: 78,
|
|
5825
|
+
scale: 0
|
|
5826
|
+
}).notNull(),
|
|
5827
|
+
upper: numeric("upper", {
|
|
5828
|
+
precision: 78,
|
|
5829
|
+
scale: 0
|
|
5830
|
+
}).notNull()
|
|
5831
|
+
}, (table) => [
|
|
5832
|
+
primaryKey({
|
|
5833
|
+
columns: [
|
|
5834
|
+
table.chainId,
|
|
5835
|
+
table.user,
|
|
5836
|
+
table.contract,
|
|
5837
|
+
table.group
|
|
5838
|
+
],
|
|
5839
|
+
name: "lots_pk"
|
|
5840
|
+
}),
|
|
5841
|
+
foreignKey({
|
|
5842
|
+
columns: [
|
|
5843
|
+
table.chainId,
|
|
5844
|
+
table.contract,
|
|
5845
|
+
table.user
|
|
5846
|
+
],
|
|
5847
|
+
foreignColumns: [
|
|
5848
|
+
positions.chainId,
|
|
5849
|
+
positions.contract,
|
|
5850
|
+
positions.user
|
|
5851
|
+
],
|
|
5852
|
+
name: "lots_positions_fk"
|
|
5853
|
+
}).onDelete("cascade"),
|
|
5854
|
+
foreignKey({
|
|
5855
|
+
columns: [
|
|
5856
|
+
table.chainId,
|
|
5857
|
+
table.user,
|
|
5858
|
+
table.group
|
|
5859
|
+
],
|
|
5860
|
+
foreignColumns: [
|
|
5861
|
+
groups.chainId,
|
|
5862
|
+
groups.maker,
|
|
5863
|
+
groups.group
|
|
5864
|
+
],
|
|
5865
|
+
name: "lots_groups_fk"
|
|
5866
|
+
}).onDelete("cascade")
|
|
5867
|
+
]);
|
|
5868
|
+
const offsets = s.table(EnumTableName.OFFSETS, {
|
|
5869
|
+
chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
|
|
5870
|
+
user: varchar("user", { length: 42 }).notNull(),
|
|
5871
|
+
contract: varchar("contract", { length: 42 }).notNull(),
|
|
5872
|
+
group: varchar("group", { length: 66 }).notNull(),
|
|
5873
|
+
value: numeric("value", {
|
|
5874
|
+
precision: 78,
|
|
5875
|
+
scale: 0
|
|
5876
|
+
}).notNull()
|
|
5877
|
+
}, (table) => [primaryKey({
|
|
5878
|
+
columns: [
|
|
5879
|
+
table.chainId,
|
|
5880
|
+
table.user,
|
|
5881
|
+
table.contract,
|
|
5882
|
+
table.group
|
|
5883
|
+
],
|
|
5884
|
+
name: "offsets_pk"
|
|
5885
|
+
}), foreignKey({
|
|
5886
|
+
columns: [
|
|
5887
|
+
table.chainId,
|
|
5888
|
+
table.contract,
|
|
5889
|
+
table.user
|
|
5890
|
+
],
|
|
5891
|
+
foreignColumns: [
|
|
5892
|
+
positions.chainId,
|
|
5893
|
+
positions.contract,
|
|
5894
|
+
positions.user
|
|
5895
|
+
],
|
|
5896
|
+
name: "offsets_positions_fk"
|
|
5897
|
+
}).onDelete("cascade")]);
|
|
5458
5898
|
const PositionTypes = s.enum("position_type", Object.values(Type));
|
|
5459
5899
|
const positionTypes = s.table("position_types", {
|
|
5460
5900
|
id: serial("id").primaryKey(),
|
|
@@ -5550,6 +5990,17 @@ const chains$1 = s.table(EnumTableName.CHAINS, {
|
|
|
5550
5990
|
}).default("0").notNull(),
|
|
5551
5991
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
5552
5992
|
}, (table) => [uniqueIndex("chains_id_epoch_idx").on(table.chainId, table.epoch)]);
|
|
5993
|
+
const trees = s.table(EnumTableName.TREES, {
|
|
5994
|
+
root: varchar("root", { length: 66 }).primaryKey(),
|
|
5995
|
+
rootSignature: varchar("root_signature", { length: 132 }).notNull(),
|
|
5996
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
5997
|
+
});
|
|
5998
|
+
const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
5999
|
+
offerHash: varchar("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
|
|
6000
|
+
treeRoot: varchar("tree_root", { length: 66 }).notNull().references(() => trees.root, { onDelete: "cascade" }),
|
|
6001
|
+
proofNodes: text("proof_nodes").notNull(),
|
|
6002
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
6003
|
+
}, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
|
|
5553
6004
|
|
|
5554
6005
|
//#endregion
|
|
5555
6006
|
//#region src/database/drizzle/index.ts
|
|
@@ -5564,15 +6015,19 @@ var drizzle_exports = /* @__PURE__ */ __export({
|
|
|
5564
6015
|
collectors: () => collectors,
|
|
5565
6016
|
consumedEvents: () => consumedEvents,
|
|
5566
6017
|
groups: () => groups,
|
|
6018
|
+
lots: () => lots,
|
|
6019
|
+
merklePaths: () => merklePaths,
|
|
5567
6020
|
obligationCollateralsV2: () => obligationCollateralsV2,
|
|
5568
6021
|
obligations: () => obligations,
|
|
5569
6022
|
offers: () => offers,
|
|
5570
6023
|
offersCallbacks: () => offersCallbacks,
|
|
6024
|
+
offsets: () => offsets,
|
|
5571
6025
|
oracles: () => oracles,
|
|
5572
6026
|
positionTypes: () => positionTypes,
|
|
5573
6027
|
positions: () => positions,
|
|
5574
6028
|
status: () => status,
|
|
5575
6029
|
transfers: () => transfers,
|
|
6030
|
+
trees: () => trees,
|
|
5576
6031
|
validations: () => validations
|
|
5577
6032
|
});
|
|
5578
6033
|
|
|
@@ -5580,7 +6035,7 @@ var drizzle_exports = /* @__PURE__ */ __export({
|
|
|
5580
6035
|
//#region src/database/domains/Book.ts
|
|
5581
6036
|
const DEFAULT_LIMIT$3 = 100;
|
|
5582
6037
|
const MAX_TOTAL_OFFERS = 500;
|
|
5583
|
-
function create$
|
|
6038
|
+
function create$12(config) {
|
|
5584
6039
|
const db = config.db;
|
|
5585
6040
|
const logger = getLogger();
|
|
5586
6041
|
const getOffers$2 = async (parameters) => {
|
|
@@ -5599,52 +6054,20 @@ function create$9(config) {
|
|
|
5599
6054
|
offers: [],
|
|
5600
6055
|
nextCursor: null
|
|
5601
6056
|
};
|
|
5602
|
-
const
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
obligationId: obligationId$1,
|
|
5613
|
-
side,
|
|
5614
|
-
now: now$1,
|
|
5615
|
-
rateSortDirection,
|
|
5616
|
-
cursor: offerCursor,
|
|
5617
|
-
limit: batchSize
|
|
5618
|
-
});
|
|
5619
|
-
if (rawOffers.length === 0) break;
|
|
5620
|
-
const newCallbackIds = rawOffers.flatMap((o) => o.callbackIds).filter((id$1) => !callbackState.has(id$1));
|
|
5621
|
-
await _updateCallbacksByIds(callbackState, db, newCallbackIds);
|
|
5622
|
-
await _updatePositionsByKeys(positionState, db, [...new Set(newCallbackIds.map((id$1) => callbackState.get(id$1)?.positionKey).filter((k) => k !== void 0 && !positionState.has(k)))]);
|
|
5623
|
-
await _updatePrices(prices, db, _collectNewOracleAddresses(rawOffers, callbackState, positionState, prices));
|
|
5624
|
-
const validOffers = _computeCrossInvalidation(rawOffers, callbackState, positionState, prices);
|
|
5625
|
-
let isOfferInPreviousPages = inputCursor === null;
|
|
5626
|
-
const cursorRate = inputCursor ? BigInt(inputCursor.rate) : 0n;
|
|
5627
|
-
for (const offer of validOffers) {
|
|
5628
|
-
if (!isOfferInPreviousPages) if (rateSortDirection === "asc" ? offer.rate > cursorRate : offer.rate < cursorRate) isOfferInPreviousPages = true;
|
|
5629
|
-
else if (offer.hash === inputCursor.hash) {
|
|
5630
|
-
isOfferInPreviousPages = true;
|
|
5631
|
-
continue;
|
|
5632
|
-
} else continue;
|
|
5633
|
-
book.push(offer);
|
|
5634
|
-
if (book.length >= effectiveLimit) {
|
|
5635
|
-
hasMoreOffers = true;
|
|
5636
|
-
break;
|
|
5637
|
-
}
|
|
5638
|
-
}
|
|
5639
|
-
offerCursor = rawNextCursor;
|
|
5640
|
-
if (!offerCursor) break;
|
|
5641
|
-
}
|
|
5642
|
-
const lastReturnedOffer = book[book.length - 1];
|
|
5643
|
-
const newTotalReturned = previouslyReturned + book.length;
|
|
6057
|
+
const { offers: offers$1, hasMore } = await _getOffers(db, {
|
|
6058
|
+
obligationId: obligationId$1,
|
|
6059
|
+
side,
|
|
6060
|
+
now: now$1,
|
|
6061
|
+
rateSortDirection,
|
|
6062
|
+
cursor: inputCursor,
|
|
6063
|
+
limit: Math.min(requestedLimit, MAX_TOTAL_OFFERS - previouslyReturned)
|
|
6064
|
+
});
|
|
6065
|
+
const lastReturnedOffer = offers$1[offers$1.length - 1];
|
|
6066
|
+
const newTotalReturned = previouslyReturned + offers$1.length;
|
|
5644
6067
|
const hasHitHardLimit = newTotalReturned >= MAX_TOTAL_OFFERS;
|
|
5645
6068
|
return {
|
|
5646
|
-
offers:
|
|
5647
|
-
nextCursor:
|
|
6069
|
+
offers: offers$1,
|
|
6070
|
+
nextCursor: offers$1.length > 0 && lastReturnedOffer && !hasHitHardLimit && hasMore ? Cursor.encode(lastReturnedOffer, newTotalReturned, now$1, side) : null
|
|
5648
6071
|
};
|
|
5649
6072
|
};
|
|
5650
6073
|
return {
|
|
@@ -5691,8 +6114,8 @@ function create$9(config) {
|
|
|
5691
6114
|
getOffers: getOffers$2
|
|
5692
6115
|
};
|
|
5693
6116
|
}
|
|
5694
|
-
/** Get offers with
|
|
5695
|
-
async function
|
|
6117
|
+
/** Get offers with computed takeable based on lot balance. */
|
|
6118
|
+
async function _getOffers(db, params) {
|
|
5696
6119
|
const { obligationId: obligationId$1, side, now: now$1, rateSortDirection, cursor, limit } = params;
|
|
5697
6120
|
const raw = await db.execute(sql`
|
|
5698
6121
|
WITH collats AS MATERIALIZED (
|
|
@@ -5760,32 +6183,202 @@ async function _getOffersWithCallbackIds(db, params) {
|
|
|
5760
6183
|
ORDER BY e.rate ${rateSortDirection === "asc" ? sql`ASC` : sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
|
|
5761
6184
|
LIMIT ${limit}
|
|
5762
6185
|
),
|
|
5763
|
-
|
|
6186
|
+
-- Compute sum of offsets per position
|
|
6187
|
+
position_offsets AS (
|
|
6188
|
+
SELECT
|
|
6189
|
+
chain_id,
|
|
6190
|
+
"user",
|
|
6191
|
+
contract,
|
|
6192
|
+
SUM(value::numeric) AS total_offset
|
|
6193
|
+
FROM ${offsets}
|
|
6194
|
+
GROUP BY chain_id, "user", contract
|
|
6195
|
+
),
|
|
6196
|
+
-- Compute position_consumed: sum of consumed from all groups with lots on each position (converted to lot terms)
|
|
6197
|
+
position_consumed AS (
|
|
5764
6198
|
SELECT
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
6199
|
+
l.chain_id,
|
|
6200
|
+
l.contract,
|
|
6201
|
+
l."user",
|
|
6202
|
+
SUM(
|
|
6203
|
+
CASE
|
|
6204
|
+
WHEN wo.assets::numeric > 0
|
|
6205
|
+
THEN COALESCE(g.consumed::numeric, 0) * (l.upper::numeric - l.lower::numeric) / wo.assets::numeric
|
|
6206
|
+
ELSE 0
|
|
6207
|
+
END
|
|
6208
|
+
) AS consumed
|
|
6209
|
+
FROM ${lots} l
|
|
6210
|
+
JOIN ${groups} g
|
|
6211
|
+
ON g.chain_id = l.chain_id
|
|
6212
|
+
AND LOWER(g.maker) = LOWER(l."user")
|
|
6213
|
+
AND g."group" = l."group"
|
|
6214
|
+
JOIN winners wo
|
|
6215
|
+
ON wo.group_chain_id = g.chain_id
|
|
6216
|
+
AND LOWER(wo.group_maker) = LOWER(g.maker)
|
|
6217
|
+
AND wo.group_group = g."group"
|
|
6218
|
+
GROUP BY l.chain_id, l.contract, l."user"
|
|
6219
|
+
),
|
|
6220
|
+
-- Compute callback contributions with lot balance
|
|
6221
|
+
callback_contributions AS (
|
|
6222
|
+
SELECT
|
|
6223
|
+
p.hash,
|
|
6224
|
+
p.obligation_id,
|
|
6225
|
+
p.assets,
|
|
6226
|
+
p.rate,
|
|
6227
|
+
p.maturity,
|
|
6228
|
+
p.expiry,
|
|
6229
|
+
p.start,
|
|
6230
|
+
p.nonce,
|
|
6231
|
+
p.buy,
|
|
6232
|
+
p.callback_address,
|
|
6233
|
+
p.callback_data,
|
|
6234
|
+
p.block_number,
|
|
6235
|
+
p.group_chain_id,
|
|
6236
|
+
p.group_maker,
|
|
6237
|
+
p.group_group,
|
|
6238
|
+
p.consumed,
|
|
6239
|
+
p.chain_id,
|
|
6240
|
+
p.loan_token,
|
|
6241
|
+
c.id AS callback_id,
|
|
6242
|
+
c.position_chain_id,
|
|
6243
|
+
c.position_contract,
|
|
6244
|
+
c.position_user,
|
|
6245
|
+
c.amount AS callback_amount,
|
|
6246
|
+
pos.balance AS position_balance,
|
|
6247
|
+
pos.asset AS position_asset,
|
|
6248
|
+
l.lower AS lot_lower,
|
|
6249
|
+
l.upper AS lot_upper,
|
|
6250
|
+
-- Compute lot_balance: min(position_balance + offset + position_consumed - lot.lower, lot.size - lot_consumed)
|
|
6251
|
+
-- lot_consumed is converted from loan token to lot terms: consumed * lot_size / assets
|
|
6252
|
+
GREATEST(0, LEAST(
|
|
6253
|
+
COALESCE(pos.balance::numeric, 0) + COALESCE(pos_offsets.total_offset, 0) + COALESCE(pc.consumed, 0) - COALESCE(l.lower::numeric, 0),
|
|
6254
|
+
(COALESCE(l.upper::numeric, 0) - COALESCE(l.lower::numeric, 0)) -
|
|
6255
|
+
CASE
|
|
6256
|
+
WHEN p.assets::numeric > 0
|
|
6257
|
+
THEN COALESCE(p.consumed::numeric, 0) * (COALESCE(l.upper::numeric, 0) - COALESCE(l.lower::numeric, 0)) / p.assets::numeric
|
|
6258
|
+
ELSE 0
|
|
6259
|
+
END
|
|
6260
|
+
)) AS lot_balance
|
|
5769
6261
|
FROM paged p
|
|
5770
6262
|
LEFT JOIN ${offersCallbacks} oc ON oc.offer_hash = p.hash
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
6263
|
+
LEFT JOIN ${callbacks} c ON c.id = oc.callback_id
|
|
6264
|
+
LEFT JOIN ${lots} l
|
|
6265
|
+
ON l.chain_id = c.position_chain_id
|
|
6266
|
+
AND LOWER(l.contract) = LOWER(c.position_contract)
|
|
6267
|
+
AND LOWER(l."user") = LOWER(c.position_user)
|
|
6268
|
+
AND l."group" = p.group_group
|
|
6269
|
+
LEFT JOIN ${positions} pos
|
|
6270
|
+
ON pos.chain_id = c.position_chain_id
|
|
6271
|
+
AND LOWER(pos.contract) = LOWER(c.position_contract)
|
|
6272
|
+
AND LOWER(pos."user") = LOWER(c.position_user)
|
|
6273
|
+
LEFT JOIN position_offsets pos_offsets
|
|
6274
|
+
ON pos_offsets.chain_id = c.position_chain_id
|
|
6275
|
+
AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
|
|
6276
|
+
AND LOWER(pos_offsets."user") = LOWER(c.position_user)
|
|
6277
|
+
LEFT JOIN position_consumed pc
|
|
6278
|
+
ON pc.chain_id = c.position_chain_id
|
|
6279
|
+
AND LOWER(pc.contract) = LOWER(c.position_contract)
|
|
6280
|
+
AND LOWER(pc."user") = LOWER(c.position_user)
|
|
6281
|
+
),
|
|
6282
|
+
-- Compute contribution per callback in loan terms (with oracle price via LEFT JOIN)
|
|
6283
|
+
callback_loan_contribution AS (
|
|
6284
|
+
SELECT
|
|
6285
|
+
cc.*,
|
|
6286
|
+
CASE
|
|
6287
|
+
-- No lot exists: contribution is 0
|
|
6288
|
+
WHEN cc.lot_lower IS NULL THEN 0
|
|
6289
|
+
-- Loan token position: use lot_balance directly, apply callback limit
|
|
6290
|
+
WHEN LOWER(cc.position_asset) = LOWER(cc.loan_token) THEN
|
|
6291
|
+
LEAST(
|
|
6292
|
+
cc.lot_balance,
|
|
6293
|
+
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
6294
|
+
)
|
|
6295
|
+
-- Collateral position: convert to loan using (amount * price / 10^36) * lltv / 10^18
|
|
6296
|
+
ELSE
|
|
6297
|
+
(
|
|
6298
|
+
LEAST(
|
|
6299
|
+
cc.lot_balance,
|
|
6300
|
+
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
6301
|
+
) * COALESCE(collat_oracle.price::numeric, 0) / 1e36
|
|
6302
|
+
) * COALESCE(collat_info.lltv::numeric, 0) / 1e18
|
|
6303
|
+
END AS contribution_in_loan
|
|
6304
|
+
FROM callback_contributions cc
|
|
6305
|
+
LEFT JOIN ${obligationCollateralsV2} collat_info
|
|
6306
|
+
ON collat_info.obligation_id = cc.obligation_id
|
|
6307
|
+
AND LOWER(collat_info.asset) = LOWER(cc.position_asset)
|
|
6308
|
+
LEFT JOIN ${oracles} collat_oracle
|
|
6309
|
+
ON collat_oracle.chain_id = collat_info.oracle_chain_id
|
|
6310
|
+
AND LOWER(collat_oracle.address) = LOWER(collat_info.oracle_address)
|
|
6311
|
+
),
|
|
6312
|
+
-- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
|
|
6313
|
+
offer_contributions AS (
|
|
6314
|
+
SELECT
|
|
6315
|
+
hash,
|
|
6316
|
+
obligation_id,
|
|
6317
|
+
assets,
|
|
6318
|
+
rate,
|
|
6319
|
+
maturity,
|
|
6320
|
+
expiry,
|
|
6321
|
+
start,
|
|
6322
|
+
nonce,
|
|
6323
|
+
buy,
|
|
6324
|
+
callback_address,
|
|
6325
|
+
callback_data,
|
|
6326
|
+
block_number,
|
|
6327
|
+
group_chain_id,
|
|
6328
|
+
group_maker,
|
|
6329
|
+
consumed,
|
|
6330
|
+
chain_id,
|
|
6331
|
+
loan_token,
|
|
6332
|
+
SUM(contribution_in_loan) AS total_available
|
|
6333
|
+
FROM (
|
|
6334
|
+
-- Take max contribution per position using DISTINCT ON (idiomatic PostgreSQL)
|
|
6335
|
+
SELECT DISTINCT ON (clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user)
|
|
6336
|
+
clc.*
|
|
6337
|
+
FROM callback_loan_contribution clc
|
|
6338
|
+
WHERE clc.callback_id IS NOT NULL
|
|
6339
|
+
ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
|
|
6340
|
+
) deduped
|
|
6341
|
+
GROUP BY hash, obligation_id, assets, rate, maturity, expiry, start, nonce, buy,
|
|
6342
|
+
callback_address, callback_data, block_number, group_chain_id, group_maker,
|
|
6343
|
+
consumed, chain_id, loan_token
|
|
5774
6344
|
)
|
|
6345
|
+
-- Final SELECT with inline takeable computation
|
|
5775
6346
|
SELECT
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
6347
|
+
oc.hash,
|
|
6348
|
+
oc.group_maker,
|
|
6349
|
+
oc.assets,
|
|
6350
|
+
oc.consumed,
|
|
6351
|
+
oc.rate,
|
|
6352
|
+
oc.maturity,
|
|
6353
|
+
oc.expiry,
|
|
6354
|
+
oc.start,
|
|
6355
|
+
oc.nonce,
|
|
6356
|
+
oc.buy,
|
|
6357
|
+
oc.chain_id,
|
|
6358
|
+
oc.loan_token,
|
|
6359
|
+
oc.callback_address,
|
|
6360
|
+
oc.callback_data,
|
|
6361
|
+
oc.block_number,
|
|
6362
|
+
-- takeable = min(assets - consumed, total_available)
|
|
6363
|
+
GREATEST(0, LEAST(
|
|
6364
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
6365
|
+
COALESCE(oc.total_available, 0)
|
|
6366
|
+
)) AS takeable,
|
|
6367
|
+
c.collaterals
|
|
6368
|
+
FROM offer_contributions oc
|
|
6369
|
+
LEFT JOIN collats c ON c.obligation_id = oc.obligation_id
|
|
6370
|
+
WHERE GREATEST(0, LEAST(
|
|
6371
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
6372
|
+
COALESCE(oc.total_available, 0)
|
|
6373
|
+
)) > 0
|
|
5781
6374
|
ORDER BY
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
6375
|
+
oc.rate ${rateSortDirection === "asc" ? sql`ASC` : sql`DESC`},
|
|
6376
|
+
oc.block_number ASC,
|
|
6377
|
+
oc.assets DESC,
|
|
6378
|
+
oc.hash ASC;
|
|
5786
6379
|
`);
|
|
5787
|
-
|
|
5788
|
-
|
|
6380
|
+
return {
|
|
6381
|
+
offers: raw.rows.map((row) => from$11({
|
|
5789
6382
|
offering: row.group_maker,
|
|
5790
6383
|
assets: BigInt(row.assets),
|
|
5791
6384
|
rate: BigInt(row.rate),
|
|
@@ -5807,165 +6400,12 @@ async function _getOffersWithCallbackIds(db, params) {
|
|
|
5807
6400
|
gasLimit: 0n
|
|
5808
6401
|
},
|
|
5809
6402
|
consumed: BigInt(row.consumed),
|
|
5810
|
-
takeable:
|
|
6403
|
+
takeable: BigInt(row.takeable.split(".")[0] ?? "0"),
|
|
5811
6404
|
blockNumber: row.block_number
|
|
5812
|
-
}),
|
|
5813
|
-
|
|
5814
|
-
}));
|
|
5815
|
-
let nextCursor = null;
|
|
5816
|
-
if (raw.rows.length === limit) {
|
|
5817
|
-
const last = raw.rows[raw.rows.length - 1];
|
|
5818
|
-
nextCursor = {
|
|
5819
|
-
rate: last.rate,
|
|
5820
|
-
blockNumber: last.block_number,
|
|
5821
|
-
assets: last.assets,
|
|
5822
|
-
hash: last.hash
|
|
5823
|
-
};
|
|
5824
|
-
}
|
|
5825
|
-
return {
|
|
5826
|
-
offers: offers$1,
|
|
5827
|
-
nextCursor
|
|
6405
|
+
})),
|
|
6406
|
+
hasMore: raw.rows.length === limit
|
|
5828
6407
|
};
|
|
5829
6408
|
}
|
|
5830
|
-
/** Get callbacks by their IDs. */
|
|
5831
|
-
async function _updateCallbacksByIds(state, db, ids) {
|
|
5832
|
-
if (ids.length === 0) return;
|
|
5833
|
-
const raw = await db.execute(sql`
|
|
5834
|
-
SELECT c.id, c.position_chain_id, c.position_contract, c.position_user, c.amount
|
|
5835
|
-
FROM ${callbacks} c
|
|
5836
|
-
WHERE c.id IN (${sql.join(ids.map((id$1) => sql`${id$1}`), sql`, `)})
|
|
5837
|
-
`);
|
|
5838
|
-
for (const row of raw.rows) if (!state.has(row.id)) state.set(row.id, {
|
|
5839
|
-
positionKey: _buildPositionKey(row.position_chain_id, row.position_contract, row.position_user),
|
|
5840
|
-
amount: row.amount != null ? BigInt(row.amount) : null
|
|
5841
|
-
});
|
|
5842
|
-
}
|
|
5843
|
-
/** Get positions by their composite keys. */
|
|
5844
|
-
async function _updatePositionsByKeys(state, db, keys) {
|
|
5845
|
-
if (keys.length === 0) return;
|
|
5846
|
-
const parsedKeys = keys.map((key) => {
|
|
5847
|
-
const parts = key.split(":");
|
|
5848
|
-
return {
|
|
5849
|
-
chainId: BigInt(parts[0]),
|
|
5850
|
-
contract: parts[1],
|
|
5851
|
-
user: parts[2]
|
|
5852
|
-
};
|
|
5853
|
-
});
|
|
5854
|
-
const raw = await db.execute(sql`
|
|
5855
|
-
SELECT p.chain_id, p.contract, p."user", p.balance, p.asset
|
|
5856
|
-
FROM ${positions} p
|
|
5857
|
-
WHERE (p.chain_id, LOWER(p.contract), LOWER(p."user")) IN (
|
|
5858
|
-
${sql.join(parsedKeys.map((k) => sql`(${k.chainId}, ${k.contract.toLowerCase()}, ${k.user.toLowerCase()})`), sql`, `)}
|
|
5859
|
-
)
|
|
5860
|
-
`);
|
|
5861
|
-
for (const row of raw.rows) {
|
|
5862
|
-
const key = _buildPositionKey(row.chain_id, row.contract, row.user);
|
|
5863
|
-
if (!state.has(key)) state.set(key, {
|
|
5864
|
-
balance: row.balance ? BigInt(row.balance) : 0n,
|
|
5865
|
-
remaining: row.balance ? BigInt(row.balance) : 0n,
|
|
5866
|
-
asset: row.asset ?? "0x0000000000000000000000000000000000000000"
|
|
5867
|
-
});
|
|
5868
|
-
}
|
|
5869
|
-
}
|
|
5870
|
-
/** Get oracle prices by chain_id and address. */
|
|
5871
|
-
async function _updatePrices(state, db, oracles$1) {
|
|
5872
|
-
if (oracles$1.length === 0) return;
|
|
5873
|
-
const raw = await db.execute(sql`
|
|
5874
|
-
SELECT o.chain_id, o.address, o.price
|
|
5875
|
-
FROM ${oracles} o
|
|
5876
|
-
WHERE (o.chain_id, LOWER(o.address)) IN (${sql.join(oracles$1.map((o) => sql`(${o.chainId}, ${o.address.toLowerCase()})`), sql`, `)})
|
|
5877
|
-
`);
|
|
5878
|
-
for (const row of raw.rows) {
|
|
5879
|
-
const key = `${row.chain_id}:${row.address.toLowerCase()}`;
|
|
5880
|
-
if (!state.has(key)) state.set(key, row.price ? BigInt(row.price) : 0n);
|
|
5881
|
-
}
|
|
5882
|
-
}
|
|
5883
|
-
/** Build a composite position key from its components. */
|
|
5884
|
-
function _buildPositionKey(chainId, contract, user) {
|
|
5885
|
-
return `${chainId}:${contract.toLowerCase()}:${user.toLowerCase()}`;
|
|
5886
|
-
}
|
|
5887
|
-
/** Collect oracle addresses that need to be Geted for collateral positions. */
|
|
5888
|
-
function _collectNewOracleAddresses(offers$1, callbackState, positionState, prices) {
|
|
5889
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5890
|
-
const result = [];
|
|
5891
|
-
for (const offer of offers$1) for (const callbackId of offer.callbackIds) {
|
|
5892
|
-
const callback$1 = callbackState.get(callbackId);
|
|
5893
|
-
if (!callback$1) continue;
|
|
5894
|
-
const position = positionState.get(callback$1.positionKey);
|
|
5895
|
-
if (!position) continue;
|
|
5896
|
-
if (position.asset.toLowerCase() === offer.loanToken.toLowerCase()) continue;
|
|
5897
|
-
const collateral = offer.collaterals.find((c) => c.asset.toLowerCase() === position.asset.toLowerCase());
|
|
5898
|
-
if (collateral) {
|
|
5899
|
-
const key = `${offer.chainId}:${collateral.oracle.toLowerCase()}`;
|
|
5900
|
-
if (!prices.has(key) && !seen.has(key)) {
|
|
5901
|
-
seen.add(key);
|
|
5902
|
-
result.push({
|
|
5903
|
-
chainId: offer.chainId,
|
|
5904
|
-
address: collateral.oracle.toLowerCase()
|
|
5905
|
-
});
|
|
5906
|
-
}
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
return result;
|
|
5910
|
-
}
|
|
5911
|
-
/**
|
|
5912
|
-
* Compute cross-invalidation for a batch of offers.
|
|
5913
|
-
* Deducts consumed liquidity from shared positions and returns offers with takeable amounts.
|
|
5914
|
-
*/
|
|
5915
|
-
function _computeCrossInvalidation(offers$1, callbackState, positionState, prices) {
|
|
5916
|
-
const result = [];
|
|
5917
|
-
for (const offer of offers$1) {
|
|
5918
|
-
const contributions = /* @__PURE__ */ new Map();
|
|
5919
|
-
for (const callbackId of offer.callbackIds) {
|
|
5920
|
-
const callback$1 = callbackState.get(callbackId);
|
|
5921
|
-
if (!callback$1) continue;
|
|
5922
|
-
const position = positionState.get(callback$1.positionKey);
|
|
5923
|
-
if (!position) continue;
|
|
5924
|
-
let conversion;
|
|
5925
|
-
if (position.asset.toLowerCase() === offer.loanToken.toLowerCase()) conversion = null;
|
|
5926
|
-
else {
|
|
5927
|
-
const collateral = offer.collaterals.find((c) => c.asset.toLowerCase() === position.asset.toLowerCase());
|
|
5928
|
-
if (!collateral) conversion = {
|
|
5929
|
-
price: 0n,
|
|
5930
|
-
lltv: 0n
|
|
5931
|
-
};
|
|
5932
|
-
else {
|
|
5933
|
-
const key = `${offer.chainId}:${collateral.oracle.toLowerCase()}`;
|
|
5934
|
-
conversion = {
|
|
5935
|
-
price: prices.get(key) ?? 0n,
|
|
5936
|
-
lltv: collateral.lltv
|
|
5937
|
-
};
|
|
5938
|
-
}
|
|
5939
|
-
}
|
|
5940
|
-
const availableFromPosition = conversion === null ? position.remaining : Conversion.collateralToLoan(position.remaining, conversion);
|
|
5941
|
-
const callbackLimitInLoanTerms = conversion === null || callback$1.amount === null ? callback$1.amount : Conversion.collateralToLoan(callback$1.amount, conversion);
|
|
5942
|
-
const callbackAvailable = callbackLimitInLoanTerms === null ? availableFromPosition : min(availableFromPosition, callbackLimitInLoanTerms);
|
|
5943
|
-
const existing = contributions.get(callback$1.positionKey);
|
|
5944
|
-
if (existing) existing.available = min(availableFromPosition, max$1(existing.available, callbackAvailable));
|
|
5945
|
-
else contributions.set(callback$1.positionKey, {
|
|
5946
|
-
available: callbackAvailable,
|
|
5947
|
-
conversion
|
|
5948
|
-
});
|
|
5949
|
-
}
|
|
5950
|
-
let totalAvailable = 0n;
|
|
5951
|
-
for (const [, contrib] of contributions) totalAvailable += contrib.available;
|
|
5952
|
-
const takeable = min(offer.assets - offer.consumed, totalAvailable);
|
|
5953
|
-
if (takeable <= 0n) continue;
|
|
5954
|
-
for (const [key, contrib] of contributions) {
|
|
5955
|
-
const position = positionState.get(key);
|
|
5956
|
-
const proportionalTakeable = totalAvailable > 0n ? contrib.available * takeable / totalAvailable : 0n;
|
|
5957
|
-
const toDeduct = contrib.conversion === null ? proportionalTakeable : Conversion.loanToCollateral(proportionalTakeable, contrib.conversion);
|
|
5958
|
-
position.remaining = position.remaining - toDeduct;
|
|
5959
|
-
if (position.remaining < 0n) position.remaining = 0n;
|
|
5960
|
-
}
|
|
5961
|
-
const { callbackIds: _, ...cleanOffer } = offer;
|
|
5962
|
-
result.push(from$11({
|
|
5963
|
-
...cleanOffer,
|
|
5964
|
-
takeable
|
|
5965
|
-
}));
|
|
5966
|
-
}
|
|
5967
|
-
return result;
|
|
5968
|
-
}
|
|
5969
6409
|
let Cursor;
|
|
5970
6410
|
(function(_Cursor) {
|
|
5971
6411
|
function encode$4(offer, totalReturned, now$1, side) {
|
|
@@ -6030,7 +6470,7 @@ let LevelCursor;
|
|
|
6030
6470
|
//#endregion
|
|
6031
6471
|
//#region src/database/domains/Chains.ts
|
|
6032
6472
|
/** Postgres implementation. */
|
|
6033
|
-
const create$
|
|
6473
|
+
const create$11 = (config) => {
|
|
6034
6474
|
const db = config.db;
|
|
6035
6475
|
const logger = getLogger();
|
|
6036
6476
|
return {
|
|
@@ -6086,7 +6526,7 @@ const create$8 = (config) => {
|
|
|
6086
6526
|
//#endregion
|
|
6087
6527
|
//#region src/database/domains/Collectors.ts
|
|
6088
6528
|
/** Postgres implementation. */
|
|
6089
|
-
const create$
|
|
6529
|
+
const create$10 = (config) => {
|
|
6090
6530
|
const db = config.db;
|
|
6091
6531
|
const logger = getLogger();
|
|
6092
6532
|
return {
|
|
@@ -6179,7 +6619,7 @@ const DEFAULT_BATCH_SIZE$1 = 4e3;
|
|
|
6179
6619
|
|
|
6180
6620
|
//#endregion
|
|
6181
6621
|
//#region src/database/domains/Consumed.ts
|
|
6182
|
-
function create$
|
|
6622
|
+
function create$9(db) {
|
|
6183
6623
|
return {
|
|
6184
6624
|
create: async (events) => {
|
|
6185
6625
|
if (events.length === 0) return;
|
|
@@ -6220,6 +6660,51 @@ function create$6(db) {
|
|
|
6220
6660
|
};
|
|
6221
6661
|
}
|
|
6222
6662
|
|
|
6663
|
+
//#endregion
|
|
6664
|
+
//#region src/database/domains/Lots.ts
|
|
6665
|
+
function create$8(db) {
|
|
6666
|
+
return {
|
|
6667
|
+
get: async (parameters) => {
|
|
6668
|
+
const { chainId, user, contract, group } = parameters ?? {};
|
|
6669
|
+
const conditions = [];
|
|
6670
|
+
if (chainId !== void 0) conditions.push(eq(lots.chainId, chainId));
|
|
6671
|
+
if (user !== void 0) conditions.push(eq(lots.user, user.toLowerCase()));
|
|
6672
|
+
if (contract !== void 0) conditions.push(eq(lots.contract, contract.toLowerCase()));
|
|
6673
|
+
if (group !== void 0) conditions.push(eq(lots.group, group));
|
|
6674
|
+
return (await db.select().from(lots).where(conditions.length > 0 ? and(...conditions) : void 0)).map((row) => ({
|
|
6675
|
+
chainId: row.chainId,
|
|
6676
|
+
user: row.user,
|
|
6677
|
+
contract: row.contract,
|
|
6678
|
+
group: row.group,
|
|
6679
|
+
lower: BigInt(row.lower),
|
|
6680
|
+
upper: BigInt(row.upper)
|
|
6681
|
+
}));
|
|
6682
|
+
},
|
|
6683
|
+
create: async (parameters) => {
|
|
6684
|
+
if (parameters.length === 0) return;
|
|
6685
|
+
const lotsByPositionGroup = /* @__PURE__ */ new Map();
|
|
6686
|
+
for (const offer of parameters) {
|
|
6687
|
+
const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}`.toLowerCase();
|
|
6688
|
+
const existing = lotsByPositionGroup.get(key);
|
|
6689
|
+
if (!existing || offer.size > existing.size) lotsByPositionGroup.set(key, offer);
|
|
6690
|
+
}
|
|
6691
|
+
for (const offer of lotsByPositionGroup.values()) if ((await db.select().from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase()), eq(lots.group, offer.group))).limit(1)).length === 0) {
|
|
6692
|
+
const maxUpperResult = await db.select({ maxUpper: sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase())));
|
|
6693
|
+
const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
|
|
6694
|
+
const newUpper = newLower + offer.size;
|
|
6695
|
+
await db.insert(lots).values({
|
|
6696
|
+
chainId: offer.positionChainId,
|
|
6697
|
+
user: offer.positionUser.toLowerCase(),
|
|
6698
|
+
contract: offer.positionContract.toLowerCase(),
|
|
6699
|
+
group: offer.group,
|
|
6700
|
+
lower: newLower.toString(),
|
|
6701
|
+
upper: newUpper.toString()
|
|
6702
|
+
});
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
};
|
|
6706
|
+
}
|
|
6707
|
+
|
|
6223
6708
|
//#endregion
|
|
6224
6709
|
//#region src/gatekeeper/Gate.ts
|
|
6225
6710
|
var Gate_exports = /* @__PURE__ */ __export({
|
|
@@ -6333,8 +6818,8 @@ function getCallback(chain, type) {
|
|
|
6333
6818
|
* @param address - Callback contract address
|
|
6334
6819
|
* @returns The callback type when found, otherwise undefined
|
|
6335
6820
|
*/
|
|
6336
|
-
function getCallbackType(chain, address) {
|
|
6337
|
-
return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
|
|
6821
|
+
function getCallbackType(chain, address$1) {
|
|
6822
|
+
return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address$1?.toLowerCase()))?.type;
|
|
6338
6823
|
}
|
|
6339
6824
|
/**
|
|
6340
6825
|
* Returns the callback addresses for a given chain and callback type, if it exists.
|
|
@@ -6448,8 +6933,8 @@ const configs = {
|
|
|
6448
6933
|
|
|
6449
6934
|
//#endregion
|
|
6450
6935
|
//#region src/gatekeeper/Gatekeeper.ts
|
|
6451
|
-
var Gatekeeper_exports = /* @__PURE__ */ __export({ create: () => create$
|
|
6452
|
-
function create$
|
|
6936
|
+
var Gatekeeper_exports = /* @__PURE__ */ __export({ create: () => create$7 });
|
|
6937
|
+
function create$7(parameters) {
|
|
6453
6938
|
return {
|
|
6454
6939
|
rules: parameters.rules,
|
|
6455
6940
|
isAllowed: async (offers$1) => {
|
|
@@ -6467,6 +6952,7 @@ var Rules_exports = /* @__PURE__ */ __export({
|
|
|
6467
6952
|
callback: () => callback,
|
|
6468
6953
|
chains: () => chains,
|
|
6469
6954
|
maturity: () => maturity,
|
|
6955
|
+
sameMaker: () => sameMaker,
|
|
6470
6956
|
token: () => token,
|
|
6471
6957
|
validity: () => validity
|
|
6472
6958
|
});
|
|
@@ -6603,10 +7089,29 @@ const token = ({ assets: assets$1 }) => single("token", "Validates that offer lo
|
|
|
6603
7089
|
if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
|
|
6604
7090
|
if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
|
|
6605
7091
|
});
|
|
7092
|
+
/**
|
|
7093
|
+
* A batch validation rule that ensures all offers in a tree have the same maker (offering address).
|
|
7094
|
+
* Returns an issue only for the first non-conforming offer.
|
|
7095
|
+
* This rule is signing-agnostic; signer verification is handled at the collector level.
|
|
7096
|
+
*/
|
|
7097
|
+
const sameMaker = () => batch("mixed_maker", "Validates that all offers in a batch have the same maker (offering address)", (offers$1) => {
|
|
7098
|
+
const issues = /* @__PURE__ */ new Map();
|
|
7099
|
+
if (offers$1.length === 0) return issues;
|
|
7100
|
+
const firstMaker = offers$1[0].offering.toLowerCase();
|
|
7101
|
+
for (let i = 1; i < offers$1.length; i++) {
|
|
7102
|
+
const offer = offers$1[i];
|
|
7103
|
+
if (offer.offering.toLowerCase() !== firstMaker) {
|
|
7104
|
+
issues.set(i, { message: `Offer has different maker ${offer.offering} than first offer ${offers$1[0].offering}` });
|
|
7105
|
+
return issues;
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
return issues;
|
|
7109
|
+
});
|
|
6606
7110
|
|
|
6607
7111
|
//#endregion
|
|
6608
7112
|
//#region src/gatekeeper/morphoRules.ts
|
|
6609
7113
|
const morphoRules = (chains$3) => [
|
|
7114
|
+
sameMaker(),
|
|
6610
7115
|
chains({ chains: chains$3 }),
|
|
6611
7116
|
maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
|
|
6612
7117
|
callback({
|
|
@@ -6623,7 +7128,7 @@ const morphoRules = (chains$3) => [
|
|
|
6623
7128
|
//#endregion
|
|
6624
7129
|
//#region src/database/domains/Offers.ts
|
|
6625
7130
|
const DEFAULT_LIMIT$2 = 100;
|
|
6626
|
-
function create$
|
|
7131
|
+
function create$6(config) {
|
|
6627
7132
|
const db = config.db;
|
|
6628
7133
|
return {
|
|
6629
7134
|
create: async (offers$1) => {
|
|
@@ -6782,6 +7287,22 @@ function create$4(config) {
|
|
|
6782
7287
|
amount: callback$1.amount
|
|
6783
7288
|
})));
|
|
6784
7289
|
for (const batch$2 of batch$1(callbacksRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(callbacks).values(batch$2).onConflictDoNothing();
|
|
7290
|
+
const lotInfos = [];
|
|
7291
|
+
for (const [offerHash, callbacks$1] of offersCallbacksMap.entries()) {
|
|
7292
|
+
const offer = inserted.find((o) => o.hash === offerHash);
|
|
7293
|
+
if (!offer) continue;
|
|
7294
|
+
for (const callback$1 of callbacks$1) {
|
|
7295
|
+
const isLoanPosition = obligationsMap.get(offer.obligationId)?.loanToken.toLowerCase() === callback$1.asset?.toLowerCase();
|
|
7296
|
+
lotInfos.push({
|
|
7297
|
+
positionChainId: callback$1.chainId,
|
|
7298
|
+
positionContract: callback$1.contract,
|
|
7299
|
+
positionUser: callback$1.user,
|
|
7300
|
+
group: offer.group,
|
|
7301
|
+
size: isLoanPosition ? BigInt(offer.assets) : BigInt(callback$1.amount)
|
|
7302
|
+
});
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7305
|
+
if (lotInfos.length > 0) await dbTx.lots.create(lotInfos);
|
|
6785
7306
|
obligationsMap.clear();
|
|
6786
7307
|
collateralsMap.clear();
|
|
6787
7308
|
oraclesMap.clear();
|
|
@@ -6964,9 +7485,29 @@ function create$4(config) {
|
|
|
6964
7485
|
};
|
|
6965
7486
|
}
|
|
6966
7487
|
|
|
7488
|
+
//#endregion
|
|
7489
|
+
//#region src/database/domains/Offsets.ts
|
|
7490
|
+
function create$5(db) {
|
|
7491
|
+
return { get: async (parameters) => {
|
|
7492
|
+
const { chainId, user, contract, group } = parameters ?? {};
|
|
7493
|
+
const conditions = [];
|
|
7494
|
+
if (chainId !== void 0) conditions.push(eq(offsets.chainId, chainId));
|
|
7495
|
+
if (user !== void 0) conditions.push(eq(offsets.user, user.toLowerCase()));
|
|
7496
|
+
if (contract !== void 0) conditions.push(eq(offsets.contract, contract.toLowerCase()));
|
|
7497
|
+
if (group !== void 0) conditions.push(eq(offsets.group, group));
|
|
7498
|
+
return (await db.select().from(offsets).where(conditions.length > 0 ? and(...conditions) : void 0)).map((row) => ({
|
|
7499
|
+
chainId: row.chainId,
|
|
7500
|
+
user: row.user,
|
|
7501
|
+
contract: row.contract,
|
|
7502
|
+
group: row.group,
|
|
7503
|
+
value: BigInt(row.value)
|
|
7504
|
+
}));
|
|
7505
|
+
} };
|
|
7506
|
+
}
|
|
7507
|
+
|
|
6967
7508
|
//#endregion
|
|
6968
7509
|
//#region src/database/domains/Oracles.ts
|
|
6969
|
-
function create$
|
|
7510
|
+
function create$4(db) {
|
|
6970
7511
|
return {
|
|
6971
7512
|
get: async ({ chainId }) => {
|
|
6972
7513
|
return (await db.select({
|
|
@@ -7006,7 +7547,7 @@ function create$3(db) {
|
|
|
7006
7547
|
//#endregion
|
|
7007
7548
|
//#region src/database/domains/Positions.ts
|
|
7008
7549
|
const DEFAULT_LIMIT$1 = 100;
|
|
7009
|
-
const create$
|
|
7550
|
+
const create$3 = (db) => {
|
|
7010
7551
|
return {
|
|
7011
7552
|
upsert: async (positions$1) => {
|
|
7012
7553
|
const positionsMap = /* @__PURE__ */ new Map();
|
|
@@ -7122,7 +7663,7 @@ const create$2 = (db) => {
|
|
|
7122
7663
|
|
|
7123
7664
|
//#endregion
|
|
7124
7665
|
//#region src/database/domains/Transfers.ts
|
|
7125
|
-
const create$
|
|
7666
|
+
const create$2 = (db) => ({ create: async (transfers$1) => {
|
|
7126
7667
|
if (transfers$1.length === 0) return 0;
|
|
7127
7668
|
return await db.transaction(async (dbTx) => {
|
|
7128
7669
|
let totalInserted = 0;
|
|
@@ -7219,6 +7760,91 @@ const create$1 = (db) => ({ create: async (transfers$1) => {
|
|
|
7219
7760
|
});
|
|
7220
7761
|
} });
|
|
7221
7762
|
|
|
7763
|
+
//#endregion
|
|
7764
|
+
//#region src/database/domains/Trees.ts
|
|
7765
|
+
/**
|
|
7766
|
+
* Creates a Trees domain instance for managing merkle tree metadata.
|
|
7767
|
+
*
|
|
7768
|
+
* @param config - Configuration with database instance
|
|
7769
|
+
* @returns TreesDomain instance
|
|
7770
|
+
*/
|
|
7771
|
+
function create$1(config) {
|
|
7772
|
+
const db = config.db;
|
|
7773
|
+
return {
|
|
7774
|
+
create: async (trees$1) => {
|
|
7775
|
+
if (trees$1.length === 0) return [];
|
|
7776
|
+
return await db.transaction(async (dbTx) => {
|
|
7777
|
+
const roots = [];
|
|
7778
|
+
for (const { tree, signature } of trees$1) {
|
|
7779
|
+
const root = tree.root.toLowerCase();
|
|
7780
|
+
roots.push(root);
|
|
7781
|
+
await dbTx.insert(trees).values({
|
|
7782
|
+
root,
|
|
7783
|
+
rootSignature: signature.toLowerCase()
|
|
7784
|
+
}).onConflictDoUpdate({
|
|
7785
|
+
target: [trees.root],
|
|
7786
|
+
set: {
|
|
7787
|
+
rootSignature: signature.toLowerCase(),
|
|
7788
|
+
createdAt: sql`NOW()`
|
|
7789
|
+
}
|
|
7790
|
+
});
|
|
7791
|
+
await dbTx.offers.create(tree.offers);
|
|
7792
|
+
const pathRows = proofs(tree).map((proof) => ({
|
|
7793
|
+
offerHash: proof.offer.hash.toLowerCase(),
|
|
7794
|
+
treeRoot: root,
|
|
7795
|
+
proofNodes: concatenateProofs(proof.path)
|
|
7796
|
+
}));
|
|
7797
|
+
for (const batch$2 of batch$1(pathRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(merklePaths).values(batch$2).onConflictDoUpdate({
|
|
7798
|
+
target: [merklePaths.offerHash],
|
|
7799
|
+
set: {
|
|
7800
|
+
treeRoot: sql`excluded.tree_root`,
|
|
7801
|
+
proofNodes: sql`excluded.proof_nodes`,
|
|
7802
|
+
createdAt: sql`NOW()`
|
|
7803
|
+
}
|
|
7804
|
+
});
|
|
7805
|
+
}
|
|
7806
|
+
return roots;
|
|
7807
|
+
});
|
|
7808
|
+
},
|
|
7809
|
+
getAttestations: async (hashes) => {
|
|
7810
|
+
if (hashes.length === 0) return /* @__PURE__ */ new Map();
|
|
7811
|
+
const normalizedHashes = hashes.map((h) => h.toLowerCase());
|
|
7812
|
+
const results = await db.select({
|
|
7813
|
+
offerHash: merklePaths.offerHash,
|
|
7814
|
+
treeRoot: merklePaths.treeRoot,
|
|
7815
|
+
proofNodes: merklePaths.proofNodes,
|
|
7816
|
+
rootSignature: trees.rootSignature
|
|
7817
|
+
}).from(merklePaths).innerJoin(trees, eq(merklePaths.treeRoot, trees.root)).where(inArray(merklePaths.offerHash, normalizedHashes));
|
|
7818
|
+
const attestationMap = /* @__PURE__ */ new Map();
|
|
7819
|
+
for (const row of results) attestationMap.set(row.offerHash, {
|
|
7820
|
+
root: row.treeRoot,
|
|
7821
|
+
signature: row.rootSignature,
|
|
7822
|
+
proof: splitProofs(row.proofNodes)
|
|
7823
|
+
});
|
|
7824
|
+
return attestationMap;
|
|
7825
|
+
}
|
|
7826
|
+
};
|
|
7827
|
+
}
|
|
7828
|
+
/**
|
|
7829
|
+
* Concatenates an array of 32-byte hex hashes into a single hex string.
|
|
7830
|
+
* Empty arrays return "0x".
|
|
7831
|
+
*/
|
|
7832
|
+
function concatenateProofs(proofs$1) {
|
|
7833
|
+
if (proofs$1.length === 0) return "0x";
|
|
7834
|
+
return `0x${proofs$1.map((p) => p.slice(2)).join("")}`;
|
|
7835
|
+
}
|
|
7836
|
+
/**
|
|
7837
|
+
* Splits a concatenated hex string back into an array of 32-byte hex hashes.
|
|
7838
|
+
* Returns empty array for "0x" or empty string.
|
|
7839
|
+
*/
|
|
7840
|
+
function splitProofs(concatenated) {
|
|
7841
|
+
if (!concatenated || concatenated === "0x" || concatenated.length <= 2) return [];
|
|
7842
|
+
const hex$1 = concatenated.slice(2);
|
|
7843
|
+
const proofs$1 = [];
|
|
7844
|
+
for (let i = 0; i < hex$1.length; i += 64) proofs$1.push(`0x${hex$1.slice(i, i + 64)}`);
|
|
7845
|
+
return proofs$1;
|
|
7846
|
+
}
|
|
7847
|
+
|
|
7222
7848
|
//#endregion
|
|
7223
7849
|
//#region src/database/domains/Validations.ts
|
|
7224
7850
|
const DEFAULT_LIMIT = 100;
|
|
@@ -7284,15 +7910,18 @@ function create(db) {
|
|
|
7284
7910
|
var Database_exports = /* @__PURE__ */ __export({ connect: () => connect$1 });
|
|
7285
7911
|
function createDomains(core) {
|
|
7286
7912
|
return {
|
|
7287
|
-
book: create$
|
|
7288
|
-
collectors: create$
|
|
7289
|
-
offers: create$
|
|
7290
|
-
chains: create$
|
|
7291
|
-
consumed: create$
|
|
7292
|
-
|
|
7913
|
+
book: create$12({ db: core }),
|
|
7914
|
+
collectors: create$10({ db: core }),
|
|
7915
|
+
offers: create$6({ db: core }),
|
|
7916
|
+
chains: create$11({ db: core }),
|
|
7917
|
+
consumed: create$9(core),
|
|
7918
|
+
lots: create$8(core),
|
|
7919
|
+
offsets: create$5(core),
|
|
7920
|
+
oracles: create$4(core),
|
|
7921
|
+
trees: create$1({ db: core }),
|
|
7293
7922
|
validations: create(core),
|
|
7294
|
-
positions: create$
|
|
7295
|
-
transfers: create$
|
|
7923
|
+
positions: create$3(core),
|
|
7924
|
+
transfers: create$2(core)
|
|
7296
7925
|
};
|
|
7297
7926
|
}
|
|
7298
7927
|
const AUGMENT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -7327,10 +7956,22 @@ function augmentWithDomains(base$1) {
|
|
|
7327
7956
|
value: dms.consumed,
|
|
7328
7957
|
enumerable: true
|
|
7329
7958
|
},
|
|
7959
|
+
lots: {
|
|
7960
|
+
value: dms.lots,
|
|
7961
|
+
enumerable: true
|
|
7962
|
+
},
|
|
7963
|
+
offsets: {
|
|
7964
|
+
value: dms.offsets,
|
|
7965
|
+
enumerable: true
|
|
7966
|
+
},
|
|
7330
7967
|
oracles: {
|
|
7331
7968
|
value: dms.oracles,
|
|
7332
7969
|
enumerable: true
|
|
7333
7970
|
},
|
|
7971
|
+
trees: {
|
|
7972
|
+
value: dms.trees,
|
|
7973
|
+
enumerable: true
|
|
7974
|
+
},
|
|
7334
7975
|
validations: {
|
|
7335
7976
|
value: dms.validations,
|
|
7336
7977
|
enumerable: true
|
|
@@ -7347,6 +7988,7 @@ function augmentWithDomains(base$1) {
|
|
|
7347
7988
|
AUGMENT_CACHE.set(base$1, wrapped);
|
|
7348
7989
|
return wrapped;
|
|
7349
7990
|
}
|
|
7991
|
+
let cachedInMemoryDatabase;
|
|
7350
7992
|
/**
|
|
7351
7993
|
* Connect to the database.
|
|
7352
7994
|
* @notice If no connection string is provided, an in-process PGLite database is created.
|
|
@@ -7369,15 +8011,17 @@ function connect$1(connectionString) {
|
|
|
7369
8011
|
clean: async () => await clean(driver$1)
|
|
7370
8012
|
});
|
|
7371
8013
|
}
|
|
8014
|
+
if (cachedInMemoryDatabase) return cachedInMemoryDatabase;
|
|
7372
8015
|
const pool = new PGlite();
|
|
7373
8016
|
const driver = drizzle$1(pool, { schema: schema_exports });
|
|
7374
8017
|
const core = augmentWithDomains(driver);
|
|
7375
|
-
|
|
8018
|
+
cachedInMemoryDatabase = Object.assign(core, {
|
|
7376
8019
|
name: "pglite",
|
|
7377
8020
|
pool,
|
|
7378
8021
|
applyMigrations: applyMigrations("pglite", driver),
|
|
7379
8022
|
clean: async () => await clean(driver)
|
|
7380
8023
|
});
|
|
8024
|
+
return cachedInMemoryDatabase;
|
|
7381
8025
|
}
|
|
7382
8026
|
const MIGRATED_DRIVERS = /* @__PURE__ */ new WeakSet();
|
|
7383
8027
|
function applyMigrations(kind, driver) {
|
|
@@ -7581,6 +8225,35 @@ async function postMigrate(driver) {
|
|
|
7581
8225
|
REFERENCING OLD TABLE AS deleted_rows
|
|
7582
8226
|
FOR EACH STATEMENT
|
|
7583
8227
|
EXECUTE FUNCTION cleanup_orphan_positions();
|
|
8228
|
+
`);
|
|
8229
|
+
await driver.execute(`
|
|
8230
|
+
CREATE OR REPLACE FUNCTION cleanup_orphan_groups()
|
|
8231
|
+
RETURNS TRIGGER AS $$
|
|
8232
|
+
BEGIN
|
|
8233
|
+
DELETE FROM "${VERSION}"."groups" g
|
|
8234
|
+
USING (
|
|
8235
|
+
SELECT DISTINCT group_chain_id, group_maker, group_group
|
|
8236
|
+
FROM deleted_rows
|
|
8237
|
+
) AS affected
|
|
8238
|
+
WHERE g.chain_id = affected.group_chain_id
|
|
8239
|
+
AND g.maker = affected.group_maker
|
|
8240
|
+
AND g."group" = affected.group_group
|
|
8241
|
+
AND NOT EXISTS (
|
|
8242
|
+
SELECT 1 FROM "${VERSION}"."offers" o
|
|
8243
|
+
WHERE o.group_chain_id = g.chain_id
|
|
8244
|
+
AND o.group_maker = g.maker
|
|
8245
|
+
AND o.group_group = g."group"
|
|
8246
|
+
);
|
|
8247
|
+
RETURN NULL;
|
|
8248
|
+
END;
|
|
8249
|
+
$$ LANGUAGE plpgsql;
|
|
8250
|
+
`);
|
|
8251
|
+
await driver.execute(`
|
|
8252
|
+
CREATE OR REPLACE TRIGGER trg_cleanup_orphan_groups
|
|
8253
|
+
AFTER DELETE ON "${VERSION}"."offers"
|
|
8254
|
+
REFERENCING OLD TABLE AS deleted_rows
|
|
8255
|
+
FOR EACH STATEMENT
|
|
8256
|
+
EXECUTE FUNCTION cleanup_orphan_groups();
|
|
7584
8257
|
`);
|
|
7585
8258
|
await driver.execute(`
|
|
7586
8259
|
CREATE OR REPLACE FUNCTION cleanup_orphan_obligations_and_oracles()
|
|
@@ -7633,6 +8306,58 @@ async function postMigrate(driver) {
|
|
|
7633
8306
|
REFERENCING OLD TABLE AS deleted_rows
|
|
7634
8307
|
FOR EACH STATEMENT
|
|
7635
8308
|
EXECUTE FUNCTION cleanup_orphan_obligations_and_oracles();
|
|
8309
|
+
`);
|
|
8310
|
+
await driver.execute(`
|
|
8311
|
+
CREATE OR REPLACE FUNCTION create_offset_on_lot_delete()
|
|
8312
|
+
RETURNS trigger
|
|
8313
|
+
LANGUAGE plpgsql AS $$
|
|
8314
|
+
BEGIN
|
|
8315
|
+
INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", value)
|
|
8316
|
+
VALUES (
|
|
8317
|
+
OLD.chain_id,
|
|
8318
|
+
OLD."user",
|
|
8319
|
+
OLD.contract,
|
|
8320
|
+
OLD."group",
|
|
8321
|
+
OLD.upper::numeric - OLD.lower::numeric
|
|
8322
|
+
)
|
|
8323
|
+
ON CONFLICT (chain_id, "user", contract, "group") DO NOTHING;
|
|
8324
|
+
RETURN OLD;
|
|
8325
|
+
END;
|
|
8326
|
+
$$;
|
|
8327
|
+
`);
|
|
8328
|
+
await driver.execute(`
|
|
8329
|
+
CREATE OR REPLACE TRIGGER trg_lots_create_offset_before_delete
|
|
8330
|
+
BEFORE DELETE ON "${VERSION}"."lots"
|
|
8331
|
+
FOR EACH ROW
|
|
8332
|
+
EXECUTE FUNCTION create_offset_on_lot_delete();
|
|
8333
|
+
`);
|
|
8334
|
+
await driver.execute(`
|
|
8335
|
+
CREATE OR REPLACE FUNCTION delete_position_if_no_lots()
|
|
8336
|
+
RETURNS trigger
|
|
8337
|
+
LANGUAGE plpgsql AS $$
|
|
8338
|
+
BEGIN
|
|
8339
|
+
-- Check if any lots remain on this position
|
|
8340
|
+
IF NOT EXISTS (
|
|
8341
|
+
SELECT 1 FROM "${VERSION}"."lots" l
|
|
8342
|
+
WHERE l.chain_id = OLD.chain_id
|
|
8343
|
+
AND l.contract = OLD.contract
|
|
8344
|
+
AND l."user" = OLD."user"
|
|
8345
|
+
) THEN
|
|
8346
|
+
-- No lots remain, delete the position (cascades to offsets)
|
|
8347
|
+
DELETE FROM "${VERSION}"."positions" p
|
|
8348
|
+
WHERE p.chain_id = OLD.chain_id
|
|
8349
|
+
AND p.contract = OLD.contract
|
|
8350
|
+
AND p."user" = OLD."user";
|
|
8351
|
+
END IF;
|
|
8352
|
+
RETURN NULL;
|
|
8353
|
+
END;
|
|
8354
|
+
$$;
|
|
8355
|
+
`);
|
|
8356
|
+
await driver.execute(`
|
|
8357
|
+
CREATE OR REPLACE TRIGGER trg_lots_delete_position_if_empty
|
|
8358
|
+
AFTER DELETE ON "${VERSION}"."lots"
|
|
8359
|
+
FOR EACH ROW
|
|
8360
|
+
EXECUTE FUNCTION delete_position_if_no_lots();
|
|
7636
8361
|
`);
|
|
7637
8362
|
});
|
|
7638
8363
|
}
|
|
@@ -7664,22 +8389,24 @@ async function add(config, offers$1) {
|
|
|
7664
8389
|
const tree = from$12(offers$1.map((o) => from$11(o)));
|
|
7665
8390
|
const chainId = await getChainId(config.client);
|
|
7666
8391
|
for (const offer of tree.offers) if (chainId !== offer.chainId) throw new ChainIdMismatchError(offer.chainId, chainId);
|
|
8392
|
+
const signature = await sign(tree.offers, config.client);
|
|
8393
|
+
const encoded = await encode$2(tree, signature);
|
|
7667
8394
|
try {
|
|
7668
8395
|
return await config.client.sendTransaction({
|
|
7669
8396
|
chain: config.client.chain,
|
|
7670
8397
|
account: config.client.account,
|
|
7671
8398
|
to: config.mempoolAddress,
|
|
7672
|
-
data:
|
|
8399
|
+
data: encoded
|
|
7673
8400
|
});
|
|
7674
8401
|
} catch (error) {
|
|
7675
8402
|
throw new ViemClientError(error instanceof Error ? error.message : "Unknown error");
|
|
7676
8403
|
}
|
|
7677
8404
|
}
|
|
7678
8405
|
async function* get(config, parameters) {
|
|
7679
|
-
const { loanToken, blockNumberGte, blockNumberLte, order
|
|
8406
|
+
const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE } = {} } = parameters || {};
|
|
7680
8407
|
yield* streamOffers(config, {
|
|
7681
8408
|
loanToken,
|
|
7682
|
-
order
|
|
8409
|
+
order,
|
|
7683
8410
|
blockNumberGte,
|
|
7684
8411
|
blockNumberLte,
|
|
7685
8412
|
options: {
|
|
@@ -7701,7 +8428,7 @@ const getChainId = async (client) => {
|
|
|
7701
8428
|
return chainId;
|
|
7702
8429
|
};
|
|
7703
8430
|
async function* streamOffers(config, parameters) {
|
|
7704
|
-
const { loanToken, blockNumberGte, blockNumberLte, order
|
|
8431
|
+
const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = config.blockWindow } = {} } = parameters;
|
|
7705
8432
|
const stream = streamLogs({
|
|
7706
8433
|
client: config.client.extend(publicActions),
|
|
7707
8434
|
contractAddress: config.mempoolAddress,
|
|
@@ -7718,13 +8445,13 @@ async function* streamOffers(config, parameters) {
|
|
|
7718
8445
|
},
|
|
7719
8446
|
blockNumberGte,
|
|
7720
8447
|
blockNumberLte,
|
|
7721
|
-
order
|
|
8448
|
+
order,
|
|
7722
8449
|
options: {
|
|
7723
8450
|
maxBatchSize,
|
|
7724
8451
|
blockWindow
|
|
7725
8452
|
}
|
|
7726
8453
|
});
|
|
7727
|
-
let blockNumber = order
|
|
8454
|
+
let blockNumber = order === "asc" ? blockNumberGte : blockNumberLte;
|
|
7728
8455
|
for await (const { logs, blockNumber: newBlockNumber } of stream) {
|
|
7729
8456
|
blockNumber = newBlockNumber;
|
|
7730
8457
|
if (logs.length === 0) continue;
|
|
@@ -7733,7 +8460,7 @@ async function* streamOffers(config, parameters) {
|
|
|
7733
8460
|
if (!log) continue;
|
|
7734
8461
|
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
7735
8462
|
try {
|
|
7736
|
-
const tree = decode$2(payload);
|
|
8463
|
+
const { tree } = await decode$2(payload);
|
|
7737
8464
|
for (const offer of tree.offers) {
|
|
7738
8465
|
if (loanToken && offer.loanToken.toLowerCase() !== loanToken.toLowerCase()) continue;
|
|
7739
8466
|
offers$1.push({
|