@morpho-dev/router 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1854 -2122
- package/dist/index.browser.d.mts +111 -283
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.d.ts +111 -283
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +120 -451
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +120 -451
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +117 -299
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.d.ts +116 -298
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +4244 -4573
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +4245 -4568
- package/dist/index.node.mjs.map +1 -1
- package/docs/integrator.md +5 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -152,7 +152,7 @@ function startActiveSpan(tracer, name, fn) {
|
|
|
152
152
|
//#endregion
|
|
153
153
|
//#region package.json
|
|
154
154
|
var name = "@morpho-dev/router";
|
|
155
|
-
var version = "0.7.
|
|
155
|
+
var version = "0.7.1";
|
|
156
156
|
var description = "Router package for Morpho protocol";
|
|
157
157
|
|
|
158
158
|
//#endregion
|
|
@@ -1483,73 +1483,10 @@ function create$20(parameters) {
|
|
|
1483
1483
|
//#region src/core/Callback.ts
|
|
1484
1484
|
let Type$1 = /* @__PURE__ */ function(Type) {
|
|
1485
1485
|
Type["BuyWithEmptyCallback"] = "buy_with_empty_callback";
|
|
1486
|
-
Type["
|
|
1487
|
-
Type["BuyVaultV1Callback"] = "buy_vault_v1_callback";
|
|
1488
|
-
Type["SellERC20Callback"] = "sell_erc20_callback";
|
|
1486
|
+
Type["SellWithEmptyCallback"] = "sell_with_empty_callback";
|
|
1489
1487
|
return Type;
|
|
1490
1488
|
}({});
|
|
1491
1489
|
const isEmptyCallback = (offer) => offer.callback.data === "0x";
|
|
1492
|
-
function decode$1(type, data) {
|
|
1493
|
-
switch (type) {
|
|
1494
|
-
case Type$1.BuyERC20: return decodeBuyERC20(data);
|
|
1495
|
-
case Type$1.BuyVaultV1Callback: return decodeBuyVaultV1Callback(data);
|
|
1496
|
-
case Type$1.SellERC20Callback: return decodeSellERC20Callback(data);
|
|
1497
|
-
default: throw new Error("Invalid callback type");
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
/**
|
|
1501
|
-
* Decodes BuyERC20 callback data into positions.
|
|
1502
|
-
* @param data - The ABI-encoded callback data containing token addresses and amounts.
|
|
1503
|
-
* @returns Array of positions with contract address and amount.
|
|
1504
|
-
* @throws If data is empty, malformed, or arrays have mismatched lengths.
|
|
1505
|
-
*/
|
|
1506
|
-
function decodeBuyERC20(data) {
|
|
1507
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1508
|
-
let tokens;
|
|
1509
|
-
let amounts;
|
|
1510
|
-
try {
|
|
1511
|
-
[tokens, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1512
|
-
} catch (_) {
|
|
1513
|
-
throw new Error("Invalid BuyERC20 callback data");
|
|
1514
|
-
}
|
|
1515
|
-
if (tokens.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1516
|
-
return tokens.map((token, index) => ({
|
|
1517
|
-
contract: token,
|
|
1518
|
-
amount: amounts[index]
|
|
1519
|
-
}));
|
|
1520
|
-
}
|
|
1521
|
-
function decodeBuyVaultV1Callback(data) {
|
|
1522
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1523
|
-
try {
|
|
1524
|
-
const [vaults, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1525
|
-
if (vaults.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1526
|
-
return vaults.map((v, i) => ({
|
|
1527
|
-
contract: v,
|
|
1528
|
-
amount: amounts[i]
|
|
1529
|
-
}));
|
|
1530
|
-
} catch (_) {
|
|
1531
|
-
throw new Error("Invalid BuyVaultV1Callback callback data");
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
function decodeSellERC20Callback(data) {
|
|
1535
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1536
|
-
try {
|
|
1537
|
-
const [collaterals, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1538
|
-
if (collaterals.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1539
|
-
return collaterals.map((c, i) => ({
|
|
1540
|
-
contract: c,
|
|
1541
|
-
amount: amounts[i]
|
|
1542
|
-
}));
|
|
1543
|
-
} catch (_) {
|
|
1544
|
-
throw new Error("Invalid SellERC20Callback callback data");
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
function encodeBuyVaultV1Callback(parameters) {
|
|
1548
|
-
return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.vaults, parameters.amounts]);
|
|
1549
|
-
}
|
|
1550
|
-
function encodeSellERC20Callback(parameters) {
|
|
1551
|
-
return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.collaterals, parameters.amounts]);
|
|
1552
|
-
}
|
|
1553
1490
|
|
|
1554
1491
|
//#endregion
|
|
1555
1492
|
//#region src/core/Maturity.ts
|
|
@@ -1671,26 +1608,6 @@ var InvalidOptionError$1 = class extends BaseError {
|
|
|
1671
1608
|
|
|
1672
1609
|
//#endregion
|
|
1673
1610
|
//#region src/gatekeeper/GateConfig.ts
|
|
1674
|
-
/**
|
|
1675
|
-
* Attempts to infer the configured callback type from a callback address on a chain.
|
|
1676
|
-
* Skips the empty callback type as it does not carry addresses.
|
|
1677
|
-
*
|
|
1678
|
-
* @param chain - Chain name for which to infer the callback type
|
|
1679
|
-
* @param address - Callback contract address
|
|
1680
|
-
* @returns The callback type when found, otherwise undefined
|
|
1681
|
-
*/
|
|
1682
|
-
function getCallbackType(chain, address) {
|
|
1683
|
-
return configs[chain].callbacks?.find((c) => c.type !== Type$1.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
|
|
1684
|
-
}
|
|
1685
|
-
/**
|
|
1686
|
-
* Returns the list of allowed non-empty callback addresses for a chain.
|
|
1687
|
-
*
|
|
1688
|
-
* @param chain - Chain name
|
|
1689
|
-
* @returns Array of allowed callback addresses (lowercased). Empty when none configured
|
|
1690
|
-
*/
|
|
1691
|
-
const getCallbackAddresses = (chain) => {
|
|
1692
|
-
return configs[chain].callbacks?.filter((c) => c.type !== Type$1.BuyWithEmptyCallback).flatMap((c) => c.addresses) ?? [];
|
|
1693
|
-
};
|
|
1694
1611
|
const assets = {
|
|
1695
1612
|
[ChainId.ETHEREUM.toString()]: [
|
|
1696
1613
|
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
@@ -1762,63 +1679,19 @@ const oracles$1 = {
|
|
|
1762
1679
|
};
|
|
1763
1680
|
const configs = {
|
|
1764
1681
|
ethereum: {
|
|
1765
|
-
callbacks: [
|
|
1766
|
-
{
|
|
1767
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1768
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1769
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1770
|
-
},
|
|
1771
|
-
{
|
|
1772
|
-
type: Type$1.SellERC20Callback,
|
|
1773
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1774
|
-
},
|
|
1775
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1776
|
-
],
|
|
1682
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1777
1683
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1778
1684
|
},
|
|
1779
1685
|
base: {
|
|
1780
|
-
callbacks: [
|
|
1781
|
-
{
|
|
1782
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1783
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1784
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0xFf62A7c278C62eD665133147129245053Bbf5918"]
|
|
1785
|
-
},
|
|
1786
|
-
{
|
|
1787
|
-
type: Type$1.SellERC20Callback,
|
|
1788
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1789
|
-
},
|
|
1790
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1791
|
-
],
|
|
1686
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1792
1687
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1793
1688
|
},
|
|
1794
1689
|
"ethereum-virtual-testnet": {
|
|
1795
|
-
callbacks: [
|
|
1796
|
-
{
|
|
1797
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1798
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1799
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1800
|
-
},
|
|
1801
|
-
{
|
|
1802
|
-
type: Type$1.SellERC20Callback,
|
|
1803
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1804
|
-
},
|
|
1805
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1806
|
-
],
|
|
1690
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1807
1691
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1808
1692
|
},
|
|
1809
1693
|
anvil: {
|
|
1810
|
-
callbacks: [
|
|
1811
|
-
{
|
|
1812
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1813
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1814
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1815
|
-
},
|
|
1816
|
-
{
|
|
1817
|
-
type: Type$1.SellERC20Callback,
|
|
1818
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1819
|
-
},
|
|
1820
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1821
|
-
],
|
|
1694
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1822
1695
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1823
1696
|
}
|
|
1824
1697
|
};
|
|
@@ -2300,7 +2173,7 @@ function random(config) {
|
|
|
2300
2173
|
const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
|
|
2301
2174
|
const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
|
|
2302
2175
|
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
|
|
2303
|
-
|
|
2176
|
+
collateralCandidates[int(collateralCandidates.length)];
|
|
2304
2177
|
const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
|
|
2305
2178
|
const maturity = config?.maturity ?? from$16(maturityOption);
|
|
2306
2179
|
const lltv = from$15(weightedChoice([
|
|
@@ -2327,21 +2200,10 @@ function random(config) {
|
|
|
2327
2200
|
const unit = BigInt(10) ** BigInt(loanTokenDecimals);
|
|
2328
2201
|
const amountBase = BigInt(100 + int(999901));
|
|
2329
2202
|
const assetsScaled = config?.assets ?? amountBase * unit;
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
};
|
|
2335
|
-
const sellCallbackAddress = "0x3333333333333333333333333333333333333333";
|
|
2336
|
-
const amount = assetsScaled * 1000000000000000000000n;
|
|
2337
|
-
return {
|
|
2338
|
-
address: sellCallbackAddress,
|
|
2339
|
-
data: encodeSellERC20Callback({
|
|
2340
|
-
collaterals: [collateralAsset],
|
|
2341
|
-
amounts: [amount]
|
|
2342
|
-
})
|
|
2343
|
-
};
|
|
2344
|
-
})();
|
|
2203
|
+
const emptyCallback = {
|
|
2204
|
+
address: zeroAddress,
|
|
2205
|
+
data: "0x"
|
|
2206
|
+
};
|
|
2345
2207
|
return from$12({
|
|
2346
2208
|
maker: config?.maker ?? address(),
|
|
2347
2209
|
assets: assetsScaled,
|
|
@@ -2360,7 +2222,7 @@ function random(config) {
|
|
|
2360
2222
|
...random$1(),
|
|
2361
2223
|
lltv
|
|
2362
2224
|
})).sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
2363
|
-
callback: config?.callback ??
|
|
2225
|
+
callback: config?.callback ?? emptyCallback
|
|
2364
2226
|
});
|
|
2365
2227
|
}
|
|
2366
2228
|
const weightedChoice = (pairs) => {
|
|
@@ -2514,6 +2376,94 @@ function obligationId(offer) {
|
|
|
2514
2376
|
}));
|
|
2515
2377
|
}
|
|
2516
2378
|
/**
|
|
2379
|
+
* ABI for the Take event emitted by the Morpho V2 contract.
|
|
2380
|
+
*/
|
|
2381
|
+
const takeEvent = {
|
|
2382
|
+
type: "event",
|
|
2383
|
+
name: "Take",
|
|
2384
|
+
inputs: [
|
|
2385
|
+
{
|
|
2386
|
+
name: "caller",
|
|
2387
|
+
type: "address",
|
|
2388
|
+
indexed: false,
|
|
2389
|
+
internalType: "address"
|
|
2390
|
+
},
|
|
2391
|
+
{
|
|
2392
|
+
name: "id",
|
|
2393
|
+
type: "bytes32",
|
|
2394
|
+
indexed: true,
|
|
2395
|
+
internalType: "bytes32"
|
|
2396
|
+
},
|
|
2397
|
+
{
|
|
2398
|
+
name: "maker",
|
|
2399
|
+
type: "address",
|
|
2400
|
+
indexed: true,
|
|
2401
|
+
internalType: "address"
|
|
2402
|
+
},
|
|
2403
|
+
{
|
|
2404
|
+
name: "taker",
|
|
2405
|
+
type: "address",
|
|
2406
|
+
indexed: true,
|
|
2407
|
+
internalType: "address"
|
|
2408
|
+
},
|
|
2409
|
+
{
|
|
2410
|
+
name: "offerIsBuy",
|
|
2411
|
+
type: "bool",
|
|
2412
|
+
indexed: false,
|
|
2413
|
+
internalType: "bool"
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
name: "buyerAssets",
|
|
2417
|
+
type: "uint256",
|
|
2418
|
+
indexed: false,
|
|
2419
|
+
internalType: "uint256"
|
|
2420
|
+
},
|
|
2421
|
+
{
|
|
2422
|
+
name: "sellerAssets",
|
|
2423
|
+
type: "uint256",
|
|
2424
|
+
indexed: false,
|
|
2425
|
+
internalType: "uint256"
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
name: "obligationUnits",
|
|
2429
|
+
type: "uint256",
|
|
2430
|
+
indexed: false,
|
|
2431
|
+
internalType: "uint256"
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
name: "obligationShares",
|
|
2435
|
+
type: "uint256",
|
|
2436
|
+
indexed: false,
|
|
2437
|
+
internalType: "uint256"
|
|
2438
|
+
},
|
|
2439
|
+
{
|
|
2440
|
+
name: "buyerIsLender",
|
|
2441
|
+
type: "bool",
|
|
2442
|
+
indexed: false,
|
|
2443
|
+
internalType: "bool"
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
name: "sellerIsBorrower",
|
|
2447
|
+
type: "bool",
|
|
2448
|
+
indexed: false,
|
|
2449
|
+
internalType: "bool"
|
|
2450
|
+
},
|
|
2451
|
+
{
|
|
2452
|
+
name: "group",
|
|
2453
|
+
type: "bytes32",
|
|
2454
|
+
indexed: false,
|
|
2455
|
+
internalType: "bytes32"
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
name: "consumed",
|
|
2459
|
+
type: "uint256",
|
|
2460
|
+
indexed: false,
|
|
2461
|
+
internalType: "uint256"
|
|
2462
|
+
}
|
|
2463
|
+
],
|
|
2464
|
+
anonymous: false
|
|
2465
|
+
};
|
|
2466
|
+
/**
|
|
2517
2467
|
* ABI for the Consume event emitted by the Obligation contract.
|
|
2518
2468
|
*/
|
|
2519
2469
|
const consumedEvent = {
|
|
@@ -2939,12 +2889,10 @@ const maturity = ({ maturities }) => single("maturity", `Validates that offer ma
|
|
|
2939
2889
|
const allowedMaturities = maturities.map((m) => from$16(m));
|
|
2940
2890
|
if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}` };
|
|
2941
2891
|
});
|
|
2942
|
-
const callback = ({ callbacks
|
|
2943
|
-
if (isEmptyCallback(offer)
|
|
2944
|
-
if (isEmptyCallback(offer) &&
|
|
2945
|
-
if (
|
|
2946
|
-
if (!allowedAddresses.includes(offer.callback.address?.toLowerCase())) return { message: `Callback address ${offer.callback.address} is not allowed.` };
|
|
2947
|
-
}
|
|
2892
|
+
const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy empty callback is ${callbacks.includes(Type$1.BuyWithEmptyCallback) ? "allowed" : "not allowed"}; sell empty callback is ${callbacks.includes(Type$1.SellWithEmptyCallback) ? "allowed" : "not allowed"}; non-empty callbacks are rejected`, (offer) => {
|
|
2893
|
+
if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
|
|
2894
|
+
if (isEmptyCallback(offer) && offer.buy && !callbacks.includes(Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
|
|
2895
|
+
if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
|
|
2948
2896
|
});
|
|
2949
2897
|
/**
|
|
2950
2898
|
* A validation rule that checks if the offer's tokens are allowed for its chain.
|
|
@@ -3009,12 +2957,8 @@ const morphoRules = (chains) => {
|
|
|
3009
2957
|
chains$1({ chains }),
|
|
3010
2958
|
maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
|
|
3011
2959
|
callback({
|
|
3012
|
-
callbacks: [
|
|
3013
|
-
|
|
3014
|
-
Type$1.BuyVaultV1Callback,
|
|
3015
|
-
Type$1.SellERC20Callback
|
|
3016
|
-
],
|
|
3017
|
-
allowedAddresses: chains.flatMap((c) => getCallbackAddresses(c.name))
|
|
2960
|
+
callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
|
|
2961
|
+
allowedAddresses: []
|
|
3018
2962
|
}),
|
|
3019
2963
|
token({ assetsByChainId }),
|
|
3020
2964
|
oracle({ oraclesByChainId })
|
|
@@ -3031,25 +2975,13 @@ const morphoRules = (chains) => {
|
|
|
3031
2975
|
function buildConfigRules(chains) {
|
|
3032
2976
|
const rules = [];
|
|
3033
2977
|
for (const chain of chains) {
|
|
3034
|
-
const
|
|
3035
|
-
const maturities = config.maturities ?? [];
|
|
2978
|
+
const maturities = configs[chain.name].maturities ?? [];
|
|
3036
2979
|
for (const maturityName of maturities) rules.push({
|
|
3037
2980
|
type: "maturity",
|
|
3038
2981
|
chain_id: chain.id,
|
|
3039
2982
|
name: maturityName,
|
|
3040
2983
|
timestamp: from$16(maturityName)
|
|
3041
2984
|
});
|
|
3042
|
-
const callbacks = config.callbacks ?? [];
|
|
3043
|
-
for (const callback of callbacks) {
|
|
3044
|
-
if (callback.type === Type$1.BuyWithEmptyCallback) continue;
|
|
3045
|
-
if (!("addresses" in callback)) continue;
|
|
3046
|
-
for (const address of callback.addresses) rules.push({
|
|
3047
|
-
type: "callback",
|
|
3048
|
-
chain_id: chain.id,
|
|
3049
|
-
address: normalizeAddress(address),
|
|
3050
|
-
callback_type: callback.type
|
|
3051
|
-
});
|
|
3052
|
-
}
|
|
3053
2985
|
const loanTokens = assets[chain.id.toString()] ?? [];
|
|
3054
2986
|
for (const address of loanTokens) rules.push({
|
|
3055
2987
|
type: "loan_token",
|
|
@@ -3367,8 +3299,8 @@ const offerExample = {
|
|
|
3367
3299
|
price: "2750000000000000000",
|
|
3368
3300
|
group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
|
|
3369
3301
|
session: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3370
|
-
callback: "
|
|
3371
|
-
callback_data: "
|
|
3302
|
+
callback: "0x0000000000000000000000000000000000000000",
|
|
3303
|
+
callback_data: "0x"
|
|
3372
3304
|
},
|
|
3373
3305
|
offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
|
|
3374
3306
|
obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
|
|
@@ -3420,25 +3352,10 @@ const validateOfferExample = {
|
|
|
3420
3352
|
lltv: "860000000000000000"
|
|
3421
3353
|
}],
|
|
3422
3354
|
callback: {
|
|
3423
|
-
address: "
|
|
3424
|
-
data: "
|
|
3355
|
+
address: "0x0000000000000000000000000000000000000000",
|
|
3356
|
+
data: "0x"
|
|
3425
3357
|
}
|
|
3426
3358
|
};
|
|
3427
|
-
const callbackTypesRequestExample = { callbacks: [{
|
|
3428
|
-
chain_id: 1,
|
|
3429
|
-
addresses: [
|
|
3430
|
-
"0x1111111111111111111111111111111111111111",
|
|
3431
|
-
"0x3333333333333333333333333333333333333333",
|
|
3432
|
-
"0x9999999999999999999999999999999999999999"
|
|
3433
|
-
]
|
|
3434
|
-
}] };
|
|
3435
|
-
const callbackTypesResponseExample = [{
|
|
3436
|
-
chain_id: 1,
|
|
3437
|
-
sell_erc20_callback: ["0x1111111111111111111111111111111111111111"],
|
|
3438
|
-
buy_erc20: ["0x5555555555555555555555555555555555555555"],
|
|
3439
|
-
buy_vault_v1_callback: ["0x3333333333333333333333333333333333333333"],
|
|
3440
|
-
not_supported: ["0x9999999999999999999999999999999999999999"]
|
|
3441
|
-
}];
|
|
3442
3359
|
const routerStatusExample = {
|
|
3443
3360
|
status: "live",
|
|
3444
3361
|
initialized: true,
|
|
@@ -3507,55 +3424,6 @@ __decorate([ApiProperty({
|
|
|
3507
3424
|
type: "string",
|
|
3508
3425
|
example: validateOfferExample.callback.data
|
|
3509
3426
|
})], ValidateCallbackRequest.prototype, "data", void 0);
|
|
3510
|
-
var CallbackTypesChainRequest = class {};
|
|
3511
|
-
__decorate([ApiProperty({
|
|
3512
|
-
type: "number",
|
|
3513
|
-
example: callbackTypesRequestExample.callbacks[0].chain_id
|
|
3514
|
-
})], CallbackTypesChainRequest.prototype, "chain_id", void 0);
|
|
3515
|
-
__decorate([ApiProperty({
|
|
3516
|
-
type: () => [String],
|
|
3517
|
-
example: callbackTypesRequestExample.callbacks[0].addresses
|
|
3518
|
-
})], CallbackTypesChainRequest.prototype, "addresses", void 0);
|
|
3519
|
-
var CallbackTypesRequest = class {};
|
|
3520
|
-
__decorate([ApiProperty({
|
|
3521
|
-
type: () => [CallbackTypesChainRequest],
|
|
3522
|
-
example: callbackTypesRequestExample.callbacks
|
|
3523
|
-
})], CallbackTypesRequest.prototype, "callbacks", void 0);
|
|
3524
|
-
var CallbackTypesChainResponse = class {};
|
|
3525
|
-
__decorate([ApiProperty({
|
|
3526
|
-
type: "number",
|
|
3527
|
-
example: callbackTypesResponseExample[0].chain_id
|
|
3528
|
-
})], CallbackTypesChainResponse.prototype, "chain_id", void 0);
|
|
3529
|
-
__decorate([ApiProperty({
|
|
3530
|
-
type: () => [String],
|
|
3531
|
-
required: false,
|
|
3532
|
-
example: callbackTypesResponseExample[0].buy_vault_v1_callback
|
|
3533
|
-
})], CallbackTypesChainResponse.prototype, "buy_vault_v1_callback", void 0);
|
|
3534
|
-
__decorate([ApiProperty({
|
|
3535
|
-
type: () => [String],
|
|
3536
|
-
required: false,
|
|
3537
|
-
example: callbackTypesResponseExample[0].sell_erc20_callback
|
|
3538
|
-
})], CallbackTypesChainResponse.prototype, "sell_erc20_callback", void 0);
|
|
3539
|
-
__decorate([ApiProperty({
|
|
3540
|
-
type: () => [String],
|
|
3541
|
-
required: false,
|
|
3542
|
-
example: callbackTypesResponseExample[0].buy_erc20
|
|
3543
|
-
})], CallbackTypesChainResponse.prototype, "buy_erc20", void 0);
|
|
3544
|
-
__decorate([ApiProperty({
|
|
3545
|
-
type: () => [String],
|
|
3546
|
-
example: callbackTypesResponseExample[0].not_supported
|
|
3547
|
-
})], CallbackTypesChainResponse.prototype, "not_supported", void 0);
|
|
3548
|
-
var CallbackTypesSuccessResponse = class extends SuccessResponse {};
|
|
3549
|
-
__decorate([ApiProperty({
|
|
3550
|
-
type: "string",
|
|
3551
|
-
nullable: true,
|
|
3552
|
-
example: "maturity:1:1730415600:end_of_next_month"
|
|
3553
|
-
})], CallbackTypesSuccessResponse.prototype, "cursor", void 0);
|
|
3554
|
-
__decorate([ApiProperty({
|
|
3555
|
-
type: () => [CallbackTypesChainResponse],
|
|
3556
|
-
description: "Callback types grouped by chain.",
|
|
3557
|
-
example: callbackTypesResponseExample
|
|
3558
|
-
})], CallbackTypesSuccessResponse.prototype, "data", void 0);
|
|
3559
3427
|
var AskResponse = class {};
|
|
3560
3428
|
__decorate([ApiProperty({
|
|
3561
3429
|
type: "string",
|
|
@@ -4073,7 +3941,7 @@ __decorate([
|
|
|
4073
3941
|
methods: ["post"],
|
|
4074
3942
|
path: "/v1/validate",
|
|
4075
3943
|
summary: "Validate offers",
|
|
4076
|
-
description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
3944
|
+
description: "Validates offers against router validation rules. Only empty callbacks (zero address, 0x data) are accepted. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
4077
3945
|
}),
|
|
4078
3946
|
ApiBody({ type: ValidateOffersRequest }),
|
|
4079
3947
|
ApiResponse({
|
|
@@ -4092,28 +3960,6 @@ ValidateController = __decorate([ApiTags("Make"), ApiResponse({
|
|
|
4092
3960
|
description: "Bad Request",
|
|
4093
3961
|
type: BadRequestResponse
|
|
4094
3962
|
})], ValidateController);
|
|
4095
|
-
let CallbacksController = class CallbacksController {
|
|
4096
|
-
async resolveCallbackTypes() {}
|
|
4097
|
-
};
|
|
4098
|
-
__decorate([
|
|
4099
|
-
ApiOperation({
|
|
4100
|
-
methods: ["post"],
|
|
4101
|
-
path: "/v1/callbacks",
|
|
4102
|
-
summary: "Resolve callback types",
|
|
4103
|
-
description: "Returns callback types for callback addresses grouped by chain."
|
|
4104
|
-
}),
|
|
4105
|
-
ApiBody({ type: CallbackTypesRequest }),
|
|
4106
|
-
ApiResponse({
|
|
4107
|
-
status: 200,
|
|
4108
|
-
description: "Success",
|
|
4109
|
-
type: CallbackTypesSuccessResponse
|
|
4110
|
-
})
|
|
4111
|
-
], CallbacksController.prototype, "resolveCallbackTypes", null);
|
|
4112
|
-
CallbacksController = __decorate([ApiTags("Make"), ApiResponse({
|
|
4113
|
-
status: 400,
|
|
4114
|
-
description: "Bad Request",
|
|
4115
|
-
type: BadRequestResponse
|
|
4116
|
-
})], CallbacksController);
|
|
4117
3963
|
let OffersController = class OffersController {
|
|
4118
3964
|
async getOffers() {}
|
|
4119
3965
|
};
|
|
@@ -4263,12 +4109,6 @@ const configRulesMaturityExample = {
|
|
|
4263
4109
|
name: "end_of_next_month",
|
|
4264
4110
|
timestamp: 1730415600
|
|
4265
4111
|
};
|
|
4266
|
-
const configRulesCallbackExample = {
|
|
4267
|
-
type: "callback",
|
|
4268
|
-
chain_id: 1,
|
|
4269
|
-
address: "0x1111111111111111111111111111111111111111",
|
|
4270
|
-
callback_type: "sell_erc20_callback"
|
|
4271
|
-
};
|
|
4272
4112
|
const configRulesLoanTokenExample = {
|
|
4273
4113
|
type: "loan_token",
|
|
4274
4114
|
chain_id: 1,
|
|
@@ -4282,7 +4122,6 @@ const configRulesOracleExample = {
|
|
|
4282
4122
|
const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
|
|
4283
4123
|
const configRulesPayloadExample = [
|
|
4284
4124
|
configRulesMaturityExample,
|
|
4285
|
-
configRulesCallbackExample,
|
|
4286
4125
|
configRulesLoanTokenExample,
|
|
4287
4126
|
configRulesOracleExample
|
|
4288
4127
|
];
|
|
@@ -4347,14 +4186,9 @@ __decorate([ApiProperty({
|
|
|
4347
4186
|
})], ConfigRulesRuleResponse.prototype, "timestamp", void 0);
|
|
4348
4187
|
__decorate([ApiProperty({
|
|
4349
4188
|
type: "string",
|
|
4350
|
-
example:
|
|
4189
|
+
example: configRulesLoanTokenExample.address,
|
|
4351
4190
|
required: false
|
|
4352
4191
|
})], ConfigRulesRuleResponse.prototype, "address", void 0);
|
|
4353
|
-
__decorate([ApiProperty({
|
|
4354
|
-
type: "string",
|
|
4355
|
-
example: configRulesCallbackExample.callback_type,
|
|
4356
|
-
required: false
|
|
4357
|
-
})], ConfigRulesRuleResponse.prototype, "callback_type", void 0);
|
|
4358
4192
|
var ConfigRulesSuccessResponse = class {};
|
|
4359
4193
|
__decorate([ApiProperty({ type: () => ConfigRulesMeta })], ConfigRulesSuccessResponse.prototype, "meta", void 0);
|
|
4360
4194
|
__decorate([ApiProperty({
|
|
@@ -4415,7 +4249,7 @@ __decorate([
|
|
|
4415
4249
|
methods: ["get"],
|
|
4416
4250
|
path: "/v1/config/rules",
|
|
4417
4251
|
summary: "Get config rules",
|
|
4418
|
-
description: "Returns configured rules for supported chains."
|
|
4252
|
+
description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
|
|
4419
4253
|
}),
|
|
4420
4254
|
ApiQuery({
|
|
4421
4255
|
name: "cursor",
|
|
@@ -4594,8 +4428,7 @@ const OpenApi = async () => {
|
|
|
4594
4428
|
ObligationsController,
|
|
4595
4429
|
HealthController,
|
|
4596
4430
|
UsersController,
|
|
4597
|
-
ValidateController
|
|
4598
|
-
CallbacksController
|
|
4431
|
+
ValidateController
|
|
4599
4432
|
],
|
|
4600
4433
|
document: {
|
|
4601
4434
|
openapi: "3.1.0",
|
|
@@ -4851,16 +4684,6 @@ const GetBookParams = z$2.object({
|
|
|
4851
4684
|
})
|
|
4852
4685
|
});
|
|
4853
4686
|
const ValidateOffersBody = z$2.object({ offers: z$2.array(z$2.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
|
|
4854
|
-
const CallbackTypesBody = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
4855
|
-
chain_id: z$2.number().int().positive().meta({
|
|
4856
|
-
description: "Chain id.",
|
|
4857
|
-
example: 1
|
|
4858
|
-
}),
|
|
4859
|
-
addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Callback address must be a valid 20-byte address" }).transform((val) => val.toLowerCase())).meta({
|
|
4860
|
-
description: "Callback contract addresses.",
|
|
4861
|
-
example: ["0x1111111111111111111111111111111111111111", "0x3333333333333333333333333333333333333333"]
|
|
4862
|
-
})
|
|
4863
|
-
}).strict()) }).strict();
|
|
4864
4687
|
const GetUserPositionsParams = z$2.object({
|
|
4865
4688
|
...PaginationQueryParams.shape,
|
|
4866
4689
|
user_address: z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
|
|
@@ -4879,7 +4702,6 @@ const schemas = {
|
|
|
4879
4702
|
get_obligation: GetObligationParams,
|
|
4880
4703
|
get_book: GetBookParams,
|
|
4881
4704
|
validate_offers: ValidateOffersBody,
|
|
4882
|
-
callback_types: CallbackTypesBody,
|
|
4883
4705
|
get_user_positions: GetUserPositionsParams
|
|
4884
4706
|
};
|
|
4885
4707
|
function safeParse(action, query, error) {
|
|
@@ -5135,35 +4957,6 @@ async function validateOffers(body, gatekeeper) {
|
|
|
5135
4957
|
}
|
|
5136
4958
|
}
|
|
5137
4959
|
|
|
5138
|
-
//#endregion
|
|
5139
|
-
//#region src/gatekeeper/CallbackTypes.ts
|
|
5140
|
-
/**
|
|
5141
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
5142
|
-
* @param parameters - Resolve parameters. {@link resolveCallbackTypes.Parameters}
|
|
5143
|
-
* @returns Callback types grouped by chain. {@link resolveCallbackTypes.ReturnType}
|
|
5144
|
-
* @throws If a chain id is unknown.
|
|
5145
|
-
*/
|
|
5146
|
-
function resolveCallbackTypes$2(parameters) {
|
|
5147
|
-
const { chains, request } = parameters;
|
|
5148
|
-
const chainsById = new Map(chains.map((chain) => [chain.id, chain]));
|
|
5149
|
-
return request.callbacks.map(({ chain_id, addresses }) => {
|
|
5150
|
-
const chain = chainsById.get(chain_id);
|
|
5151
|
-
if (!chain) throw new Error(`Unknown chain id ${chain_id}`);
|
|
5152
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
5153
|
-
const uniqueAddresses = new Set(addresses.map((address) => address.toLowerCase()));
|
|
5154
|
-
for (const address of uniqueAddresses) {
|
|
5155
|
-
const bucketKey = getCallbackType(chain.name, address) ?? "not_supported";
|
|
5156
|
-
const list = buckets.get(bucketKey) ?? [];
|
|
5157
|
-
list.push(address);
|
|
5158
|
-
buckets.set(bucketKey, list);
|
|
5159
|
-
}
|
|
5160
|
-
const response = { chain_id };
|
|
5161
|
-
for (const [type, list] of buckets.entries()) response[type] = list;
|
|
5162
|
-
if (!response.not_supported) response.not_supported = [];
|
|
5163
|
-
return response;
|
|
5164
|
-
});
|
|
5165
|
-
}
|
|
5166
|
-
|
|
5167
4960
|
//#endregion
|
|
5168
4961
|
//#region src/gatekeeper/Service.ts
|
|
5169
4962
|
/**
|
|
@@ -5171,10 +4964,6 @@ function resolveCallbackTypes$2(parameters) {
|
|
|
5171
4964
|
* @param parameters - App parameters including the {@link Gatekeeper} instance.
|
|
5172
4965
|
* @returns Hono app exposing gatekeeper endpoints.
|
|
5173
4966
|
*/
|
|
5174
|
-
const CallbackTypesRequestSchema = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
5175
|
-
chain_id: z$2.number(),
|
|
5176
|
-
addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/))
|
|
5177
|
-
})) });
|
|
5178
4967
|
function createApp(parameters) {
|
|
5179
4968
|
const { gatekeeper, chainRegistry } = parameters;
|
|
5180
4969
|
const app = new Hono();
|
|
@@ -5183,12 +4972,12 @@ function createApp(parameters) {
|
|
|
5183
4972
|
try {
|
|
5184
4973
|
body = await c.req.json();
|
|
5185
4974
|
} catch (err) {
|
|
5186
|
-
const failure$
|
|
5187
|
-
return c.json(failure$
|
|
4975
|
+
const failure$4 = failure(err);
|
|
4976
|
+
return c.json(failure$4.body, failure$4.statusCode);
|
|
5188
4977
|
}
|
|
5189
4978
|
if (body === null || typeof body !== "object") {
|
|
5190
|
-
const failure$
|
|
5191
|
-
return c.json(failure$
|
|
4979
|
+
const failure$3 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
4980
|
+
return c.json(failure$3.body, failure$3.statusCode);
|
|
5192
4981
|
}
|
|
5193
4982
|
const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
|
|
5194
4983
|
return c.json(payload, statusCode);
|
|
@@ -5197,34 +4986,6 @@ function createApp(parameters) {
|
|
|
5197
4986
|
const { statusCode, body } = await getConfigRules(c.req.query(), chainRegistry.list());
|
|
5198
4987
|
return c.json(body, statusCode);
|
|
5199
4988
|
});
|
|
5200
|
-
app.post("/v1/callbacks", async (c) => {
|
|
5201
|
-
let body;
|
|
5202
|
-
try {
|
|
5203
|
-
body = await c.req.json();
|
|
5204
|
-
} catch (err) {
|
|
5205
|
-
const failure$8 = failure(err);
|
|
5206
|
-
return c.json(failure$8.body, failure$8.statusCode);
|
|
5207
|
-
}
|
|
5208
|
-
if (body === null || typeof body !== "object") {
|
|
5209
|
-
const failure$6 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
5210
|
-
return c.json(failure$6.body, failure$6.statusCode);
|
|
5211
|
-
}
|
|
5212
|
-
try {
|
|
5213
|
-
const request = CallbackTypesRequestSchema.parse(body);
|
|
5214
|
-
const chainIds = new Set(chainRegistry.list().map((chain) => chain.id));
|
|
5215
|
-
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
5216
|
-
if (unknown) throw new BadRequestError(`Unknown chain id ${unknown.chain_id}`);
|
|
5217
|
-
const data = resolveCallbackTypes$2({
|
|
5218
|
-
chains: chainRegistry.list(),
|
|
5219
|
-
request
|
|
5220
|
-
});
|
|
5221
|
-
const response = success({ data });
|
|
5222
|
-
return c.json(response.body, response.statusCode);
|
|
5223
|
-
} catch (err) {
|
|
5224
|
-
const failure$7 = failure(err);
|
|
5225
|
-
return c.json(failure$7.body, failure$7.statusCode);
|
|
5226
|
-
}
|
|
5227
|
-
});
|
|
5228
4989
|
return app;
|
|
5229
4990
|
}
|
|
5230
4991
|
/**
|
|
@@ -5320,1559 +5081,8 @@ function now() {
|
|
|
5320
5081
|
}
|
|
5321
5082
|
|
|
5322
5083
|
//#endregion
|
|
5323
|
-
//#region src/
|
|
5324
|
-
|
|
5325
|
-
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5326
|
-
const logger = getLogger();
|
|
5327
|
-
let startBlock = blockNumber;
|
|
5328
|
-
let reorgDetected = false;
|
|
5329
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5330
|
-
const stream = streamLogs({
|
|
5331
|
-
client,
|
|
5332
|
-
contractAddress: client.chain.custom.morpho.address,
|
|
5333
|
-
event: consumedEvent,
|
|
5334
|
-
blockNumberGte: blockNumber,
|
|
5335
|
-
blockNumberLte: latestBlockNumberChain,
|
|
5336
|
-
order: "asc",
|
|
5337
|
-
options: {
|
|
5338
|
-
maxBatchSize,
|
|
5339
|
-
blockWindow
|
|
5340
|
-
}
|
|
5341
|
-
});
|
|
5342
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5343
|
-
const parsedLogs = parseEventLogs({
|
|
5344
|
-
abi: [consumedEvent],
|
|
5345
|
-
logs
|
|
5346
|
-
});
|
|
5347
|
-
const events = [];
|
|
5348
|
-
for (const log of parsedLogs) {
|
|
5349
|
-
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
5350
|
-
logger.debug({
|
|
5351
|
-
collector,
|
|
5352
|
-
chainId: client.chain.id,
|
|
5353
|
-
msg: "Skipping log because it is missing required fields"
|
|
5354
|
-
});
|
|
5355
|
-
continue;
|
|
5356
|
-
}
|
|
5357
|
-
events.push({
|
|
5358
|
-
id: `${log.blockNumber.toString()}-${log.logIndex.toString()}-${client.chain.id}-${log.transactionHash}`,
|
|
5359
|
-
chainId: client.chain.id,
|
|
5360
|
-
maker: log.args.user,
|
|
5361
|
-
group: log.args.group,
|
|
5362
|
-
amount: log.args.amount,
|
|
5363
|
-
blockNumber: Number(log.blockNumber)
|
|
5364
|
-
});
|
|
5365
|
-
}
|
|
5366
|
-
await db.transaction(async (dbTx) => {
|
|
5367
|
-
try {
|
|
5368
|
-
await dbTx.consumed.create(events);
|
|
5369
|
-
if (events.length > 0) logger.info({
|
|
5370
|
-
msg: `Events indexed`,
|
|
5371
|
-
collector,
|
|
5372
|
-
count: events.length,
|
|
5373
|
-
chain_id: client.chain.id,
|
|
5374
|
-
block_range: [startBlock, lastStreamBlockNumber]
|
|
5375
|
-
});
|
|
5376
|
-
} catch (err) {
|
|
5377
|
-
logger.error({
|
|
5378
|
-
err,
|
|
5379
|
-
msg: "Failed to process offer_consumed events"
|
|
5380
|
-
});
|
|
5381
|
-
}
|
|
5382
|
-
blockNumber = lastStreamBlockNumber;
|
|
5383
|
-
try {
|
|
5384
|
-
await dbTx.blocks.advanceCollector({
|
|
5385
|
-
collectorName: collector,
|
|
5386
|
-
chainId: client.chain.id,
|
|
5387
|
-
blockNumber,
|
|
5388
|
-
epoch
|
|
5389
|
-
});
|
|
5390
|
-
} catch (_) {
|
|
5391
|
-
try {
|
|
5392
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
5393
|
-
collectorName: collector,
|
|
5394
|
-
chainId: client.chain.id
|
|
5395
|
-
});
|
|
5396
|
-
blockNumber = ancestor.blockNumber;
|
|
5397
|
-
const deleted = await dbTx.consumed.delete({
|
|
5398
|
-
chainId: client.chain.id,
|
|
5399
|
-
blockNumberGte: blockNumber + 1
|
|
5400
|
-
});
|
|
5401
|
-
logger.info({
|
|
5402
|
-
collector,
|
|
5403
|
-
chain_id: client.chain.id,
|
|
5404
|
-
msg: `Reorg detected, events deleted`,
|
|
5405
|
-
count: deleted,
|
|
5406
|
-
block_number: blockNumber
|
|
5407
|
-
});
|
|
5408
|
-
await dbTx.blocks.advanceCollector({
|
|
5409
|
-
collectorName: collector,
|
|
5410
|
-
chainId: client.chain.id,
|
|
5411
|
-
blockNumber,
|
|
5412
|
-
epoch: ancestor.epoch
|
|
5413
|
-
});
|
|
5414
|
-
reorgDetected = true;
|
|
5415
|
-
} catch (err) {
|
|
5416
|
-
const msg = "Failed to delete consumed events when handling reorg.";
|
|
5417
|
-
logger.error({
|
|
5418
|
-
collector,
|
|
5419
|
-
chainId: client.chain.id,
|
|
5420
|
-
msg,
|
|
5421
|
-
err
|
|
5422
|
-
});
|
|
5423
|
-
throw new Error(msg);
|
|
5424
|
-
}
|
|
5425
|
-
}
|
|
5426
|
-
});
|
|
5427
|
-
if (reorgDetected) return;
|
|
5428
|
-
yield blockNumber;
|
|
5429
|
-
startBlock = blockNumber;
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
|
|
5433
|
-
//#endregion
|
|
5434
|
-
//#region src/indexer/collectors/CollectFunctions/collectOffers.ts
|
|
5435
|
-
async function* collectOffersV2(parameters) {
|
|
5436
|
-
let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5437
|
-
const logger = getLogger();
|
|
5438
|
-
let startBlock = blockNumber;
|
|
5439
|
-
let reorgDetected = false;
|
|
5440
|
-
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
5441
|
-
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
5442
|
-
logger.error({
|
|
5443
|
-
msg,
|
|
5444
|
-
chain_id: client.chain.id
|
|
5445
|
-
});
|
|
5446
|
-
throw new Error(msg);
|
|
5447
|
-
}
|
|
5448
|
-
const signatureDomain = {
|
|
5449
|
-
chainId: client.chain.id,
|
|
5450
|
-
verifyingContract: client.chain.custom.morpho.address
|
|
5451
|
-
};
|
|
5452
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5453
|
-
const stream = streamLogs({
|
|
5454
|
-
client,
|
|
5455
|
-
contractAddress: client.chain.custom.mempool.address,
|
|
5456
|
-
event: {
|
|
5457
|
-
type: "event",
|
|
5458
|
-
name: "Event",
|
|
5459
|
-
inputs: [{
|
|
5460
|
-
name: "data",
|
|
5461
|
-
type: "bytes",
|
|
5462
|
-
indexed: false,
|
|
5463
|
-
internalType: "bytes"
|
|
5464
|
-
}],
|
|
5465
|
-
anonymous: false
|
|
5466
|
-
},
|
|
5467
|
-
blockNumberGte: blockNumber,
|
|
5468
|
-
blockNumberLte: latestBlockNumberChain,
|
|
5469
|
-
order: "asc",
|
|
5470
|
-
options: {
|
|
5471
|
-
maxBatchSize,
|
|
5472
|
-
blockWindow
|
|
5473
|
-
}
|
|
5474
|
-
});
|
|
5475
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5476
|
-
blockNumber = lastStreamBlockNumber;
|
|
5477
|
-
const decodedTrees = [];
|
|
5478
|
-
for (const log of logs) {
|
|
5479
|
-
if (!log) continue;
|
|
5480
|
-
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
5481
|
-
try {
|
|
5482
|
-
const { tree, signature, signer } = await decode(payload, signatureDomain);
|
|
5483
|
-
const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
|
|
5484
|
-
if (signerMismatch) {
|
|
5485
|
-
logger.debug({
|
|
5486
|
-
msg: "Tree rejected: signer mismatch",
|
|
5487
|
-
reason: "signer_mismatch",
|
|
5488
|
-
signer,
|
|
5489
|
-
maker: signerMismatch.maker,
|
|
5490
|
-
chain_id: client.chain.id
|
|
5491
|
-
});
|
|
5492
|
-
continue;
|
|
5493
|
-
}
|
|
5494
|
-
decodedTrees.push({
|
|
5495
|
-
tree,
|
|
5496
|
-
signature,
|
|
5497
|
-
signer,
|
|
5498
|
-
blockNumber: Number(log.blockNumber)
|
|
5499
|
-
});
|
|
5500
|
-
} catch (err) {
|
|
5501
|
-
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
5502
|
-
logger.debug({
|
|
5503
|
-
msg: "Tree decode failed",
|
|
5504
|
-
reason,
|
|
5505
|
-
chain_id: client.chain.id,
|
|
5506
|
-
err: err instanceof Error ? err.message : String(err)
|
|
5507
|
-
});
|
|
5508
|
-
}
|
|
5509
|
-
}
|
|
5510
|
-
await db.transaction(async (dbTx) => {
|
|
5511
|
-
const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
5512
|
-
const treesToInsert = [];
|
|
5513
|
-
let totalValidOffers = 0;
|
|
5514
|
-
const offersWithBlock = [];
|
|
5515
|
-
for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
|
|
5516
|
-
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
5517
|
-
const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
|
|
5518
|
-
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
5519
|
-
if (allowedResults.issues.length > 0) {
|
|
5520
|
-
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
5521
|
-
logger.debug({
|
|
5522
|
-
msg: "Tree offers rejected by gatekeeper",
|
|
5523
|
-
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
5524
|
-
chain_id: client.chain.id,
|
|
5525
|
-
issues_count: allowedResults.issues.length
|
|
5526
|
-
});
|
|
5527
|
-
} else if (hasBlockWindowViolation) logger.debug({
|
|
5528
|
-
msg: "Tree rejected: offers outside block window",
|
|
5529
|
-
reason: "block_window",
|
|
5530
|
-
chain_id: client.chain.id
|
|
5531
|
-
});
|
|
5532
|
-
continue;
|
|
5533
|
-
}
|
|
5534
|
-
treesToInsert.push({
|
|
5535
|
-
tree,
|
|
5536
|
-
signature
|
|
5537
|
-
});
|
|
5538
|
-
totalValidOffers += tree.offers.length;
|
|
5539
|
-
offersWithBlock.push(...tree.offers.map((offer) => ({
|
|
5540
|
-
offer,
|
|
5541
|
-
blockNumber: treeBlockNumber
|
|
5542
|
-
})));
|
|
5543
|
-
} catch (err) {
|
|
5544
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5545
|
-
logger.error({
|
|
5546
|
-
err: error,
|
|
5547
|
-
msg: "Gatekeeper validation failed",
|
|
5548
|
-
chain_id: client.chain.id
|
|
5549
|
-
});
|
|
5550
|
-
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
5551
|
-
}
|
|
5552
|
-
const dependencies = buildOfferDependencies$1(offersWithBlock);
|
|
5553
|
-
await dbTx.oracles.upsert(dependencies.oracles);
|
|
5554
|
-
await dbTx.obligations.create(dependencies.obligations);
|
|
5555
|
-
await dbTx.groups.create(dependencies.groups);
|
|
5556
|
-
const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
|
|
5557
|
-
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
5558
|
-
const insertedOffers = filterInsertedOffers({
|
|
5559
|
-
offers: offersWithBlock,
|
|
5560
|
-
hashes: insertedHashes
|
|
5561
|
-
});
|
|
5562
|
-
const { callbacks, positions, lots } = await decodeCallbacks({
|
|
5563
|
-
chainId: client.chain.id,
|
|
5564
|
-
gatekeeper,
|
|
5565
|
-
offers: insertedOffers
|
|
5566
|
-
});
|
|
5567
|
-
if (positions.length > 0) await dbTx.positions.upsert(positions);
|
|
5568
|
-
if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
|
|
5569
|
-
if (lots.length > 0) await dbTx.lots.create(lots);
|
|
5570
|
-
try {
|
|
5571
|
-
await dbTx.blocks.advanceCollector({
|
|
5572
|
-
collectorName: collector,
|
|
5573
|
-
chainId: client.chain.id,
|
|
5574
|
-
blockNumber,
|
|
5575
|
-
epoch
|
|
5576
|
-
});
|
|
5577
|
-
if (totalValidOffers > 0) logger.info({
|
|
5578
|
-
msg: `New offers`,
|
|
5579
|
-
collector,
|
|
5580
|
-
count: totalValidOffers,
|
|
5581
|
-
trees_count: treesToInsert.length,
|
|
5582
|
-
chain_id: client.chain.id,
|
|
5583
|
-
block_range: [startBlock, lastStreamBlockNumber]
|
|
5584
|
-
});
|
|
5585
|
-
} catch (_) {
|
|
5586
|
-
try {
|
|
5587
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
5588
|
-
collectorName: collector,
|
|
5589
|
-
chainId: client.chain.id
|
|
5590
|
-
});
|
|
5591
|
-
blockNumber = ancestor.blockNumber;
|
|
5592
|
-
const deleted = await dbTx.offers.delete({
|
|
5593
|
-
blockNumberGte: blockNumber + 1,
|
|
5594
|
-
chainId: client.chain.id
|
|
5595
|
-
});
|
|
5596
|
-
logger.info({
|
|
5597
|
-
collector,
|
|
5598
|
-
chain_id: client.chain.id,
|
|
5599
|
-
msg: `Reorg detected, offers deleted`,
|
|
5600
|
-
count: deleted,
|
|
5601
|
-
block_number: blockNumber
|
|
5602
|
-
});
|
|
5603
|
-
await dbTx.blocks.advanceCollector({
|
|
5604
|
-
collectorName: collector,
|
|
5605
|
-
chainId: client.chain.id,
|
|
5606
|
-
blockNumber,
|
|
5607
|
-
epoch: ancestor.epoch
|
|
5608
|
-
});
|
|
5609
|
-
reorgDetected = true;
|
|
5610
|
-
} catch (err) {
|
|
5611
|
-
const msg = "Failed to delete offers when handling reorg.";
|
|
5612
|
-
logger.error({
|
|
5613
|
-
collector,
|
|
5614
|
-
chainId: client.chain.id,
|
|
5615
|
-
msg,
|
|
5616
|
-
err
|
|
5617
|
-
});
|
|
5618
|
-
throw new Error(msg);
|
|
5619
|
-
}
|
|
5620
|
-
}
|
|
5621
|
-
});
|
|
5622
|
-
if (reorgDetected) return;
|
|
5623
|
-
yield blockNumber;
|
|
5624
|
-
startBlock = blockNumber;
|
|
5625
|
-
}
|
|
5626
|
-
}
|
|
5627
|
-
async function decodeCallbacks(parameters) {
|
|
5628
|
-
const { chainId, gatekeeper, offers } = parameters;
|
|
5629
|
-
if (offers.length === 0) return {
|
|
5630
|
-
callbacks: [],
|
|
5631
|
-
positions: [],
|
|
5632
|
-
lots: []
|
|
5633
|
-
};
|
|
5634
|
-
const addresses = offers.filter((entry) => entry.offer.callback.data !== "0x").map((entry) => entry.offer.callback.address);
|
|
5635
|
-
if (addresses.length === 0) return {
|
|
5636
|
-
callbacks: [],
|
|
5637
|
-
positions: [],
|
|
5638
|
-
lots: []
|
|
5639
|
-
};
|
|
5640
|
-
let response;
|
|
5641
|
-
try {
|
|
5642
|
-
response = await gatekeeper.getCallbackTypes({ callbacks: [{
|
|
5643
|
-
chain_id: chainId,
|
|
5644
|
-
addresses
|
|
5645
|
-
}] });
|
|
5646
|
-
} catch (err) {
|
|
5647
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5648
|
-
throw new Error("Failed to resolve callback types", { cause: error });
|
|
5649
|
-
}
|
|
5650
|
-
const entry = response.find((item) => item.chain_id === chainId);
|
|
5651
|
-
const typeByAddress = /* @__PURE__ */ new Map();
|
|
5652
|
-
if (entry) for (const [key, list] of Object.entries(entry)) {
|
|
5653
|
-
if (key === "chain_id" || key === "not_supported") continue;
|
|
5654
|
-
if (!Array.isArray(list)) continue;
|
|
5655
|
-
for (const address of list) typeByAddress.set(address.toLowerCase(), key);
|
|
5656
|
-
}
|
|
5657
|
-
const callbacks = [];
|
|
5658
|
-
const positions = [];
|
|
5659
|
-
const lots = [];
|
|
5660
|
-
for (const { offer, blockNumber: offerBlockNumber } of offers) {
|
|
5661
|
-
if (offer.callback.data === "0x") continue;
|
|
5662
|
-
const callbackType = typeByAddress.get(offer.callback.address.toLowerCase());
|
|
5663
|
-
if (!callbackType) continue;
|
|
5664
|
-
let decoded;
|
|
5665
|
-
try {
|
|
5666
|
-
decoded = decode$1(callbackType, offer.callback.data);
|
|
5667
|
-
} catch (err) {
|
|
5668
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5669
|
-
throw new Error("Failed to decode callback data", { cause: error });
|
|
5670
|
-
}
|
|
5671
|
-
if (decoded.length === 0) continue;
|
|
5672
|
-
const offerHash = hash(offer);
|
|
5673
|
-
const callbackInputs = decoded.map((callback) => ({
|
|
5674
|
-
chainId: offer.chainId,
|
|
5675
|
-
contract: callback.contract,
|
|
5676
|
-
user: offer.maker,
|
|
5677
|
-
amount: callback.amount
|
|
5678
|
-
}));
|
|
5679
|
-
callbacks.push({
|
|
5680
|
-
offerHash,
|
|
5681
|
-
callbacks: callbackInputs
|
|
5682
|
-
});
|
|
5683
|
-
for (const callback of decoded) {
|
|
5684
|
-
const contract = callback.contract;
|
|
5685
|
-
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
5686
|
-
const asset = callbackType === Type$1.BuyVaultV1Callback ? void 0 : contract;
|
|
5687
|
-
positions.push(from$10({
|
|
5688
|
-
chainId: offer.chainId,
|
|
5689
|
-
contract,
|
|
5690
|
-
user: offer.maker,
|
|
5691
|
-
type: positionType,
|
|
5692
|
-
asset,
|
|
5693
|
-
blockNumber: offerBlockNumber
|
|
5694
|
-
}));
|
|
5695
|
-
const isLoanPosition = offer.loanToken.toLowerCase() === asset?.toLowerCase();
|
|
5696
|
-
lots.push({
|
|
5697
|
-
positionChainId: offer.chainId,
|
|
5698
|
-
positionContract: contract,
|
|
5699
|
-
positionUser: offer.maker,
|
|
5700
|
-
group: offer.group,
|
|
5701
|
-
size: isLoanPosition ? offer.assets : callback.amount
|
|
5702
|
-
});
|
|
5703
|
-
}
|
|
5704
|
-
}
|
|
5705
|
-
return {
|
|
5706
|
-
callbacks,
|
|
5707
|
-
positions,
|
|
5708
|
-
lots
|
|
5709
|
-
};
|
|
5710
|
-
}
|
|
5711
|
-
function buildOfferDependencies$1(offers) {
|
|
5712
|
-
const obligationsById = /* @__PURE__ */ new Map();
|
|
5713
|
-
const oraclesByKey = /* @__PURE__ */ new Map();
|
|
5714
|
-
const groupsByKey = /* @__PURE__ */ new Map();
|
|
5715
|
-
const offersByBlock = /* @__PURE__ */ new Map();
|
|
5716
|
-
for (const { offer, blockNumber } of offers) {
|
|
5717
|
-
const list = offersByBlock.get(blockNumber) ?? [];
|
|
5718
|
-
list.push(offer);
|
|
5719
|
-
offersByBlock.set(blockNumber, list);
|
|
5720
|
-
const obligationId$2 = obligationId(offer);
|
|
5721
|
-
if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
|
|
5722
|
-
chainId: offer.chainId,
|
|
5723
|
-
loanToken: offer.loanToken,
|
|
5724
|
-
maturity: offer.maturity,
|
|
5725
|
-
collaterals: offer.collaterals
|
|
5726
|
-
}));
|
|
5727
|
-
for (const collateral of offer.collaterals) {
|
|
5728
|
-
const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
5729
|
-
if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
|
|
5730
|
-
chainId: offer.chainId,
|
|
5731
|
-
address: collateral.oracle,
|
|
5732
|
-
price: null,
|
|
5733
|
-
blockNumber
|
|
5734
|
-
}));
|
|
5735
|
-
}
|
|
5736
|
-
const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
|
|
5737
|
-
if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
|
|
5738
|
-
chainId: offer.chainId,
|
|
5739
|
-
maker: offer.maker,
|
|
5740
|
-
group: offer.group,
|
|
5741
|
-
blockNumber
|
|
5742
|
-
});
|
|
5743
|
-
}
|
|
5744
|
-
return {
|
|
5745
|
-
obligations: Array.from(obligationsById.values()),
|
|
5746
|
-
oracles: Array.from(oraclesByKey.values()),
|
|
5747
|
-
groups: Array.from(groupsByKey.values()),
|
|
5748
|
-
offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
|
|
5749
|
-
blockNumber,
|
|
5750
|
-
offers: items
|
|
5751
|
-
}))
|
|
5752
|
-
};
|
|
5753
|
-
}
|
|
5754
|
-
function filterInsertedOffers(parameters) {
|
|
5755
|
-
if (parameters.hashes.length === 0) return [];
|
|
5756
|
-
const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
|
|
5757
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5758
|
-
const filtered = [];
|
|
5759
|
-
for (const entry of parameters.offers) {
|
|
5760
|
-
const hash$2 = hash(entry.offer).toLowerCase();
|
|
5761
|
-
if (!inserted.has(hash$2)) continue;
|
|
5762
|
-
if (seen.has(hash$2)) continue;
|
|
5763
|
-
seen.add(hash$2);
|
|
5764
|
-
filtered.push(entry);
|
|
5765
|
-
}
|
|
5766
|
-
return filtered;
|
|
5767
|
-
}
|
|
5768
|
-
|
|
5769
|
-
//#endregion
|
|
5770
|
-
//#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
|
|
5771
|
-
/**
|
|
5772
|
-
* Fetches prices from multiple oracle contracts using multicall.
|
|
5773
|
-
*
|
|
5774
|
-
* - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
|
|
5775
|
-
* - Requires a client supporting {@link PublicActions.multicall | multicall}.
|
|
5776
|
-
*
|
|
5777
|
-
* @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
|
|
5778
|
-
* {@link Address | addresses}, fetch options, and the multicall-enabled client.
|
|
5779
|
-
* @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
|
|
5780
|
-
*/
|
|
5781
|
-
async function fetchOraclePrices(parameters) {
|
|
5782
|
-
const { client, oracles, options } = parameters;
|
|
5783
|
-
if (oracles.length === 0) return /* @__PURE__ */ new Map();
|
|
5784
|
-
const batchSize = Math.max(1, options?.batchSize ?? 5e3);
|
|
5785
|
-
const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
|
|
5786
|
-
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
|
|
5787
|
-
const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
|
|
5788
|
-
const out = /* @__PURE__ */ new Map();
|
|
5789
|
-
for (const oraclesBatch of batch$1(oracles, batchSize)) {
|
|
5790
|
-
const priceCalls = [];
|
|
5791
|
-
for (const oracle of oraclesBatch) priceCalls.push({
|
|
5792
|
-
address: oracle,
|
|
5793
|
-
abi: Oracle,
|
|
5794
|
-
functionName: "price",
|
|
5795
|
-
args: []
|
|
5796
|
-
});
|
|
5797
|
-
const prices = await batchMulticall({
|
|
5798
|
-
client,
|
|
5799
|
-
calls: priceCalls,
|
|
5800
|
-
batchSize,
|
|
5801
|
-
retryAttempts,
|
|
5802
|
-
retryDelayMs,
|
|
5803
|
-
blockNumber
|
|
5804
|
-
});
|
|
5805
|
-
for (let i = 0; i < oraclesBatch.length; i++) {
|
|
5806
|
-
const oracle = oraclesBatch[i];
|
|
5807
|
-
const price = prices[i];
|
|
5808
|
-
out.set(oracle, price);
|
|
5809
|
-
}
|
|
5810
|
-
}
|
|
5811
|
-
return out;
|
|
5812
|
-
}
|
|
5813
|
-
|
|
5814
|
-
//#endregion
|
|
5815
|
-
//#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
|
|
5816
|
-
/**
|
|
5817
|
-
* Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
|
|
5818
|
-
* @notice This method does not mutate positions and returns a new array.
|
|
5819
|
-
*
|
|
5820
|
-
* @param parameters - {@link snapshotERC20Positions.Parameters}
|
|
5821
|
-
* @returns Positions - {@link snapshotERC20Positions.ReturnType}
|
|
5822
|
-
*/
|
|
5823
|
-
async function snapshotERC20Positions(parameters) {
|
|
5824
|
-
const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5825
|
-
if (oldPositions.length === 0) return [];
|
|
5826
|
-
const calls = [];
|
|
5827
|
-
for (const position of oldPositions) calls.push({
|
|
5828
|
-
address: position.contract,
|
|
5829
|
-
abi: erc20Abi,
|
|
5830
|
-
functionName: "balanceOf",
|
|
5831
|
-
args: [position.user]
|
|
5832
|
-
});
|
|
5833
|
-
const balances = await batchMulticall({
|
|
5834
|
-
client: parameters.client,
|
|
5835
|
-
calls,
|
|
5836
|
-
blockNumber: BigInt(blockNumber),
|
|
5837
|
-
batchSize: maxBatchSize,
|
|
5838
|
-
retryAttempts,
|
|
5839
|
-
retryDelayMs
|
|
5840
|
-
});
|
|
5841
|
-
const positions = [];
|
|
5842
|
-
for (let i = 0; i < balances.length; i++) {
|
|
5843
|
-
const oldPosition = oldPositions[i];
|
|
5844
|
-
if (!oldPosition) continue;
|
|
5845
|
-
positions.push({
|
|
5846
|
-
...oldPosition,
|
|
5847
|
-
balance: balances[i],
|
|
5848
|
-
blockNumber
|
|
5849
|
-
});
|
|
5850
|
-
}
|
|
5851
|
-
return positions;
|
|
5852
|
-
}
|
|
5853
|
-
|
|
5854
|
-
//#endregion
|
|
5855
|
-
//#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
|
|
5856
|
-
/**
|
|
5857
|
-
* Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
|
|
5858
|
-
* @notice This method does not mutate positions and returns a new array.
|
|
5859
|
-
*
|
|
5860
|
-
* @param parameters - {@link snapshotVaultPositions.Parameters}
|
|
5861
|
-
* @returns Positions - {@link snapshotVaultPositions.ReturnType}
|
|
5862
|
-
*/
|
|
5863
|
-
async function snapshotVaultPositions(parameters) {
|
|
5864
|
-
const logger = getLogger();
|
|
5865
|
-
const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5866
|
-
const calls = [];
|
|
5867
|
-
const contracts = /* @__PURE__ */ new Map();
|
|
5868
|
-
const positions = structuredClone(oldPositions);
|
|
5869
|
-
for (const position of positions) {
|
|
5870
|
-
calls.push({
|
|
5871
|
-
address: position.contract,
|
|
5872
|
-
abi: MetaMorpho,
|
|
5873
|
-
functionName: "balanceOf",
|
|
5874
|
-
args: [position.user],
|
|
5875
|
-
convertToAssets: (shares) => {
|
|
5876
|
-
const contract = contracts.get(position.contract.toLowerCase());
|
|
5877
|
-
if (!contract) return;
|
|
5878
|
-
if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
|
|
5879
|
-
try {
|
|
5880
|
-
position.balance = convertToAssets({
|
|
5881
|
-
shares,
|
|
5882
|
-
totalAssets: contract.totalAssets,
|
|
5883
|
-
totalSupply: contract.totalSupply,
|
|
5884
|
-
decimalsOffset: contract.decimalsOffset
|
|
5885
|
-
});
|
|
5886
|
-
position.asset = contract.asset;
|
|
5887
|
-
position.blockNumber = blockNumber;
|
|
5888
|
-
} catch (err) {
|
|
5889
|
-
if (err instanceof DenominatorIsZeroError) {
|
|
5890
|
-
logger.error({
|
|
5891
|
-
msg: "Failed to convert shares to assets",
|
|
5892
|
-
chain_id: client.chain.id,
|
|
5893
|
-
block_number: blockNumber,
|
|
5894
|
-
position_contract: position.contract,
|
|
5895
|
-
position_user: position.user,
|
|
5896
|
-
shares,
|
|
5897
|
-
err
|
|
5898
|
-
});
|
|
5899
|
-
return;
|
|
5900
|
-
}
|
|
5901
|
-
throw err;
|
|
5902
|
-
}
|
|
5903
|
-
}
|
|
5904
|
-
});
|
|
5905
|
-
if (contracts.has(position.contract.toLowerCase())) continue;
|
|
5906
|
-
calls.push({
|
|
5907
|
-
address: position.contract,
|
|
5908
|
-
abi: MetaMorpho,
|
|
5909
|
-
functionName: "DECIMALS_OFFSET",
|
|
5910
|
-
args: []
|
|
5911
|
-
}, {
|
|
5912
|
-
address: position.contract,
|
|
5913
|
-
abi: MetaMorpho,
|
|
5914
|
-
functionName: "totalAssets",
|
|
5915
|
-
args: []
|
|
5916
|
-
}, {
|
|
5917
|
-
address: position.contract,
|
|
5918
|
-
abi: MetaMorpho,
|
|
5919
|
-
functionName: "totalSupply",
|
|
5920
|
-
args: []
|
|
5921
|
-
}, {
|
|
5922
|
-
address: position.contract,
|
|
5923
|
-
abi: MetaMorpho,
|
|
5924
|
-
functionName: "asset",
|
|
5925
|
-
args: []
|
|
5926
|
-
});
|
|
5927
|
-
contracts.set(position.contract.toLowerCase(), {
|
|
5928
|
-
decimalsOffset: void 0,
|
|
5929
|
-
totalAssets: void 0,
|
|
5930
|
-
totalSupply: void 0,
|
|
5931
|
-
asset: void 0
|
|
5932
|
-
});
|
|
5933
|
-
}
|
|
5934
|
-
const results = await batchMulticall({
|
|
5935
|
-
client,
|
|
5936
|
-
calls,
|
|
5937
|
-
blockNumber: BigInt(blockNumber),
|
|
5938
|
-
batchSize: maxBatchSize,
|
|
5939
|
-
retryAttempts,
|
|
5940
|
-
retryDelayMs
|
|
5941
|
-
});
|
|
5942
|
-
const convertToAssetsList = [];
|
|
5943
|
-
for (let i = 0; i < results.length; i++) {
|
|
5944
|
-
const call = calls[i];
|
|
5945
|
-
const value = results[i];
|
|
5946
|
-
const contract = contracts.get(call.address.toLowerCase());
|
|
5947
|
-
if (!contract) continue;
|
|
5948
|
-
switch (call.functionName) {
|
|
5949
|
-
case "balanceOf":
|
|
5950
|
-
convertToAssetsList.push(() => call.convertToAssets(value));
|
|
5951
|
-
break;
|
|
5952
|
-
case "DECIMALS_OFFSET":
|
|
5953
|
-
contract.decimalsOffset = value;
|
|
5954
|
-
break;
|
|
5955
|
-
case "totalAssets":
|
|
5956
|
-
contract.totalAssets = value;
|
|
5957
|
-
break;
|
|
5958
|
-
case "totalSupply":
|
|
5959
|
-
contract.totalSupply = value;
|
|
5960
|
-
break;
|
|
5961
|
-
case "asset":
|
|
5962
|
-
contract.asset = value.toLowerCase();
|
|
5963
|
-
break;
|
|
5964
|
-
}
|
|
5965
|
-
}
|
|
5966
|
-
for (const convertToAssets of convertToAssetsList) convertToAssets();
|
|
5967
|
-
return positions;
|
|
5968
|
-
}
|
|
5969
|
-
|
|
5970
|
-
//#endregion
|
|
5971
|
-
//#region src/indexer/collectors/CollectFunctions/collectPositions.ts
|
|
5972
|
-
async function* collectPositions(parameters) {
|
|
5973
|
-
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
|
|
5974
|
-
const logger = getLogger();
|
|
5975
|
-
let startBlock = blockNumber;
|
|
5976
|
-
let reorgDetected = false;
|
|
5977
|
-
const TransferEvent = {
|
|
5978
|
-
type: "event",
|
|
5979
|
-
name: "Transfer",
|
|
5980
|
-
inputs: [
|
|
5981
|
-
{
|
|
5982
|
-
name: "from",
|
|
5983
|
-
type: "address",
|
|
5984
|
-
indexed: true
|
|
5985
|
-
},
|
|
5986
|
-
{
|
|
5987
|
-
name: "to",
|
|
5988
|
-
type: "address",
|
|
5989
|
-
indexed: true
|
|
5990
|
-
},
|
|
5991
|
-
{
|
|
5992
|
-
name: "value",
|
|
5993
|
-
type: "uint256",
|
|
5994
|
-
indexed: false
|
|
5995
|
-
}
|
|
5996
|
-
]
|
|
5997
|
-
};
|
|
5998
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5999
|
-
const stream = streamLogs({
|
|
6000
|
-
client,
|
|
6001
|
-
event: TransferEvent,
|
|
6002
|
-
blockNumberGte: blockNumber,
|
|
6003
|
-
blockNumberLte: latestBlockNumberChain,
|
|
6004
|
-
order: "asc",
|
|
6005
|
-
options: {
|
|
6006
|
-
maxBatchSize,
|
|
6007
|
-
blockWindow
|
|
6008
|
-
}
|
|
6009
|
-
});
|
|
6010
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
6011
|
-
blockNumber = lastStreamBlockNumber;
|
|
6012
|
-
const parsedLogs = parseEventLogs({
|
|
6013
|
-
abi: [TransferEvent],
|
|
6014
|
-
logs
|
|
6015
|
-
});
|
|
6016
|
-
const transfers = [];
|
|
6017
|
-
for (const log of parsedLogs) {
|
|
6018
|
-
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
6019
|
-
logger.debug({
|
|
6020
|
-
collector,
|
|
6021
|
-
chainId: client.chain.id,
|
|
6022
|
-
msg: "Skipping log because it is missing required fields"
|
|
6023
|
-
});
|
|
6024
|
-
continue;
|
|
6025
|
-
}
|
|
6026
|
-
transfers.push(from$8({
|
|
6027
|
-
id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
|
|
6028
|
-
chainId: client.chain.id,
|
|
6029
|
-
contract: log.address,
|
|
6030
|
-
from: log.args.from,
|
|
6031
|
-
to: log.args.to,
|
|
6032
|
-
value: log.args.value,
|
|
6033
|
-
blockNumber: Number(log.blockNumber)
|
|
6034
|
-
}));
|
|
6035
|
-
}
|
|
6036
|
-
const { positions } = await db.positions.get({
|
|
6037
|
-
chainId: client.chain.id,
|
|
6038
|
-
filled: false
|
|
6039
|
-
});
|
|
6040
|
-
const newPositions = [];
|
|
6041
|
-
try {
|
|
6042
|
-
newPositions.push(...(await _snapshot({
|
|
6043
|
-
positions,
|
|
6044
|
-
blockNumber: latestBlockNumberChain,
|
|
6045
|
-
client,
|
|
6046
|
-
maxBatchSize,
|
|
6047
|
-
retryAttempts,
|
|
6048
|
-
retryDelayMs
|
|
6049
|
-
})).map((p) => ({
|
|
6050
|
-
...p,
|
|
6051
|
-
blockNumber: p.blockNumber + 1
|
|
6052
|
-
})));
|
|
6053
|
-
} catch (err) {
|
|
6054
|
-
logger.error({
|
|
6055
|
-
msg: "Failed to snapshot new empty positions",
|
|
6056
|
-
collector,
|
|
6057
|
-
chain_id: client.chain.id,
|
|
6058
|
-
block_number: latestBlockNumberChain,
|
|
6059
|
-
err
|
|
6060
|
-
});
|
|
6061
|
-
yield startBlock;
|
|
6062
|
-
return;
|
|
6063
|
-
}
|
|
6064
|
-
try {
|
|
6065
|
-
await db.transaction(async (dbTx) => {
|
|
6066
|
-
const insertPositions = async () => {
|
|
6067
|
-
if (newPositions.length === 0) return;
|
|
6068
|
-
try {
|
|
6069
|
-
const count = await dbTx.positions.upsert(newPositions);
|
|
6070
|
-
logger.info({
|
|
6071
|
-
msg: `New positions`,
|
|
6072
|
-
collector,
|
|
6073
|
-
count,
|
|
6074
|
-
chain_id: client.chain.id,
|
|
6075
|
-
block_number: latestBlockNumberChain
|
|
6076
|
-
});
|
|
6077
|
-
} catch (err) {
|
|
6078
|
-
throw new InsertPositionsError(err);
|
|
6079
|
-
}
|
|
6080
|
-
};
|
|
6081
|
-
const insertTransfers = async () => {
|
|
6082
|
-
if (transfers.length === 0) return;
|
|
6083
|
-
try {
|
|
6084
|
-
const created = await dbTx.transfers.create(transfers);
|
|
6085
|
-
logger.info({
|
|
6086
|
-
msg: `New transfers`,
|
|
6087
|
-
collector,
|
|
6088
|
-
count: created,
|
|
6089
|
-
chain_id: client.chain.id,
|
|
6090
|
-
block_range: [startBlock, blockNumber]
|
|
6091
|
-
});
|
|
6092
|
-
} catch (err) {
|
|
6093
|
-
throw new InsertTransfersError(err);
|
|
6094
|
-
}
|
|
6095
|
-
};
|
|
6096
|
-
const saveBlockNumber = async () => {
|
|
6097
|
-
try {
|
|
6098
|
-
await dbTx.blocks.advanceCollector({
|
|
6099
|
-
collectorName: collector,
|
|
6100
|
-
chainId: client.chain.id,
|
|
6101
|
-
blockNumber,
|
|
6102
|
-
epoch
|
|
6103
|
-
});
|
|
6104
|
-
} catch (_) {
|
|
6105
|
-
throw new ReorgError(blockNumber);
|
|
6106
|
-
}
|
|
6107
|
-
};
|
|
6108
|
-
await insertPositions();
|
|
6109
|
-
await insertTransfers();
|
|
6110
|
-
await saveBlockNumber();
|
|
6111
|
-
});
|
|
6112
|
-
} catch (err) {
|
|
6113
|
-
if (err instanceof ReorgError) {
|
|
6114
|
-
logger.info({
|
|
6115
|
-
msg: "Reorg detected, positions and transfers insertion aborted",
|
|
6116
|
-
collector,
|
|
6117
|
-
count: newPositions.length,
|
|
6118
|
-
chain_id: client.chain.id,
|
|
6119
|
-
block_number: blockNumber
|
|
6120
|
-
});
|
|
6121
|
-
reorgDetected = true;
|
|
6122
|
-
}
|
|
6123
|
-
if (err instanceof InsertPositionsError) {
|
|
6124
|
-
logger.error({
|
|
6125
|
-
msg: "Failed to insert positions",
|
|
6126
|
-
collector,
|
|
6127
|
-
count: newPositions.length,
|
|
6128
|
-
chain_id: client.chain.id,
|
|
6129
|
-
block_number: latestBlockNumberChain,
|
|
6130
|
-
err
|
|
6131
|
-
});
|
|
6132
|
-
throw err.cause;
|
|
6133
|
-
}
|
|
6134
|
-
if (err instanceof InsertTransfersError) {
|
|
6135
|
-
logger.error({
|
|
6136
|
-
msg: "Failed to insert transfers",
|
|
6137
|
-
collector,
|
|
6138
|
-
count: transfers.length,
|
|
6139
|
-
chain_id: client.chain.id,
|
|
6140
|
-
block_number: blockNumber,
|
|
6141
|
-
err
|
|
6142
|
-
});
|
|
6143
|
-
throw err.cause;
|
|
6144
|
-
}
|
|
6145
|
-
}
|
|
6146
|
-
if (!reorgDetected) {
|
|
6147
|
-
startBlock = blockNumber;
|
|
6148
|
-
if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
|
|
6149
|
-
yield blockNumber;
|
|
6150
|
-
continue;
|
|
6151
|
-
}
|
|
6152
|
-
await db.transaction(async (dbTx) => {
|
|
6153
|
-
try {
|
|
6154
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
6155
|
-
collectorName: collector,
|
|
6156
|
-
chainId: client.chain.id
|
|
6157
|
-
});
|
|
6158
|
-
blockNumber = ancestor.blockNumber;
|
|
6159
|
-
const emptied = await dbTx.positions.setEmptyAfter({
|
|
6160
|
-
chainId: client.chain.id,
|
|
6161
|
-
blockNumber: blockNumber + 1
|
|
6162
|
-
});
|
|
6163
|
-
logger.info({
|
|
6164
|
-
msg: "Reorg detected, positions set to empty",
|
|
6165
|
-
collector,
|
|
6166
|
-
count: emptied,
|
|
6167
|
-
chain_id: client.chain.id,
|
|
6168
|
-
block_number_gte: blockNumber + 1
|
|
6169
|
-
});
|
|
6170
|
-
await dbTx.blocks.advanceCollector({
|
|
6171
|
-
collectorName: collector,
|
|
6172
|
-
chainId: client.chain.id,
|
|
6173
|
-
blockNumber,
|
|
6174
|
-
epoch: ancestor.epoch
|
|
6175
|
-
});
|
|
6176
|
-
} catch (err) {
|
|
6177
|
-
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6178
|
-
logger.error({
|
|
6179
|
-
collector,
|
|
6180
|
-
chainId: client.chain.id,
|
|
6181
|
-
msg,
|
|
6182
|
-
err
|
|
6183
|
-
});
|
|
6184
|
-
throw new Error(msg);
|
|
6185
|
-
}
|
|
6186
|
-
});
|
|
6187
|
-
return;
|
|
6188
|
-
}
|
|
6189
|
-
}
|
|
6190
|
-
/**
|
|
6191
|
-
* @internal
|
|
6192
|
-
*
|
|
6193
|
-
* Snapshots positions and returns the new positions.
|
|
6194
|
-
* @param parameters - {@link _snapshot.Parameters}
|
|
6195
|
-
* @returns The new positions. {@link _snapshot.ReturnType}
|
|
6196
|
-
*/
|
|
6197
|
-
async function _snapshot(parameters) {
|
|
6198
|
-
const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
|
|
6199
|
-
const vaultV1Positions = [];
|
|
6200
|
-
const erc20Positions = [];
|
|
6201
|
-
for (const position of positions) switch (position.type) {
|
|
6202
|
-
case Type.VAULT_V1:
|
|
6203
|
-
vaultV1Positions.push(position);
|
|
6204
|
-
break;
|
|
6205
|
-
case Type.ERC20:
|
|
6206
|
-
erc20Positions.push(position);
|
|
6207
|
-
break;
|
|
6208
|
-
default: throw new Error("Invalid position type");
|
|
6209
|
-
}
|
|
6210
|
-
const promises = [snapshotVaultPositions({
|
|
6211
|
-
client,
|
|
6212
|
-
positions: vaultV1Positions,
|
|
6213
|
-
blockNumber,
|
|
6214
|
-
options: {
|
|
6215
|
-
maxBatchSize,
|
|
6216
|
-
retryAttempts,
|
|
6217
|
-
retryDelayMs
|
|
6218
|
-
}
|
|
6219
|
-
}), snapshotERC20Positions({
|
|
6220
|
-
client,
|
|
6221
|
-
positions: erc20Positions,
|
|
6222
|
-
blockNumber,
|
|
6223
|
-
options: {
|
|
6224
|
-
maxBatchSize,
|
|
6225
|
-
retryAttempts,
|
|
6226
|
-
retryDelayMs
|
|
6227
|
-
}
|
|
6228
|
-
})];
|
|
6229
|
-
return (await Promise.all(promises)).flat();
|
|
6230
|
-
}
|
|
6231
|
-
var InsertPositionsError = class extends BaseError {
|
|
6232
|
-
name = "InsertPositionsError";
|
|
6233
|
-
constructor(err) {
|
|
6234
|
-
super("Failed to insert positions", { cause: err });
|
|
6235
|
-
}
|
|
6236
|
-
};
|
|
6237
|
-
var InsertTransfersError = class extends BaseError {
|
|
6238
|
-
name = "InsertTransfersError";
|
|
6239
|
-
constructor(err) {
|
|
6240
|
-
super("Failed to insert transfers", { cause: err });
|
|
6241
|
-
}
|
|
6242
|
-
};
|
|
6243
|
-
|
|
6244
|
-
//#endregion
|
|
6245
|
-
//#region src/indexer/collectors/CollectFunctions/collectPrices.ts
|
|
6246
|
-
/**
|
|
6247
|
-
* Collects oracle prices from on-chain oracles and persists them.
|
|
6248
|
-
*
|
|
6249
|
-
* - Reads oracle definitions as {@link Oracle.Oracle}.
|
|
6250
|
-
* - Uses chain metadata from {@link Chain.Chain}.
|
|
6251
|
-
*
|
|
6252
|
-
* @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
|
|
6253
|
-
* with a client supporting {@link PublicActions.multicall | multicall}.
|
|
6254
|
-
* @yields Latest processed block number after each successful update.
|
|
6255
|
-
*/
|
|
6256
|
-
async function* collectPrices(parameters) {
|
|
6257
|
-
const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6258
|
-
const logger = getLogger();
|
|
6259
|
-
let blockNumber = parameters.lastBlockNumber;
|
|
6260
|
-
const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
|
|
6261
|
-
const updatedOracles = [];
|
|
6262
|
-
try {
|
|
6263
|
-
const pricesMap = await fetchOraclePrices({
|
|
6264
|
-
client,
|
|
6265
|
-
oracles: oracles.map((oracle) => oracle.address),
|
|
6266
|
-
options: {
|
|
6267
|
-
batchSize: maxBatchSize,
|
|
6268
|
-
blockNumber: latestBlockNumberChain,
|
|
6269
|
-
retryAttempts,
|
|
6270
|
-
retryDelayMs
|
|
6271
|
-
}
|
|
6272
|
-
});
|
|
6273
|
-
for (const oracle of oracles) {
|
|
6274
|
-
const price = pricesMap.get(oracle.address);
|
|
6275
|
-
if (price !== void 0) updatedOracles.push({
|
|
6276
|
-
chainId: client.chain.id,
|
|
6277
|
-
address: oracle.address,
|
|
6278
|
-
price,
|
|
6279
|
-
blockNumber: latestBlockNumberChain
|
|
6280
|
-
});
|
|
6281
|
-
}
|
|
6282
|
-
} catch (err) {
|
|
6283
|
-
logger.error({
|
|
6284
|
-
msg: "Failed to fetch oracle prices",
|
|
6285
|
-
collector,
|
|
6286
|
-
chain_id: client.chain.id,
|
|
6287
|
-
block_number: latestBlockNumberChain,
|
|
6288
|
-
err
|
|
6289
|
-
});
|
|
6290
|
-
yield blockNumber;
|
|
6291
|
-
return;
|
|
6292
|
-
}
|
|
6293
|
-
let reorgDetected = false;
|
|
6294
|
-
try {
|
|
6295
|
-
await db.transaction(async (dbTx) => {
|
|
6296
|
-
if (updatedOracles.length > 0) {
|
|
6297
|
-
await dbTx.oracles.upsert(updatedOracles);
|
|
6298
|
-
logger.info({
|
|
6299
|
-
msg: "Oracle prices updated",
|
|
6300
|
-
collector,
|
|
6301
|
-
count: updatedOracles.length,
|
|
6302
|
-
chain_id: client.chain.id,
|
|
6303
|
-
block_number: latestBlockNumberChain
|
|
6304
|
-
});
|
|
6305
|
-
}
|
|
6306
|
-
try {
|
|
6307
|
-
await dbTx.blocks.advanceCollector({
|
|
6308
|
-
collectorName: collector,
|
|
6309
|
-
chainId: client.chain.id,
|
|
6310
|
-
blockNumber: latestBlockNumberChain,
|
|
6311
|
-
epoch
|
|
6312
|
-
});
|
|
6313
|
-
} catch (_) {
|
|
6314
|
-
throw new ReorgError(latestBlockNumberChain);
|
|
6315
|
-
}
|
|
6316
|
-
blockNumber = latestBlockNumberChain;
|
|
6317
|
-
});
|
|
6318
|
-
} catch (err) {
|
|
6319
|
-
if (err instanceof ReorgError) {
|
|
6320
|
-
logger.info({
|
|
6321
|
-
msg: "Reorg detected, prices update aborted",
|
|
6322
|
-
collector,
|
|
6323
|
-
count: updatedOracles.length,
|
|
6324
|
-
chain_id: client.chain.id,
|
|
6325
|
-
block_number: latestBlockNumberChain
|
|
6326
|
-
});
|
|
6327
|
-
reorgDetected = true;
|
|
6328
|
-
} else throw new Error("Failed to collect oracle prices", { cause: err });
|
|
6329
|
-
}
|
|
6330
|
-
if (!reorgDetected) {
|
|
6331
|
-
yield blockNumber;
|
|
6332
|
-
return;
|
|
6333
|
-
}
|
|
6334
|
-
await db.transaction(async (dbTx) => {
|
|
6335
|
-
try {
|
|
6336
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
6337
|
-
collectorName: collector,
|
|
6338
|
-
chainId: client.chain.id
|
|
6339
|
-
});
|
|
6340
|
-
blockNumber = ancestor.blockNumber;
|
|
6341
|
-
await dbTx.blocks.advanceCollector({
|
|
6342
|
-
collectorName: collector,
|
|
6343
|
-
chainId: client.chain.id,
|
|
6344
|
-
blockNumber,
|
|
6345
|
-
epoch: ancestor.epoch
|
|
6346
|
-
});
|
|
6347
|
-
} catch (err) {
|
|
6348
|
-
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6349
|
-
logger.error({
|
|
6350
|
-
collector,
|
|
6351
|
-
chainId: client.chain.id,
|
|
6352
|
-
msg,
|
|
6353
|
-
err
|
|
6354
|
-
});
|
|
6355
|
-
throw new Error(msg);
|
|
6356
|
-
}
|
|
6357
|
-
});
|
|
6358
|
-
yield blockNumber;
|
|
6359
|
-
}
|
|
6360
|
-
|
|
6361
|
-
//#endregion
|
|
6362
|
-
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
6363
|
-
function createBuilder(parameters) {
|
|
6364
|
-
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
|
|
6365
|
-
const createCollector = (name, collect) => create$16({
|
|
6366
|
-
name,
|
|
6367
|
-
collect,
|
|
6368
|
-
client,
|
|
6369
|
-
db,
|
|
6370
|
-
options: {
|
|
6371
|
-
maxBlockNumber,
|
|
6372
|
-
interval
|
|
6373
|
-
}
|
|
6374
|
-
});
|
|
6375
|
-
return {
|
|
6376
|
-
buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
|
|
6377
|
-
return createCollector("offers", (p) => collectOffersV2({
|
|
6378
|
-
...p,
|
|
6379
|
-
gatekeeper,
|
|
6380
|
-
collector: "offers",
|
|
6381
|
-
client,
|
|
6382
|
-
options: {
|
|
6383
|
-
maxBatchSize,
|
|
6384
|
-
blockWindow
|
|
6385
|
-
}
|
|
6386
|
-
}));
|
|
6387
|
-
},
|
|
6388
|
-
buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
|
|
6389
|
-
return createCollector("consumed_events", (p) => collectConsumedEvents({
|
|
6390
|
-
...p,
|
|
6391
|
-
collector: "consumed_events",
|
|
6392
|
-
options: {
|
|
6393
|
-
maxBatchSize,
|
|
6394
|
-
blockWindow
|
|
6395
|
-
}
|
|
6396
|
-
}));
|
|
6397
|
-
},
|
|
6398
|
-
buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6399
|
-
return createCollector("prices", (p) => collectPrices({
|
|
6400
|
-
...p,
|
|
6401
|
-
collector: "prices",
|
|
6402
|
-
options: {
|
|
6403
|
-
maxBatchSize,
|
|
6404
|
-
retryAttempts,
|
|
6405
|
-
retryDelayMs
|
|
6406
|
-
}
|
|
6407
|
-
}));
|
|
6408
|
-
},
|
|
6409
|
-
buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6410
|
-
return createCollector("positions", (p) => collectPositions({
|
|
6411
|
-
...p,
|
|
6412
|
-
collector: "positions",
|
|
6413
|
-
options: {
|
|
6414
|
-
maxBatchSize,
|
|
6415
|
-
retryAttempts,
|
|
6416
|
-
retryDelayMs,
|
|
6417
|
-
blockWindow
|
|
6418
|
-
}
|
|
6419
|
-
}));
|
|
6420
|
-
}
|
|
6421
|
-
};
|
|
6422
|
-
}
|
|
6423
|
-
|
|
6424
|
-
//#endregion
|
|
6425
|
-
//#region src/indexer/collectors/Collectors.ts
|
|
6426
|
-
const from$2 = (parameters) => {
|
|
6427
|
-
const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
|
|
6428
|
-
const collectorBuilder = createBuilder({
|
|
6429
|
-
client,
|
|
6430
|
-
db,
|
|
6431
|
-
gatekeeper,
|
|
6432
|
-
options: {
|
|
6433
|
-
maxBlockNumber,
|
|
6434
|
-
blockWindow,
|
|
6435
|
-
interval
|
|
6436
|
-
}
|
|
6437
|
-
});
|
|
6438
|
-
return {
|
|
6439
|
-
offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
|
|
6440
|
-
consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
|
|
6441
|
-
pricesCollector: collectorBuilder.buildPricesCollector({ options: {
|
|
6442
|
-
maxBatchSize,
|
|
6443
|
-
retryAttempts,
|
|
6444
|
-
retryDelayMs
|
|
6445
|
-
} }),
|
|
6446
|
-
positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
|
|
6447
|
-
maxBatchSize,
|
|
6448
|
-
retryAttempts,
|
|
6449
|
-
retryDelayMs
|
|
6450
|
-
} })
|
|
6451
|
-
};
|
|
6452
|
-
};
|
|
6453
|
-
|
|
6454
|
-
//#endregion
|
|
6455
|
-
//#region src/indexer/Indexer.ts
|
|
6456
|
-
function from$1(config) {
|
|
6457
|
-
const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
|
|
6458
|
-
const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
|
|
6459
|
-
client,
|
|
6460
|
-
db,
|
|
6461
|
-
gatekeeper,
|
|
6462
|
-
maxBatchSize,
|
|
6463
|
-
maxBlockNumber,
|
|
6464
|
-
blockWindow,
|
|
6465
|
-
interval,
|
|
6466
|
-
retryAttempts,
|
|
6467
|
-
retryDelayMs
|
|
6468
|
-
});
|
|
6469
|
-
return create$18({
|
|
6470
|
-
client,
|
|
6471
|
-
collectors: [
|
|
6472
|
-
offersCollector,
|
|
6473
|
-
consumedEventsCollector,
|
|
6474
|
-
positionsCollector,
|
|
6475
|
-
pricesCollector
|
|
6476
|
-
]
|
|
6477
|
-
});
|
|
6478
|
-
}
|
|
6479
|
-
function create$18(params) {
|
|
6480
|
-
const { collectors, client } = params;
|
|
6481
|
-
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
6482
|
-
const tracer = getTracer(`router.${indexerId}`);
|
|
6483
|
-
const iterators = collectors.map((collector) => collector.collect());
|
|
6484
|
-
const next = async () => {
|
|
6485
|
-
await startActiveSpan(tracer, `${indexerId}.next`, async () => {
|
|
6486
|
-
await Promise.all(iterators.map((iterator) => iterator.next()));
|
|
6487
|
-
});
|
|
6488
|
-
};
|
|
6489
|
-
const _return = async () => {
|
|
6490
|
-
await Promise.all(iterators.map(async (iterator) => iterator.return()));
|
|
6491
|
-
};
|
|
6492
|
-
return {
|
|
6493
|
-
start: () => {
|
|
6494
|
-
const stops = collectors.map((collector) => start(collector));
|
|
6495
|
-
return () => {
|
|
6496
|
-
stops.forEach((stop) => {
|
|
6497
|
-
stop();
|
|
6498
|
-
});
|
|
6499
|
-
};
|
|
6500
|
-
},
|
|
6501
|
-
next,
|
|
6502
|
-
return: _return
|
|
6503
|
-
};
|
|
6504
|
-
}
|
|
6505
|
-
|
|
6506
|
-
//#endregion
|
|
6507
|
-
//#region src/indexer/collectors/Admin.ts
|
|
6508
|
-
function create$17(parameters) {
|
|
6509
|
-
const collector = "admin";
|
|
6510
|
-
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
6511
|
-
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
6512
|
-
let finalizedBlock = null;
|
|
6513
|
-
let unfinalizedBlocks = [];
|
|
6514
|
-
let initialized = false;
|
|
6515
|
-
const logger = getLogger();
|
|
6516
|
-
let isMaxBlockNumberReached = false;
|
|
6517
|
-
let tick = 0;
|
|
6518
|
-
return { syncBlock: async () => {
|
|
6519
|
-
if (!initialized) {
|
|
6520
|
-
await Promise.all(names.map((collectorName) => db.blocks.init({
|
|
6521
|
-
chainId: client.chain.id,
|
|
6522
|
-
collectorName
|
|
6523
|
-
})));
|
|
6524
|
-
initialized = true;
|
|
6525
|
-
}
|
|
6526
|
-
if (isMaxBlockNumberReached) return true;
|
|
6527
|
-
const head = await client.getBlock({
|
|
6528
|
-
blockTag: "latest",
|
|
6529
|
-
includeTransactions: false
|
|
6530
|
-
});
|
|
6531
|
-
await db.transaction(async (dbTx) => {
|
|
6532
|
-
const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
6533
|
-
if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
|
|
6534
|
-
logger.info({
|
|
6535
|
-
msg: `Head is greater than max block number`,
|
|
6536
|
-
collector,
|
|
6537
|
-
chainId: client.chain.id,
|
|
6538
|
-
block_number: head.number,
|
|
6539
|
-
max_block_number: maxBlockNumber
|
|
6540
|
-
});
|
|
6541
|
-
await dbTx.blocks.handleReorg({
|
|
6542
|
-
chainId: client.chain.id,
|
|
6543
|
-
blockNumber: maxBlockNumber,
|
|
6544
|
-
epoch: epoch + 1n
|
|
6545
|
-
});
|
|
6546
|
-
isMaxBlockNumberReached = true;
|
|
6547
|
-
return isMaxBlockNumberReached;
|
|
6548
|
-
}
|
|
6549
|
-
finalizedBlock = await fetchFinalizedBlock({
|
|
6550
|
-
tick,
|
|
6551
|
-
client,
|
|
6552
|
-
logger,
|
|
6553
|
-
collector,
|
|
6554
|
-
unfinalizedBlocks,
|
|
6555
|
-
previousFinalizedBlock: finalizedBlock
|
|
6556
|
-
});
|
|
6557
|
-
tick++;
|
|
6558
|
-
let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6559
|
-
client,
|
|
6560
|
-
block: head,
|
|
6561
|
-
unfinalizedBlocks,
|
|
6562
|
-
finalizedBlock,
|
|
6563
|
-
logger,
|
|
6564
|
-
collector,
|
|
6565
|
-
maxBatchSize
|
|
6566
|
-
});
|
|
6567
|
-
unfinalizedBlocks = newUnfinalizedBlocks;
|
|
6568
|
-
const blockNumber = Number(returnedBlock.number);
|
|
6569
|
-
didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
|
|
6570
|
-
if (didReorgHappened) await dbTx.blocks.handleReorg({
|
|
6571
|
-
chainId: client.chain.id,
|
|
6572
|
-
blockNumber,
|
|
6573
|
-
epoch: epoch + 1n
|
|
6574
|
-
});
|
|
6575
|
-
else await dbTx.blocks.advanceChain({
|
|
6576
|
-
chainId: client.chain.id,
|
|
6577
|
-
blockNumber,
|
|
6578
|
-
epoch
|
|
6579
|
-
});
|
|
6580
|
-
});
|
|
6581
|
-
return isMaxBlockNumberReached;
|
|
6582
|
-
} };
|
|
6583
|
-
}
|
|
6584
|
-
const commonAncestor = (block, unfinalizedBlocks) => {
|
|
6585
|
-
const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
|
|
6586
|
-
if (parent) return parent;
|
|
6587
|
-
return null;
|
|
6588
|
-
};
|
|
6589
|
-
const fetchFinalizedBlock = async (parameters) => {
|
|
6590
|
-
let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
|
|
6591
|
-
let finalizedBlock = previousFinalizedBlock;
|
|
6592
|
-
if (tick % 20 === 0 || previousFinalizedBlock === null) {
|
|
6593
|
-
finalizedBlock = await client.getBlock({
|
|
6594
|
-
blockTag: "finalized",
|
|
6595
|
-
includeTransactions: false
|
|
6596
|
-
});
|
|
6597
|
-
if (finalizedBlock === null || finalizedBlock.number === null) {
|
|
6598
|
-
const msg = "Failed to get finalized block";
|
|
6599
|
-
logger.fatal({
|
|
6600
|
-
collector,
|
|
6601
|
-
chainId: client.chain.id,
|
|
6602
|
-
msg
|
|
6603
|
-
});
|
|
6604
|
-
throw new Error(msg);
|
|
6605
|
-
}
|
|
6606
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
|
|
6607
|
-
}
|
|
6608
|
-
if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
|
|
6609
|
-
const msg = "Failed to get finalized block";
|
|
6610
|
-
logger.fatal({
|
|
6611
|
-
collector,
|
|
6612
|
-
chainId: client.chain.id,
|
|
6613
|
-
msg
|
|
6614
|
-
});
|
|
6615
|
-
throw new Error(msg);
|
|
6616
|
-
}
|
|
6617
|
-
return {
|
|
6618
|
-
hash: finalizedBlock.hash,
|
|
6619
|
-
number: finalizedBlock.number,
|
|
6620
|
-
parentHash: finalizedBlock.parentHash
|
|
6621
|
-
};
|
|
6622
|
-
};
|
|
6623
|
-
const reconcile = async (parameters) => {
|
|
6624
|
-
let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
|
|
6625
|
-
const chain = client.chain;
|
|
6626
|
-
if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
|
|
6627
|
-
const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
|
|
6628
|
-
if (latestBlock === void 0) {
|
|
6629
|
-
const newBlock = {
|
|
6630
|
-
hash: block.hash,
|
|
6631
|
-
number: block.number,
|
|
6632
|
-
parentHash: block.parentHash
|
|
6633
|
-
};
|
|
6634
|
-
unfinalizedBlocks.push(newBlock);
|
|
6635
|
-
return {
|
|
6636
|
-
block: newBlock,
|
|
6637
|
-
didReorgHappened: false,
|
|
6638
|
-
unfinalizedBlocks
|
|
6639
|
-
};
|
|
6640
|
-
}
|
|
6641
|
-
if (latestBlock.hash === block.hash) return {
|
|
6642
|
-
block: latestBlock,
|
|
6643
|
-
didReorgHappened: false,
|
|
6644
|
-
unfinalizedBlocks
|
|
6645
|
-
};
|
|
6646
|
-
if (latestBlock.number >= block.number) {
|
|
6647
|
-
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6648
|
-
logger.info({
|
|
6649
|
-
msg: `Reorg detected, latestBlock.number >= block.number`,
|
|
6650
|
-
collector,
|
|
6651
|
-
chain_id: chain.id,
|
|
6652
|
-
ancestor: ancestor.number,
|
|
6653
|
-
latest_block_number: latestBlock.number,
|
|
6654
|
-
block_number: block.number
|
|
6655
|
-
});
|
|
6656
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6657
|
-
return {
|
|
6658
|
-
block: ancestor,
|
|
6659
|
-
didReorgHappened: true,
|
|
6660
|
-
unfinalizedBlocks
|
|
6661
|
-
};
|
|
6662
|
-
}
|
|
6663
|
-
if (latestBlock.number + 1n < block.number) {
|
|
6664
|
-
logger.debug({
|
|
6665
|
-
collector,
|
|
6666
|
-
chain_id: chain.id,
|
|
6667
|
-
block_range: [latestBlock.number, block.number],
|
|
6668
|
-
msg: `Missing blocks`
|
|
6669
|
-
});
|
|
6670
|
-
const missingBlockNumbers = (() => {
|
|
6671
|
-
const missingBlockNumbers = [];
|
|
6672
|
-
let start = latestBlock.number + 1n;
|
|
6673
|
-
const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
|
|
6674
|
-
while (start < threshold) {
|
|
6675
|
-
missingBlockNumbers.push(start);
|
|
6676
|
-
start = start + 1n;
|
|
6677
|
-
}
|
|
6678
|
-
return missingBlockNumbers;
|
|
6679
|
-
})();
|
|
6680
|
-
const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
|
|
6681
|
-
blockNumber,
|
|
6682
|
-
includeTransactions: false
|
|
6683
|
-
}))));
|
|
6684
|
-
for (const missingBlock of missingBlocks) {
|
|
6685
|
-
const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6686
|
-
client,
|
|
6687
|
-
block: missingBlock,
|
|
6688
|
-
unfinalizedBlocks,
|
|
6689
|
-
finalizedBlock,
|
|
6690
|
-
logger,
|
|
6691
|
-
collector,
|
|
6692
|
-
maxBatchSize
|
|
6693
|
-
});
|
|
6694
|
-
if (returnedBlock.number !== missingBlock.number) return {
|
|
6695
|
-
block: returnedBlock,
|
|
6696
|
-
didReorgHappened,
|
|
6697
|
-
unfinalizedBlocks: newUnfinalizedBlocks
|
|
6698
|
-
};
|
|
6699
|
-
}
|
|
6700
|
-
return reconcile({
|
|
6701
|
-
client,
|
|
6702
|
-
block,
|
|
6703
|
-
unfinalizedBlocks,
|
|
6704
|
-
finalizedBlock,
|
|
6705
|
-
logger,
|
|
6706
|
-
collector,
|
|
6707
|
-
maxBatchSize
|
|
6708
|
-
});
|
|
6709
|
-
}
|
|
6710
|
-
if (block.parentHash !== latestBlock.hash) {
|
|
6711
|
-
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6712
|
-
logger.info({
|
|
6713
|
-
msg: `Reorg detected, block parent hash !== latest block hash`,
|
|
6714
|
-
collector,
|
|
6715
|
-
chain_id: chain.id,
|
|
6716
|
-
ancestor: ancestor.number,
|
|
6717
|
-
latest_block_number: latestBlock.number,
|
|
6718
|
-
block_number: block.number
|
|
6719
|
-
});
|
|
6720
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6721
|
-
return {
|
|
6722
|
-
block: ancestor,
|
|
6723
|
-
didReorgHappened: true,
|
|
6724
|
-
unfinalizedBlocks
|
|
6725
|
-
};
|
|
6726
|
-
}
|
|
6727
|
-
const newBlock = {
|
|
6728
|
-
hash: block.hash,
|
|
6729
|
-
number: block.number,
|
|
6730
|
-
parentHash: block.parentHash
|
|
6731
|
-
};
|
|
6732
|
-
unfinalizedBlocks.push(newBlock);
|
|
6733
|
-
return {
|
|
6734
|
-
block: newBlock,
|
|
6735
|
-
didReorgHappened: false,
|
|
6736
|
-
unfinalizedBlocks
|
|
6737
|
-
};
|
|
6738
|
-
};
|
|
6739
|
-
|
|
6740
|
-
//#endregion
|
|
6741
|
-
//#region src/indexer/collectors/Collector.ts
|
|
6742
|
-
const names = [
|
|
6743
|
-
"offers",
|
|
6744
|
-
"consumed_events",
|
|
6745
|
-
"positions",
|
|
6746
|
-
"prices"
|
|
6747
|
-
];
|
|
6748
|
-
function create$16({ name, collect, client, db, options }) {
|
|
6749
|
-
const admin = create$17({
|
|
6750
|
-
client,
|
|
6751
|
-
db,
|
|
6752
|
-
options
|
|
6753
|
-
});
|
|
6754
|
-
return {
|
|
6755
|
-
name,
|
|
6756
|
-
chain: client.chain,
|
|
6757
|
-
client,
|
|
6758
|
-
db,
|
|
6759
|
-
interval: options.interval ?? 1e4,
|
|
6760
|
-
collect: async function* () {
|
|
6761
|
-
const collector = name;
|
|
6762
|
-
const chain = client.chain;
|
|
6763
|
-
const logger = getLogger();
|
|
6764
|
-
const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
|
|
6765
|
-
const tracer = getTracer(`router.${collectorId}`);
|
|
6766
|
-
logger.info({
|
|
6767
|
-
msg: `Collector started`,
|
|
6768
|
-
collector,
|
|
6769
|
-
chain_id: chain.id
|
|
6770
|
-
});
|
|
6771
|
-
let iterator = null;
|
|
6772
|
-
let lastBlockNumber;
|
|
6773
|
-
while (true) try {
|
|
6774
|
-
if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
|
|
6775
|
-
const { collector: collectorBlock } = await db.blocks.init({
|
|
6776
|
-
collectorName: name,
|
|
6777
|
-
chainId: chain.id
|
|
6778
|
-
});
|
|
6779
|
-
lastBlockNumber = collectorBlock.blockNumber;
|
|
6780
|
-
return collect({
|
|
6781
|
-
client,
|
|
6782
|
-
collector: name,
|
|
6783
|
-
epoch: collectorBlock.epoch,
|
|
6784
|
-
lastBlockNumber,
|
|
6785
|
-
db
|
|
6786
|
-
});
|
|
6787
|
-
});
|
|
6788
|
-
if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
|
|
6789
|
-
const isMaxBlockNumberReached = await admin.syncBlock();
|
|
6790
|
-
span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
|
|
6791
|
-
return isMaxBlockNumberReached;
|
|
6792
|
-
}) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
|
|
6793
|
-
const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
|
|
6794
|
-
if (iterator === null) throw new Error("Iterator is not initialized");
|
|
6795
|
-
const { value: blockNumber, done } = await iterator.next();
|
|
6796
|
-
return {
|
|
6797
|
-
blockNumber,
|
|
6798
|
-
done
|
|
6799
|
-
};
|
|
6800
|
-
});
|
|
6801
|
-
if (done) iterator = null;
|
|
6802
|
-
else {
|
|
6803
|
-
lastBlockNumber = blockNumber;
|
|
6804
|
-
yield blockNumber;
|
|
6805
|
-
}
|
|
6806
|
-
} catch (err) {
|
|
6807
|
-
const isError = err instanceof Error;
|
|
6808
|
-
logger.error({
|
|
6809
|
-
msg: "Collector error",
|
|
6810
|
-
collector,
|
|
6811
|
-
chain_id: chain.id,
|
|
6812
|
-
error: isError ? err.message : String(err),
|
|
6813
|
-
stack: isError ? err.stack : void 0
|
|
6814
|
-
});
|
|
6815
|
-
}
|
|
6816
|
-
}
|
|
6817
|
-
};
|
|
6818
|
-
}
|
|
6819
|
-
/** Start a collector with its own polling cadence based on chain head lag.
|
|
6820
|
-
* @param collector - The collector to start.
|
|
6821
|
-
* @returns A function to stop the collector.
|
|
6822
|
-
*/
|
|
6823
|
-
function start(collector) {
|
|
6824
|
-
let stopped = false;
|
|
6825
|
-
const it = collector.collect();
|
|
6826
|
-
const logger = getLogger();
|
|
6827
|
-
const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
|
|
6828
|
-
const tracer = getTracer(`router.${collectorId}`);
|
|
6829
|
-
const blocks = collector.db.blocks;
|
|
6830
|
-
let initialized = false;
|
|
6831
|
-
(async () => {
|
|
6832
|
-
while (!stopped) try {
|
|
6833
|
-
await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
|
|
6834
|
-
if (!initialized) {
|
|
6835
|
-
await blocks.init({
|
|
6836
|
-
chainId: collector.chain.id,
|
|
6837
|
-
collectorName: collector.name
|
|
6838
|
-
});
|
|
6839
|
-
initialized = true;
|
|
6840
|
-
}
|
|
6841
|
-
const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
|
|
6842
|
-
blockTag: "latest",
|
|
6843
|
-
includeTransactions: false
|
|
6844
|
-
}), blocks.getCollector({
|
|
6845
|
-
collectorName: collector.name,
|
|
6846
|
-
chainId: collector.chain.id
|
|
6847
|
-
})]);
|
|
6848
|
-
const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
|
|
6849
|
-
const waitSpan = tracer.startSpan(`${collectorId}.wait`);
|
|
6850
|
-
try {
|
|
6851
|
-
await wait(delay);
|
|
6852
|
-
} finally {
|
|
6853
|
-
waitSpan.end();
|
|
6854
|
-
}
|
|
6855
|
-
await it.next();
|
|
6856
|
-
});
|
|
6857
|
-
} catch (err) {
|
|
6858
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
6859
|
-
logger.error({
|
|
6860
|
-
msg: "Collector polling error",
|
|
6861
|
-
collector: collector.name,
|
|
6862
|
-
chain_id: collector.chain.id,
|
|
6863
|
-
err: error
|
|
6864
|
-
});
|
|
6865
|
-
}
|
|
6866
|
-
await it.return();
|
|
6867
|
-
})();
|
|
6868
|
-
return () => {
|
|
6869
|
-
stopped = true;
|
|
6870
|
-
};
|
|
6871
|
-
}
|
|
6872
|
-
|
|
6873
|
-
//#endregion
|
|
6874
|
-
//#region src/database/drizzle/VERSION.ts
|
|
6875
|
-
const VERSION = "router_v1.6";
|
|
5084
|
+
//#region src/database/drizzle/VERSION.ts
|
|
5085
|
+
const VERSION = "router_v1.6";
|
|
6876
5086
|
|
|
6877
5087
|
//#endregion
|
|
6878
5088
|
//#region src/database/drizzle/schema.ts
|
|
@@ -7274,6 +5484,1654 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
|
7274
5484
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7275
5485
|
}, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
|
|
7276
5486
|
|
|
5487
|
+
//#endregion
|
|
5488
|
+
//#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
|
|
5489
|
+
const buildGroupKey = (parameters) => {
|
|
5490
|
+
return `${parameters.chainId}-${parameters.maker.toLowerCase()}-${parameters.group.toLowerCase()}`;
|
|
5491
|
+
};
|
|
5492
|
+
async function* collectConsumedEvents(parameters) {
|
|
5493
|
+
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5494
|
+
const logger = getLogger();
|
|
5495
|
+
let startBlock = blockNumber;
|
|
5496
|
+
let reorgDetected = false;
|
|
5497
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5498
|
+
const stream = streamLogs({
|
|
5499
|
+
client,
|
|
5500
|
+
contractAddress: client.chain.custom.morpho.address,
|
|
5501
|
+
blockNumberGte: blockNumber,
|
|
5502
|
+
blockNumberLte: latestBlockNumberChain,
|
|
5503
|
+
order: "asc",
|
|
5504
|
+
options: {
|
|
5505
|
+
maxBatchSize,
|
|
5506
|
+
blockWindow
|
|
5507
|
+
}
|
|
5508
|
+
});
|
|
5509
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5510
|
+
const parsedLogs = parseEventLogs({
|
|
5511
|
+
abi: [consumedEvent, takeEvent],
|
|
5512
|
+
logs,
|
|
5513
|
+
strict: false
|
|
5514
|
+
});
|
|
5515
|
+
const normalizedLogs = [];
|
|
5516
|
+
const groups$3 = /* @__PURE__ */ new Map();
|
|
5517
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
5518
|
+
const recordLog = (log) => {
|
|
5519
|
+
normalizedLogs.push(log);
|
|
5520
|
+
eventIds.add(log.id);
|
|
5521
|
+
const groupKey = buildGroupKey({
|
|
5522
|
+
chainId: log.chainId,
|
|
5523
|
+
maker: log.maker,
|
|
5524
|
+
group: log.group
|
|
5525
|
+
});
|
|
5526
|
+
if (!groups$3.has(groupKey)) groups$3.set(groupKey, {
|
|
5527
|
+
chainId: log.chainId,
|
|
5528
|
+
maker: log.maker.toLowerCase(),
|
|
5529
|
+
group: log.group.toLowerCase()
|
|
5530
|
+
});
|
|
5531
|
+
};
|
|
5532
|
+
for (const rawLog of parsedLogs) {
|
|
5533
|
+
if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
|
|
5534
|
+
logger.debug({
|
|
5535
|
+
collector,
|
|
5536
|
+
chainId: client.chain.id,
|
|
5537
|
+
msg: "Skipping log because it is missing required fields"
|
|
5538
|
+
});
|
|
5539
|
+
continue;
|
|
5540
|
+
}
|
|
5541
|
+
if (rawLog.eventName === consumedEvent.name) {
|
|
5542
|
+
const consumeArgs = rawLog.args;
|
|
5543
|
+
if (consumeArgs.user === void 0 || consumeArgs.group === void 0 || consumeArgs.amount === void 0) {
|
|
5544
|
+
logger.debug({
|
|
5545
|
+
collector,
|
|
5546
|
+
chainId: client.chain.id,
|
|
5547
|
+
msg: "Skipping Consume log because it is missing required args"
|
|
5548
|
+
});
|
|
5549
|
+
continue;
|
|
5550
|
+
}
|
|
5551
|
+
recordLog({
|
|
5552
|
+
kind: "consume",
|
|
5553
|
+
id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
|
|
5554
|
+
chainId: client.chain.id,
|
|
5555
|
+
maker: consumeArgs.user,
|
|
5556
|
+
group: consumeArgs.group,
|
|
5557
|
+
amount: consumeArgs.amount,
|
|
5558
|
+
blockNumber: Number(rawLog.blockNumber)
|
|
5559
|
+
});
|
|
5560
|
+
continue;
|
|
5561
|
+
}
|
|
5562
|
+
if (rawLog.eventName === takeEvent.name) {
|
|
5563
|
+
const takeArgs = rawLog.args;
|
|
5564
|
+
if (takeArgs.maker === void 0 || takeArgs.group === void 0 || takeArgs.consumed === void 0) {
|
|
5565
|
+
logger.debug({
|
|
5566
|
+
collector,
|
|
5567
|
+
chainId: client.chain.id,
|
|
5568
|
+
msg: "Skipping Take log because it is missing required args"
|
|
5569
|
+
});
|
|
5570
|
+
continue;
|
|
5571
|
+
}
|
|
5572
|
+
recordLog({
|
|
5573
|
+
kind: "take",
|
|
5574
|
+
id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
|
|
5575
|
+
chainId: client.chain.id,
|
|
5576
|
+
maker: takeArgs.maker,
|
|
5577
|
+
group: takeArgs.group,
|
|
5578
|
+
consumed: takeArgs.consumed,
|
|
5579
|
+
blockNumber: Number(rawLog.blockNumber)
|
|
5580
|
+
});
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
await db.transaction(async (dbTx) => {
|
|
5584
|
+
const existingEventIds = /* @__PURE__ */ new Set();
|
|
5585
|
+
if (eventIds.size > 0) {
|
|
5586
|
+
const ids = Array.from(eventIds);
|
|
5587
|
+
for (let index = 0; index < ids.length; index += 500) {
|
|
5588
|
+
const slice = ids.slice(index, index + 500);
|
|
5589
|
+
const { rows } = await dbTx.execute(sql`
|
|
5590
|
+
SELECT event_id
|
|
5591
|
+
FROM ${consumedEvents}
|
|
5592
|
+
WHERE event_id IN (${sql.join(slice.map((id) => sql`${id}`), sql`,`)});
|
|
5593
|
+
`);
|
|
5594
|
+
for (const row of rows) existingEventIds.add(row.event_id);
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
const consumedByGroup = /* @__PURE__ */ new Map();
|
|
5598
|
+
if (groups$3.size > 0) {
|
|
5599
|
+
const groupList = Array.from(groups$3.values());
|
|
5600
|
+
for (let index = 0; index < groupList.length; index += 500) {
|
|
5601
|
+
const slice = groupList.slice(index, index + 500);
|
|
5602
|
+
const { rows } = await dbTx.execute(sql`
|
|
5603
|
+
WITH targets(chain_id, maker, "group") AS (
|
|
5604
|
+
VALUES ${sql.join(slice.map((group) => sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), sql`,`)}
|
|
5605
|
+
)
|
|
5606
|
+
SELECT
|
|
5607
|
+
targets.chain_id,
|
|
5608
|
+
targets.maker,
|
|
5609
|
+
targets."group",
|
|
5610
|
+
COALESCE(g.consumed, 0)::numeric AS consumed
|
|
5611
|
+
FROM targets
|
|
5612
|
+
LEFT JOIN ${groups} g
|
|
5613
|
+
ON g.chain_id = targets.chain_id
|
|
5614
|
+
AND g.maker = targets.maker
|
|
5615
|
+
AND g."group" = targets."group";
|
|
5616
|
+
`);
|
|
5617
|
+
for (const row of rows) {
|
|
5618
|
+
const groupKey = buildGroupKey({
|
|
5619
|
+
chainId: Number(row.chain_id),
|
|
5620
|
+
maker: row.maker,
|
|
5621
|
+
group: row.group
|
|
5622
|
+
});
|
|
5623
|
+
consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
const events = [];
|
|
5628
|
+
for (const log of normalizedLogs) {
|
|
5629
|
+
if (existingEventIds.has(log.id)) continue;
|
|
5630
|
+
const groupKey = buildGroupKey({
|
|
5631
|
+
chainId: log.chainId,
|
|
5632
|
+
maker: log.maker,
|
|
5633
|
+
group: log.group
|
|
5634
|
+
});
|
|
5635
|
+
const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
|
|
5636
|
+
if (log.kind === "consume") {
|
|
5637
|
+
events.push({
|
|
5638
|
+
id: log.id,
|
|
5639
|
+
chainId: log.chainId,
|
|
5640
|
+
maker: log.maker,
|
|
5641
|
+
group: log.group,
|
|
5642
|
+
amount: log.amount,
|
|
5643
|
+
blockNumber: log.blockNumber
|
|
5644
|
+
});
|
|
5645
|
+
consumedByGroup.set(groupKey, previousConsumed + log.amount);
|
|
5646
|
+
continue;
|
|
5647
|
+
}
|
|
5648
|
+
const delta = log.consumed - previousConsumed;
|
|
5649
|
+
if (delta <= 0n) {
|
|
5650
|
+
logger.debug({
|
|
5651
|
+
collector,
|
|
5652
|
+
chainId: client.chain.id,
|
|
5653
|
+
msg: "Skipping Take log because consumed did not increase",
|
|
5654
|
+
previous_consumed: previousConsumed.toString(),
|
|
5655
|
+
consumed: log.consumed.toString()
|
|
5656
|
+
});
|
|
5657
|
+
continue;
|
|
5658
|
+
}
|
|
5659
|
+
events.push({
|
|
5660
|
+
id: log.id,
|
|
5661
|
+
chainId: log.chainId,
|
|
5662
|
+
maker: log.maker,
|
|
5663
|
+
group: log.group,
|
|
5664
|
+
amount: delta,
|
|
5665
|
+
blockNumber: log.blockNumber
|
|
5666
|
+
});
|
|
5667
|
+
consumedByGroup.set(groupKey, log.consumed);
|
|
5668
|
+
}
|
|
5669
|
+
try {
|
|
5670
|
+
await dbTx.consumed.create(events);
|
|
5671
|
+
if (events.length > 0) logger.info({
|
|
5672
|
+
msg: `Events indexed`,
|
|
5673
|
+
collector,
|
|
5674
|
+
count: events.length,
|
|
5675
|
+
chain_id: client.chain.id,
|
|
5676
|
+
block_range: [startBlock, lastStreamBlockNumber]
|
|
5677
|
+
});
|
|
5678
|
+
} catch (err) {
|
|
5679
|
+
logger.error({
|
|
5680
|
+
err,
|
|
5681
|
+
msg: "Failed to process consumed events"
|
|
5682
|
+
});
|
|
5683
|
+
}
|
|
5684
|
+
blockNumber = lastStreamBlockNumber;
|
|
5685
|
+
try {
|
|
5686
|
+
await dbTx.blocks.advanceCollector({
|
|
5687
|
+
collectorName: collector,
|
|
5688
|
+
chainId: client.chain.id,
|
|
5689
|
+
blockNumber,
|
|
5690
|
+
epoch
|
|
5691
|
+
});
|
|
5692
|
+
} catch (_) {
|
|
5693
|
+
try {
|
|
5694
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
5695
|
+
collectorName: collector,
|
|
5696
|
+
chainId: client.chain.id
|
|
5697
|
+
});
|
|
5698
|
+
blockNumber = ancestor.blockNumber;
|
|
5699
|
+
const deleted = await dbTx.consumed.delete({
|
|
5700
|
+
chainId: client.chain.id,
|
|
5701
|
+
blockNumberGte: blockNumber + 1
|
|
5702
|
+
});
|
|
5703
|
+
logger.info({
|
|
5704
|
+
collector,
|
|
5705
|
+
chain_id: client.chain.id,
|
|
5706
|
+
msg: `Reorg detected, events deleted`,
|
|
5707
|
+
count: deleted,
|
|
5708
|
+
block_number: blockNumber
|
|
5709
|
+
});
|
|
5710
|
+
await dbTx.blocks.advanceCollector({
|
|
5711
|
+
collectorName: collector,
|
|
5712
|
+
chainId: client.chain.id,
|
|
5713
|
+
blockNumber,
|
|
5714
|
+
epoch: ancestor.epoch
|
|
5715
|
+
});
|
|
5716
|
+
reorgDetected = true;
|
|
5717
|
+
} catch (err) {
|
|
5718
|
+
const msg = "Failed to delete consumed events when handling reorg.";
|
|
5719
|
+
logger.error({
|
|
5720
|
+
collector,
|
|
5721
|
+
chainId: client.chain.id,
|
|
5722
|
+
msg,
|
|
5723
|
+
err
|
|
5724
|
+
});
|
|
5725
|
+
throw new Error(msg, { cause: err });
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
});
|
|
5729
|
+
if (reorgDetected) return;
|
|
5730
|
+
yield blockNumber;
|
|
5731
|
+
startBlock = blockNumber;
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
|
|
5735
|
+
//#endregion
|
|
5736
|
+
//#region src/indexer/collectors/CollectFunctions/collectOffers.ts
|
|
5737
|
+
async function* collectOffersV2(parameters) {
|
|
5738
|
+
let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5739
|
+
const logger = getLogger();
|
|
5740
|
+
let startBlock = blockNumber;
|
|
5741
|
+
let reorgDetected = false;
|
|
5742
|
+
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
5743
|
+
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
5744
|
+
logger.error({
|
|
5745
|
+
msg,
|
|
5746
|
+
chain_id: client.chain.id
|
|
5747
|
+
});
|
|
5748
|
+
throw new Error(msg);
|
|
5749
|
+
}
|
|
5750
|
+
const signatureDomain = {
|
|
5751
|
+
chainId: client.chain.id,
|
|
5752
|
+
verifyingContract: client.chain.custom.morpho.address
|
|
5753
|
+
};
|
|
5754
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5755
|
+
const stream = streamLogs({
|
|
5756
|
+
client,
|
|
5757
|
+
contractAddress: client.chain.custom.mempool.address,
|
|
5758
|
+
event: {
|
|
5759
|
+
type: "event",
|
|
5760
|
+
name: "Event",
|
|
5761
|
+
inputs: [{
|
|
5762
|
+
name: "data",
|
|
5763
|
+
type: "bytes",
|
|
5764
|
+
indexed: false,
|
|
5765
|
+
internalType: "bytes"
|
|
5766
|
+
}],
|
|
5767
|
+
anonymous: false
|
|
5768
|
+
},
|
|
5769
|
+
blockNumberGte: blockNumber,
|
|
5770
|
+
blockNumberLte: latestBlockNumberChain,
|
|
5771
|
+
order: "asc",
|
|
5772
|
+
options: {
|
|
5773
|
+
maxBatchSize,
|
|
5774
|
+
blockWindow
|
|
5775
|
+
}
|
|
5776
|
+
});
|
|
5777
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5778
|
+
blockNumber = lastStreamBlockNumber;
|
|
5779
|
+
const decodedTrees = [];
|
|
5780
|
+
for (const log of logs) {
|
|
5781
|
+
if (!log) continue;
|
|
5782
|
+
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
5783
|
+
try {
|
|
5784
|
+
const { tree, signature, signer } = await decode(payload, signatureDomain);
|
|
5785
|
+
const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
|
|
5786
|
+
if (signerMismatch) {
|
|
5787
|
+
logger.debug({
|
|
5788
|
+
msg: "Tree rejected: signer mismatch",
|
|
5789
|
+
reason: "signer_mismatch",
|
|
5790
|
+
signer,
|
|
5791
|
+
maker: signerMismatch.maker,
|
|
5792
|
+
chain_id: client.chain.id
|
|
5793
|
+
});
|
|
5794
|
+
continue;
|
|
5795
|
+
}
|
|
5796
|
+
decodedTrees.push({
|
|
5797
|
+
tree,
|
|
5798
|
+
signature,
|
|
5799
|
+
signer,
|
|
5800
|
+
blockNumber: Number(log.blockNumber)
|
|
5801
|
+
});
|
|
5802
|
+
} catch (err) {
|
|
5803
|
+
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
5804
|
+
logger.debug({
|
|
5805
|
+
msg: "Tree decode failed",
|
|
5806
|
+
reason,
|
|
5807
|
+
chain_id: client.chain.id,
|
|
5808
|
+
err: err instanceof Error ? err.message : String(err)
|
|
5809
|
+
});
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
await db.transaction(async (dbTx) => {
|
|
5813
|
+
const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
5814
|
+
const treesToInsert = [];
|
|
5815
|
+
let totalValidOffers = 0;
|
|
5816
|
+
const offersWithBlock = [];
|
|
5817
|
+
for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
|
|
5818
|
+
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
5819
|
+
const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
|
|
5820
|
+
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
5821
|
+
if (allowedResults.issues.length > 0) {
|
|
5822
|
+
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
5823
|
+
logger.debug({
|
|
5824
|
+
msg: "Tree offers rejected by gatekeeper",
|
|
5825
|
+
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
5826
|
+
chain_id: client.chain.id,
|
|
5827
|
+
issues_count: allowedResults.issues.length
|
|
5828
|
+
});
|
|
5829
|
+
} else if (hasBlockWindowViolation) logger.debug({
|
|
5830
|
+
msg: "Tree rejected: offers outside block window",
|
|
5831
|
+
reason: "block_window",
|
|
5832
|
+
chain_id: client.chain.id
|
|
5833
|
+
});
|
|
5834
|
+
continue;
|
|
5835
|
+
}
|
|
5836
|
+
treesToInsert.push({
|
|
5837
|
+
tree,
|
|
5838
|
+
signature
|
|
5839
|
+
});
|
|
5840
|
+
totalValidOffers += tree.offers.length;
|
|
5841
|
+
offersWithBlock.push(...tree.offers.map((offer) => ({
|
|
5842
|
+
offer,
|
|
5843
|
+
blockNumber: treeBlockNumber
|
|
5844
|
+
})));
|
|
5845
|
+
} catch (err) {
|
|
5846
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5847
|
+
logger.error({
|
|
5848
|
+
err: error,
|
|
5849
|
+
msg: "Gatekeeper validation failed",
|
|
5850
|
+
chain_id: client.chain.id
|
|
5851
|
+
});
|
|
5852
|
+
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
5853
|
+
}
|
|
5854
|
+
const dependencies = buildOfferDependencies$1(offersWithBlock);
|
|
5855
|
+
await dbTx.oracles.upsert(dependencies.oracles);
|
|
5856
|
+
await dbTx.obligations.create(dependencies.obligations);
|
|
5857
|
+
await dbTx.groups.create(dependencies.groups);
|
|
5858
|
+
const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
|
|
5859
|
+
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
5860
|
+
const insertedOffers = filterInsertedOffers({
|
|
5861
|
+
offers: offersWithBlock,
|
|
5862
|
+
hashes: insertedHashes
|
|
5863
|
+
});
|
|
5864
|
+
const { callbacks, positions, lots } = decodeCallbacks({
|
|
5865
|
+
chainId: client.chain.id,
|
|
5866
|
+
offers: insertedOffers
|
|
5867
|
+
});
|
|
5868
|
+
if (positions.length > 0) await dbTx.positions.upsert(positions);
|
|
5869
|
+
if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
|
|
5870
|
+
if (lots.length > 0) await dbTx.lots.create(lots);
|
|
5871
|
+
try {
|
|
5872
|
+
await dbTx.blocks.advanceCollector({
|
|
5873
|
+
collectorName: collector,
|
|
5874
|
+
chainId: client.chain.id,
|
|
5875
|
+
blockNumber,
|
|
5876
|
+
epoch
|
|
5877
|
+
});
|
|
5878
|
+
if (totalValidOffers > 0) logger.info({
|
|
5879
|
+
msg: `New offers`,
|
|
5880
|
+
collector,
|
|
5881
|
+
count: totalValidOffers,
|
|
5882
|
+
trees_count: treesToInsert.length,
|
|
5883
|
+
chain_id: client.chain.id,
|
|
5884
|
+
block_range: [startBlock, lastStreamBlockNumber]
|
|
5885
|
+
});
|
|
5886
|
+
} catch (_) {
|
|
5887
|
+
try {
|
|
5888
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
5889
|
+
collectorName: collector,
|
|
5890
|
+
chainId: client.chain.id
|
|
5891
|
+
});
|
|
5892
|
+
blockNumber = ancestor.blockNumber;
|
|
5893
|
+
const deleted = await dbTx.offers.delete({
|
|
5894
|
+
blockNumberGte: blockNumber + 1,
|
|
5895
|
+
chainId: client.chain.id
|
|
5896
|
+
});
|
|
5897
|
+
logger.info({
|
|
5898
|
+
collector,
|
|
5899
|
+
chain_id: client.chain.id,
|
|
5900
|
+
msg: `Reorg detected, offers deleted`,
|
|
5901
|
+
count: deleted,
|
|
5902
|
+
block_number: blockNumber
|
|
5903
|
+
});
|
|
5904
|
+
await dbTx.blocks.advanceCollector({
|
|
5905
|
+
collectorName: collector,
|
|
5906
|
+
chainId: client.chain.id,
|
|
5907
|
+
blockNumber,
|
|
5908
|
+
epoch: ancestor.epoch
|
|
5909
|
+
});
|
|
5910
|
+
reorgDetected = true;
|
|
5911
|
+
} catch (err) {
|
|
5912
|
+
const msg = "Failed to delete offers when handling reorg.";
|
|
5913
|
+
logger.error({
|
|
5914
|
+
collector,
|
|
5915
|
+
chainId: client.chain.id,
|
|
5916
|
+
msg,
|
|
5917
|
+
err
|
|
5918
|
+
});
|
|
5919
|
+
throw new Error(msg);
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
});
|
|
5923
|
+
if (reorgDetected) return;
|
|
5924
|
+
yield blockNumber;
|
|
5925
|
+
startBlock = blockNumber;
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
function decodeCallbacks(parameters) {
|
|
5929
|
+
const { offers } = parameters;
|
|
5930
|
+
if (offers.length === 0) return {
|
|
5931
|
+
callbacks: [],
|
|
5932
|
+
positions: [],
|
|
5933
|
+
lots: []
|
|
5934
|
+
};
|
|
5935
|
+
const callbacks = [];
|
|
5936
|
+
const positions = [];
|
|
5937
|
+
const lots = [];
|
|
5938
|
+
for (const { offer, blockNumber: offerBlockNumber } of offers) {
|
|
5939
|
+
if (!offer.buy) continue;
|
|
5940
|
+
if (!isEmptyCallback(offer)) continue;
|
|
5941
|
+
const loanToken = offer.loanToken.toLowerCase();
|
|
5942
|
+
positions.push(from$10({
|
|
5943
|
+
chainId: offer.chainId,
|
|
5944
|
+
contract: loanToken,
|
|
5945
|
+
user: offer.maker,
|
|
5946
|
+
type: Type.ERC20,
|
|
5947
|
+
asset: loanToken,
|
|
5948
|
+
blockNumber: offerBlockNumber
|
|
5949
|
+
}));
|
|
5950
|
+
lots.push({
|
|
5951
|
+
positionChainId: offer.chainId,
|
|
5952
|
+
positionContract: loanToken,
|
|
5953
|
+
positionUser: offer.maker,
|
|
5954
|
+
group: offer.group,
|
|
5955
|
+
size: offer.assets
|
|
5956
|
+
});
|
|
5957
|
+
callbacks.push({
|
|
5958
|
+
offerHash: hash(offer),
|
|
5959
|
+
callbacks: [{
|
|
5960
|
+
chainId: offer.chainId,
|
|
5961
|
+
contract: loanToken,
|
|
5962
|
+
user: offer.maker,
|
|
5963
|
+
amount: offer.assets
|
|
5964
|
+
}]
|
|
5965
|
+
});
|
|
5966
|
+
}
|
|
5967
|
+
return {
|
|
5968
|
+
callbacks,
|
|
5969
|
+
positions,
|
|
5970
|
+
lots
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5973
|
+
function buildOfferDependencies$1(offers) {
|
|
5974
|
+
const obligationsById = /* @__PURE__ */ new Map();
|
|
5975
|
+
const oraclesByKey = /* @__PURE__ */ new Map();
|
|
5976
|
+
const groupsByKey = /* @__PURE__ */ new Map();
|
|
5977
|
+
const offersByBlock = /* @__PURE__ */ new Map();
|
|
5978
|
+
for (const { offer, blockNumber } of offers) {
|
|
5979
|
+
const list = offersByBlock.get(blockNumber) ?? [];
|
|
5980
|
+
list.push(offer);
|
|
5981
|
+
offersByBlock.set(blockNumber, list);
|
|
5982
|
+
const obligationId$2 = obligationId(offer);
|
|
5983
|
+
if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
|
|
5984
|
+
chainId: offer.chainId,
|
|
5985
|
+
loanToken: offer.loanToken,
|
|
5986
|
+
maturity: offer.maturity,
|
|
5987
|
+
collaterals: offer.collaterals
|
|
5988
|
+
}));
|
|
5989
|
+
for (const collateral of offer.collaterals) {
|
|
5990
|
+
const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
5991
|
+
if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
|
|
5992
|
+
chainId: offer.chainId,
|
|
5993
|
+
address: collateral.oracle,
|
|
5994
|
+
price: null,
|
|
5995
|
+
blockNumber
|
|
5996
|
+
}));
|
|
5997
|
+
}
|
|
5998
|
+
const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
|
|
5999
|
+
if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
|
|
6000
|
+
chainId: offer.chainId,
|
|
6001
|
+
maker: offer.maker,
|
|
6002
|
+
group: offer.group,
|
|
6003
|
+
blockNumber
|
|
6004
|
+
});
|
|
6005
|
+
}
|
|
6006
|
+
return {
|
|
6007
|
+
obligations: Array.from(obligationsById.values()),
|
|
6008
|
+
oracles: Array.from(oraclesByKey.values()),
|
|
6009
|
+
groups: Array.from(groupsByKey.values()),
|
|
6010
|
+
offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
|
|
6011
|
+
blockNumber,
|
|
6012
|
+
offers: items
|
|
6013
|
+
}))
|
|
6014
|
+
};
|
|
6015
|
+
}
|
|
6016
|
+
function filterInsertedOffers(parameters) {
|
|
6017
|
+
if (parameters.hashes.length === 0) return [];
|
|
6018
|
+
const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
|
|
6019
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6020
|
+
const filtered = [];
|
|
6021
|
+
for (const entry of parameters.offers) {
|
|
6022
|
+
const hash$2 = hash(entry.offer).toLowerCase();
|
|
6023
|
+
if (!inserted.has(hash$2)) continue;
|
|
6024
|
+
if (seen.has(hash$2)) continue;
|
|
6025
|
+
seen.add(hash$2);
|
|
6026
|
+
filtered.push(entry);
|
|
6027
|
+
}
|
|
6028
|
+
return filtered;
|
|
6029
|
+
}
|
|
6030
|
+
|
|
6031
|
+
//#endregion
|
|
6032
|
+
//#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
|
|
6033
|
+
/**
|
|
6034
|
+
* Fetches prices from multiple oracle contracts using multicall.
|
|
6035
|
+
*
|
|
6036
|
+
* - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
|
|
6037
|
+
* - Requires a client supporting {@link PublicActions.multicall | multicall}.
|
|
6038
|
+
*
|
|
6039
|
+
* @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
|
|
6040
|
+
* {@link Address | addresses}, fetch options, and the multicall-enabled client.
|
|
6041
|
+
* @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
|
|
6042
|
+
*/
|
|
6043
|
+
async function fetchOraclePrices(parameters) {
|
|
6044
|
+
const { client, oracles, options } = parameters;
|
|
6045
|
+
if (oracles.length === 0) return /* @__PURE__ */ new Map();
|
|
6046
|
+
const batchSize = Math.max(1, options?.batchSize ?? 5e3);
|
|
6047
|
+
const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
|
|
6048
|
+
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
|
|
6049
|
+
const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
|
|
6050
|
+
const out = /* @__PURE__ */ new Map();
|
|
6051
|
+
for (const oraclesBatch of batch$1(oracles, batchSize)) {
|
|
6052
|
+
const priceCalls = [];
|
|
6053
|
+
for (const oracle of oraclesBatch) priceCalls.push({
|
|
6054
|
+
address: oracle,
|
|
6055
|
+
abi: Oracle,
|
|
6056
|
+
functionName: "price",
|
|
6057
|
+
args: []
|
|
6058
|
+
});
|
|
6059
|
+
const prices = await batchMulticall({
|
|
6060
|
+
client,
|
|
6061
|
+
calls: priceCalls,
|
|
6062
|
+
batchSize,
|
|
6063
|
+
retryAttempts,
|
|
6064
|
+
retryDelayMs,
|
|
6065
|
+
blockNumber
|
|
6066
|
+
});
|
|
6067
|
+
for (let i = 0; i < oraclesBatch.length; i++) {
|
|
6068
|
+
const oracle = oraclesBatch[i];
|
|
6069
|
+
const price = prices[i];
|
|
6070
|
+
out.set(oracle, price);
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
return out;
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
//#endregion
|
|
6077
|
+
//#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
|
|
6078
|
+
/**
|
|
6079
|
+
* Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
|
|
6080
|
+
* @notice This method does not mutate positions and returns a new array.
|
|
6081
|
+
*
|
|
6082
|
+
* @param parameters - {@link snapshotERC20Positions.Parameters}
|
|
6083
|
+
* @returns Positions - {@link snapshotERC20Positions.ReturnType}
|
|
6084
|
+
*/
|
|
6085
|
+
async function snapshotERC20Positions(parameters) {
|
|
6086
|
+
const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6087
|
+
if (oldPositions.length === 0) return [];
|
|
6088
|
+
const calls = [];
|
|
6089
|
+
for (const position of oldPositions) calls.push({
|
|
6090
|
+
address: position.contract,
|
|
6091
|
+
abi: erc20Abi,
|
|
6092
|
+
functionName: "balanceOf",
|
|
6093
|
+
args: [position.user]
|
|
6094
|
+
});
|
|
6095
|
+
const balances = await batchMulticall({
|
|
6096
|
+
client: parameters.client,
|
|
6097
|
+
calls,
|
|
6098
|
+
blockNumber: BigInt(blockNumber),
|
|
6099
|
+
batchSize: maxBatchSize,
|
|
6100
|
+
retryAttempts,
|
|
6101
|
+
retryDelayMs
|
|
6102
|
+
});
|
|
6103
|
+
const positions = [];
|
|
6104
|
+
for (let i = 0; i < balances.length; i++) {
|
|
6105
|
+
const oldPosition = oldPositions[i];
|
|
6106
|
+
if (!oldPosition) continue;
|
|
6107
|
+
positions.push({
|
|
6108
|
+
...oldPosition,
|
|
6109
|
+
balance: balances[i],
|
|
6110
|
+
blockNumber
|
|
6111
|
+
});
|
|
6112
|
+
}
|
|
6113
|
+
return positions;
|
|
6114
|
+
}
|
|
6115
|
+
|
|
6116
|
+
//#endregion
|
|
6117
|
+
//#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
|
|
6118
|
+
/**
|
|
6119
|
+
* Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
|
|
6120
|
+
* @notice This method does not mutate positions and returns a new array.
|
|
6121
|
+
*
|
|
6122
|
+
* @param parameters - {@link snapshotVaultPositions.Parameters}
|
|
6123
|
+
* @returns Positions - {@link snapshotVaultPositions.ReturnType}
|
|
6124
|
+
*/
|
|
6125
|
+
async function snapshotVaultPositions(parameters) {
|
|
6126
|
+
const logger = getLogger();
|
|
6127
|
+
const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6128
|
+
const calls = [];
|
|
6129
|
+
const contracts = /* @__PURE__ */ new Map();
|
|
6130
|
+
const positions = structuredClone(oldPositions);
|
|
6131
|
+
for (const position of positions) {
|
|
6132
|
+
calls.push({
|
|
6133
|
+
address: position.contract,
|
|
6134
|
+
abi: MetaMorpho,
|
|
6135
|
+
functionName: "balanceOf",
|
|
6136
|
+
args: [position.user],
|
|
6137
|
+
convertToAssets: (shares) => {
|
|
6138
|
+
const contract = contracts.get(position.contract.toLowerCase());
|
|
6139
|
+
if (!contract) return;
|
|
6140
|
+
if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
|
|
6141
|
+
try {
|
|
6142
|
+
position.balance = convertToAssets({
|
|
6143
|
+
shares,
|
|
6144
|
+
totalAssets: contract.totalAssets,
|
|
6145
|
+
totalSupply: contract.totalSupply,
|
|
6146
|
+
decimalsOffset: contract.decimalsOffset
|
|
6147
|
+
});
|
|
6148
|
+
position.asset = contract.asset;
|
|
6149
|
+
position.blockNumber = blockNumber;
|
|
6150
|
+
} catch (err) {
|
|
6151
|
+
if (err instanceof DenominatorIsZeroError) {
|
|
6152
|
+
logger.error({
|
|
6153
|
+
msg: "Failed to convert shares to assets",
|
|
6154
|
+
chain_id: client.chain.id,
|
|
6155
|
+
block_number: blockNumber,
|
|
6156
|
+
position_contract: position.contract,
|
|
6157
|
+
position_user: position.user,
|
|
6158
|
+
shares,
|
|
6159
|
+
err
|
|
6160
|
+
});
|
|
6161
|
+
return;
|
|
6162
|
+
}
|
|
6163
|
+
throw err;
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
});
|
|
6167
|
+
if (contracts.has(position.contract.toLowerCase())) continue;
|
|
6168
|
+
calls.push({
|
|
6169
|
+
address: position.contract,
|
|
6170
|
+
abi: MetaMorpho,
|
|
6171
|
+
functionName: "DECIMALS_OFFSET",
|
|
6172
|
+
args: []
|
|
6173
|
+
}, {
|
|
6174
|
+
address: position.contract,
|
|
6175
|
+
abi: MetaMorpho,
|
|
6176
|
+
functionName: "totalAssets",
|
|
6177
|
+
args: []
|
|
6178
|
+
}, {
|
|
6179
|
+
address: position.contract,
|
|
6180
|
+
abi: MetaMorpho,
|
|
6181
|
+
functionName: "totalSupply",
|
|
6182
|
+
args: []
|
|
6183
|
+
}, {
|
|
6184
|
+
address: position.contract,
|
|
6185
|
+
abi: MetaMorpho,
|
|
6186
|
+
functionName: "asset",
|
|
6187
|
+
args: []
|
|
6188
|
+
});
|
|
6189
|
+
contracts.set(position.contract.toLowerCase(), {
|
|
6190
|
+
decimalsOffset: void 0,
|
|
6191
|
+
totalAssets: void 0,
|
|
6192
|
+
totalSupply: void 0,
|
|
6193
|
+
asset: void 0
|
|
6194
|
+
});
|
|
6195
|
+
}
|
|
6196
|
+
const results = await batchMulticall({
|
|
6197
|
+
client,
|
|
6198
|
+
calls,
|
|
6199
|
+
blockNumber: BigInt(blockNumber),
|
|
6200
|
+
batchSize: maxBatchSize,
|
|
6201
|
+
retryAttempts,
|
|
6202
|
+
retryDelayMs
|
|
6203
|
+
});
|
|
6204
|
+
const convertToAssetsList = [];
|
|
6205
|
+
for (let i = 0; i < results.length; i++) {
|
|
6206
|
+
const call = calls[i];
|
|
6207
|
+
const value = results[i];
|
|
6208
|
+
const contract = contracts.get(call.address.toLowerCase());
|
|
6209
|
+
if (!contract) continue;
|
|
6210
|
+
switch (call.functionName) {
|
|
6211
|
+
case "balanceOf":
|
|
6212
|
+
convertToAssetsList.push(() => call.convertToAssets(value));
|
|
6213
|
+
break;
|
|
6214
|
+
case "DECIMALS_OFFSET":
|
|
6215
|
+
contract.decimalsOffset = value;
|
|
6216
|
+
break;
|
|
6217
|
+
case "totalAssets":
|
|
6218
|
+
contract.totalAssets = value;
|
|
6219
|
+
break;
|
|
6220
|
+
case "totalSupply":
|
|
6221
|
+
contract.totalSupply = value;
|
|
6222
|
+
break;
|
|
6223
|
+
case "asset":
|
|
6224
|
+
contract.asset = value.toLowerCase();
|
|
6225
|
+
break;
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
for (const convertToAssets of convertToAssetsList) convertToAssets();
|
|
6229
|
+
return positions;
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6232
|
+
//#endregion
|
|
6233
|
+
//#region src/indexer/collectors/CollectFunctions/collectPositions.ts
|
|
6234
|
+
async function* collectPositions(parameters) {
|
|
6235
|
+
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
|
|
6236
|
+
const logger = getLogger();
|
|
6237
|
+
let startBlock = blockNumber;
|
|
6238
|
+
let reorgDetected = false;
|
|
6239
|
+
const TransferEvent = {
|
|
6240
|
+
type: "event",
|
|
6241
|
+
name: "Transfer",
|
|
6242
|
+
inputs: [
|
|
6243
|
+
{
|
|
6244
|
+
name: "from",
|
|
6245
|
+
type: "address",
|
|
6246
|
+
indexed: true
|
|
6247
|
+
},
|
|
6248
|
+
{
|
|
6249
|
+
name: "to",
|
|
6250
|
+
type: "address",
|
|
6251
|
+
indexed: true
|
|
6252
|
+
},
|
|
6253
|
+
{
|
|
6254
|
+
name: "value",
|
|
6255
|
+
type: "uint256",
|
|
6256
|
+
indexed: false
|
|
6257
|
+
}
|
|
6258
|
+
]
|
|
6259
|
+
};
|
|
6260
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
6261
|
+
const stream = streamLogs({
|
|
6262
|
+
client,
|
|
6263
|
+
event: TransferEvent,
|
|
6264
|
+
blockNumberGte: blockNumber,
|
|
6265
|
+
blockNumberLte: latestBlockNumberChain,
|
|
6266
|
+
order: "asc",
|
|
6267
|
+
options: {
|
|
6268
|
+
maxBatchSize,
|
|
6269
|
+
blockWindow
|
|
6270
|
+
}
|
|
6271
|
+
});
|
|
6272
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
6273
|
+
blockNumber = lastStreamBlockNumber;
|
|
6274
|
+
const parsedLogs = parseEventLogs({
|
|
6275
|
+
abi: [TransferEvent],
|
|
6276
|
+
logs
|
|
6277
|
+
});
|
|
6278
|
+
const transfers = [];
|
|
6279
|
+
for (const log of parsedLogs) {
|
|
6280
|
+
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
6281
|
+
logger.debug({
|
|
6282
|
+
collector,
|
|
6283
|
+
chainId: client.chain.id,
|
|
6284
|
+
msg: "Skipping log because it is missing required fields"
|
|
6285
|
+
});
|
|
6286
|
+
continue;
|
|
6287
|
+
}
|
|
6288
|
+
transfers.push(from$8({
|
|
6289
|
+
id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
|
|
6290
|
+
chainId: client.chain.id,
|
|
6291
|
+
contract: log.address,
|
|
6292
|
+
from: log.args.from,
|
|
6293
|
+
to: log.args.to,
|
|
6294
|
+
value: log.args.value,
|
|
6295
|
+
blockNumber: Number(log.blockNumber)
|
|
6296
|
+
}));
|
|
6297
|
+
}
|
|
6298
|
+
const { positions } = await db.positions.get({
|
|
6299
|
+
chainId: client.chain.id,
|
|
6300
|
+
filled: false
|
|
6301
|
+
});
|
|
6302
|
+
const newPositions = [];
|
|
6303
|
+
try {
|
|
6304
|
+
newPositions.push(...(await _snapshot({
|
|
6305
|
+
positions,
|
|
6306
|
+
blockNumber: latestBlockNumberChain,
|
|
6307
|
+
client,
|
|
6308
|
+
maxBatchSize,
|
|
6309
|
+
retryAttempts,
|
|
6310
|
+
retryDelayMs
|
|
6311
|
+
})).map((p) => ({
|
|
6312
|
+
...p,
|
|
6313
|
+
blockNumber: p.blockNumber + 1
|
|
6314
|
+
})));
|
|
6315
|
+
} catch (err) {
|
|
6316
|
+
logger.error({
|
|
6317
|
+
msg: "Failed to snapshot new empty positions",
|
|
6318
|
+
collector,
|
|
6319
|
+
chain_id: client.chain.id,
|
|
6320
|
+
block_number: latestBlockNumberChain,
|
|
6321
|
+
err
|
|
6322
|
+
});
|
|
6323
|
+
yield startBlock;
|
|
6324
|
+
return;
|
|
6325
|
+
}
|
|
6326
|
+
try {
|
|
6327
|
+
await db.transaction(async (dbTx) => {
|
|
6328
|
+
const insertPositions = async () => {
|
|
6329
|
+
if (newPositions.length === 0) return;
|
|
6330
|
+
try {
|
|
6331
|
+
const count = await dbTx.positions.upsert(newPositions);
|
|
6332
|
+
logger.info({
|
|
6333
|
+
msg: `New positions`,
|
|
6334
|
+
collector,
|
|
6335
|
+
count,
|
|
6336
|
+
chain_id: client.chain.id,
|
|
6337
|
+
block_number: latestBlockNumberChain
|
|
6338
|
+
});
|
|
6339
|
+
} catch (err) {
|
|
6340
|
+
throw new InsertPositionsError(err);
|
|
6341
|
+
}
|
|
6342
|
+
};
|
|
6343
|
+
const insertTransfers = async () => {
|
|
6344
|
+
if (transfers.length === 0) return;
|
|
6345
|
+
try {
|
|
6346
|
+
const created = await dbTx.transfers.create(transfers);
|
|
6347
|
+
logger.info({
|
|
6348
|
+
msg: `New transfers`,
|
|
6349
|
+
collector,
|
|
6350
|
+
count: created,
|
|
6351
|
+
chain_id: client.chain.id,
|
|
6352
|
+
block_range: [startBlock, blockNumber]
|
|
6353
|
+
});
|
|
6354
|
+
} catch (err) {
|
|
6355
|
+
throw new InsertTransfersError(err);
|
|
6356
|
+
}
|
|
6357
|
+
};
|
|
6358
|
+
const saveBlockNumber = async () => {
|
|
6359
|
+
try {
|
|
6360
|
+
await dbTx.blocks.advanceCollector({
|
|
6361
|
+
collectorName: collector,
|
|
6362
|
+
chainId: client.chain.id,
|
|
6363
|
+
blockNumber,
|
|
6364
|
+
epoch
|
|
6365
|
+
});
|
|
6366
|
+
} catch (_) {
|
|
6367
|
+
throw new ReorgError(blockNumber);
|
|
6368
|
+
}
|
|
6369
|
+
};
|
|
6370
|
+
await insertPositions();
|
|
6371
|
+
await insertTransfers();
|
|
6372
|
+
await saveBlockNumber();
|
|
6373
|
+
});
|
|
6374
|
+
} catch (err) {
|
|
6375
|
+
if (err instanceof ReorgError) {
|
|
6376
|
+
logger.info({
|
|
6377
|
+
msg: "Reorg detected, positions and transfers insertion aborted",
|
|
6378
|
+
collector,
|
|
6379
|
+
count: newPositions.length,
|
|
6380
|
+
chain_id: client.chain.id,
|
|
6381
|
+
block_number: blockNumber
|
|
6382
|
+
});
|
|
6383
|
+
reorgDetected = true;
|
|
6384
|
+
}
|
|
6385
|
+
if (err instanceof InsertPositionsError) {
|
|
6386
|
+
logger.error({
|
|
6387
|
+
msg: "Failed to insert positions",
|
|
6388
|
+
collector,
|
|
6389
|
+
count: newPositions.length,
|
|
6390
|
+
chain_id: client.chain.id,
|
|
6391
|
+
block_number: latestBlockNumberChain,
|
|
6392
|
+
err
|
|
6393
|
+
});
|
|
6394
|
+
throw err.cause;
|
|
6395
|
+
}
|
|
6396
|
+
if (err instanceof InsertTransfersError) {
|
|
6397
|
+
logger.error({
|
|
6398
|
+
msg: "Failed to insert transfers",
|
|
6399
|
+
collector,
|
|
6400
|
+
count: transfers.length,
|
|
6401
|
+
chain_id: client.chain.id,
|
|
6402
|
+
block_number: blockNumber,
|
|
6403
|
+
err
|
|
6404
|
+
});
|
|
6405
|
+
throw err.cause;
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
if (!reorgDetected) {
|
|
6409
|
+
startBlock = blockNumber;
|
|
6410
|
+
if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
|
|
6411
|
+
yield blockNumber;
|
|
6412
|
+
continue;
|
|
6413
|
+
}
|
|
6414
|
+
await db.transaction(async (dbTx) => {
|
|
6415
|
+
try {
|
|
6416
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
6417
|
+
collectorName: collector,
|
|
6418
|
+
chainId: client.chain.id
|
|
6419
|
+
});
|
|
6420
|
+
blockNumber = ancestor.blockNumber;
|
|
6421
|
+
const emptied = await dbTx.positions.setEmptyAfter({
|
|
6422
|
+
chainId: client.chain.id,
|
|
6423
|
+
blockNumber: blockNumber + 1
|
|
6424
|
+
});
|
|
6425
|
+
logger.info({
|
|
6426
|
+
msg: "Reorg detected, positions set to empty",
|
|
6427
|
+
collector,
|
|
6428
|
+
count: emptied,
|
|
6429
|
+
chain_id: client.chain.id,
|
|
6430
|
+
block_number_gte: blockNumber + 1
|
|
6431
|
+
});
|
|
6432
|
+
await dbTx.blocks.advanceCollector({
|
|
6433
|
+
collectorName: collector,
|
|
6434
|
+
chainId: client.chain.id,
|
|
6435
|
+
blockNumber,
|
|
6436
|
+
epoch: ancestor.epoch
|
|
6437
|
+
});
|
|
6438
|
+
} catch (err) {
|
|
6439
|
+
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6440
|
+
logger.error({
|
|
6441
|
+
collector,
|
|
6442
|
+
chainId: client.chain.id,
|
|
6443
|
+
msg,
|
|
6444
|
+
err
|
|
6445
|
+
});
|
|
6446
|
+
throw new Error(msg);
|
|
6447
|
+
}
|
|
6448
|
+
});
|
|
6449
|
+
return;
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
/**
|
|
6453
|
+
* @internal
|
|
6454
|
+
*
|
|
6455
|
+
* Snapshots positions and returns the new positions.
|
|
6456
|
+
* @param parameters - {@link _snapshot.Parameters}
|
|
6457
|
+
* @returns The new positions. {@link _snapshot.ReturnType}
|
|
6458
|
+
*/
|
|
6459
|
+
async function _snapshot(parameters) {
|
|
6460
|
+
const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
|
|
6461
|
+
const vaultV1Positions = [];
|
|
6462
|
+
const erc20Positions = [];
|
|
6463
|
+
for (const position of positions) switch (position.type) {
|
|
6464
|
+
case Type.VAULT_V1:
|
|
6465
|
+
vaultV1Positions.push(position);
|
|
6466
|
+
break;
|
|
6467
|
+
case Type.ERC20:
|
|
6468
|
+
erc20Positions.push(position);
|
|
6469
|
+
break;
|
|
6470
|
+
default: throw new Error("Invalid position type");
|
|
6471
|
+
}
|
|
6472
|
+
const promises = [snapshotVaultPositions({
|
|
6473
|
+
client,
|
|
6474
|
+
positions: vaultV1Positions,
|
|
6475
|
+
blockNumber,
|
|
6476
|
+
options: {
|
|
6477
|
+
maxBatchSize,
|
|
6478
|
+
retryAttempts,
|
|
6479
|
+
retryDelayMs
|
|
6480
|
+
}
|
|
6481
|
+
}), snapshotERC20Positions({
|
|
6482
|
+
client,
|
|
6483
|
+
positions: erc20Positions,
|
|
6484
|
+
blockNumber,
|
|
6485
|
+
options: {
|
|
6486
|
+
maxBatchSize,
|
|
6487
|
+
retryAttempts,
|
|
6488
|
+
retryDelayMs
|
|
6489
|
+
}
|
|
6490
|
+
})];
|
|
6491
|
+
return (await Promise.all(promises)).flat();
|
|
6492
|
+
}
|
|
6493
|
+
var InsertPositionsError = class extends BaseError {
|
|
6494
|
+
name = "InsertPositionsError";
|
|
6495
|
+
constructor(err) {
|
|
6496
|
+
super("Failed to insert positions", { cause: err });
|
|
6497
|
+
}
|
|
6498
|
+
};
|
|
6499
|
+
var InsertTransfersError = class extends BaseError {
|
|
6500
|
+
name = "InsertTransfersError";
|
|
6501
|
+
constructor(err) {
|
|
6502
|
+
super("Failed to insert transfers", { cause: err });
|
|
6503
|
+
}
|
|
6504
|
+
};
|
|
6505
|
+
|
|
6506
|
+
//#endregion
|
|
6507
|
+
//#region src/indexer/collectors/CollectFunctions/collectPrices.ts
|
|
6508
|
+
/**
|
|
6509
|
+
* Collects oracle prices from on-chain oracles and persists them.
|
|
6510
|
+
*
|
|
6511
|
+
* - Reads oracle definitions as {@link Oracle.Oracle}.
|
|
6512
|
+
* - Uses chain metadata from {@link Chain.Chain}.
|
|
6513
|
+
*
|
|
6514
|
+
* @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
|
|
6515
|
+
* with a client supporting {@link PublicActions.multicall | multicall}.
|
|
6516
|
+
* @yields Latest processed block number after each successful update.
|
|
6517
|
+
*/
|
|
6518
|
+
async function* collectPrices(parameters) {
|
|
6519
|
+
const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6520
|
+
const logger = getLogger();
|
|
6521
|
+
let blockNumber = parameters.lastBlockNumber;
|
|
6522
|
+
const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
|
|
6523
|
+
const updatedOracles = [];
|
|
6524
|
+
try {
|
|
6525
|
+
const pricesMap = await fetchOraclePrices({
|
|
6526
|
+
client,
|
|
6527
|
+
oracles: oracles.map((oracle) => oracle.address),
|
|
6528
|
+
options: {
|
|
6529
|
+
batchSize: maxBatchSize,
|
|
6530
|
+
blockNumber: latestBlockNumberChain,
|
|
6531
|
+
retryAttempts,
|
|
6532
|
+
retryDelayMs
|
|
6533
|
+
}
|
|
6534
|
+
});
|
|
6535
|
+
for (const oracle of oracles) {
|
|
6536
|
+
const price = pricesMap.get(oracle.address);
|
|
6537
|
+
if (price !== void 0) updatedOracles.push({
|
|
6538
|
+
chainId: client.chain.id,
|
|
6539
|
+
address: oracle.address,
|
|
6540
|
+
price,
|
|
6541
|
+
blockNumber: latestBlockNumberChain
|
|
6542
|
+
});
|
|
6543
|
+
}
|
|
6544
|
+
} catch (err) {
|
|
6545
|
+
logger.error({
|
|
6546
|
+
msg: "Failed to fetch oracle prices",
|
|
6547
|
+
collector,
|
|
6548
|
+
chain_id: client.chain.id,
|
|
6549
|
+
block_number: latestBlockNumberChain,
|
|
6550
|
+
err
|
|
6551
|
+
});
|
|
6552
|
+
yield blockNumber;
|
|
6553
|
+
return;
|
|
6554
|
+
}
|
|
6555
|
+
let reorgDetected = false;
|
|
6556
|
+
try {
|
|
6557
|
+
await db.transaction(async (dbTx) => {
|
|
6558
|
+
if (updatedOracles.length > 0) {
|
|
6559
|
+
await dbTx.oracles.upsert(updatedOracles);
|
|
6560
|
+
logger.info({
|
|
6561
|
+
msg: "Oracle prices updated",
|
|
6562
|
+
collector,
|
|
6563
|
+
count: updatedOracles.length,
|
|
6564
|
+
chain_id: client.chain.id,
|
|
6565
|
+
block_number: latestBlockNumberChain
|
|
6566
|
+
});
|
|
6567
|
+
}
|
|
6568
|
+
try {
|
|
6569
|
+
await dbTx.blocks.advanceCollector({
|
|
6570
|
+
collectorName: collector,
|
|
6571
|
+
chainId: client.chain.id,
|
|
6572
|
+
blockNumber: latestBlockNumberChain,
|
|
6573
|
+
epoch
|
|
6574
|
+
});
|
|
6575
|
+
} catch (_) {
|
|
6576
|
+
throw new ReorgError(latestBlockNumberChain);
|
|
6577
|
+
}
|
|
6578
|
+
blockNumber = latestBlockNumberChain;
|
|
6579
|
+
});
|
|
6580
|
+
} catch (err) {
|
|
6581
|
+
if (err instanceof ReorgError) {
|
|
6582
|
+
logger.info({
|
|
6583
|
+
msg: "Reorg detected, prices update aborted",
|
|
6584
|
+
collector,
|
|
6585
|
+
count: updatedOracles.length,
|
|
6586
|
+
chain_id: client.chain.id,
|
|
6587
|
+
block_number: latestBlockNumberChain
|
|
6588
|
+
});
|
|
6589
|
+
reorgDetected = true;
|
|
6590
|
+
} else throw new Error("Failed to collect oracle prices", { cause: err });
|
|
6591
|
+
}
|
|
6592
|
+
if (!reorgDetected) {
|
|
6593
|
+
yield blockNumber;
|
|
6594
|
+
return;
|
|
6595
|
+
}
|
|
6596
|
+
await db.transaction(async (dbTx) => {
|
|
6597
|
+
try {
|
|
6598
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
6599
|
+
collectorName: collector,
|
|
6600
|
+
chainId: client.chain.id
|
|
6601
|
+
});
|
|
6602
|
+
blockNumber = ancestor.blockNumber;
|
|
6603
|
+
await dbTx.blocks.advanceCollector({
|
|
6604
|
+
collectorName: collector,
|
|
6605
|
+
chainId: client.chain.id,
|
|
6606
|
+
blockNumber,
|
|
6607
|
+
epoch: ancestor.epoch
|
|
6608
|
+
});
|
|
6609
|
+
} catch (err) {
|
|
6610
|
+
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6611
|
+
logger.error({
|
|
6612
|
+
collector,
|
|
6613
|
+
chainId: client.chain.id,
|
|
6614
|
+
msg,
|
|
6615
|
+
err
|
|
6616
|
+
});
|
|
6617
|
+
throw new Error(msg);
|
|
6618
|
+
}
|
|
6619
|
+
});
|
|
6620
|
+
yield blockNumber;
|
|
6621
|
+
}
|
|
6622
|
+
|
|
6623
|
+
//#endregion
|
|
6624
|
+
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
6625
|
+
function createBuilder(parameters) {
|
|
6626
|
+
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
|
|
6627
|
+
const createCollector = (name, collect) => create$16({
|
|
6628
|
+
name,
|
|
6629
|
+
collect,
|
|
6630
|
+
client,
|
|
6631
|
+
db,
|
|
6632
|
+
options: {
|
|
6633
|
+
maxBlockNumber,
|
|
6634
|
+
interval
|
|
6635
|
+
}
|
|
6636
|
+
});
|
|
6637
|
+
return {
|
|
6638
|
+
buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
|
|
6639
|
+
return createCollector("offers", (p) => collectOffersV2({
|
|
6640
|
+
...p,
|
|
6641
|
+
gatekeeper,
|
|
6642
|
+
collector: "offers",
|
|
6643
|
+
client,
|
|
6644
|
+
options: {
|
|
6645
|
+
maxBatchSize,
|
|
6646
|
+
blockWindow
|
|
6647
|
+
}
|
|
6648
|
+
}));
|
|
6649
|
+
},
|
|
6650
|
+
buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
|
|
6651
|
+
return createCollector("consumed_events", (p) => collectConsumedEvents({
|
|
6652
|
+
...p,
|
|
6653
|
+
collector: "consumed_events",
|
|
6654
|
+
options: {
|
|
6655
|
+
maxBatchSize,
|
|
6656
|
+
blockWindow
|
|
6657
|
+
}
|
|
6658
|
+
}));
|
|
6659
|
+
},
|
|
6660
|
+
buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6661
|
+
return createCollector("prices", (p) => collectPrices({
|
|
6662
|
+
...p,
|
|
6663
|
+
collector: "prices",
|
|
6664
|
+
options: {
|
|
6665
|
+
maxBatchSize,
|
|
6666
|
+
retryAttempts,
|
|
6667
|
+
retryDelayMs
|
|
6668
|
+
}
|
|
6669
|
+
}));
|
|
6670
|
+
},
|
|
6671
|
+
buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6672
|
+
return createCollector("positions", (p) => collectPositions({
|
|
6673
|
+
...p,
|
|
6674
|
+
collector: "positions",
|
|
6675
|
+
options: {
|
|
6676
|
+
maxBatchSize,
|
|
6677
|
+
retryAttempts,
|
|
6678
|
+
retryDelayMs,
|
|
6679
|
+
blockWindow
|
|
6680
|
+
}
|
|
6681
|
+
}));
|
|
6682
|
+
}
|
|
6683
|
+
};
|
|
6684
|
+
}
|
|
6685
|
+
|
|
6686
|
+
//#endregion
|
|
6687
|
+
//#region src/indexer/collectors/Collectors.ts
|
|
6688
|
+
const from$2 = (parameters) => {
|
|
6689
|
+
const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
|
|
6690
|
+
const collectorBuilder = createBuilder({
|
|
6691
|
+
client,
|
|
6692
|
+
db,
|
|
6693
|
+
gatekeeper,
|
|
6694
|
+
options: {
|
|
6695
|
+
maxBlockNumber,
|
|
6696
|
+
blockWindow,
|
|
6697
|
+
interval
|
|
6698
|
+
}
|
|
6699
|
+
});
|
|
6700
|
+
return {
|
|
6701
|
+
offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
|
|
6702
|
+
consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
|
|
6703
|
+
pricesCollector: collectorBuilder.buildPricesCollector({ options: {
|
|
6704
|
+
maxBatchSize,
|
|
6705
|
+
retryAttempts,
|
|
6706
|
+
retryDelayMs
|
|
6707
|
+
} }),
|
|
6708
|
+
positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
|
|
6709
|
+
maxBatchSize,
|
|
6710
|
+
retryAttempts,
|
|
6711
|
+
retryDelayMs
|
|
6712
|
+
} })
|
|
6713
|
+
};
|
|
6714
|
+
};
|
|
6715
|
+
|
|
6716
|
+
//#endregion
|
|
6717
|
+
//#region src/indexer/Indexer.ts
|
|
6718
|
+
function from$1(config) {
|
|
6719
|
+
const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
|
|
6720
|
+
const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
|
|
6721
|
+
client,
|
|
6722
|
+
db,
|
|
6723
|
+
gatekeeper,
|
|
6724
|
+
maxBatchSize,
|
|
6725
|
+
maxBlockNumber,
|
|
6726
|
+
blockWindow,
|
|
6727
|
+
interval,
|
|
6728
|
+
retryAttempts,
|
|
6729
|
+
retryDelayMs
|
|
6730
|
+
});
|
|
6731
|
+
return create$18({
|
|
6732
|
+
client,
|
|
6733
|
+
collectors: [
|
|
6734
|
+
offersCollector,
|
|
6735
|
+
consumedEventsCollector,
|
|
6736
|
+
positionsCollector,
|
|
6737
|
+
pricesCollector
|
|
6738
|
+
]
|
|
6739
|
+
});
|
|
6740
|
+
}
|
|
6741
|
+
function create$18(params) {
|
|
6742
|
+
const { collectors, client } = params;
|
|
6743
|
+
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
6744
|
+
const tracer = getTracer(`router.${indexerId}`);
|
|
6745
|
+
const iterators = collectors.map((collector) => collector.collect());
|
|
6746
|
+
const next = async () => {
|
|
6747
|
+
await startActiveSpan(tracer, `${indexerId}.next`, async () => {
|
|
6748
|
+
await Promise.all(iterators.map((iterator) => iterator.next()));
|
|
6749
|
+
});
|
|
6750
|
+
};
|
|
6751
|
+
const _return = async () => {
|
|
6752
|
+
await Promise.all(iterators.map(async (iterator) => iterator.return()));
|
|
6753
|
+
};
|
|
6754
|
+
return {
|
|
6755
|
+
start: () => {
|
|
6756
|
+
const stops = collectors.map((collector) => start(collector));
|
|
6757
|
+
return () => {
|
|
6758
|
+
stops.forEach((stop) => {
|
|
6759
|
+
stop();
|
|
6760
|
+
});
|
|
6761
|
+
};
|
|
6762
|
+
},
|
|
6763
|
+
next,
|
|
6764
|
+
return: _return
|
|
6765
|
+
};
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6768
|
+
//#endregion
|
|
6769
|
+
//#region src/indexer/collectors/Admin.ts
|
|
6770
|
+
function create$17(parameters) {
|
|
6771
|
+
const collector = "admin";
|
|
6772
|
+
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
6773
|
+
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
6774
|
+
let finalizedBlock = null;
|
|
6775
|
+
let unfinalizedBlocks = [];
|
|
6776
|
+
let initialized = false;
|
|
6777
|
+
const logger = getLogger();
|
|
6778
|
+
let isMaxBlockNumberReached = false;
|
|
6779
|
+
let tick = 0;
|
|
6780
|
+
return { syncBlock: async () => {
|
|
6781
|
+
if (!initialized) {
|
|
6782
|
+
await Promise.all(names.map((collectorName) => db.blocks.init({
|
|
6783
|
+
chainId: client.chain.id,
|
|
6784
|
+
collectorName
|
|
6785
|
+
})));
|
|
6786
|
+
initialized = true;
|
|
6787
|
+
}
|
|
6788
|
+
if (isMaxBlockNumberReached) return true;
|
|
6789
|
+
const head = await client.getBlock({
|
|
6790
|
+
blockTag: "latest",
|
|
6791
|
+
includeTransactions: false
|
|
6792
|
+
});
|
|
6793
|
+
await db.transaction(async (dbTx) => {
|
|
6794
|
+
const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
6795
|
+
if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
|
|
6796
|
+
logger.info({
|
|
6797
|
+
msg: `Head is greater than max block number`,
|
|
6798
|
+
collector,
|
|
6799
|
+
chainId: client.chain.id,
|
|
6800
|
+
block_number: head.number,
|
|
6801
|
+
max_block_number: maxBlockNumber
|
|
6802
|
+
});
|
|
6803
|
+
await dbTx.blocks.handleReorg({
|
|
6804
|
+
chainId: client.chain.id,
|
|
6805
|
+
blockNumber: maxBlockNumber,
|
|
6806
|
+
epoch: epoch + 1n
|
|
6807
|
+
});
|
|
6808
|
+
isMaxBlockNumberReached = true;
|
|
6809
|
+
return isMaxBlockNumberReached;
|
|
6810
|
+
}
|
|
6811
|
+
finalizedBlock = await fetchFinalizedBlock({
|
|
6812
|
+
tick,
|
|
6813
|
+
client,
|
|
6814
|
+
logger,
|
|
6815
|
+
collector,
|
|
6816
|
+
unfinalizedBlocks,
|
|
6817
|
+
previousFinalizedBlock: finalizedBlock
|
|
6818
|
+
});
|
|
6819
|
+
tick++;
|
|
6820
|
+
let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6821
|
+
client,
|
|
6822
|
+
block: head,
|
|
6823
|
+
unfinalizedBlocks,
|
|
6824
|
+
finalizedBlock,
|
|
6825
|
+
logger,
|
|
6826
|
+
collector,
|
|
6827
|
+
maxBatchSize
|
|
6828
|
+
});
|
|
6829
|
+
unfinalizedBlocks = newUnfinalizedBlocks;
|
|
6830
|
+
const blockNumber = Number(returnedBlock.number);
|
|
6831
|
+
didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
|
|
6832
|
+
if (didReorgHappened) await dbTx.blocks.handleReorg({
|
|
6833
|
+
chainId: client.chain.id,
|
|
6834
|
+
blockNumber,
|
|
6835
|
+
epoch: epoch + 1n
|
|
6836
|
+
});
|
|
6837
|
+
else await dbTx.blocks.advanceChain({
|
|
6838
|
+
chainId: client.chain.id,
|
|
6839
|
+
blockNumber,
|
|
6840
|
+
epoch
|
|
6841
|
+
});
|
|
6842
|
+
});
|
|
6843
|
+
return isMaxBlockNumberReached;
|
|
6844
|
+
} };
|
|
6845
|
+
}
|
|
6846
|
+
const commonAncestor = (block, unfinalizedBlocks) => {
|
|
6847
|
+
const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
|
|
6848
|
+
if (parent) return parent;
|
|
6849
|
+
return null;
|
|
6850
|
+
};
|
|
6851
|
+
const fetchFinalizedBlock = async (parameters) => {
|
|
6852
|
+
let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
|
|
6853
|
+
let finalizedBlock = previousFinalizedBlock;
|
|
6854
|
+
if (tick % 20 === 0 || previousFinalizedBlock === null) {
|
|
6855
|
+
finalizedBlock = await client.getBlock({
|
|
6856
|
+
blockTag: "finalized",
|
|
6857
|
+
includeTransactions: false
|
|
6858
|
+
});
|
|
6859
|
+
if (finalizedBlock === null || finalizedBlock.number === null) {
|
|
6860
|
+
const msg = "Failed to get finalized block";
|
|
6861
|
+
logger.fatal({
|
|
6862
|
+
collector,
|
|
6863
|
+
chainId: client.chain.id,
|
|
6864
|
+
msg
|
|
6865
|
+
});
|
|
6866
|
+
throw new Error(msg);
|
|
6867
|
+
}
|
|
6868
|
+
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
|
|
6869
|
+
}
|
|
6870
|
+
if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
|
|
6871
|
+
const msg = "Failed to get finalized block";
|
|
6872
|
+
logger.fatal({
|
|
6873
|
+
collector,
|
|
6874
|
+
chainId: client.chain.id,
|
|
6875
|
+
msg
|
|
6876
|
+
});
|
|
6877
|
+
throw new Error(msg);
|
|
6878
|
+
}
|
|
6879
|
+
return {
|
|
6880
|
+
hash: finalizedBlock.hash,
|
|
6881
|
+
number: finalizedBlock.number,
|
|
6882
|
+
parentHash: finalizedBlock.parentHash
|
|
6883
|
+
};
|
|
6884
|
+
};
|
|
6885
|
+
const reconcile = async (parameters) => {
|
|
6886
|
+
let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
|
|
6887
|
+
const chain = client.chain;
|
|
6888
|
+
if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
|
|
6889
|
+
const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
|
|
6890
|
+
if (latestBlock === void 0) {
|
|
6891
|
+
const newBlock = {
|
|
6892
|
+
hash: block.hash,
|
|
6893
|
+
number: block.number,
|
|
6894
|
+
parentHash: block.parentHash
|
|
6895
|
+
};
|
|
6896
|
+
unfinalizedBlocks.push(newBlock);
|
|
6897
|
+
return {
|
|
6898
|
+
block: newBlock,
|
|
6899
|
+
didReorgHappened: false,
|
|
6900
|
+
unfinalizedBlocks
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6903
|
+
if (latestBlock.hash === block.hash) return {
|
|
6904
|
+
block: latestBlock,
|
|
6905
|
+
didReorgHappened: false,
|
|
6906
|
+
unfinalizedBlocks
|
|
6907
|
+
};
|
|
6908
|
+
if (latestBlock.number >= block.number) {
|
|
6909
|
+
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6910
|
+
logger.info({
|
|
6911
|
+
msg: `Reorg detected, latestBlock.number >= block.number`,
|
|
6912
|
+
collector,
|
|
6913
|
+
chain_id: chain.id,
|
|
6914
|
+
ancestor: ancestor.number,
|
|
6915
|
+
latest_block_number: latestBlock.number,
|
|
6916
|
+
block_number: block.number
|
|
6917
|
+
});
|
|
6918
|
+
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6919
|
+
return {
|
|
6920
|
+
block: ancestor,
|
|
6921
|
+
didReorgHappened: true,
|
|
6922
|
+
unfinalizedBlocks
|
|
6923
|
+
};
|
|
6924
|
+
}
|
|
6925
|
+
if (latestBlock.number + 1n < block.number) {
|
|
6926
|
+
logger.debug({
|
|
6927
|
+
collector,
|
|
6928
|
+
chain_id: chain.id,
|
|
6929
|
+
block_range: [latestBlock.number, block.number],
|
|
6930
|
+
msg: `Missing blocks`
|
|
6931
|
+
});
|
|
6932
|
+
const missingBlockNumbers = (() => {
|
|
6933
|
+
const missingBlockNumbers = [];
|
|
6934
|
+
let start = latestBlock.number + 1n;
|
|
6935
|
+
const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
|
|
6936
|
+
while (start < threshold) {
|
|
6937
|
+
missingBlockNumbers.push(start);
|
|
6938
|
+
start = start + 1n;
|
|
6939
|
+
}
|
|
6940
|
+
return missingBlockNumbers;
|
|
6941
|
+
})();
|
|
6942
|
+
const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
|
|
6943
|
+
blockNumber,
|
|
6944
|
+
includeTransactions: false
|
|
6945
|
+
}))));
|
|
6946
|
+
for (const missingBlock of missingBlocks) {
|
|
6947
|
+
const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6948
|
+
client,
|
|
6949
|
+
block: missingBlock,
|
|
6950
|
+
unfinalizedBlocks,
|
|
6951
|
+
finalizedBlock,
|
|
6952
|
+
logger,
|
|
6953
|
+
collector,
|
|
6954
|
+
maxBatchSize
|
|
6955
|
+
});
|
|
6956
|
+
if (returnedBlock.number !== missingBlock.number) return {
|
|
6957
|
+
block: returnedBlock,
|
|
6958
|
+
didReorgHappened,
|
|
6959
|
+
unfinalizedBlocks: newUnfinalizedBlocks
|
|
6960
|
+
};
|
|
6961
|
+
}
|
|
6962
|
+
return reconcile({
|
|
6963
|
+
client,
|
|
6964
|
+
block,
|
|
6965
|
+
unfinalizedBlocks,
|
|
6966
|
+
finalizedBlock,
|
|
6967
|
+
logger,
|
|
6968
|
+
collector,
|
|
6969
|
+
maxBatchSize
|
|
6970
|
+
});
|
|
6971
|
+
}
|
|
6972
|
+
if (block.parentHash !== latestBlock.hash) {
|
|
6973
|
+
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6974
|
+
logger.info({
|
|
6975
|
+
msg: `Reorg detected, block parent hash !== latest block hash`,
|
|
6976
|
+
collector,
|
|
6977
|
+
chain_id: chain.id,
|
|
6978
|
+
ancestor: ancestor.number,
|
|
6979
|
+
latest_block_number: latestBlock.number,
|
|
6980
|
+
block_number: block.number
|
|
6981
|
+
});
|
|
6982
|
+
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6983
|
+
return {
|
|
6984
|
+
block: ancestor,
|
|
6985
|
+
didReorgHappened: true,
|
|
6986
|
+
unfinalizedBlocks
|
|
6987
|
+
};
|
|
6988
|
+
}
|
|
6989
|
+
const newBlock = {
|
|
6990
|
+
hash: block.hash,
|
|
6991
|
+
number: block.number,
|
|
6992
|
+
parentHash: block.parentHash
|
|
6993
|
+
};
|
|
6994
|
+
unfinalizedBlocks.push(newBlock);
|
|
6995
|
+
return {
|
|
6996
|
+
block: newBlock,
|
|
6997
|
+
didReorgHappened: false,
|
|
6998
|
+
unfinalizedBlocks
|
|
6999
|
+
};
|
|
7000
|
+
};
|
|
7001
|
+
|
|
7002
|
+
//#endregion
|
|
7003
|
+
//#region src/indexer/collectors/Collector.ts
|
|
7004
|
+
const names = [
|
|
7005
|
+
"offers",
|
|
7006
|
+
"consumed_events",
|
|
7007
|
+
"positions",
|
|
7008
|
+
"prices"
|
|
7009
|
+
];
|
|
7010
|
+
function create$16({ name, collect, client, db, options }) {
|
|
7011
|
+
const admin = create$17({
|
|
7012
|
+
client,
|
|
7013
|
+
db,
|
|
7014
|
+
options
|
|
7015
|
+
});
|
|
7016
|
+
return {
|
|
7017
|
+
name,
|
|
7018
|
+
chain: client.chain,
|
|
7019
|
+
client,
|
|
7020
|
+
db,
|
|
7021
|
+
interval: options.interval ?? 1e4,
|
|
7022
|
+
collect: async function* () {
|
|
7023
|
+
const collector = name;
|
|
7024
|
+
const chain = client.chain;
|
|
7025
|
+
const logger = getLogger();
|
|
7026
|
+
const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
|
|
7027
|
+
const tracer = getTracer(`router.${collectorId}`);
|
|
7028
|
+
logger.info({
|
|
7029
|
+
msg: `Collector started`,
|
|
7030
|
+
collector,
|
|
7031
|
+
chain_id: chain.id
|
|
7032
|
+
});
|
|
7033
|
+
let iterator = null;
|
|
7034
|
+
let lastBlockNumber;
|
|
7035
|
+
while (true) try {
|
|
7036
|
+
if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
|
|
7037
|
+
const { collector: collectorBlock } = await db.blocks.init({
|
|
7038
|
+
collectorName: name,
|
|
7039
|
+
chainId: chain.id
|
|
7040
|
+
});
|
|
7041
|
+
lastBlockNumber = collectorBlock.blockNumber;
|
|
7042
|
+
return collect({
|
|
7043
|
+
client,
|
|
7044
|
+
collector: name,
|
|
7045
|
+
epoch: collectorBlock.epoch,
|
|
7046
|
+
lastBlockNumber,
|
|
7047
|
+
db
|
|
7048
|
+
});
|
|
7049
|
+
});
|
|
7050
|
+
if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
|
|
7051
|
+
const isMaxBlockNumberReached = await admin.syncBlock();
|
|
7052
|
+
span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
|
|
7053
|
+
return isMaxBlockNumberReached;
|
|
7054
|
+
}) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
|
|
7055
|
+
const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
|
|
7056
|
+
if (iterator === null) throw new Error("Iterator is not initialized");
|
|
7057
|
+
const { value: blockNumber, done } = await iterator.next();
|
|
7058
|
+
return {
|
|
7059
|
+
blockNumber,
|
|
7060
|
+
done
|
|
7061
|
+
};
|
|
7062
|
+
});
|
|
7063
|
+
if (done) iterator = null;
|
|
7064
|
+
else {
|
|
7065
|
+
lastBlockNumber = blockNumber;
|
|
7066
|
+
yield blockNumber;
|
|
7067
|
+
}
|
|
7068
|
+
} catch (err) {
|
|
7069
|
+
const isError = err instanceof Error;
|
|
7070
|
+
logger.error({
|
|
7071
|
+
msg: "Collector error",
|
|
7072
|
+
collector,
|
|
7073
|
+
chain_id: chain.id,
|
|
7074
|
+
error: isError ? err.message : String(err),
|
|
7075
|
+
stack: isError ? err.stack : void 0
|
|
7076
|
+
});
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
};
|
|
7080
|
+
}
|
|
7081
|
+
/** Start a collector with its own polling cadence based on chain head lag.
|
|
7082
|
+
* @param collector - The collector to start.
|
|
7083
|
+
* @returns A function to stop the collector.
|
|
7084
|
+
*/
|
|
7085
|
+
function start(collector) {
|
|
7086
|
+
let stopped = false;
|
|
7087
|
+
const it = collector.collect();
|
|
7088
|
+
const logger = getLogger();
|
|
7089
|
+
const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
|
|
7090
|
+
const tracer = getTracer(`router.${collectorId}`);
|
|
7091
|
+
const blocks = collector.db.blocks;
|
|
7092
|
+
let initialized = false;
|
|
7093
|
+
(async () => {
|
|
7094
|
+
while (!stopped) try {
|
|
7095
|
+
await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
|
|
7096
|
+
if (!initialized) {
|
|
7097
|
+
await blocks.init({
|
|
7098
|
+
chainId: collector.chain.id,
|
|
7099
|
+
collectorName: collector.name
|
|
7100
|
+
});
|
|
7101
|
+
initialized = true;
|
|
7102
|
+
}
|
|
7103
|
+
const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
|
|
7104
|
+
blockTag: "latest",
|
|
7105
|
+
includeTransactions: false
|
|
7106
|
+
}), blocks.getCollector({
|
|
7107
|
+
collectorName: collector.name,
|
|
7108
|
+
chainId: collector.chain.id
|
|
7109
|
+
})]);
|
|
7110
|
+
const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
|
|
7111
|
+
const waitSpan = tracer.startSpan(`${collectorId}.wait`);
|
|
7112
|
+
try {
|
|
7113
|
+
await wait(delay);
|
|
7114
|
+
} finally {
|
|
7115
|
+
waitSpan.end();
|
|
7116
|
+
}
|
|
7117
|
+
await it.next();
|
|
7118
|
+
});
|
|
7119
|
+
} catch (err) {
|
|
7120
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
7121
|
+
logger.error({
|
|
7122
|
+
msg: "Collector polling error",
|
|
7123
|
+
collector: collector.name,
|
|
7124
|
+
chain_id: collector.chain.id,
|
|
7125
|
+
err: error
|
|
7126
|
+
});
|
|
7127
|
+
}
|
|
7128
|
+
await it.return();
|
|
7129
|
+
})();
|
|
7130
|
+
return () => {
|
|
7131
|
+
stopped = true;
|
|
7132
|
+
};
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7277
7135
|
//#endregion
|
|
7278
7136
|
//#region src/database/domains/Blocks.ts
|
|
7279
7137
|
/** Postgres implementation. */
|
|
@@ -7705,35 +7563,15 @@ async function _getOffers(db, params) {
|
|
|
7705
7563
|
AND LOWER(pc.contract) = LOWER(c.position_contract)
|
|
7706
7564
|
AND LOWER(pc."user") = LOWER(c.position_user)
|
|
7707
7565
|
),
|
|
7708
|
-
-- Compute contribution per callback in loan terms (
|
|
7566
|
+
-- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
|
|
7709
7567
|
callback_loan_contribution AS (
|
|
7710
7568
|
SELECT
|
|
7711
7569
|
cc.*,
|
|
7712
7570
|
CASE
|
|
7713
|
-
-- No lot exists: contribution is 0
|
|
7714
7571
|
WHEN cc.lot_lower IS NULL THEN 0
|
|
7715
|
-
|
|
7716
|
-
WHEN LOWER(cc.position_asset) = LOWER(cc.loan_token) THEN
|
|
7717
|
-
LEAST(
|
|
7718
|
-
cc.lot_balance,
|
|
7719
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
7720
|
-
)
|
|
7721
|
-
-- Collateral position: convert to loan using (amount * price / 10^36) * lltv / 10^18
|
|
7722
|
-
ELSE
|
|
7723
|
-
(
|
|
7724
|
-
LEAST(
|
|
7725
|
-
cc.lot_balance,
|
|
7726
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
7727
|
-
) * COALESCE(collat_oracle.price::numeric, 0) / 1e36
|
|
7728
|
-
) * COALESCE(collat_info.lltv::numeric, 0) / 1e18
|
|
7572
|
+
ELSE LEAST(cc.lot_balance, COALESCE(cc.callback_amount::numeric, cc.lot_balance))
|
|
7729
7573
|
END AS contribution_in_loan
|
|
7730
7574
|
FROM callback_contributions cc
|
|
7731
|
-
LEFT JOIN ${obligationCollateralsV2} collat_info
|
|
7732
|
-
ON collat_info.obligation_id = cc.obligation_id
|
|
7733
|
-
AND LOWER(collat_info.asset) = LOWER(cc.position_asset)
|
|
7734
|
-
LEFT JOIN ${oracles} collat_oracle
|
|
7735
|
-
ON collat_oracle.chain_id = collat_info.oracle_chain_id
|
|
7736
|
-
AND LOWER(collat_oracle.address) = LOWER(collat_info.oracle_address)
|
|
7737
7575
|
),
|
|
7738
7576
|
-- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
|
|
7739
7577
|
offer_contributions AS (
|
|
@@ -7770,6 +7608,22 @@ async function _getOffers(db, params) {
|
|
|
7770
7608
|
GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
|
|
7771
7609
|
callback_address, callback_data, block_number, group_chain_id, group_maker,
|
|
7772
7610
|
consumed, chain_id, loan_token, session
|
|
7611
|
+
UNION ALL
|
|
7612
|
+
-- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
|
|
7613
|
+
SELECT
|
|
7614
|
+
p.hash, p.obligation_id, p.assets, p.price,
|
|
7615
|
+
p.obligation_units, p.obligation_shares,
|
|
7616
|
+
p.maturity, p.expiry, p.start, p.group_group,
|
|
7617
|
+
p.buy, p.callback_address, p.callback_data,
|
|
7618
|
+
p.block_number, p.group_chain_id, p.group_maker,
|
|
7619
|
+
p.consumed, p.chain_id, p.loan_token, p.session,
|
|
7620
|
+
0 AS total_available
|
|
7621
|
+
FROM paged p
|
|
7622
|
+
WHERE p.buy = false
|
|
7623
|
+
AND NOT EXISTS (
|
|
7624
|
+
SELECT 1 FROM ${offersCallbacks} oc2
|
|
7625
|
+
WHERE oc2.offer_hash = p.hash
|
|
7626
|
+
)
|
|
7773
7627
|
)
|
|
7774
7628
|
-- Final SELECT with inline takeable computation
|
|
7775
7629
|
SELECT
|
|
@@ -7792,18 +7646,24 @@ async function _getOffers(db, params) {
|
|
|
7792
7646
|
oc.block_number,
|
|
7793
7647
|
oc.session,
|
|
7794
7648
|
COALESCE(oc.total_available, 0) AS available,
|
|
7795
|
-
-- takeable
|
|
7796
|
-
|
|
7797
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
7798
|
-
|
|
7799
|
-
|
|
7649
|
+
-- takeable: sell offers use assets - consumed directly (collateral positions not indexed yet)
|
|
7650
|
+
CASE WHEN oc.buy = false
|
|
7651
|
+
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
7652
|
+
ELSE GREATEST(0, LEAST(
|
|
7653
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
7654
|
+
COALESCE(oc.total_available, 0)
|
|
7655
|
+
))
|
|
7656
|
+
END AS takeable,
|
|
7800
7657
|
c.collaterals
|
|
7801
7658
|
FROM offer_contributions oc
|
|
7802
7659
|
LEFT JOIN collats c ON c.obligation_id = oc.obligation_id
|
|
7803
|
-
WHERE
|
|
7804
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
7805
|
-
|
|
7806
|
-
|
|
7660
|
+
WHERE CASE WHEN oc.buy = false
|
|
7661
|
+
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
7662
|
+
ELSE GREATEST(0, LEAST(
|
|
7663
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
7664
|
+
COALESCE(oc.total_available, 0)
|
|
7665
|
+
))
|
|
7666
|
+
END > 0
|
|
7807
7667
|
ORDER BY
|
|
7808
7668
|
oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
|
|
7809
7669
|
oc.block_number ASC,
|
|
@@ -10177,6 +10037,7 @@ async function getOffersQuery(db, parameters) {
|
|
|
10177
10037
|
if (cursor !== null && cursor !== void 0) {
|
|
10178
10038
|
if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
|
|
10179
10039
|
}
|
|
10040
|
+
const now = Math.floor((Date.now() - 1) / 1e3);
|
|
10180
10041
|
const collateralsLateral = db.select({ collaterals: sql`COALESCE(
|
|
10181
10042
|
jsonb_agg(
|
|
10182
10043
|
jsonb_build_object(
|
|
@@ -10190,27 +10051,14 @@ async function getOffersQuery(db, parameters) {
|
|
|
10190
10051
|
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
10191
10052
|
const availableLateral = db.select({ available: sql`COALESCE(SUM(
|
|
10192
10053
|
CASE
|
|
10193
|
-
-- If asset is null, position available is 0
|
|
10194
10054
|
WHEN ${positions.asset} IS NULL THEN 0
|
|
10195
|
-
|
|
10196
|
-
-- Position asset matches loan token: no conversion needed
|
|
10197
|
-
WHEN ${positions.asset} = ${obligations.loanToken} THEN
|
|
10055
|
+
ELSE
|
|
10198
10056
|
CASE
|
|
10199
10057
|
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
10200
10058
|
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
10201
10059
|
END
|
|
10202
|
-
|
|
10203
|
-
-- Position asset is collateral: apply oracle price * lltv
|
|
10204
|
-
-- Formula: balance * price / 1e36 * lltv / 1e18
|
|
10205
|
-
ELSE
|
|
10206
|
-
(CASE
|
|
10207
|
-
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
10208
|
-
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
10209
|
-
END)
|
|
10210
|
-
* COALESCE(${oracles.price}, 0)::numeric / 1e36
|
|
10211
|
-
* COALESCE(${obligationCollateralsV2.lltv}, 0)::numeric / 1e18
|
|
10212
10060
|
END
|
|
10213
|
-
), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, eq(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, and(eq(callbacks.positionChainId, positions.chainId), eq(callbacks.positionContract, positions.contract), eq(callbacks.positionUser, positions.user))).
|
|
10061
|
+
), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, eq(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, and(eq(callbacks.positionChainId, positions.chainId), eq(callbacks.positionContract, positions.contract), eq(callbacks.positionUser, positions.user))).where(eq(offersCallbacks.offerHash, offers.hash)).as("available_lateral");
|
|
10214
10062
|
const rows = (await db.select({
|
|
10215
10063
|
hash: offers.hash,
|
|
10216
10064
|
maker: offers.groupMaker,
|
|
@@ -10232,17 +10080,24 @@ async function getOffersQuery(db, parameters) {
|
|
|
10232
10080
|
collaterals: collateralsLateral.collaterals,
|
|
10233
10081
|
blockNumber: offers.blockNumber,
|
|
10234
10082
|
available: sql`COALESCE(${availableLateral.available}::numeric, 0)`.as("available"),
|
|
10235
|
-
takeable: sql`FLOOR(GREATEST(
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10083
|
+
takeable: sql`FLOOR(GREATEST(0,
|
|
10084
|
+
CASE WHEN ${offers.buy} = false
|
|
10085
|
+
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
10086
|
+
ELSE LEAST(
|
|
10087
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
10088
|
+
COALESCE(${availableLateral.available}::numeric, 0)
|
|
10089
|
+
)
|
|
10090
|
+
END
|
|
10241
10091
|
))`.as("takeable")
|
|
10242
|
-
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).leftJoinLateral(availableLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, maker === void 0 ? sql`GREATEST(0,
|
|
10243
|
-
${offers.
|
|
10244
|
-
|
|
10245
|
-
|
|
10092
|
+
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).leftJoinLateral(availableLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, gte(offers.expiry, now), gte(offers.maturity, now), maker === void 0 ? sql`GREATEST(0,
|
|
10093
|
+
CASE WHEN ${offers.buy} = false
|
|
10094
|
+
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
10095
|
+
ELSE LEAST(
|
|
10096
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
10097
|
+
COALESCE(${availableLateral.available}::numeric, 0)
|
|
10098
|
+
)
|
|
10099
|
+
END
|
|
10100
|
+
) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
|
|
10246
10101
|
return {
|
|
10247
10102
|
hash: row.hash,
|
|
10248
10103
|
maker: row.maker,
|
|
@@ -10352,35 +10207,6 @@ async function getUserPositions(queryParameters, db) {
|
|
|
10352
10207
|
}
|
|
10353
10208
|
}
|
|
10354
10209
|
|
|
10355
|
-
//#endregion
|
|
10356
|
-
//#region src/api/Controllers/resolveCallbackTypes.ts
|
|
10357
|
-
/**
|
|
10358
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
10359
|
-
* @param body - Request body with callback addresses. {@link CallbackTypesRequest}
|
|
10360
|
-
* @param chains - Chains to resolve callback types against. {@link Chain.Chain}
|
|
10361
|
-
* @returns Callback types grouped by chain. {@link CallbackTypesPayload}
|
|
10362
|
-
*/
|
|
10363
|
-
async function resolveCallbackTypes$1(body, chains) {
|
|
10364
|
-
const result = safeParse("callback_types", body, (issue) => issue.message);
|
|
10365
|
-
if (!result.success) return failure(result.error);
|
|
10366
|
-
const request = result.data;
|
|
10367
|
-
const chainIds = new Set(chains.map((chain) => chain.id));
|
|
10368
|
-
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
10369
|
-
if (unknown) return failure(new BadRequestError(`Unknown chain id ${unknown.chain_id}`));
|
|
10370
|
-
try {
|
|
10371
|
-
const data = resolveCallbackTypes$2({
|
|
10372
|
-
chains,
|
|
10373
|
-
request
|
|
10374
|
-
});
|
|
10375
|
-
return success({
|
|
10376
|
-
data,
|
|
10377
|
-
cursor: null
|
|
10378
|
-
});
|
|
10379
|
-
} catch (err) {
|
|
10380
|
-
return failure(err);
|
|
10381
|
-
}
|
|
10382
|
-
}
|
|
10383
|
-
|
|
10384
10210
|
//#endregion
|
|
10385
10211
|
//#region src/api/Api.ts
|
|
10386
10212
|
function from(config) {
|
|
@@ -10450,24 +10276,9 @@ function serve$1(parameters) {
|
|
|
10450
10276
|
const { statusCode, body } = await gatekeeper.validate(reqBody);
|
|
10451
10277
|
return c.json(body, statusCode);
|
|
10452
10278
|
} catch (err) {
|
|
10453
|
-
const failure$
|
|
10454
|
-
return c.json(failure$1.body, failure$1.statusCode);
|
|
10455
|
-
}
|
|
10456
|
-
});
|
|
10457
|
-
app.post("/v1/callbacks", async (c) => {
|
|
10458
|
-
let body;
|
|
10459
|
-
try {
|
|
10460
|
-
body = await c.req.json();
|
|
10461
|
-
} catch (err) {
|
|
10462
|
-
const failure$3 = failure(err);
|
|
10463
|
-
return c.json(failure$3.body, failure$3.statusCode);
|
|
10464
|
-
}
|
|
10465
|
-
if (body === null || typeof body !== "object") {
|
|
10466
|
-
const failure$2 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
10279
|
+
const failure$2 = failure(err);
|
|
10467
10280
|
return c.json(failure$2.body, failure$2.statusCode);
|
|
10468
10281
|
}
|
|
10469
|
-
const { statusCode, body: responseBody } = await resolveCallbackTypes$1(body, chainRegistry.list());
|
|
10470
|
-
return c.json(responseBody, statusCode);
|
|
10471
10282
|
});
|
|
10472
10283
|
app.get("/v1/users/:userAddress/positions", async (c) => {
|
|
10473
10284
|
const query = c.req.query();
|
|
@@ -10499,8 +10310,8 @@ function serve$1(parameters) {
|
|
|
10499
10310
|
const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
|
|
10500
10311
|
return c.json(body, statusCode);
|
|
10501
10312
|
} catch (err) {
|
|
10502
|
-
const failure$
|
|
10503
|
-
return c.json(failure$
|
|
10313
|
+
const failure$1 = failure(err);
|
|
10314
|
+
return c.json(failure$1.body, failure$1.statusCode);
|
|
10504
10315
|
}
|
|
10505
10316
|
});
|
|
10506
10317
|
app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
|
|
@@ -10591,23 +10402,11 @@ function createHttpClient(config) {
|
|
|
10591
10402
|
issues: []
|
|
10592
10403
|
};
|
|
10593
10404
|
};
|
|
10594
|
-
const getCallbackTypes = async (requestPayload) => {
|
|
10595
|
-
const response = await request("/v1/callbacks", {
|
|
10596
|
-
method: "POST",
|
|
10597
|
-
headers: { "content-type": "application/json" },
|
|
10598
|
-
body: JSON.stringify(requestPayload)
|
|
10599
|
-
});
|
|
10600
|
-
const json = await response.json();
|
|
10601
|
-
if (!response.ok) throw new Error(`Gatekeeper callbacks request failed: ${extractErrorMessage(json) ?? response.statusText}`);
|
|
10602
|
-
if (!("data" in json) || !Array.isArray(json.data)) throw new Error("Gatekeeper callbacks response is invalid.");
|
|
10603
|
-
return json.data;
|
|
10604
|
-
};
|
|
10605
10405
|
return {
|
|
10606
10406
|
baseUrl,
|
|
10607
10407
|
validate,
|
|
10608
10408
|
getConfigRules,
|
|
10609
|
-
isAllowed
|
|
10610
|
-
getCallbackTypes
|
|
10409
|
+
isAllowed
|
|
10611
10410
|
};
|
|
10612
10411
|
}
|
|
10613
10412
|
function mergeHeaders(base, extra) {
|
|
@@ -10843,35 +10642,17 @@ function createMockOffers(parameters) {
|
|
|
10843
10642
|
lltv
|
|
10844
10643
|
}));
|
|
10845
10644
|
if (!chainRegistry.getById(offer.chainId)) throw new Error(`Missing chain config for id ${offer.chainId}`);
|
|
10846
|
-
const callbackType = buy ? Type$1.BuyVaultV1Callback : Type$1.SellERC20Callback;
|
|
10847
|
-
const callbackAddress = buy ? BUY_CALLBACK_ADDRESS : SELL_CALLBACK_ADDRESS;
|
|
10848
|
-
const callbackData = buildMockCallbackData(callbackType, {
|
|
10849
|
-
...offer,
|
|
10850
|
-
collaterals
|
|
10851
|
-
});
|
|
10852
10645
|
return from$12({
|
|
10853
10646
|
...offer,
|
|
10854
10647
|
loanToken,
|
|
10855
10648
|
collaterals,
|
|
10856
10649
|
callback: {
|
|
10857
|
-
address:
|
|
10858
|
-
data:
|
|
10650
|
+
address: zeroAddress,
|
|
10651
|
+
data: "0x"
|
|
10859
10652
|
}
|
|
10860
10653
|
});
|
|
10861
10654
|
});
|
|
10862
10655
|
}
|
|
10863
|
-
function buildMockCallbackData(callbackType, offer) {
|
|
10864
|
-
const assets = offer.collaterals.map((collateral) => collateral.asset);
|
|
10865
|
-
const amounts = offer.collaterals.map(() => offer.assets);
|
|
10866
|
-
if (callbackType === Type$1.BuyVaultV1Callback) return encodeBuyVaultV1Callback({
|
|
10867
|
-
vaults: assets,
|
|
10868
|
-
amounts
|
|
10869
|
-
});
|
|
10870
|
-
return encodeSellERC20Callback({
|
|
10871
|
-
collaterals: assets,
|
|
10872
|
-
amounts
|
|
10873
|
-
});
|
|
10874
|
-
}
|
|
10875
10656
|
function seedMockOracles(offers, price, blockNumber) {
|
|
10876
10657
|
const oracleMap = /* @__PURE__ */ new Map();
|
|
10877
10658
|
for (const offer of offers) for (const collateral of offer.collaterals) {
|
|
@@ -10886,8 +10667,6 @@ function seedMockOracles(offers, price, blockNumber) {
|
|
|
10886
10667
|
}
|
|
10887
10668
|
return Array.from(oracleMap.values());
|
|
10888
10669
|
}
|
|
10889
|
-
const BUY_CALLBACK_ADDRESS = "0x3333333333333333333333333333333333333333";
|
|
10890
|
-
const SELL_CALLBACK_ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
10891
10670
|
async function seedOffers(parameters) {
|
|
10892
10671
|
const { db, offers, blockNumber } = parameters;
|
|
10893
10672
|
if (offers.length === 0) return;
|
|
@@ -10904,14 +10683,10 @@ async function seedOffers(parameters) {
|
|
|
10904
10683
|
}]);
|
|
10905
10684
|
}
|
|
10906
10685
|
async function seedOfferAssociations(parameters) {
|
|
10907
|
-
const { db,
|
|
10686
|
+
const { db, offers, blockNumber } = parameters;
|
|
10908
10687
|
if (offers.length === 0) return;
|
|
10909
10688
|
const { callbacks, positions, lots } = buildOfferAssociationsFromOffers({
|
|
10910
10689
|
offers,
|
|
10911
|
-
callbackTypes: await resolveCallbackTypes({
|
|
10912
|
-
gatekeeper,
|
|
10913
|
-
offers
|
|
10914
|
-
}),
|
|
10915
10690
|
blockNumber
|
|
10916
10691
|
});
|
|
10917
10692
|
if (positions.length > 0) await db.positions.upsert(positions);
|
|
@@ -10954,83 +10729,40 @@ function buildOfferDependencies(parameters) {
|
|
|
10954
10729
|
groups: Array.from(groupsByKey.values())
|
|
10955
10730
|
};
|
|
10956
10731
|
}
|
|
10957
|
-
async function resolveCallbackTypes(parameters) {
|
|
10958
|
-
const { gatekeeper, offers } = parameters;
|
|
10959
|
-
const addressesByChain = /* @__PURE__ */ new Map();
|
|
10960
|
-
for (const offer of offers) {
|
|
10961
|
-
if (offer.callback.data === "0x") continue;
|
|
10962
|
-
const set = addressesByChain.get(offer.chainId) ?? /* @__PURE__ */ new Set();
|
|
10963
|
-
set.add(offer.callback.address.toLowerCase());
|
|
10964
|
-
addressesByChain.set(offer.chainId, set);
|
|
10965
|
-
}
|
|
10966
|
-
if (addressesByChain.size === 0) return /* @__PURE__ */ new Map();
|
|
10967
|
-
const request = { callbacks: Array.from(addressesByChain.entries()).map(([chainId, addresses]) => ({
|
|
10968
|
-
chain_id: chainId,
|
|
10969
|
-
addresses: Array.from(addresses)
|
|
10970
|
-
})) };
|
|
10971
|
-
const response = await gatekeeper.getCallbackTypes(request);
|
|
10972
|
-
const typeByAddress = /* @__PURE__ */ new Map();
|
|
10973
|
-
for (const entry of response) {
|
|
10974
|
-
const chainId = entry.chain_id;
|
|
10975
|
-
for (const [key, list] of Object.entries(entry)) {
|
|
10976
|
-
if (key === "chain_id" || key === "not_supported") continue;
|
|
10977
|
-
if (!Array.isArray(list)) continue;
|
|
10978
|
-
for (const address of list) {
|
|
10979
|
-
const mapKey = `${chainId}-${address.toLowerCase()}`;
|
|
10980
|
-
typeByAddress.set(mapKey, key);
|
|
10981
|
-
}
|
|
10982
|
-
}
|
|
10983
|
-
}
|
|
10984
|
-
return typeByAddress;
|
|
10985
|
-
}
|
|
10986
10732
|
function buildOfferAssociationsFromOffers(parameters) {
|
|
10987
|
-
const { offers,
|
|
10733
|
+
const { offers, blockNumber } = parameters;
|
|
10988
10734
|
const callbacks = [];
|
|
10989
10735
|
const positions = [];
|
|
10990
10736
|
const lots = [];
|
|
10991
10737
|
for (const offer of offers) {
|
|
10992
|
-
if (offer.
|
|
10993
|
-
const
|
|
10994
|
-
const
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
10738
|
+
if (!offer.buy) continue;
|
|
10739
|
+
const loanToken = offer.loanToken.toLowerCase();
|
|
10740
|
+
const offerHash = hash(offer);
|
|
10741
|
+
positions.push(from$10({
|
|
10742
|
+
chainId: offer.chainId,
|
|
10743
|
+
contract: loanToken,
|
|
10744
|
+
user: offer.maker,
|
|
10745
|
+
type: Type.ERC20,
|
|
10746
|
+
asset: loanToken,
|
|
10747
|
+
balance: offer.assets,
|
|
10748
|
+
blockNumber
|
|
10749
|
+
}));
|
|
11004
10750
|
callbacks.push({
|
|
11005
|
-
offerHash
|
|
11006
|
-
callbacks:
|
|
10751
|
+
offerHash,
|
|
10752
|
+
callbacks: [{
|
|
11007
10753
|
chainId: offer.chainId,
|
|
11008
|
-
contract:
|
|
10754
|
+
contract: loanToken,
|
|
11009
10755
|
user: offer.maker,
|
|
11010
|
-
amount:
|
|
11011
|
-
}
|
|
10756
|
+
amount: offer.assets
|
|
10757
|
+
}]
|
|
10758
|
+
});
|
|
10759
|
+
lots.push({
|
|
10760
|
+
positionChainId: offer.chainId,
|
|
10761
|
+
positionContract: loanToken,
|
|
10762
|
+
positionUser: offer.maker,
|
|
10763
|
+
group: offer.group,
|
|
10764
|
+
size: offer.assets
|
|
11012
10765
|
});
|
|
11013
|
-
for (const callback of decoded) {
|
|
11014
|
-
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
11015
|
-
const positionAsset = callbackType === Type$1.BuyVaultV1Callback ? offer.loanToken : callback.contract;
|
|
11016
|
-
positions.push(from$10({
|
|
11017
|
-
chainId: offer.chainId,
|
|
11018
|
-
contract: callback.contract,
|
|
11019
|
-
user: offer.maker,
|
|
11020
|
-
type: positionType,
|
|
11021
|
-
balance: callback.amount * 2n,
|
|
11022
|
-
asset: positionAsset,
|
|
11023
|
-
blockNumber
|
|
11024
|
-
}));
|
|
11025
|
-
const isLoanPosition = positionAsset.toLowerCase() === offer.loanToken.toLowerCase();
|
|
11026
|
-
lots.push({
|
|
11027
|
-
positionChainId: offer.chainId,
|
|
11028
|
-
positionContract: callback.contract,
|
|
11029
|
-
positionUser: offer.maker,
|
|
11030
|
-
group: offer.group,
|
|
11031
|
-
size: isLoanPosition ? offer.assets : callback.amount
|
|
11032
|
-
});
|
|
11033
|
-
}
|
|
11034
10766
|
}
|
|
11035
10767
|
return {
|
|
11036
10768
|
callbacks,
|