@morpho-dev/router 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2040 -2156
- package/dist/index.browser.d.mts +154 -290
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.d.ts +154 -290
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +223 -464
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +223 -464
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +155 -301
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.d.ts +154 -300
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +1966 -2190
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +1970 -2188
- package/dist/index.node.mjs.map +1 -1
- package/docs/integrator.md +5 -6
- package/package.json +1 -1
package/dist/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.
|
|
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,38 +1608,23 @@ 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",
|
|
1697
1614
|
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
1698
1615
|
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
1699
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
1616
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
1617
|
+
"0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
|
|
1618
|
+
"0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
|
|
1700
1619
|
],
|
|
1701
1620
|
[ChainId.BASE.toString()]: [
|
|
1702
1621
|
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
1703
1622
|
"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
|
1704
1623
|
"0x4200000000000000000000000000000000000006",
|
|
1705
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
1624
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
1625
|
+
"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
|
1626
|
+
"0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
|
|
1627
|
+
"0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
|
|
1706
1628
|
],
|
|
1707
1629
|
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
1708
1630
|
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
@@ -1718,65 +1640,58 @@ const assets = {
|
|
|
1718
1640
|
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
1719
1641
|
]
|
|
1720
1642
|
};
|
|
1643
|
+
const oracles$1 = {
|
|
1644
|
+
[ChainId.ETHEREUM.toString()]: [
|
|
1645
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
1646
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
1647
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
1648
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
1649
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
1650
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
1651
|
+
],
|
|
1652
|
+
[ChainId.BASE.toString()]: [
|
|
1653
|
+
"0xD09048c8B568Dbf5f189302beA26c9edABFC4858",
|
|
1654
|
+
"0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4",
|
|
1655
|
+
"0x05D2618404668D725B66c0f32B39e4EC15B393dC",
|
|
1656
|
+
"0xE1bb8E5b4930eC9FeC7f7943FCF6227649F14B37",
|
|
1657
|
+
"0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9",
|
|
1658
|
+
"0x10b95702a0ce895972C91e432C4f7E19811D320E",
|
|
1659
|
+
"0x8C87DbD7A0c647A4291592Bc2994dbF95880fE2F",
|
|
1660
|
+
"0x4A11590e5326138B514E08A9B52202D42077Ca65",
|
|
1661
|
+
"0xa54122f0E0766258377Ffe732e454A3248f454F4"
|
|
1662
|
+
],
|
|
1663
|
+
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
1664
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
1665
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
1666
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
1667
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
1668
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
1669
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
1670
|
+
],
|
|
1671
|
+
[ChainId.ANVIL.toString()]: [
|
|
1672
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
1673
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
1674
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
1675
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
1676
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
1677
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
1678
|
+
]
|
|
1679
|
+
};
|
|
1721
1680
|
const configs = {
|
|
1722
1681
|
ethereum: {
|
|
1723
|
-
callbacks: [
|
|
1724
|
-
{
|
|
1725
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1726
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1727
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1728
|
-
},
|
|
1729
|
-
{
|
|
1730
|
-
type: Type$1.SellERC20Callback,
|
|
1731
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1732
|
-
},
|
|
1733
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1734
|
-
],
|
|
1682
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1735
1683
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1736
1684
|
},
|
|
1737
1685
|
base: {
|
|
1738
|
-
callbacks: [
|
|
1739
|
-
{
|
|
1740
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1741
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1742
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0xFf62A7c278C62eD665133147129245053Bbf5918"]
|
|
1743
|
-
},
|
|
1744
|
-
{
|
|
1745
|
-
type: Type$1.SellERC20Callback,
|
|
1746
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1747
|
-
},
|
|
1748
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1749
|
-
],
|
|
1686
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1750
1687
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1751
1688
|
},
|
|
1752
1689
|
"ethereum-virtual-testnet": {
|
|
1753
|
-
callbacks: [
|
|
1754
|
-
{
|
|
1755
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1756
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1757
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1758
|
-
},
|
|
1759
|
-
{
|
|
1760
|
-
type: Type$1.SellERC20Callback,
|
|
1761
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1762
|
-
},
|
|
1763
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1764
|
-
],
|
|
1690
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1765
1691
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1766
1692
|
},
|
|
1767
1693
|
anvil: {
|
|
1768
|
-
callbacks: [
|
|
1769
|
-
{
|
|
1770
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1771
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1772
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1773
|
-
},
|
|
1774
|
-
{
|
|
1775
|
-
type: Type$1.SellERC20Callback,
|
|
1776
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1777
|
-
},
|
|
1778
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1779
|
-
],
|
|
1694
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1780
1695
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1781
1696
|
}
|
|
1782
1697
|
};
|
|
@@ -2258,7 +2173,7 @@ function random(config) {
|
|
|
2258
2173
|
const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
|
|
2259
2174
|
const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
|
|
2260
2175
|
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
|
|
2261
|
-
|
|
2176
|
+
collateralCandidates[int(collateralCandidates.length)];
|
|
2262
2177
|
const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
|
|
2263
2178
|
const maturity = config?.maturity ?? from$16(maturityOption);
|
|
2264
2179
|
const lltv = from$15(weightedChoice([
|
|
@@ -2285,21 +2200,10 @@ function random(config) {
|
|
|
2285
2200
|
const unit = BigInt(10) ** BigInt(loanTokenDecimals);
|
|
2286
2201
|
const amountBase = BigInt(100 + int(999901));
|
|
2287
2202
|
const assetsScaled = config?.assets ?? amountBase * unit;
|
|
2288
|
-
const
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
};
|
|
2293
|
-
const sellCallbackAddress = "0x3333333333333333333333333333333333333333";
|
|
2294
|
-
const amount = assetsScaled * 1000000000000000000000n;
|
|
2295
|
-
return {
|
|
2296
|
-
address: sellCallbackAddress,
|
|
2297
|
-
data: encodeSellERC20Callback({
|
|
2298
|
-
collaterals: [collateralAsset],
|
|
2299
|
-
amounts: [amount]
|
|
2300
|
-
})
|
|
2301
|
-
};
|
|
2302
|
-
})();
|
|
2203
|
+
const emptyCallback = {
|
|
2204
|
+
address: zeroAddress,
|
|
2205
|
+
data: "0x"
|
|
2206
|
+
};
|
|
2303
2207
|
return from$12({
|
|
2304
2208
|
maker: config?.maker ?? address(),
|
|
2305
2209
|
assets: assetsScaled,
|
|
@@ -2318,7 +2222,7 @@ function random(config) {
|
|
|
2318
2222
|
...random$1(),
|
|
2319
2223
|
lltv
|
|
2320
2224
|
})).sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
2321
|
-
callback: config?.callback ??
|
|
2225
|
+
callback: config?.callback ?? emptyCallback
|
|
2322
2226
|
});
|
|
2323
2227
|
}
|
|
2324
2228
|
const weightedChoice = (pairs) => {
|
|
@@ -2472,6 +2376,94 @@ function obligationId(offer) {
|
|
|
2472
2376
|
}));
|
|
2473
2377
|
}
|
|
2474
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
|
+
/**
|
|
2475
2467
|
* ABI for the Consume event emitted by the Obligation contract.
|
|
2476
2468
|
*/
|
|
2477
2469
|
const consumedEvent = {
|
|
@@ -2897,12 +2889,10 @@ const maturity = ({ maturities }) => single("maturity", `Validates that offer ma
|
|
|
2897
2889
|
const allowedMaturities = maturities.map((m) => from$16(m));
|
|
2898
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}` };
|
|
2899
2891
|
});
|
|
2900
|
-
const callback = ({ callbacks
|
|
2901
|
-
if (isEmptyCallback(offer)
|
|
2902
|
-
if (isEmptyCallback(offer) &&
|
|
2903
|
-
if (
|
|
2904
|
-
if (!allowedAddresses.includes(offer.callback.address?.toLowerCase())) return { message: `Callback address ${offer.callback.address} is not allowed.` };
|
|
2905
|
-
}
|
|
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." };
|
|
2906
2896
|
});
|
|
2907
2897
|
/**
|
|
2908
2898
|
* A validation rule that checks if the offer's tokens are allowed for its chain.
|
|
@@ -2916,6 +2906,16 @@ const token = ({ assetsByChainId }) => single("token", "Validates that offer loa
|
|
|
2916
2906
|
if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
|
|
2917
2907
|
});
|
|
2918
2908
|
/**
|
|
2909
|
+
* A validation rule that checks if the offer's oracle addresses are allowed for its chain.
|
|
2910
|
+
* @param oraclesByChainId - Allowed oracles indexed by chain id.
|
|
2911
|
+
* @returns The issue that was found. If the offer is valid, this will be undefined.
|
|
2912
|
+
*/
|
|
2913
|
+
const oracle = ({ oraclesByChainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the offer chain", (offer) => {
|
|
2914
|
+
const allowedOracles = oraclesByChainId[offer.chainId]?.map((oracle) => oracle.toLowerCase());
|
|
2915
|
+
if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${offer.chainId}` };
|
|
2916
|
+
if (offer.collaterals.some((collateral) => !allowedOracles.includes(collateral.oracle.toLowerCase()))) return { message: "Oracle is not allowed" };
|
|
2917
|
+
});
|
|
2918
|
+
/**
|
|
2919
2919
|
* A batch validation rule that ensures all offers in a tree have the same maker address.
|
|
2920
2920
|
* Returns an issue only for the first non-conforming offer.
|
|
2921
2921
|
* This rule is signing-agnostic; signer verification is handled at the collector level.
|
|
@@ -2946,59 +2946,54 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
|
|
|
2946
2946
|
//#region src/gatekeeper/morphoRules.ts
|
|
2947
2947
|
const morphoRules = (chains) => {
|
|
2948
2948
|
const assetsByChainId = {};
|
|
2949
|
-
|
|
2949
|
+
const oraclesByChainId = {};
|
|
2950
|
+
for (const chain of chains) {
|
|
2951
|
+
assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
|
|
2952
|
+
oraclesByChainId[chain.id] = oracles$1[chain.id.toString()] ?? [];
|
|
2953
|
+
}
|
|
2950
2954
|
return [
|
|
2951
2955
|
sameMaker(),
|
|
2952
2956
|
amountMutualExclusivity(),
|
|
2953
2957
|
chains$1({ chains }),
|
|
2954
2958
|
maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
|
|
2955
2959
|
callback({
|
|
2956
|
-
callbacks: [
|
|
2957
|
-
|
|
2958
|
-
Type$1.BuyVaultV1Callback,
|
|
2959
|
-
Type$1.SellERC20Callback
|
|
2960
|
-
],
|
|
2961
|
-
allowedAddresses: chains.flatMap((c) => getCallbackAddresses(c.name))
|
|
2960
|
+
callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
|
|
2961
|
+
allowedAddresses: []
|
|
2962
2962
|
}),
|
|
2963
|
-
token({ assetsByChainId })
|
|
2963
|
+
token({ assetsByChainId }),
|
|
2964
|
+
oracle({ oraclesByChainId })
|
|
2964
2965
|
];
|
|
2965
2966
|
};
|
|
2966
2967
|
|
|
2967
2968
|
//#endregion
|
|
2968
2969
|
//#region src/gatekeeper/ConfigRules.ts
|
|
2969
2970
|
/**
|
|
2970
|
-
* Build the configured rules (maturities + callback addresses + loan tokens) for the provided chains.
|
|
2971
|
+
* Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
|
|
2971
2972
|
* @param chains - Chains to include in the configured rules.
|
|
2972
2973
|
* @returns Sorted list of config rules.
|
|
2973
2974
|
*/
|
|
2974
2975
|
function buildConfigRules(chains) {
|
|
2975
2976
|
const rules = [];
|
|
2976
2977
|
for (const chain of chains) {
|
|
2977
|
-
const
|
|
2978
|
-
const maturities = config.maturities ?? [];
|
|
2978
|
+
const maturities = configs[chain.name].maturities ?? [];
|
|
2979
2979
|
for (const maturityName of maturities) rules.push({
|
|
2980
2980
|
type: "maturity",
|
|
2981
2981
|
chain_id: chain.id,
|
|
2982
2982
|
name: maturityName,
|
|
2983
2983
|
timestamp: from$16(maturityName)
|
|
2984
2984
|
});
|
|
2985
|
-
const callbacks = config.callbacks ?? [];
|
|
2986
|
-
for (const callback of callbacks) {
|
|
2987
|
-
if (callback.type === Type$1.BuyWithEmptyCallback) continue;
|
|
2988
|
-
if (!("addresses" in callback)) continue;
|
|
2989
|
-
for (const address of callback.addresses) rules.push({
|
|
2990
|
-
type: "callback",
|
|
2991
|
-
chain_id: chain.id,
|
|
2992
|
-
address: normalizeAddress(address),
|
|
2993
|
-
callback_type: callback.type
|
|
2994
|
-
});
|
|
2995
|
-
}
|
|
2996
2985
|
const loanTokens = assets[chain.id.toString()] ?? [];
|
|
2997
2986
|
for (const address of loanTokens) rules.push({
|
|
2998
2987
|
type: "loan_token",
|
|
2999
2988
|
chain_id: chain.id,
|
|
3000
2989
|
address: normalizeAddress(address)
|
|
3001
2990
|
});
|
|
2991
|
+
const oracles = oracles$1[chain.id.toString()] ?? [];
|
|
2992
|
+
for (const address of oracles) rules.push({
|
|
2993
|
+
type: "oracle",
|
|
2994
|
+
chain_id: chain.id,
|
|
2995
|
+
address: normalizeAddress(address)
|
|
2996
|
+
});
|
|
3002
2997
|
}
|
|
3003
2998
|
rules.sort(compareConfigRules);
|
|
3004
2999
|
return rules;
|
|
@@ -3020,6 +3015,10 @@ function buildConfigRulesChecksum(rules) {
|
|
|
3020
3015
|
hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
|
|
3021
3016
|
continue;
|
|
3022
3017
|
}
|
|
3018
|
+
if (rule.type === "oracle") {
|
|
3019
|
+
hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3023
3022
|
hash.update(`loan_token:${rule.chain_id}:${rule.address}\n`);
|
|
3024
3023
|
}
|
|
3025
3024
|
return hash.digest("hex");
|
|
@@ -3036,6 +3035,7 @@ function compareConfigRules(left, right) {
|
|
|
3036
3035
|
return left.address.localeCompare(right.address);
|
|
3037
3036
|
}
|
|
3038
3037
|
if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
|
|
3038
|
+
if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
|
|
3039
3039
|
return 0;
|
|
3040
3040
|
}
|
|
3041
3041
|
|
|
@@ -3299,8 +3299,8 @@ const offerExample = {
|
|
|
3299
3299
|
price: "2750000000000000000",
|
|
3300
3300
|
group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
|
|
3301
3301
|
session: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3302
|
-
callback: "
|
|
3303
|
-
callback_data: "
|
|
3302
|
+
callback: "0x0000000000000000000000000000000000000000",
|
|
3303
|
+
callback_data: "0x"
|
|
3304
3304
|
},
|
|
3305
3305
|
offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
|
|
3306
3306
|
obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
|
|
@@ -3352,25 +3352,10 @@ const validateOfferExample = {
|
|
|
3352
3352
|
lltv: "860000000000000000"
|
|
3353
3353
|
}],
|
|
3354
3354
|
callback: {
|
|
3355
|
-
address: "
|
|
3356
|
-
data: "
|
|
3355
|
+
address: "0x0000000000000000000000000000000000000000",
|
|
3356
|
+
data: "0x"
|
|
3357
3357
|
}
|
|
3358
3358
|
};
|
|
3359
|
-
const callbackTypesRequestExample = { callbacks: [{
|
|
3360
|
-
chain_id: 1,
|
|
3361
|
-
addresses: [
|
|
3362
|
-
"0x1111111111111111111111111111111111111111",
|
|
3363
|
-
"0x3333333333333333333333333333333333333333",
|
|
3364
|
-
"0x9999999999999999999999999999999999999999"
|
|
3365
|
-
]
|
|
3366
|
-
}] };
|
|
3367
|
-
const callbackTypesResponseExample = [{
|
|
3368
|
-
chain_id: 1,
|
|
3369
|
-
sell_erc20_callback: ["0x1111111111111111111111111111111111111111"],
|
|
3370
|
-
buy_erc20: ["0x5555555555555555555555555555555555555555"],
|
|
3371
|
-
buy_vault_v1_callback: ["0x3333333333333333333333333333333333333333"],
|
|
3372
|
-
not_supported: ["0x9999999999999999999999999999999999999999"]
|
|
3373
|
-
}];
|
|
3374
3359
|
const routerStatusExample = {
|
|
3375
3360
|
status: "live",
|
|
3376
3361
|
initialized: true,
|
|
@@ -3439,55 +3424,6 @@ __decorate([ApiProperty({
|
|
|
3439
3424
|
type: "string",
|
|
3440
3425
|
example: validateOfferExample.callback.data
|
|
3441
3426
|
})], ValidateCallbackRequest.prototype, "data", void 0);
|
|
3442
|
-
var CallbackTypesChainRequest = class {};
|
|
3443
|
-
__decorate([ApiProperty({
|
|
3444
|
-
type: "number",
|
|
3445
|
-
example: callbackTypesRequestExample.callbacks[0].chain_id
|
|
3446
|
-
})], CallbackTypesChainRequest.prototype, "chain_id", void 0);
|
|
3447
|
-
__decorate([ApiProperty({
|
|
3448
|
-
type: () => [String],
|
|
3449
|
-
example: callbackTypesRequestExample.callbacks[0].addresses
|
|
3450
|
-
})], CallbackTypesChainRequest.prototype, "addresses", void 0);
|
|
3451
|
-
var CallbackTypesRequest = class {};
|
|
3452
|
-
__decorate([ApiProperty({
|
|
3453
|
-
type: () => [CallbackTypesChainRequest],
|
|
3454
|
-
example: callbackTypesRequestExample.callbacks
|
|
3455
|
-
})], CallbackTypesRequest.prototype, "callbacks", void 0);
|
|
3456
|
-
var CallbackTypesChainResponse = class {};
|
|
3457
|
-
__decorate([ApiProperty({
|
|
3458
|
-
type: "number",
|
|
3459
|
-
example: callbackTypesResponseExample[0].chain_id
|
|
3460
|
-
})], CallbackTypesChainResponse.prototype, "chain_id", void 0);
|
|
3461
|
-
__decorate([ApiProperty({
|
|
3462
|
-
type: () => [String],
|
|
3463
|
-
required: false,
|
|
3464
|
-
example: callbackTypesResponseExample[0].buy_vault_v1_callback
|
|
3465
|
-
})], CallbackTypesChainResponse.prototype, "buy_vault_v1_callback", void 0);
|
|
3466
|
-
__decorate([ApiProperty({
|
|
3467
|
-
type: () => [String],
|
|
3468
|
-
required: false,
|
|
3469
|
-
example: callbackTypesResponseExample[0].sell_erc20_callback
|
|
3470
|
-
})], CallbackTypesChainResponse.prototype, "sell_erc20_callback", void 0);
|
|
3471
|
-
__decorate([ApiProperty({
|
|
3472
|
-
type: () => [String],
|
|
3473
|
-
required: false,
|
|
3474
|
-
example: callbackTypesResponseExample[0].buy_erc20
|
|
3475
|
-
})], CallbackTypesChainResponse.prototype, "buy_erc20", void 0);
|
|
3476
|
-
__decorate([ApiProperty({
|
|
3477
|
-
type: () => [String],
|
|
3478
|
-
example: callbackTypesResponseExample[0].not_supported
|
|
3479
|
-
})], CallbackTypesChainResponse.prototype, "not_supported", void 0);
|
|
3480
|
-
var CallbackTypesSuccessResponse = class extends SuccessResponse {};
|
|
3481
|
-
__decorate([ApiProperty({
|
|
3482
|
-
type: "string",
|
|
3483
|
-
nullable: true,
|
|
3484
|
-
example: "maturity:1:1730415600:end_of_next_month"
|
|
3485
|
-
})], CallbackTypesSuccessResponse.prototype, "cursor", void 0);
|
|
3486
|
-
__decorate([ApiProperty({
|
|
3487
|
-
type: () => [CallbackTypesChainResponse],
|
|
3488
|
-
description: "Callback types grouped by chain.",
|
|
3489
|
-
example: callbackTypesResponseExample
|
|
3490
|
-
})], CallbackTypesSuccessResponse.prototype, "data", void 0);
|
|
3491
3427
|
var AskResponse = class {};
|
|
3492
3428
|
__decorate([ApiProperty({
|
|
3493
3429
|
type: "string",
|
|
@@ -3652,7 +3588,8 @@ var OfferListResponse = class extends SuccessResponse {};
|
|
|
3652
3588
|
__decorate([ApiProperty({
|
|
3653
3589
|
type: "string",
|
|
3654
3590
|
nullable: true,
|
|
3655
|
-
example: offerCursorExample
|
|
3591
|
+
example: offerCursorExample,
|
|
3592
|
+
description: "Pagination cursor. Offer hash (0x...) for maker queries; base64url-encoded cursor for obligation queries."
|
|
3656
3593
|
})], OfferListResponse.prototype, "cursor", void 0);
|
|
3657
3594
|
__decorate([ApiProperty({
|
|
3658
3595
|
type: () => [OfferListItemResponse],
|
|
@@ -4004,7 +3941,7 @@ __decorate([
|
|
|
4004
3941
|
methods: ["post"],
|
|
4005
3942
|
path: "/v1/validate",
|
|
4006
3943
|
summary: "Validate offers",
|
|
4007
|
-
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."
|
|
4008
3945
|
}),
|
|
4009
3946
|
ApiBody({ type: ValidateOffersRequest }),
|
|
4010
3947
|
ApiResponse({
|
|
@@ -4023,28 +3960,6 @@ ValidateController = __decorate([ApiTags("Make"), ApiResponse({
|
|
|
4023
3960
|
description: "Bad Request",
|
|
4024
3961
|
type: BadRequestResponse
|
|
4025
3962
|
})], ValidateController);
|
|
4026
|
-
let CallbacksController = class CallbacksController {
|
|
4027
|
-
async resolveCallbackTypes() {}
|
|
4028
|
-
};
|
|
4029
|
-
__decorate([
|
|
4030
|
-
ApiOperation({
|
|
4031
|
-
methods: ["post"],
|
|
4032
|
-
path: "/v1/callbacks",
|
|
4033
|
-
summary: "Resolve callback types",
|
|
4034
|
-
description: "Returns callback types for callback addresses grouped by chain."
|
|
4035
|
-
}),
|
|
4036
|
-
ApiBody({ type: CallbackTypesRequest }),
|
|
4037
|
-
ApiResponse({
|
|
4038
|
-
status: 200,
|
|
4039
|
-
description: "Success",
|
|
4040
|
-
type: CallbackTypesSuccessResponse
|
|
4041
|
-
})
|
|
4042
|
-
], CallbacksController.prototype, "resolveCallbackTypes", null);
|
|
4043
|
-
CallbacksController = __decorate([ApiTags("Make"), ApiResponse({
|
|
4044
|
-
status: 400,
|
|
4045
|
-
description: "Bad Request",
|
|
4046
|
-
type: BadRequestResponse
|
|
4047
|
-
})], CallbacksController);
|
|
4048
3963
|
let OffersController = class OffersController {
|
|
4049
3964
|
async getOffers() {}
|
|
4050
3965
|
};
|
|
@@ -4194,22 +4109,21 @@ const configRulesMaturityExample = {
|
|
|
4194
4109
|
name: "end_of_next_month",
|
|
4195
4110
|
timestamp: 1730415600
|
|
4196
4111
|
};
|
|
4197
|
-
const configRulesCallbackExample = {
|
|
4198
|
-
type: "callback",
|
|
4199
|
-
chain_id: 1,
|
|
4200
|
-
address: "0x1111111111111111111111111111111111111111",
|
|
4201
|
-
callback_type: "sell_erc20_callback"
|
|
4202
|
-
};
|
|
4203
4112
|
const configRulesLoanTokenExample = {
|
|
4204
4113
|
type: "loan_token",
|
|
4205
4114
|
chain_id: 1,
|
|
4206
4115
|
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
4207
4116
|
};
|
|
4117
|
+
const configRulesOracleExample = {
|
|
4118
|
+
type: "oracle",
|
|
4119
|
+
chain_id: 1,
|
|
4120
|
+
address: "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83"
|
|
4121
|
+
};
|
|
4208
4122
|
const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
|
|
4209
4123
|
const configRulesPayloadExample = [
|
|
4210
4124
|
configRulesMaturityExample,
|
|
4211
|
-
|
|
4212
|
-
|
|
4125
|
+
configRulesLoanTokenExample,
|
|
4126
|
+
configRulesOracleExample
|
|
4213
4127
|
];
|
|
4214
4128
|
const configContractNames = [
|
|
4215
4129
|
"mempool",
|
|
@@ -4272,14 +4186,9 @@ __decorate([ApiProperty({
|
|
|
4272
4186
|
})], ConfigRulesRuleResponse.prototype, "timestamp", void 0);
|
|
4273
4187
|
__decorate([ApiProperty({
|
|
4274
4188
|
type: "string",
|
|
4275
|
-
example:
|
|
4189
|
+
example: configRulesLoanTokenExample.address,
|
|
4276
4190
|
required: false
|
|
4277
4191
|
})], ConfigRulesRuleResponse.prototype, "address", void 0);
|
|
4278
|
-
__decorate([ApiProperty({
|
|
4279
|
-
type: "string",
|
|
4280
|
-
example: configRulesCallbackExample.callback_type,
|
|
4281
|
-
required: false
|
|
4282
|
-
})], ConfigRulesRuleResponse.prototype, "callback_type", void 0);
|
|
4283
4192
|
var ConfigRulesSuccessResponse = class {};
|
|
4284
4193
|
__decorate([ApiProperty({ type: () => ConfigRulesMeta })], ConfigRulesSuccessResponse.prototype, "meta", void 0);
|
|
4285
4194
|
__decorate([ApiProperty({
|
|
@@ -4340,7 +4249,7 @@ __decorate([
|
|
|
4340
4249
|
methods: ["get"],
|
|
4341
4250
|
path: "/v1/config/rules",
|
|
4342
4251
|
summary: "Get config rules",
|
|
4343
|
-
description: "Returns configured rules for supported chains."
|
|
4252
|
+
description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
|
|
4344
4253
|
}),
|
|
4345
4254
|
ApiQuery({
|
|
4346
4255
|
name: "cursor",
|
|
@@ -4360,7 +4269,7 @@ __decorate([
|
|
|
4360
4269
|
name: "types",
|
|
4361
4270
|
type: ["string"],
|
|
4362
4271
|
required: false,
|
|
4363
|
-
example: "maturity,loan_token",
|
|
4272
|
+
example: "maturity,loan_token,oracle",
|
|
4364
4273
|
description: "Filter by rule types (comma-separated).",
|
|
4365
4274
|
style: "form",
|
|
4366
4275
|
explode: false
|
|
@@ -4519,8 +4428,7 @@ const OpenApi = async () => {
|
|
|
4519
4428
|
ObligationsController,
|
|
4520
4429
|
HealthController,
|
|
4521
4430
|
UsersController,
|
|
4522
|
-
ValidateController
|
|
4523
|
-
CallbacksController
|
|
4431
|
+
ValidateController
|
|
4524
4432
|
],
|
|
4525
4433
|
document: {
|
|
4526
4434
|
openapi: "3.1.0",
|
|
@@ -4590,6 +4498,9 @@ function isValidBase64urlJson(val) {
|
|
|
4590
4498
|
return false;
|
|
4591
4499
|
}
|
|
4592
4500
|
}
|
|
4501
|
+
function isValidOfferHashCursor(val) {
|
|
4502
|
+
return /^0x[a-f0-9]{64}$/i.test(val);
|
|
4503
|
+
}
|
|
4593
4504
|
const csvArray = (schema) => z$2.preprocess((value) => {
|
|
4594
4505
|
if (value === void 0) return void 0;
|
|
4595
4506
|
if (Array.isArray(value)) {
|
|
@@ -4615,10 +4526,11 @@ const PaginationQueryParams = z$2.object({
|
|
|
4615
4526
|
const ConfigRuleTypes = z$2.enum([
|
|
4616
4527
|
"maturity",
|
|
4617
4528
|
"callback",
|
|
4618
|
-
"loan_token"
|
|
4529
|
+
"loan_token",
|
|
4530
|
+
"oracle"
|
|
4619
4531
|
]);
|
|
4620
4532
|
const GetConfigRulesQueryParams = z$2.object({
|
|
4621
|
-
cursor: z$2.string().regex(/^(maturity|callback|loan_token):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
|
|
4533
|
+
cursor: z$2.string().regex(/^(maturity|callback|loan_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
|
|
4622
4534
|
description: "Pagination cursor in type:chain_id:<value> format",
|
|
4623
4535
|
example: "maturity:1:1730415600:end_of_next_month"
|
|
4624
4536
|
}),
|
|
@@ -4628,7 +4540,7 @@ const GetConfigRulesQueryParams = z$2.object({
|
|
|
4628
4540
|
}),
|
|
4629
4541
|
types: csvArray(ConfigRuleTypes).meta({
|
|
4630
4542
|
description: "Filter by rule types (comma-separated).",
|
|
4631
|
-
example: "maturity,loan_token"
|
|
4543
|
+
example: "maturity,loan_token,oracle"
|
|
4632
4544
|
}),
|
|
4633
4545
|
chains: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
4634
4546
|
description: "Filter by chain IDs (comma-separated).",
|
|
@@ -4649,8 +4561,11 @@ const GetConfigContractsQueryParams = z$2.object({
|
|
|
4649
4561
|
example: "1,8453"
|
|
4650
4562
|
})
|
|
4651
4563
|
});
|
|
4652
|
-
const GetOffersQueryParams =
|
|
4653
|
-
|
|
4564
|
+
const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend({
|
|
4565
|
+
cursor: z$2.string().optional().meta({
|
|
4566
|
+
description: "Pagination cursor. Use offer hash (0x...) for maker queries, base64url for obligation queries.",
|
|
4567
|
+
example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
|
|
4568
|
+
}),
|
|
4654
4569
|
side: z$2.enum(["buy", "sell"]).optional().meta({
|
|
4655
4570
|
description: "Side of the offer. Required when using obligation_id.",
|
|
4656
4571
|
example: "buy"
|
|
@@ -4674,11 +4589,29 @@ const GetOffersQueryParams = z$2.object({
|
|
|
4674
4589
|
});
|
|
4675
4590
|
return;
|
|
4676
4591
|
}
|
|
4677
|
-
if (hasMaker)
|
|
4592
|
+
if (hasMaker) {
|
|
4593
|
+
if (val.cursor !== void 0 && !isValidOfferHashCursor(val.cursor)) ctx.addIssue({
|
|
4594
|
+
code: "custom",
|
|
4595
|
+
path: ["cursor"],
|
|
4596
|
+
message: "Cursor must be a 32-byte hex offer hash when filtering by maker"
|
|
4597
|
+
});
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4678
4600
|
if (!hasObligation || !hasSide) ctx.addIssue({
|
|
4679
4601
|
code: "custom",
|
|
4680
4602
|
message: "Must provide either maker or both obligation_id and side"
|
|
4681
4603
|
});
|
|
4604
|
+
if (val.cursor !== void 0 && !isValidBase64urlJson(val.cursor)) ctx.addIssue({
|
|
4605
|
+
code: "custom",
|
|
4606
|
+
path: ["cursor"],
|
|
4607
|
+
message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
|
|
4608
|
+
});
|
|
4609
|
+
}).transform((val) => {
|
|
4610
|
+
if (val.maker && val.cursor) return {
|
|
4611
|
+
...val,
|
|
4612
|
+
cursor: val.cursor.toLowerCase()
|
|
4613
|
+
};
|
|
4614
|
+
return val;
|
|
4682
4615
|
});
|
|
4683
4616
|
const GetObligationsQueryParams = z$2.object({
|
|
4684
4617
|
...PaginationQueryParams.shape,
|
|
@@ -4751,16 +4684,6 @@ const GetBookParams = z$2.object({
|
|
|
4751
4684
|
})
|
|
4752
4685
|
});
|
|
4753
4686
|
const ValidateOffersBody = z$2.object({ offers: z$2.array(z$2.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
|
|
4754
|
-
const CallbackTypesBody = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
4755
|
-
chain_id: z$2.number().int().positive().meta({
|
|
4756
|
-
description: "Chain id.",
|
|
4757
|
-
example: 1
|
|
4758
|
-
}),
|
|
4759
|
-
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({
|
|
4760
|
-
description: "Callback contract addresses.",
|
|
4761
|
-
example: ["0x1111111111111111111111111111111111111111", "0x3333333333333333333333333333333333333333"]
|
|
4762
|
-
})
|
|
4763
|
-
}).strict()) }).strict();
|
|
4764
4687
|
const GetUserPositionsParams = z$2.object({
|
|
4765
4688
|
...PaginationQueryParams.shape,
|
|
4766
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({
|
|
@@ -4779,7 +4702,6 @@ const schemas = {
|
|
|
4779
4702
|
get_obligation: GetObligationParams,
|
|
4780
4703
|
get_book: GetBookParams,
|
|
4781
4704
|
validate_offers: ValidateOffersBody,
|
|
4782
|
-
callback_types: CallbackTypesBody,
|
|
4783
4705
|
get_user_positions: GetUserPositionsParams
|
|
4784
4706
|
};
|
|
4785
4707
|
function safeParse(action, query, error) {
|
|
@@ -4827,6 +4749,7 @@ async function getConfigRules(query, chains) {
|
|
|
4827
4749
|
function formatCursor$1(rule) {
|
|
4828
4750
|
if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
|
|
4829
4751
|
if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
|
|
4752
|
+
if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
4830
4753
|
return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
4831
4754
|
}
|
|
4832
4755
|
function parseCursor$1(cursor) {
|
|
@@ -4859,13 +4782,16 @@ function parseCursor$1(cursor) {
|
|
|
4859
4782
|
address: parseAddress(addressValue, "Cursor address")
|
|
4860
4783
|
};
|
|
4861
4784
|
}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4785
|
+
if (type === "loan_token" || type === "oracle") {
|
|
4786
|
+
const addressValue = rest.join(":");
|
|
4787
|
+
if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
|
|
4788
|
+
return {
|
|
4789
|
+
type,
|
|
4790
|
+
chain_id,
|
|
4791
|
+
address: parseAddress(addressValue, "Cursor address")
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
throw new BadRequestError("Cursor has an invalid rule type");
|
|
4869
4795
|
}
|
|
4870
4796
|
function findStartIndex$1(rules, cursor) {
|
|
4871
4797
|
let low = 0;
|
|
@@ -4883,7 +4809,7 @@ function parseAddress(address, label) {
|
|
|
4883
4809
|
return address.toLowerCase();
|
|
4884
4810
|
}
|
|
4885
4811
|
function isConfigRuleType(value) {
|
|
4886
|
-
return value === "maturity" || value === "callback" || value === "loan_token";
|
|
4812
|
+
return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
|
|
4887
4813
|
}
|
|
4888
4814
|
function isMaturityType(value) {
|
|
4889
4815
|
return Object.values(MaturityType).includes(value);
|
|
@@ -5031,35 +4957,6 @@ async function validateOffers(body, gatekeeper) {
|
|
|
5031
4957
|
}
|
|
5032
4958
|
}
|
|
5033
4959
|
|
|
5034
|
-
//#endregion
|
|
5035
|
-
//#region src/gatekeeper/CallbackTypes.ts
|
|
5036
|
-
/**
|
|
5037
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
5038
|
-
* @param parameters - Resolve parameters. {@link resolveCallbackTypes.Parameters}
|
|
5039
|
-
* @returns Callback types grouped by chain. {@link resolveCallbackTypes.ReturnType}
|
|
5040
|
-
* @throws If a chain id is unknown.
|
|
5041
|
-
*/
|
|
5042
|
-
function resolveCallbackTypes$2(parameters) {
|
|
5043
|
-
const { chains, request } = parameters;
|
|
5044
|
-
const chainsById = new Map(chains.map((chain) => [chain.id, chain]));
|
|
5045
|
-
return request.callbacks.map(({ chain_id, addresses }) => {
|
|
5046
|
-
const chain = chainsById.get(chain_id);
|
|
5047
|
-
if (!chain) throw new Error(`Unknown chain id ${chain_id}`);
|
|
5048
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
5049
|
-
const uniqueAddresses = new Set(addresses.map((address) => address.toLowerCase()));
|
|
5050
|
-
for (const address of uniqueAddresses) {
|
|
5051
|
-
const bucketKey = getCallbackType(chain.name, address) ?? "not_supported";
|
|
5052
|
-
const list = buckets.get(bucketKey) ?? [];
|
|
5053
|
-
list.push(address);
|
|
5054
|
-
buckets.set(bucketKey, list);
|
|
5055
|
-
}
|
|
5056
|
-
const response = { chain_id };
|
|
5057
|
-
for (const [type, list] of buckets.entries()) response[type] = list;
|
|
5058
|
-
if (!response.not_supported) response.not_supported = [];
|
|
5059
|
-
return response;
|
|
5060
|
-
});
|
|
5061
|
-
}
|
|
5062
|
-
|
|
5063
4960
|
//#endregion
|
|
5064
4961
|
//#region src/gatekeeper/Service.ts
|
|
5065
4962
|
/**
|
|
@@ -5067,10 +4964,6 @@ function resolveCallbackTypes$2(parameters) {
|
|
|
5067
4964
|
* @param parameters - App parameters including the {@link Gatekeeper} instance.
|
|
5068
4965
|
* @returns Hono app exposing gatekeeper endpoints.
|
|
5069
4966
|
*/
|
|
5070
|
-
const CallbackTypesRequestSchema = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
5071
|
-
chain_id: z$2.number(),
|
|
5072
|
-
addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/))
|
|
5073
|
-
})) });
|
|
5074
4967
|
function createApp(parameters) {
|
|
5075
4968
|
const { gatekeeper, chainRegistry } = parameters;
|
|
5076
4969
|
const app = new Hono();
|
|
@@ -5079,12 +4972,12 @@ function createApp(parameters) {
|
|
|
5079
4972
|
try {
|
|
5080
4973
|
body = await c.req.json();
|
|
5081
4974
|
} catch (err) {
|
|
5082
|
-
const failure$
|
|
5083
|
-
return c.json(failure$
|
|
4975
|
+
const failure$4 = failure(err);
|
|
4976
|
+
return c.json(failure$4.body, failure$4.statusCode);
|
|
5084
4977
|
}
|
|
5085
4978
|
if (body === null || typeof body !== "object") {
|
|
5086
|
-
const failure$
|
|
5087
|
-
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);
|
|
5088
4981
|
}
|
|
5089
4982
|
const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
|
|
5090
4983
|
return c.json(payload, statusCode);
|
|
@@ -5093,34 +4986,6 @@ function createApp(parameters) {
|
|
|
5093
4986
|
const { statusCode, body } = await getConfigRules(c.req.query(), chainRegistry.list());
|
|
5094
4987
|
return c.json(body, statusCode);
|
|
5095
4988
|
});
|
|
5096
|
-
app.post("/v1/callbacks", async (c) => {
|
|
5097
|
-
let body;
|
|
5098
|
-
try {
|
|
5099
|
-
body = await c.req.json();
|
|
5100
|
-
} catch (err) {
|
|
5101
|
-
const failure$8 = failure(err);
|
|
5102
|
-
return c.json(failure$8.body, failure$8.statusCode);
|
|
5103
|
-
}
|
|
5104
|
-
if (body === null || typeof body !== "object") {
|
|
5105
|
-
const failure$6 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
5106
|
-
return c.json(failure$6.body, failure$6.statusCode);
|
|
5107
|
-
}
|
|
5108
|
-
try {
|
|
5109
|
-
const request = CallbackTypesRequestSchema.parse(body);
|
|
5110
|
-
const chainIds = new Set(chainRegistry.list().map((chain) => chain.id));
|
|
5111
|
-
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
5112
|
-
if (unknown) throw new BadRequestError(`Unknown chain id ${unknown.chain_id}`);
|
|
5113
|
-
const data = resolveCallbackTypes$2({
|
|
5114
|
-
chains: chainRegistry.list(),
|
|
5115
|
-
request
|
|
5116
|
-
});
|
|
5117
|
-
const response = success({ data });
|
|
5118
|
-
return c.json(response.body, response.statusCode);
|
|
5119
|
-
} catch (err) {
|
|
5120
|
-
const failure$7 = failure(err);
|
|
5121
|
-
return c.json(failure$7.body, failure$7.statusCode);
|
|
5122
|
-
}
|
|
5123
|
-
});
|
|
5124
4989
|
return app;
|
|
5125
4990
|
}
|
|
5126
4991
|
/**
|
|
@@ -5216,1559 +5081,8 @@ function now() {
|
|
|
5216
5081
|
}
|
|
5217
5082
|
|
|
5218
5083
|
//#endregion
|
|
5219
|
-
//#region src/
|
|
5220
|
-
|
|
5221
|
-
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5222
|
-
const logger = getLogger();
|
|
5223
|
-
let startBlock = blockNumber;
|
|
5224
|
-
let reorgDetected = false;
|
|
5225
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5226
|
-
const stream = streamLogs({
|
|
5227
|
-
client,
|
|
5228
|
-
contractAddress: client.chain.custom.morpho.address,
|
|
5229
|
-
event: consumedEvent,
|
|
5230
|
-
blockNumberGte: blockNumber,
|
|
5231
|
-
blockNumberLte: latestBlockNumberChain,
|
|
5232
|
-
order: "asc",
|
|
5233
|
-
options: {
|
|
5234
|
-
maxBatchSize,
|
|
5235
|
-
blockWindow
|
|
5236
|
-
}
|
|
5237
|
-
});
|
|
5238
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5239
|
-
const parsedLogs = parseEventLogs({
|
|
5240
|
-
abi: [consumedEvent],
|
|
5241
|
-
logs
|
|
5242
|
-
});
|
|
5243
|
-
const events = [];
|
|
5244
|
-
for (const log of parsedLogs) {
|
|
5245
|
-
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
5246
|
-
logger.debug({
|
|
5247
|
-
collector,
|
|
5248
|
-
chainId: client.chain.id,
|
|
5249
|
-
msg: "Skipping log because it is missing required fields"
|
|
5250
|
-
});
|
|
5251
|
-
continue;
|
|
5252
|
-
}
|
|
5253
|
-
events.push({
|
|
5254
|
-
id: `${log.blockNumber.toString()}-${log.logIndex.toString()}-${client.chain.id}-${log.transactionHash}`,
|
|
5255
|
-
chainId: client.chain.id,
|
|
5256
|
-
maker: log.args.user,
|
|
5257
|
-
group: log.args.group,
|
|
5258
|
-
amount: log.args.amount,
|
|
5259
|
-
blockNumber: Number(log.blockNumber)
|
|
5260
|
-
});
|
|
5261
|
-
}
|
|
5262
|
-
await db.transaction(async (dbTx) => {
|
|
5263
|
-
try {
|
|
5264
|
-
await dbTx.consumed.create(events);
|
|
5265
|
-
if (events.length > 0) logger.info({
|
|
5266
|
-
msg: `Events indexed`,
|
|
5267
|
-
collector,
|
|
5268
|
-
count: events.length,
|
|
5269
|
-
chain_id: client.chain.id,
|
|
5270
|
-
block_range: [startBlock, lastStreamBlockNumber]
|
|
5271
|
-
});
|
|
5272
|
-
} catch (err) {
|
|
5273
|
-
logger.error({
|
|
5274
|
-
err,
|
|
5275
|
-
msg: "Failed to process offer_consumed events"
|
|
5276
|
-
});
|
|
5277
|
-
}
|
|
5278
|
-
blockNumber = lastStreamBlockNumber;
|
|
5279
|
-
try {
|
|
5280
|
-
await dbTx.blocks.advanceCollector({
|
|
5281
|
-
collectorName: collector,
|
|
5282
|
-
chainId: client.chain.id,
|
|
5283
|
-
blockNumber,
|
|
5284
|
-
epoch
|
|
5285
|
-
});
|
|
5286
|
-
} catch (_) {
|
|
5287
|
-
try {
|
|
5288
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
5289
|
-
collectorName: collector,
|
|
5290
|
-
chainId: client.chain.id
|
|
5291
|
-
});
|
|
5292
|
-
blockNumber = ancestor.blockNumber;
|
|
5293
|
-
const deleted = await dbTx.consumed.delete({
|
|
5294
|
-
chainId: client.chain.id,
|
|
5295
|
-
blockNumberGte: blockNumber + 1
|
|
5296
|
-
});
|
|
5297
|
-
logger.info({
|
|
5298
|
-
collector,
|
|
5299
|
-
chain_id: client.chain.id,
|
|
5300
|
-
msg: `Reorg detected, events deleted`,
|
|
5301
|
-
count: deleted,
|
|
5302
|
-
block_number: blockNumber
|
|
5303
|
-
});
|
|
5304
|
-
await dbTx.blocks.advanceCollector({
|
|
5305
|
-
collectorName: collector,
|
|
5306
|
-
chainId: client.chain.id,
|
|
5307
|
-
blockNumber,
|
|
5308
|
-
epoch: ancestor.epoch
|
|
5309
|
-
});
|
|
5310
|
-
reorgDetected = true;
|
|
5311
|
-
} catch (err) {
|
|
5312
|
-
const msg = "Failed to delete consumed events when handling reorg.";
|
|
5313
|
-
logger.error({
|
|
5314
|
-
collector,
|
|
5315
|
-
chainId: client.chain.id,
|
|
5316
|
-
msg,
|
|
5317
|
-
err
|
|
5318
|
-
});
|
|
5319
|
-
throw new Error(msg);
|
|
5320
|
-
}
|
|
5321
|
-
}
|
|
5322
|
-
});
|
|
5323
|
-
if (reorgDetected) return;
|
|
5324
|
-
yield blockNumber;
|
|
5325
|
-
startBlock = blockNumber;
|
|
5326
|
-
}
|
|
5327
|
-
}
|
|
5328
|
-
|
|
5329
|
-
//#endregion
|
|
5330
|
-
//#region src/indexer/collectors/CollectFunctions/collectOffers.ts
|
|
5331
|
-
async function* collectOffersV2(parameters) {
|
|
5332
|
-
let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5333
|
-
const logger = getLogger();
|
|
5334
|
-
let startBlock = blockNumber;
|
|
5335
|
-
let reorgDetected = false;
|
|
5336
|
-
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
5337
|
-
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
5338
|
-
logger.error({
|
|
5339
|
-
msg,
|
|
5340
|
-
chain_id: client.chain.id
|
|
5341
|
-
});
|
|
5342
|
-
throw new Error(msg);
|
|
5343
|
-
}
|
|
5344
|
-
const signatureDomain = {
|
|
5345
|
-
chainId: client.chain.id,
|
|
5346
|
-
verifyingContract: client.chain.custom.morpho.address
|
|
5347
|
-
};
|
|
5348
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5349
|
-
const stream = streamLogs({
|
|
5350
|
-
client,
|
|
5351
|
-
contractAddress: client.chain.custom.mempool.address,
|
|
5352
|
-
event: {
|
|
5353
|
-
type: "event",
|
|
5354
|
-
name: "Event",
|
|
5355
|
-
inputs: [{
|
|
5356
|
-
name: "data",
|
|
5357
|
-
type: "bytes",
|
|
5358
|
-
indexed: false,
|
|
5359
|
-
internalType: "bytes"
|
|
5360
|
-
}],
|
|
5361
|
-
anonymous: false
|
|
5362
|
-
},
|
|
5363
|
-
blockNumberGte: blockNumber,
|
|
5364
|
-
blockNumberLte: latestBlockNumberChain,
|
|
5365
|
-
order: "asc",
|
|
5366
|
-
options: {
|
|
5367
|
-
maxBatchSize,
|
|
5368
|
-
blockWindow
|
|
5369
|
-
}
|
|
5370
|
-
});
|
|
5371
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5372
|
-
blockNumber = lastStreamBlockNumber;
|
|
5373
|
-
const decodedTrees = [];
|
|
5374
|
-
for (const log of logs) {
|
|
5375
|
-
if (!log) continue;
|
|
5376
|
-
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
5377
|
-
try {
|
|
5378
|
-
const { tree, signature, signer } = await decode(payload, signatureDomain);
|
|
5379
|
-
const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
|
|
5380
|
-
if (signerMismatch) {
|
|
5381
|
-
logger.debug({
|
|
5382
|
-
msg: "Tree rejected: signer mismatch",
|
|
5383
|
-
reason: "signer_mismatch",
|
|
5384
|
-
signer,
|
|
5385
|
-
maker: signerMismatch.maker,
|
|
5386
|
-
chain_id: client.chain.id
|
|
5387
|
-
});
|
|
5388
|
-
continue;
|
|
5389
|
-
}
|
|
5390
|
-
decodedTrees.push({
|
|
5391
|
-
tree,
|
|
5392
|
-
signature,
|
|
5393
|
-
signer,
|
|
5394
|
-
blockNumber: Number(log.blockNumber)
|
|
5395
|
-
});
|
|
5396
|
-
} catch (err) {
|
|
5397
|
-
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
5398
|
-
logger.debug({
|
|
5399
|
-
msg: "Tree decode failed",
|
|
5400
|
-
reason,
|
|
5401
|
-
chain_id: client.chain.id,
|
|
5402
|
-
err: err instanceof Error ? err.message : String(err)
|
|
5403
|
-
});
|
|
5404
|
-
}
|
|
5405
|
-
}
|
|
5406
|
-
await db.transaction(async (dbTx) => {
|
|
5407
|
-
const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
5408
|
-
const treesToInsert = [];
|
|
5409
|
-
let totalValidOffers = 0;
|
|
5410
|
-
const offersWithBlock = [];
|
|
5411
|
-
for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
|
|
5412
|
-
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
5413
|
-
const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
|
|
5414
|
-
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
5415
|
-
if (allowedResults.issues.length > 0) {
|
|
5416
|
-
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
5417
|
-
logger.debug({
|
|
5418
|
-
msg: "Tree offers rejected by gatekeeper",
|
|
5419
|
-
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
5420
|
-
chain_id: client.chain.id,
|
|
5421
|
-
issues_count: allowedResults.issues.length
|
|
5422
|
-
});
|
|
5423
|
-
} else if (hasBlockWindowViolation) logger.debug({
|
|
5424
|
-
msg: "Tree rejected: offers outside block window",
|
|
5425
|
-
reason: "block_window",
|
|
5426
|
-
chain_id: client.chain.id
|
|
5427
|
-
});
|
|
5428
|
-
continue;
|
|
5429
|
-
}
|
|
5430
|
-
treesToInsert.push({
|
|
5431
|
-
tree,
|
|
5432
|
-
signature
|
|
5433
|
-
});
|
|
5434
|
-
totalValidOffers += tree.offers.length;
|
|
5435
|
-
offersWithBlock.push(...tree.offers.map((offer) => ({
|
|
5436
|
-
offer,
|
|
5437
|
-
blockNumber: treeBlockNumber
|
|
5438
|
-
})));
|
|
5439
|
-
} catch (err) {
|
|
5440
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5441
|
-
logger.error({
|
|
5442
|
-
err: error,
|
|
5443
|
-
msg: "Gatekeeper validation failed",
|
|
5444
|
-
chain_id: client.chain.id
|
|
5445
|
-
});
|
|
5446
|
-
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
5447
|
-
}
|
|
5448
|
-
const dependencies = buildOfferDependencies$1(offersWithBlock);
|
|
5449
|
-
await dbTx.oracles.upsert(dependencies.oracles);
|
|
5450
|
-
await dbTx.obligations.create(dependencies.obligations);
|
|
5451
|
-
await dbTx.groups.create(dependencies.groups);
|
|
5452
|
-
const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
|
|
5453
|
-
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
5454
|
-
const insertedOffers = filterInsertedOffers({
|
|
5455
|
-
offers: offersWithBlock,
|
|
5456
|
-
hashes: insertedHashes
|
|
5457
|
-
});
|
|
5458
|
-
const { callbacks, positions, lots } = await decodeCallbacks({
|
|
5459
|
-
chainId: client.chain.id,
|
|
5460
|
-
gatekeeper,
|
|
5461
|
-
offers: insertedOffers
|
|
5462
|
-
});
|
|
5463
|
-
if (positions.length > 0) await dbTx.positions.upsert(positions);
|
|
5464
|
-
if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
|
|
5465
|
-
if (lots.length > 0) await dbTx.lots.create(lots);
|
|
5466
|
-
try {
|
|
5467
|
-
await dbTx.blocks.advanceCollector({
|
|
5468
|
-
collectorName: collector,
|
|
5469
|
-
chainId: client.chain.id,
|
|
5470
|
-
blockNumber,
|
|
5471
|
-
epoch
|
|
5472
|
-
});
|
|
5473
|
-
if (totalValidOffers > 0) logger.info({
|
|
5474
|
-
msg: `New offers`,
|
|
5475
|
-
collector,
|
|
5476
|
-
count: totalValidOffers,
|
|
5477
|
-
trees_count: treesToInsert.length,
|
|
5478
|
-
chain_id: client.chain.id,
|
|
5479
|
-
block_range: [startBlock, lastStreamBlockNumber]
|
|
5480
|
-
});
|
|
5481
|
-
} catch (_) {
|
|
5482
|
-
try {
|
|
5483
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
5484
|
-
collectorName: collector,
|
|
5485
|
-
chainId: client.chain.id
|
|
5486
|
-
});
|
|
5487
|
-
blockNumber = ancestor.blockNumber;
|
|
5488
|
-
const deleted = await dbTx.offers.delete({
|
|
5489
|
-
blockNumberGte: blockNumber + 1,
|
|
5490
|
-
chainId: client.chain.id
|
|
5491
|
-
});
|
|
5492
|
-
logger.info({
|
|
5493
|
-
collector,
|
|
5494
|
-
chain_id: client.chain.id,
|
|
5495
|
-
msg: `Reorg detected, offers deleted`,
|
|
5496
|
-
count: deleted,
|
|
5497
|
-
block_number: blockNumber
|
|
5498
|
-
});
|
|
5499
|
-
await dbTx.blocks.advanceCollector({
|
|
5500
|
-
collectorName: collector,
|
|
5501
|
-
chainId: client.chain.id,
|
|
5502
|
-
blockNumber,
|
|
5503
|
-
epoch: ancestor.epoch
|
|
5504
|
-
});
|
|
5505
|
-
reorgDetected = true;
|
|
5506
|
-
} catch (err) {
|
|
5507
|
-
const msg = "Failed to delete offers when handling reorg.";
|
|
5508
|
-
logger.error({
|
|
5509
|
-
collector,
|
|
5510
|
-
chainId: client.chain.id,
|
|
5511
|
-
msg,
|
|
5512
|
-
err
|
|
5513
|
-
});
|
|
5514
|
-
throw new Error(msg);
|
|
5515
|
-
}
|
|
5516
|
-
}
|
|
5517
|
-
});
|
|
5518
|
-
if (reorgDetected) return;
|
|
5519
|
-
yield blockNumber;
|
|
5520
|
-
startBlock = blockNumber;
|
|
5521
|
-
}
|
|
5522
|
-
}
|
|
5523
|
-
async function decodeCallbacks(parameters) {
|
|
5524
|
-
const { chainId, gatekeeper, offers } = parameters;
|
|
5525
|
-
if (offers.length === 0) return {
|
|
5526
|
-
callbacks: [],
|
|
5527
|
-
positions: [],
|
|
5528
|
-
lots: []
|
|
5529
|
-
};
|
|
5530
|
-
const addresses = offers.filter((entry) => entry.offer.callback.data !== "0x").map((entry) => entry.offer.callback.address);
|
|
5531
|
-
if (addresses.length === 0) return {
|
|
5532
|
-
callbacks: [],
|
|
5533
|
-
positions: [],
|
|
5534
|
-
lots: []
|
|
5535
|
-
};
|
|
5536
|
-
let response;
|
|
5537
|
-
try {
|
|
5538
|
-
response = await gatekeeper.getCallbackTypes({ callbacks: [{
|
|
5539
|
-
chain_id: chainId,
|
|
5540
|
-
addresses
|
|
5541
|
-
}] });
|
|
5542
|
-
} catch (err) {
|
|
5543
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5544
|
-
throw new Error("Failed to resolve callback types", { cause: error });
|
|
5545
|
-
}
|
|
5546
|
-
const entry = response.find((item) => item.chain_id === chainId);
|
|
5547
|
-
const typeByAddress = /* @__PURE__ */ new Map();
|
|
5548
|
-
if (entry) for (const [key, list] of Object.entries(entry)) {
|
|
5549
|
-
if (key === "chain_id" || key === "not_supported") continue;
|
|
5550
|
-
if (!Array.isArray(list)) continue;
|
|
5551
|
-
for (const address of list) typeByAddress.set(address.toLowerCase(), key);
|
|
5552
|
-
}
|
|
5553
|
-
const callbacks = [];
|
|
5554
|
-
const positions = [];
|
|
5555
|
-
const lots = [];
|
|
5556
|
-
for (const { offer, blockNumber: offerBlockNumber } of offers) {
|
|
5557
|
-
if (offer.callback.data === "0x") continue;
|
|
5558
|
-
const callbackType = typeByAddress.get(offer.callback.address.toLowerCase());
|
|
5559
|
-
if (!callbackType) continue;
|
|
5560
|
-
let decoded;
|
|
5561
|
-
try {
|
|
5562
|
-
decoded = decode$1(callbackType, offer.callback.data);
|
|
5563
|
-
} catch (err) {
|
|
5564
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5565
|
-
throw new Error("Failed to decode callback data", { cause: error });
|
|
5566
|
-
}
|
|
5567
|
-
if (decoded.length === 0) continue;
|
|
5568
|
-
const offerHash = hash(offer);
|
|
5569
|
-
const callbackInputs = decoded.map((callback) => ({
|
|
5570
|
-
chainId: offer.chainId,
|
|
5571
|
-
contract: callback.contract,
|
|
5572
|
-
user: offer.maker,
|
|
5573
|
-
amount: callback.amount
|
|
5574
|
-
}));
|
|
5575
|
-
callbacks.push({
|
|
5576
|
-
offerHash,
|
|
5577
|
-
callbacks: callbackInputs
|
|
5578
|
-
});
|
|
5579
|
-
for (const callback of decoded) {
|
|
5580
|
-
const contract = callback.contract;
|
|
5581
|
-
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
5582
|
-
const asset = callbackType === Type$1.BuyVaultV1Callback ? void 0 : contract;
|
|
5583
|
-
positions.push(from$10({
|
|
5584
|
-
chainId: offer.chainId,
|
|
5585
|
-
contract,
|
|
5586
|
-
user: offer.maker,
|
|
5587
|
-
type: positionType,
|
|
5588
|
-
asset,
|
|
5589
|
-
blockNumber: offerBlockNumber
|
|
5590
|
-
}));
|
|
5591
|
-
const isLoanPosition = offer.loanToken.toLowerCase() === asset?.toLowerCase();
|
|
5592
|
-
lots.push({
|
|
5593
|
-
positionChainId: offer.chainId,
|
|
5594
|
-
positionContract: contract,
|
|
5595
|
-
positionUser: offer.maker,
|
|
5596
|
-
group: offer.group,
|
|
5597
|
-
size: isLoanPosition ? offer.assets : callback.amount
|
|
5598
|
-
});
|
|
5599
|
-
}
|
|
5600
|
-
}
|
|
5601
|
-
return {
|
|
5602
|
-
callbacks,
|
|
5603
|
-
positions,
|
|
5604
|
-
lots
|
|
5605
|
-
};
|
|
5606
|
-
}
|
|
5607
|
-
function buildOfferDependencies$1(offers) {
|
|
5608
|
-
const obligationsById = /* @__PURE__ */ new Map();
|
|
5609
|
-
const oraclesByKey = /* @__PURE__ */ new Map();
|
|
5610
|
-
const groupsByKey = /* @__PURE__ */ new Map();
|
|
5611
|
-
const offersByBlock = /* @__PURE__ */ new Map();
|
|
5612
|
-
for (const { offer, blockNumber } of offers) {
|
|
5613
|
-
const list = offersByBlock.get(blockNumber) ?? [];
|
|
5614
|
-
list.push(offer);
|
|
5615
|
-
offersByBlock.set(blockNumber, list);
|
|
5616
|
-
const obligationId$2 = obligationId(offer);
|
|
5617
|
-
if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
|
|
5618
|
-
chainId: offer.chainId,
|
|
5619
|
-
loanToken: offer.loanToken,
|
|
5620
|
-
maturity: offer.maturity,
|
|
5621
|
-
collaterals: offer.collaterals
|
|
5622
|
-
}));
|
|
5623
|
-
for (const collateral of offer.collaterals) {
|
|
5624
|
-
const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
5625
|
-
if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
|
|
5626
|
-
chainId: offer.chainId,
|
|
5627
|
-
address: collateral.oracle,
|
|
5628
|
-
price: null,
|
|
5629
|
-
blockNumber
|
|
5630
|
-
}));
|
|
5631
|
-
}
|
|
5632
|
-
const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
|
|
5633
|
-
if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
|
|
5634
|
-
chainId: offer.chainId,
|
|
5635
|
-
maker: offer.maker,
|
|
5636
|
-
group: offer.group,
|
|
5637
|
-
blockNumber
|
|
5638
|
-
});
|
|
5639
|
-
}
|
|
5640
|
-
return {
|
|
5641
|
-
obligations: Array.from(obligationsById.values()),
|
|
5642
|
-
oracles: Array.from(oraclesByKey.values()),
|
|
5643
|
-
groups: Array.from(groupsByKey.values()),
|
|
5644
|
-
offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
|
|
5645
|
-
blockNumber,
|
|
5646
|
-
offers: items
|
|
5647
|
-
}))
|
|
5648
|
-
};
|
|
5649
|
-
}
|
|
5650
|
-
function filterInsertedOffers(parameters) {
|
|
5651
|
-
if (parameters.hashes.length === 0) return [];
|
|
5652
|
-
const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
|
|
5653
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5654
|
-
const filtered = [];
|
|
5655
|
-
for (const entry of parameters.offers) {
|
|
5656
|
-
const hash$2 = hash(entry.offer).toLowerCase();
|
|
5657
|
-
if (!inserted.has(hash$2)) continue;
|
|
5658
|
-
if (seen.has(hash$2)) continue;
|
|
5659
|
-
seen.add(hash$2);
|
|
5660
|
-
filtered.push(entry);
|
|
5661
|
-
}
|
|
5662
|
-
return filtered;
|
|
5663
|
-
}
|
|
5664
|
-
|
|
5665
|
-
//#endregion
|
|
5666
|
-
//#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
|
|
5667
|
-
/**
|
|
5668
|
-
* Fetches prices from multiple oracle contracts using multicall.
|
|
5669
|
-
*
|
|
5670
|
-
* - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
|
|
5671
|
-
* - Requires a client supporting {@link PublicActions.multicall | multicall}.
|
|
5672
|
-
*
|
|
5673
|
-
* @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
|
|
5674
|
-
* {@link Address | addresses}, fetch options, and the multicall-enabled client.
|
|
5675
|
-
* @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
|
|
5676
|
-
*/
|
|
5677
|
-
async function fetchOraclePrices(parameters) {
|
|
5678
|
-
const { client, oracles, options } = parameters;
|
|
5679
|
-
if (oracles.length === 0) return /* @__PURE__ */ new Map();
|
|
5680
|
-
const batchSize = Math.max(1, options?.batchSize ?? 5e3);
|
|
5681
|
-
const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
|
|
5682
|
-
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
|
|
5683
|
-
const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
|
|
5684
|
-
const out = /* @__PURE__ */ new Map();
|
|
5685
|
-
for (const oraclesBatch of batch$1(oracles, batchSize)) {
|
|
5686
|
-
const priceCalls = [];
|
|
5687
|
-
for (const oracle of oraclesBatch) priceCalls.push({
|
|
5688
|
-
address: oracle,
|
|
5689
|
-
abi: Oracle,
|
|
5690
|
-
functionName: "price",
|
|
5691
|
-
args: []
|
|
5692
|
-
});
|
|
5693
|
-
const prices = await batchMulticall({
|
|
5694
|
-
client,
|
|
5695
|
-
calls: priceCalls,
|
|
5696
|
-
batchSize,
|
|
5697
|
-
retryAttempts,
|
|
5698
|
-
retryDelayMs,
|
|
5699
|
-
blockNumber
|
|
5700
|
-
});
|
|
5701
|
-
for (let i = 0; i < oraclesBatch.length; i++) {
|
|
5702
|
-
const oracle = oraclesBatch[i];
|
|
5703
|
-
const price = prices[i];
|
|
5704
|
-
out.set(oracle, price);
|
|
5705
|
-
}
|
|
5706
|
-
}
|
|
5707
|
-
return out;
|
|
5708
|
-
}
|
|
5709
|
-
|
|
5710
|
-
//#endregion
|
|
5711
|
-
//#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
|
|
5712
|
-
/**
|
|
5713
|
-
* Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
|
|
5714
|
-
* @notice This method does not mutate positions and returns a new array.
|
|
5715
|
-
*
|
|
5716
|
-
* @param parameters - {@link snapshotERC20Positions.Parameters}
|
|
5717
|
-
* @returns Positions - {@link snapshotERC20Positions.ReturnType}
|
|
5718
|
-
*/
|
|
5719
|
-
async function snapshotERC20Positions(parameters) {
|
|
5720
|
-
const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5721
|
-
if (oldPositions.length === 0) return [];
|
|
5722
|
-
const calls = [];
|
|
5723
|
-
for (const position of oldPositions) calls.push({
|
|
5724
|
-
address: position.contract,
|
|
5725
|
-
abi: erc20Abi,
|
|
5726
|
-
functionName: "balanceOf",
|
|
5727
|
-
args: [position.user]
|
|
5728
|
-
});
|
|
5729
|
-
const balances = await batchMulticall({
|
|
5730
|
-
client: parameters.client,
|
|
5731
|
-
calls,
|
|
5732
|
-
blockNumber: BigInt(blockNumber),
|
|
5733
|
-
batchSize: maxBatchSize,
|
|
5734
|
-
retryAttempts,
|
|
5735
|
-
retryDelayMs
|
|
5736
|
-
});
|
|
5737
|
-
const positions = [];
|
|
5738
|
-
for (let i = 0; i < balances.length; i++) {
|
|
5739
|
-
const oldPosition = oldPositions[i];
|
|
5740
|
-
if (!oldPosition) continue;
|
|
5741
|
-
positions.push({
|
|
5742
|
-
...oldPosition,
|
|
5743
|
-
balance: balances[i],
|
|
5744
|
-
blockNumber
|
|
5745
|
-
});
|
|
5746
|
-
}
|
|
5747
|
-
return positions;
|
|
5748
|
-
}
|
|
5749
|
-
|
|
5750
|
-
//#endregion
|
|
5751
|
-
//#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
|
|
5752
|
-
/**
|
|
5753
|
-
* Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
|
|
5754
|
-
* @notice This method does not mutate positions and returns a new array.
|
|
5755
|
-
*
|
|
5756
|
-
* @param parameters - {@link snapshotVaultPositions.Parameters}
|
|
5757
|
-
* @returns Positions - {@link snapshotVaultPositions.ReturnType}
|
|
5758
|
-
*/
|
|
5759
|
-
async function snapshotVaultPositions(parameters) {
|
|
5760
|
-
const logger = getLogger();
|
|
5761
|
-
const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5762
|
-
const calls = [];
|
|
5763
|
-
const contracts = /* @__PURE__ */ new Map();
|
|
5764
|
-
const positions = structuredClone(oldPositions);
|
|
5765
|
-
for (const position of positions) {
|
|
5766
|
-
calls.push({
|
|
5767
|
-
address: position.contract,
|
|
5768
|
-
abi: MetaMorpho,
|
|
5769
|
-
functionName: "balanceOf",
|
|
5770
|
-
args: [position.user],
|
|
5771
|
-
convertToAssets: (shares) => {
|
|
5772
|
-
const contract = contracts.get(position.contract.toLowerCase());
|
|
5773
|
-
if (!contract) return;
|
|
5774
|
-
if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
|
|
5775
|
-
try {
|
|
5776
|
-
position.balance = convertToAssets({
|
|
5777
|
-
shares,
|
|
5778
|
-
totalAssets: contract.totalAssets,
|
|
5779
|
-
totalSupply: contract.totalSupply,
|
|
5780
|
-
decimalsOffset: contract.decimalsOffset
|
|
5781
|
-
});
|
|
5782
|
-
position.asset = contract.asset;
|
|
5783
|
-
position.blockNumber = blockNumber;
|
|
5784
|
-
} catch (err) {
|
|
5785
|
-
if (err instanceof DenominatorIsZeroError) {
|
|
5786
|
-
logger.error({
|
|
5787
|
-
msg: "Failed to convert shares to assets",
|
|
5788
|
-
chain_id: client.chain.id,
|
|
5789
|
-
block_number: blockNumber,
|
|
5790
|
-
position_contract: position.contract,
|
|
5791
|
-
position_user: position.user,
|
|
5792
|
-
shares,
|
|
5793
|
-
err
|
|
5794
|
-
});
|
|
5795
|
-
return;
|
|
5796
|
-
}
|
|
5797
|
-
throw err;
|
|
5798
|
-
}
|
|
5799
|
-
}
|
|
5800
|
-
});
|
|
5801
|
-
if (contracts.has(position.contract.toLowerCase())) continue;
|
|
5802
|
-
calls.push({
|
|
5803
|
-
address: position.contract,
|
|
5804
|
-
abi: MetaMorpho,
|
|
5805
|
-
functionName: "DECIMALS_OFFSET",
|
|
5806
|
-
args: []
|
|
5807
|
-
}, {
|
|
5808
|
-
address: position.contract,
|
|
5809
|
-
abi: MetaMorpho,
|
|
5810
|
-
functionName: "totalAssets",
|
|
5811
|
-
args: []
|
|
5812
|
-
}, {
|
|
5813
|
-
address: position.contract,
|
|
5814
|
-
abi: MetaMorpho,
|
|
5815
|
-
functionName: "totalSupply",
|
|
5816
|
-
args: []
|
|
5817
|
-
}, {
|
|
5818
|
-
address: position.contract,
|
|
5819
|
-
abi: MetaMorpho,
|
|
5820
|
-
functionName: "asset",
|
|
5821
|
-
args: []
|
|
5822
|
-
});
|
|
5823
|
-
contracts.set(position.contract.toLowerCase(), {
|
|
5824
|
-
decimalsOffset: void 0,
|
|
5825
|
-
totalAssets: void 0,
|
|
5826
|
-
totalSupply: void 0,
|
|
5827
|
-
asset: void 0
|
|
5828
|
-
});
|
|
5829
|
-
}
|
|
5830
|
-
const results = await batchMulticall({
|
|
5831
|
-
client,
|
|
5832
|
-
calls,
|
|
5833
|
-
blockNumber: BigInt(blockNumber),
|
|
5834
|
-
batchSize: maxBatchSize,
|
|
5835
|
-
retryAttempts,
|
|
5836
|
-
retryDelayMs
|
|
5837
|
-
});
|
|
5838
|
-
const convertToAssetsList = [];
|
|
5839
|
-
for (let i = 0; i < results.length; i++) {
|
|
5840
|
-
const call = calls[i];
|
|
5841
|
-
const value = results[i];
|
|
5842
|
-
const contract = contracts.get(call.address.toLowerCase());
|
|
5843
|
-
if (!contract) continue;
|
|
5844
|
-
switch (call.functionName) {
|
|
5845
|
-
case "balanceOf":
|
|
5846
|
-
convertToAssetsList.push(() => call.convertToAssets(value));
|
|
5847
|
-
break;
|
|
5848
|
-
case "DECIMALS_OFFSET":
|
|
5849
|
-
contract.decimalsOffset = value;
|
|
5850
|
-
break;
|
|
5851
|
-
case "totalAssets":
|
|
5852
|
-
contract.totalAssets = value;
|
|
5853
|
-
break;
|
|
5854
|
-
case "totalSupply":
|
|
5855
|
-
contract.totalSupply = value;
|
|
5856
|
-
break;
|
|
5857
|
-
case "asset":
|
|
5858
|
-
contract.asset = value.toLowerCase();
|
|
5859
|
-
break;
|
|
5860
|
-
}
|
|
5861
|
-
}
|
|
5862
|
-
for (const convertToAssets of convertToAssetsList) convertToAssets();
|
|
5863
|
-
return positions;
|
|
5864
|
-
}
|
|
5865
|
-
|
|
5866
|
-
//#endregion
|
|
5867
|
-
//#region src/indexer/collectors/CollectFunctions/collectPositions.ts
|
|
5868
|
-
async function* collectPositions(parameters) {
|
|
5869
|
-
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
|
|
5870
|
-
const logger = getLogger();
|
|
5871
|
-
let startBlock = blockNumber;
|
|
5872
|
-
let reorgDetected = false;
|
|
5873
|
-
const TransferEvent = {
|
|
5874
|
-
type: "event",
|
|
5875
|
-
name: "Transfer",
|
|
5876
|
-
inputs: [
|
|
5877
|
-
{
|
|
5878
|
-
name: "from",
|
|
5879
|
-
type: "address",
|
|
5880
|
-
indexed: true
|
|
5881
|
-
},
|
|
5882
|
-
{
|
|
5883
|
-
name: "to",
|
|
5884
|
-
type: "address",
|
|
5885
|
-
indexed: true
|
|
5886
|
-
},
|
|
5887
|
-
{
|
|
5888
|
-
name: "value",
|
|
5889
|
-
type: "uint256",
|
|
5890
|
-
indexed: false
|
|
5891
|
-
}
|
|
5892
|
-
]
|
|
5893
|
-
};
|
|
5894
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5895
|
-
const stream = streamLogs({
|
|
5896
|
-
client,
|
|
5897
|
-
event: TransferEvent,
|
|
5898
|
-
blockNumberGte: blockNumber,
|
|
5899
|
-
blockNumberLte: latestBlockNumberChain,
|
|
5900
|
-
order: "asc",
|
|
5901
|
-
options: {
|
|
5902
|
-
maxBatchSize,
|
|
5903
|
-
blockWindow
|
|
5904
|
-
}
|
|
5905
|
-
});
|
|
5906
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5907
|
-
blockNumber = lastStreamBlockNumber;
|
|
5908
|
-
const parsedLogs = parseEventLogs({
|
|
5909
|
-
abi: [TransferEvent],
|
|
5910
|
-
logs
|
|
5911
|
-
});
|
|
5912
|
-
const transfers = [];
|
|
5913
|
-
for (const log of parsedLogs) {
|
|
5914
|
-
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
5915
|
-
logger.debug({
|
|
5916
|
-
collector,
|
|
5917
|
-
chainId: client.chain.id,
|
|
5918
|
-
msg: "Skipping log because it is missing required fields"
|
|
5919
|
-
});
|
|
5920
|
-
continue;
|
|
5921
|
-
}
|
|
5922
|
-
transfers.push(from$8({
|
|
5923
|
-
id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
|
|
5924
|
-
chainId: client.chain.id,
|
|
5925
|
-
contract: log.address,
|
|
5926
|
-
from: log.args.from,
|
|
5927
|
-
to: log.args.to,
|
|
5928
|
-
value: log.args.value,
|
|
5929
|
-
blockNumber: Number(log.blockNumber)
|
|
5930
|
-
}));
|
|
5931
|
-
}
|
|
5932
|
-
const { positions } = await db.positions.get({
|
|
5933
|
-
chainId: client.chain.id,
|
|
5934
|
-
filled: false
|
|
5935
|
-
});
|
|
5936
|
-
const newPositions = [];
|
|
5937
|
-
try {
|
|
5938
|
-
newPositions.push(...(await _snapshot({
|
|
5939
|
-
positions,
|
|
5940
|
-
blockNumber: latestBlockNumberChain,
|
|
5941
|
-
client,
|
|
5942
|
-
maxBatchSize,
|
|
5943
|
-
retryAttempts,
|
|
5944
|
-
retryDelayMs
|
|
5945
|
-
})).map((p) => ({
|
|
5946
|
-
...p,
|
|
5947
|
-
blockNumber: p.blockNumber + 1
|
|
5948
|
-
})));
|
|
5949
|
-
} catch (err) {
|
|
5950
|
-
logger.error({
|
|
5951
|
-
msg: "Failed to snapshot new empty positions",
|
|
5952
|
-
collector,
|
|
5953
|
-
chain_id: client.chain.id,
|
|
5954
|
-
block_number: latestBlockNumberChain,
|
|
5955
|
-
err
|
|
5956
|
-
});
|
|
5957
|
-
yield startBlock;
|
|
5958
|
-
return;
|
|
5959
|
-
}
|
|
5960
|
-
try {
|
|
5961
|
-
await db.transaction(async (dbTx) => {
|
|
5962
|
-
const insertPositions = async () => {
|
|
5963
|
-
if (newPositions.length === 0) return;
|
|
5964
|
-
try {
|
|
5965
|
-
const count = await dbTx.positions.upsert(newPositions);
|
|
5966
|
-
logger.info({
|
|
5967
|
-
msg: `New positions`,
|
|
5968
|
-
collector,
|
|
5969
|
-
count,
|
|
5970
|
-
chain_id: client.chain.id,
|
|
5971
|
-
block_number: latestBlockNumberChain
|
|
5972
|
-
});
|
|
5973
|
-
} catch (err) {
|
|
5974
|
-
throw new InsertPositionsError(err);
|
|
5975
|
-
}
|
|
5976
|
-
};
|
|
5977
|
-
const insertTransfers = async () => {
|
|
5978
|
-
if (transfers.length === 0) return;
|
|
5979
|
-
try {
|
|
5980
|
-
const created = await dbTx.transfers.create(transfers);
|
|
5981
|
-
logger.info({
|
|
5982
|
-
msg: `New transfers`,
|
|
5983
|
-
collector,
|
|
5984
|
-
count: created,
|
|
5985
|
-
chain_id: client.chain.id,
|
|
5986
|
-
block_range: [startBlock, blockNumber]
|
|
5987
|
-
});
|
|
5988
|
-
} catch (err) {
|
|
5989
|
-
throw new InsertTransfersError(err);
|
|
5990
|
-
}
|
|
5991
|
-
};
|
|
5992
|
-
const saveBlockNumber = async () => {
|
|
5993
|
-
try {
|
|
5994
|
-
await dbTx.blocks.advanceCollector({
|
|
5995
|
-
collectorName: collector,
|
|
5996
|
-
chainId: client.chain.id,
|
|
5997
|
-
blockNumber,
|
|
5998
|
-
epoch
|
|
5999
|
-
});
|
|
6000
|
-
} catch (_) {
|
|
6001
|
-
throw new ReorgError(blockNumber);
|
|
6002
|
-
}
|
|
6003
|
-
};
|
|
6004
|
-
await insertPositions();
|
|
6005
|
-
await insertTransfers();
|
|
6006
|
-
await saveBlockNumber();
|
|
6007
|
-
});
|
|
6008
|
-
} catch (err) {
|
|
6009
|
-
if (err instanceof ReorgError) {
|
|
6010
|
-
logger.info({
|
|
6011
|
-
msg: "Reorg detected, positions and transfers insertion aborted",
|
|
6012
|
-
collector,
|
|
6013
|
-
count: newPositions.length,
|
|
6014
|
-
chain_id: client.chain.id,
|
|
6015
|
-
block_number: blockNumber
|
|
6016
|
-
});
|
|
6017
|
-
reorgDetected = true;
|
|
6018
|
-
}
|
|
6019
|
-
if (err instanceof InsertPositionsError) {
|
|
6020
|
-
logger.error({
|
|
6021
|
-
msg: "Failed to insert positions",
|
|
6022
|
-
collector,
|
|
6023
|
-
count: newPositions.length,
|
|
6024
|
-
chain_id: client.chain.id,
|
|
6025
|
-
block_number: latestBlockNumberChain,
|
|
6026
|
-
err
|
|
6027
|
-
});
|
|
6028
|
-
throw err.cause;
|
|
6029
|
-
}
|
|
6030
|
-
if (err instanceof InsertTransfersError) {
|
|
6031
|
-
logger.error({
|
|
6032
|
-
msg: "Failed to insert transfers",
|
|
6033
|
-
collector,
|
|
6034
|
-
count: transfers.length,
|
|
6035
|
-
chain_id: client.chain.id,
|
|
6036
|
-
block_number: blockNumber,
|
|
6037
|
-
err
|
|
6038
|
-
});
|
|
6039
|
-
throw err.cause;
|
|
6040
|
-
}
|
|
6041
|
-
}
|
|
6042
|
-
if (!reorgDetected) {
|
|
6043
|
-
startBlock = blockNumber;
|
|
6044
|
-
if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
|
|
6045
|
-
yield blockNumber;
|
|
6046
|
-
continue;
|
|
6047
|
-
}
|
|
6048
|
-
await db.transaction(async (dbTx) => {
|
|
6049
|
-
try {
|
|
6050
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
6051
|
-
collectorName: collector,
|
|
6052
|
-
chainId: client.chain.id
|
|
6053
|
-
});
|
|
6054
|
-
blockNumber = ancestor.blockNumber;
|
|
6055
|
-
const emptied = await dbTx.positions.setEmptyAfter({
|
|
6056
|
-
chainId: client.chain.id,
|
|
6057
|
-
blockNumber: blockNumber + 1
|
|
6058
|
-
});
|
|
6059
|
-
logger.info({
|
|
6060
|
-
msg: "Reorg detected, positions set to empty",
|
|
6061
|
-
collector,
|
|
6062
|
-
count: emptied,
|
|
6063
|
-
chain_id: client.chain.id,
|
|
6064
|
-
block_number_gte: blockNumber + 1
|
|
6065
|
-
});
|
|
6066
|
-
await dbTx.blocks.advanceCollector({
|
|
6067
|
-
collectorName: collector,
|
|
6068
|
-
chainId: client.chain.id,
|
|
6069
|
-
blockNumber,
|
|
6070
|
-
epoch: ancestor.epoch
|
|
6071
|
-
});
|
|
6072
|
-
} catch (err) {
|
|
6073
|
-
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6074
|
-
logger.error({
|
|
6075
|
-
collector,
|
|
6076
|
-
chainId: client.chain.id,
|
|
6077
|
-
msg,
|
|
6078
|
-
err
|
|
6079
|
-
});
|
|
6080
|
-
throw new Error(msg);
|
|
6081
|
-
}
|
|
6082
|
-
});
|
|
6083
|
-
return;
|
|
6084
|
-
}
|
|
6085
|
-
}
|
|
6086
|
-
/**
|
|
6087
|
-
* @internal
|
|
6088
|
-
*
|
|
6089
|
-
* Snapshots positions and returns the new positions.
|
|
6090
|
-
* @param parameters - {@link _snapshot.Parameters}
|
|
6091
|
-
* @returns The new positions. {@link _snapshot.ReturnType}
|
|
6092
|
-
*/
|
|
6093
|
-
async function _snapshot(parameters) {
|
|
6094
|
-
const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
|
|
6095
|
-
const vaultV1Positions = [];
|
|
6096
|
-
const erc20Positions = [];
|
|
6097
|
-
for (const position of positions) switch (position.type) {
|
|
6098
|
-
case Type.VAULT_V1:
|
|
6099
|
-
vaultV1Positions.push(position);
|
|
6100
|
-
break;
|
|
6101
|
-
case Type.ERC20:
|
|
6102
|
-
erc20Positions.push(position);
|
|
6103
|
-
break;
|
|
6104
|
-
default: throw new Error("Invalid position type");
|
|
6105
|
-
}
|
|
6106
|
-
const promises = [snapshotVaultPositions({
|
|
6107
|
-
client,
|
|
6108
|
-
positions: vaultV1Positions,
|
|
6109
|
-
blockNumber,
|
|
6110
|
-
options: {
|
|
6111
|
-
maxBatchSize,
|
|
6112
|
-
retryAttempts,
|
|
6113
|
-
retryDelayMs
|
|
6114
|
-
}
|
|
6115
|
-
}), snapshotERC20Positions({
|
|
6116
|
-
client,
|
|
6117
|
-
positions: erc20Positions,
|
|
6118
|
-
blockNumber,
|
|
6119
|
-
options: {
|
|
6120
|
-
maxBatchSize,
|
|
6121
|
-
retryAttempts,
|
|
6122
|
-
retryDelayMs
|
|
6123
|
-
}
|
|
6124
|
-
})];
|
|
6125
|
-
return (await Promise.all(promises)).flat();
|
|
6126
|
-
}
|
|
6127
|
-
var InsertPositionsError = class extends BaseError {
|
|
6128
|
-
name = "InsertPositionsError";
|
|
6129
|
-
constructor(err) {
|
|
6130
|
-
super("Failed to insert positions", { cause: err });
|
|
6131
|
-
}
|
|
6132
|
-
};
|
|
6133
|
-
var InsertTransfersError = class extends BaseError {
|
|
6134
|
-
name = "InsertTransfersError";
|
|
6135
|
-
constructor(err) {
|
|
6136
|
-
super("Failed to insert transfers", { cause: err });
|
|
6137
|
-
}
|
|
6138
|
-
};
|
|
6139
|
-
|
|
6140
|
-
//#endregion
|
|
6141
|
-
//#region src/indexer/collectors/CollectFunctions/collectPrices.ts
|
|
6142
|
-
/**
|
|
6143
|
-
* Collects oracle prices from on-chain oracles and persists them.
|
|
6144
|
-
*
|
|
6145
|
-
* - Reads oracle definitions as {@link Oracle.Oracle}.
|
|
6146
|
-
* - Uses chain metadata from {@link Chain.Chain}.
|
|
6147
|
-
*
|
|
6148
|
-
* @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
|
|
6149
|
-
* with a client supporting {@link PublicActions.multicall | multicall}.
|
|
6150
|
-
* @yields Latest processed block number after each successful update.
|
|
6151
|
-
*/
|
|
6152
|
-
async function* collectPrices(parameters) {
|
|
6153
|
-
const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6154
|
-
const logger = getLogger();
|
|
6155
|
-
let blockNumber = parameters.lastBlockNumber;
|
|
6156
|
-
const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
|
|
6157
|
-
const updatedOracles = [];
|
|
6158
|
-
try {
|
|
6159
|
-
const pricesMap = await fetchOraclePrices({
|
|
6160
|
-
client,
|
|
6161
|
-
oracles: oracles.map((oracle) => oracle.address),
|
|
6162
|
-
options: {
|
|
6163
|
-
batchSize: maxBatchSize,
|
|
6164
|
-
blockNumber: latestBlockNumberChain,
|
|
6165
|
-
retryAttempts,
|
|
6166
|
-
retryDelayMs
|
|
6167
|
-
}
|
|
6168
|
-
});
|
|
6169
|
-
for (const oracle of oracles) {
|
|
6170
|
-
const price = pricesMap.get(oracle.address);
|
|
6171
|
-
if (price !== void 0) updatedOracles.push({
|
|
6172
|
-
chainId: client.chain.id,
|
|
6173
|
-
address: oracle.address,
|
|
6174
|
-
price,
|
|
6175
|
-
blockNumber: latestBlockNumberChain
|
|
6176
|
-
});
|
|
6177
|
-
}
|
|
6178
|
-
} catch (err) {
|
|
6179
|
-
logger.error({
|
|
6180
|
-
msg: "Failed to fetch oracle prices",
|
|
6181
|
-
collector,
|
|
6182
|
-
chain_id: client.chain.id,
|
|
6183
|
-
block_number: latestBlockNumberChain,
|
|
6184
|
-
err
|
|
6185
|
-
});
|
|
6186
|
-
yield blockNumber;
|
|
6187
|
-
return;
|
|
6188
|
-
}
|
|
6189
|
-
let reorgDetected = false;
|
|
6190
|
-
try {
|
|
6191
|
-
await db.transaction(async (dbTx) => {
|
|
6192
|
-
if (updatedOracles.length > 0) {
|
|
6193
|
-
await dbTx.oracles.upsert(updatedOracles);
|
|
6194
|
-
logger.info({
|
|
6195
|
-
msg: "Oracle prices updated",
|
|
6196
|
-
collector,
|
|
6197
|
-
count: updatedOracles.length,
|
|
6198
|
-
chain_id: client.chain.id,
|
|
6199
|
-
block_number: latestBlockNumberChain
|
|
6200
|
-
});
|
|
6201
|
-
}
|
|
6202
|
-
try {
|
|
6203
|
-
await dbTx.blocks.advanceCollector({
|
|
6204
|
-
collectorName: collector,
|
|
6205
|
-
chainId: client.chain.id,
|
|
6206
|
-
blockNumber: latestBlockNumberChain,
|
|
6207
|
-
epoch
|
|
6208
|
-
});
|
|
6209
|
-
} catch (_) {
|
|
6210
|
-
throw new ReorgError(latestBlockNumberChain);
|
|
6211
|
-
}
|
|
6212
|
-
blockNumber = latestBlockNumberChain;
|
|
6213
|
-
});
|
|
6214
|
-
} catch (err) {
|
|
6215
|
-
if (err instanceof ReorgError) {
|
|
6216
|
-
logger.info({
|
|
6217
|
-
msg: "Reorg detected, prices update aborted",
|
|
6218
|
-
collector,
|
|
6219
|
-
count: updatedOracles.length,
|
|
6220
|
-
chain_id: client.chain.id,
|
|
6221
|
-
block_number: latestBlockNumberChain
|
|
6222
|
-
});
|
|
6223
|
-
reorgDetected = true;
|
|
6224
|
-
} else throw new Error("Failed to collect oracle prices", { cause: err });
|
|
6225
|
-
}
|
|
6226
|
-
if (!reorgDetected) {
|
|
6227
|
-
yield blockNumber;
|
|
6228
|
-
return;
|
|
6229
|
-
}
|
|
6230
|
-
await db.transaction(async (dbTx) => {
|
|
6231
|
-
try {
|
|
6232
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
6233
|
-
collectorName: collector,
|
|
6234
|
-
chainId: client.chain.id
|
|
6235
|
-
});
|
|
6236
|
-
blockNumber = ancestor.blockNumber;
|
|
6237
|
-
await dbTx.blocks.advanceCollector({
|
|
6238
|
-
collectorName: collector,
|
|
6239
|
-
chainId: client.chain.id,
|
|
6240
|
-
blockNumber,
|
|
6241
|
-
epoch: ancestor.epoch
|
|
6242
|
-
});
|
|
6243
|
-
} catch (err) {
|
|
6244
|
-
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6245
|
-
logger.error({
|
|
6246
|
-
collector,
|
|
6247
|
-
chainId: client.chain.id,
|
|
6248
|
-
msg,
|
|
6249
|
-
err
|
|
6250
|
-
});
|
|
6251
|
-
throw new Error(msg);
|
|
6252
|
-
}
|
|
6253
|
-
});
|
|
6254
|
-
yield blockNumber;
|
|
6255
|
-
}
|
|
6256
|
-
|
|
6257
|
-
//#endregion
|
|
6258
|
-
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
6259
|
-
function createBuilder(parameters) {
|
|
6260
|
-
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
|
|
6261
|
-
const createCollector = (name, collect) => create$16({
|
|
6262
|
-
name,
|
|
6263
|
-
collect,
|
|
6264
|
-
client,
|
|
6265
|
-
db,
|
|
6266
|
-
options: {
|
|
6267
|
-
maxBlockNumber,
|
|
6268
|
-
interval
|
|
6269
|
-
}
|
|
6270
|
-
});
|
|
6271
|
-
return {
|
|
6272
|
-
buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
|
|
6273
|
-
return createCollector("offers", (p) => collectOffersV2({
|
|
6274
|
-
...p,
|
|
6275
|
-
gatekeeper,
|
|
6276
|
-
collector: "offers",
|
|
6277
|
-
client,
|
|
6278
|
-
options: {
|
|
6279
|
-
maxBatchSize,
|
|
6280
|
-
blockWindow
|
|
6281
|
-
}
|
|
6282
|
-
}));
|
|
6283
|
-
},
|
|
6284
|
-
buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
|
|
6285
|
-
return createCollector("consumed_events", (p) => collectConsumedEvents({
|
|
6286
|
-
...p,
|
|
6287
|
-
collector: "consumed_events",
|
|
6288
|
-
options: {
|
|
6289
|
-
maxBatchSize,
|
|
6290
|
-
blockWindow
|
|
6291
|
-
}
|
|
6292
|
-
}));
|
|
6293
|
-
},
|
|
6294
|
-
buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6295
|
-
return createCollector("prices", (p) => collectPrices({
|
|
6296
|
-
...p,
|
|
6297
|
-
collector: "prices",
|
|
6298
|
-
options: {
|
|
6299
|
-
maxBatchSize,
|
|
6300
|
-
retryAttempts,
|
|
6301
|
-
retryDelayMs
|
|
6302
|
-
}
|
|
6303
|
-
}));
|
|
6304
|
-
},
|
|
6305
|
-
buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6306
|
-
return createCollector("positions", (p) => collectPositions({
|
|
6307
|
-
...p,
|
|
6308
|
-
collector: "positions",
|
|
6309
|
-
options: {
|
|
6310
|
-
maxBatchSize,
|
|
6311
|
-
retryAttempts,
|
|
6312
|
-
retryDelayMs,
|
|
6313
|
-
blockWindow
|
|
6314
|
-
}
|
|
6315
|
-
}));
|
|
6316
|
-
}
|
|
6317
|
-
};
|
|
6318
|
-
}
|
|
6319
|
-
|
|
6320
|
-
//#endregion
|
|
6321
|
-
//#region src/indexer/collectors/Collectors.ts
|
|
6322
|
-
const from$2 = (parameters) => {
|
|
6323
|
-
const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
|
|
6324
|
-
const collectorBuilder = createBuilder({
|
|
6325
|
-
client,
|
|
6326
|
-
db,
|
|
6327
|
-
gatekeeper,
|
|
6328
|
-
options: {
|
|
6329
|
-
maxBlockNumber,
|
|
6330
|
-
blockWindow,
|
|
6331
|
-
interval
|
|
6332
|
-
}
|
|
6333
|
-
});
|
|
6334
|
-
return {
|
|
6335
|
-
offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
|
|
6336
|
-
consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
|
|
6337
|
-
pricesCollector: collectorBuilder.buildPricesCollector({ options: {
|
|
6338
|
-
maxBatchSize,
|
|
6339
|
-
retryAttempts,
|
|
6340
|
-
retryDelayMs
|
|
6341
|
-
} }),
|
|
6342
|
-
positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
|
|
6343
|
-
maxBatchSize,
|
|
6344
|
-
retryAttempts,
|
|
6345
|
-
retryDelayMs
|
|
6346
|
-
} })
|
|
6347
|
-
};
|
|
6348
|
-
};
|
|
6349
|
-
|
|
6350
|
-
//#endregion
|
|
6351
|
-
//#region src/indexer/Indexer.ts
|
|
6352
|
-
function from$1(config) {
|
|
6353
|
-
const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
|
|
6354
|
-
const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
|
|
6355
|
-
client,
|
|
6356
|
-
db,
|
|
6357
|
-
gatekeeper,
|
|
6358
|
-
maxBatchSize,
|
|
6359
|
-
maxBlockNumber,
|
|
6360
|
-
blockWindow,
|
|
6361
|
-
interval,
|
|
6362
|
-
retryAttempts,
|
|
6363
|
-
retryDelayMs
|
|
6364
|
-
});
|
|
6365
|
-
return create$18({
|
|
6366
|
-
client,
|
|
6367
|
-
collectors: [
|
|
6368
|
-
offersCollector,
|
|
6369
|
-
consumedEventsCollector,
|
|
6370
|
-
positionsCollector,
|
|
6371
|
-
pricesCollector
|
|
6372
|
-
]
|
|
6373
|
-
});
|
|
6374
|
-
}
|
|
6375
|
-
function create$18(params) {
|
|
6376
|
-
const { collectors, client } = params;
|
|
6377
|
-
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
6378
|
-
const tracer = getTracer(`router.${indexerId}`);
|
|
6379
|
-
const iterators = collectors.map((collector) => collector.collect());
|
|
6380
|
-
const next = async () => {
|
|
6381
|
-
await startActiveSpan(tracer, `${indexerId}.next`, async () => {
|
|
6382
|
-
await Promise.all(iterators.map((iterator) => iterator.next()));
|
|
6383
|
-
});
|
|
6384
|
-
};
|
|
6385
|
-
const _return = async () => {
|
|
6386
|
-
await Promise.all(iterators.map(async (iterator) => iterator.return()));
|
|
6387
|
-
};
|
|
6388
|
-
return {
|
|
6389
|
-
start: () => {
|
|
6390
|
-
const stops = collectors.map((collector) => start(collector));
|
|
6391
|
-
return () => {
|
|
6392
|
-
stops.forEach((stop) => {
|
|
6393
|
-
stop();
|
|
6394
|
-
});
|
|
6395
|
-
};
|
|
6396
|
-
},
|
|
6397
|
-
next,
|
|
6398
|
-
return: _return
|
|
6399
|
-
};
|
|
6400
|
-
}
|
|
6401
|
-
|
|
6402
|
-
//#endregion
|
|
6403
|
-
//#region src/indexer/collectors/Admin.ts
|
|
6404
|
-
function create$17(parameters) {
|
|
6405
|
-
const collector = "admin";
|
|
6406
|
-
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
6407
|
-
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
6408
|
-
let finalizedBlock = null;
|
|
6409
|
-
let unfinalizedBlocks = [];
|
|
6410
|
-
let initialized = false;
|
|
6411
|
-
const logger = getLogger();
|
|
6412
|
-
let isMaxBlockNumberReached = false;
|
|
6413
|
-
let tick = 0;
|
|
6414
|
-
return { syncBlock: async () => {
|
|
6415
|
-
if (!initialized) {
|
|
6416
|
-
await Promise.all(names.map((collectorName) => db.blocks.init({
|
|
6417
|
-
chainId: client.chain.id,
|
|
6418
|
-
collectorName
|
|
6419
|
-
})));
|
|
6420
|
-
initialized = true;
|
|
6421
|
-
}
|
|
6422
|
-
if (isMaxBlockNumberReached) return true;
|
|
6423
|
-
const head = await client.getBlock({
|
|
6424
|
-
blockTag: "latest",
|
|
6425
|
-
includeTransactions: false
|
|
6426
|
-
});
|
|
6427
|
-
await db.transaction(async (dbTx) => {
|
|
6428
|
-
const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
6429
|
-
if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
|
|
6430
|
-
logger.info({
|
|
6431
|
-
msg: `Head is greater than max block number`,
|
|
6432
|
-
collector,
|
|
6433
|
-
chainId: client.chain.id,
|
|
6434
|
-
block_number: head.number,
|
|
6435
|
-
max_block_number: maxBlockNumber
|
|
6436
|
-
});
|
|
6437
|
-
await dbTx.blocks.handleReorg({
|
|
6438
|
-
chainId: client.chain.id,
|
|
6439
|
-
blockNumber: maxBlockNumber,
|
|
6440
|
-
epoch: epoch + 1n
|
|
6441
|
-
});
|
|
6442
|
-
isMaxBlockNumberReached = true;
|
|
6443
|
-
return isMaxBlockNumberReached;
|
|
6444
|
-
}
|
|
6445
|
-
finalizedBlock = await fetchFinalizedBlock({
|
|
6446
|
-
tick,
|
|
6447
|
-
client,
|
|
6448
|
-
logger,
|
|
6449
|
-
collector,
|
|
6450
|
-
unfinalizedBlocks,
|
|
6451
|
-
previousFinalizedBlock: finalizedBlock
|
|
6452
|
-
});
|
|
6453
|
-
tick++;
|
|
6454
|
-
let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6455
|
-
client,
|
|
6456
|
-
block: head,
|
|
6457
|
-
unfinalizedBlocks,
|
|
6458
|
-
finalizedBlock,
|
|
6459
|
-
logger,
|
|
6460
|
-
collector,
|
|
6461
|
-
maxBatchSize
|
|
6462
|
-
});
|
|
6463
|
-
unfinalizedBlocks = newUnfinalizedBlocks;
|
|
6464
|
-
const blockNumber = Number(returnedBlock.number);
|
|
6465
|
-
didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
|
|
6466
|
-
if (didReorgHappened) await dbTx.blocks.handleReorg({
|
|
6467
|
-
chainId: client.chain.id,
|
|
6468
|
-
blockNumber,
|
|
6469
|
-
epoch: epoch + 1n
|
|
6470
|
-
});
|
|
6471
|
-
else await dbTx.blocks.advanceChain({
|
|
6472
|
-
chainId: client.chain.id,
|
|
6473
|
-
blockNumber,
|
|
6474
|
-
epoch
|
|
6475
|
-
});
|
|
6476
|
-
});
|
|
6477
|
-
return isMaxBlockNumberReached;
|
|
6478
|
-
} };
|
|
6479
|
-
}
|
|
6480
|
-
const commonAncestor = (block, unfinalizedBlocks) => {
|
|
6481
|
-
const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
|
|
6482
|
-
if (parent) return parent;
|
|
6483
|
-
return null;
|
|
6484
|
-
};
|
|
6485
|
-
const fetchFinalizedBlock = async (parameters) => {
|
|
6486
|
-
let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
|
|
6487
|
-
let finalizedBlock = previousFinalizedBlock;
|
|
6488
|
-
if (tick % 20 === 0 || previousFinalizedBlock === null) {
|
|
6489
|
-
finalizedBlock = await client.getBlock({
|
|
6490
|
-
blockTag: "finalized",
|
|
6491
|
-
includeTransactions: false
|
|
6492
|
-
});
|
|
6493
|
-
if (finalizedBlock === null || finalizedBlock.number === null) {
|
|
6494
|
-
const msg = "Failed to get finalized block";
|
|
6495
|
-
logger.fatal({
|
|
6496
|
-
collector,
|
|
6497
|
-
chainId: client.chain.id,
|
|
6498
|
-
msg
|
|
6499
|
-
});
|
|
6500
|
-
throw new Error(msg);
|
|
6501
|
-
}
|
|
6502
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
|
|
6503
|
-
}
|
|
6504
|
-
if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
|
|
6505
|
-
const msg = "Failed to get finalized block";
|
|
6506
|
-
logger.fatal({
|
|
6507
|
-
collector,
|
|
6508
|
-
chainId: client.chain.id,
|
|
6509
|
-
msg
|
|
6510
|
-
});
|
|
6511
|
-
throw new Error(msg);
|
|
6512
|
-
}
|
|
6513
|
-
return {
|
|
6514
|
-
hash: finalizedBlock.hash,
|
|
6515
|
-
number: finalizedBlock.number,
|
|
6516
|
-
parentHash: finalizedBlock.parentHash
|
|
6517
|
-
};
|
|
6518
|
-
};
|
|
6519
|
-
const reconcile = async (parameters) => {
|
|
6520
|
-
let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
|
|
6521
|
-
const chain = client.chain;
|
|
6522
|
-
if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
|
|
6523
|
-
const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
|
|
6524
|
-
if (latestBlock === void 0) {
|
|
6525
|
-
const newBlock = {
|
|
6526
|
-
hash: block.hash,
|
|
6527
|
-
number: block.number,
|
|
6528
|
-
parentHash: block.parentHash
|
|
6529
|
-
};
|
|
6530
|
-
unfinalizedBlocks.push(newBlock);
|
|
6531
|
-
return {
|
|
6532
|
-
block: newBlock,
|
|
6533
|
-
didReorgHappened: false,
|
|
6534
|
-
unfinalizedBlocks
|
|
6535
|
-
};
|
|
6536
|
-
}
|
|
6537
|
-
if (latestBlock.hash === block.hash) return {
|
|
6538
|
-
block: latestBlock,
|
|
6539
|
-
didReorgHappened: false,
|
|
6540
|
-
unfinalizedBlocks
|
|
6541
|
-
};
|
|
6542
|
-
if (latestBlock.number >= block.number) {
|
|
6543
|
-
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6544
|
-
logger.info({
|
|
6545
|
-
msg: `Reorg detected, latestBlock.number >= block.number`,
|
|
6546
|
-
collector,
|
|
6547
|
-
chain_id: chain.id,
|
|
6548
|
-
ancestor: ancestor.number,
|
|
6549
|
-
latest_block_number: latestBlock.number,
|
|
6550
|
-
block_number: block.number
|
|
6551
|
-
});
|
|
6552
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6553
|
-
return {
|
|
6554
|
-
block: ancestor,
|
|
6555
|
-
didReorgHappened: true,
|
|
6556
|
-
unfinalizedBlocks
|
|
6557
|
-
};
|
|
6558
|
-
}
|
|
6559
|
-
if (latestBlock.number + 1n < block.number) {
|
|
6560
|
-
logger.debug({
|
|
6561
|
-
collector,
|
|
6562
|
-
chain_id: chain.id,
|
|
6563
|
-
block_range: [latestBlock.number, block.number],
|
|
6564
|
-
msg: `Missing blocks`
|
|
6565
|
-
});
|
|
6566
|
-
const missingBlockNumbers = (() => {
|
|
6567
|
-
const missingBlockNumbers = [];
|
|
6568
|
-
let start = latestBlock.number + 1n;
|
|
6569
|
-
const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
|
|
6570
|
-
while (start < threshold) {
|
|
6571
|
-
missingBlockNumbers.push(start);
|
|
6572
|
-
start = start + 1n;
|
|
6573
|
-
}
|
|
6574
|
-
return missingBlockNumbers;
|
|
6575
|
-
})();
|
|
6576
|
-
const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
|
|
6577
|
-
blockNumber,
|
|
6578
|
-
includeTransactions: false
|
|
6579
|
-
}))));
|
|
6580
|
-
for (const missingBlock of missingBlocks) {
|
|
6581
|
-
const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6582
|
-
client,
|
|
6583
|
-
block: missingBlock,
|
|
6584
|
-
unfinalizedBlocks,
|
|
6585
|
-
finalizedBlock,
|
|
6586
|
-
logger,
|
|
6587
|
-
collector,
|
|
6588
|
-
maxBatchSize
|
|
6589
|
-
});
|
|
6590
|
-
if (returnedBlock.number !== missingBlock.number) return {
|
|
6591
|
-
block: returnedBlock,
|
|
6592
|
-
didReorgHappened,
|
|
6593
|
-
unfinalizedBlocks: newUnfinalizedBlocks
|
|
6594
|
-
};
|
|
6595
|
-
}
|
|
6596
|
-
return reconcile({
|
|
6597
|
-
client,
|
|
6598
|
-
block,
|
|
6599
|
-
unfinalizedBlocks,
|
|
6600
|
-
finalizedBlock,
|
|
6601
|
-
logger,
|
|
6602
|
-
collector,
|
|
6603
|
-
maxBatchSize
|
|
6604
|
-
});
|
|
6605
|
-
}
|
|
6606
|
-
if (block.parentHash !== latestBlock.hash) {
|
|
6607
|
-
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6608
|
-
logger.info({
|
|
6609
|
-
msg: `Reorg detected, block parent hash !== latest block hash`,
|
|
6610
|
-
collector,
|
|
6611
|
-
chain_id: chain.id,
|
|
6612
|
-
ancestor: ancestor.number,
|
|
6613
|
-
latest_block_number: latestBlock.number,
|
|
6614
|
-
block_number: block.number
|
|
6615
|
-
});
|
|
6616
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6617
|
-
return {
|
|
6618
|
-
block: ancestor,
|
|
6619
|
-
didReorgHappened: true,
|
|
6620
|
-
unfinalizedBlocks
|
|
6621
|
-
};
|
|
6622
|
-
}
|
|
6623
|
-
const newBlock = {
|
|
6624
|
-
hash: block.hash,
|
|
6625
|
-
number: block.number,
|
|
6626
|
-
parentHash: block.parentHash
|
|
6627
|
-
};
|
|
6628
|
-
unfinalizedBlocks.push(newBlock);
|
|
6629
|
-
return {
|
|
6630
|
-
block: newBlock,
|
|
6631
|
-
didReorgHappened: false,
|
|
6632
|
-
unfinalizedBlocks
|
|
6633
|
-
};
|
|
6634
|
-
};
|
|
6635
|
-
|
|
6636
|
-
//#endregion
|
|
6637
|
-
//#region src/indexer/collectors/Collector.ts
|
|
6638
|
-
const names = [
|
|
6639
|
-
"offers",
|
|
6640
|
-
"consumed_events",
|
|
6641
|
-
"positions",
|
|
6642
|
-
"prices"
|
|
6643
|
-
];
|
|
6644
|
-
function create$16({ name, collect, client, db, options }) {
|
|
6645
|
-
const admin = create$17({
|
|
6646
|
-
client,
|
|
6647
|
-
db,
|
|
6648
|
-
options
|
|
6649
|
-
});
|
|
6650
|
-
return {
|
|
6651
|
-
name,
|
|
6652
|
-
chain: client.chain,
|
|
6653
|
-
client,
|
|
6654
|
-
db,
|
|
6655
|
-
interval: options.interval ?? 1e4,
|
|
6656
|
-
collect: async function* () {
|
|
6657
|
-
const collector = name;
|
|
6658
|
-
const chain = client.chain;
|
|
6659
|
-
const logger = getLogger();
|
|
6660
|
-
const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
|
|
6661
|
-
const tracer = getTracer(`router.${collectorId}`);
|
|
6662
|
-
logger.info({
|
|
6663
|
-
msg: `Collector started`,
|
|
6664
|
-
collector,
|
|
6665
|
-
chain_id: chain.id
|
|
6666
|
-
});
|
|
6667
|
-
let iterator = null;
|
|
6668
|
-
let lastBlockNumber;
|
|
6669
|
-
while (true) try {
|
|
6670
|
-
if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
|
|
6671
|
-
const { collector: collectorBlock } = await db.blocks.init({
|
|
6672
|
-
collectorName: name,
|
|
6673
|
-
chainId: chain.id
|
|
6674
|
-
});
|
|
6675
|
-
lastBlockNumber = collectorBlock.blockNumber;
|
|
6676
|
-
return collect({
|
|
6677
|
-
client,
|
|
6678
|
-
collector: name,
|
|
6679
|
-
epoch: collectorBlock.epoch,
|
|
6680
|
-
lastBlockNumber,
|
|
6681
|
-
db
|
|
6682
|
-
});
|
|
6683
|
-
});
|
|
6684
|
-
if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
|
|
6685
|
-
const isMaxBlockNumberReached = await admin.syncBlock();
|
|
6686
|
-
span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
|
|
6687
|
-
return isMaxBlockNumberReached;
|
|
6688
|
-
}) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
|
|
6689
|
-
const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
|
|
6690
|
-
if (iterator === null) throw new Error("Iterator is not initialized");
|
|
6691
|
-
const { value: blockNumber, done } = await iterator.next();
|
|
6692
|
-
return {
|
|
6693
|
-
blockNumber,
|
|
6694
|
-
done
|
|
6695
|
-
};
|
|
6696
|
-
});
|
|
6697
|
-
if (done) iterator = null;
|
|
6698
|
-
else {
|
|
6699
|
-
lastBlockNumber = blockNumber;
|
|
6700
|
-
yield blockNumber;
|
|
6701
|
-
}
|
|
6702
|
-
} catch (err) {
|
|
6703
|
-
const isError = err instanceof Error;
|
|
6704
|
-
logger.error({
|
|
6705
|
-
msg: "Collector error",
|
|
6706
|
-
collector,
|
|
6707
|
-
chain_id: chain.id,
|
|
6708
|
-
error: isError ? err.message : String(err),
|
|
6709
|
-
stack: isError ? err.stack : void 0
|
|
6710
|
-
});
|
|
6711
|
-
}
|
|
6712
|
-
}
|
|
6713
|
-
};
|
|
6714
|
-
}
|
|
6715
|
-
/** Start a collector with its own polling cadence based on chain head lag.
|
|
6716
|
-
* @param collector - The collector to start.
|
|
6717
|
-
* @returns A function to stop the collector.
|
|
6718
|
-
*/
|
|
6719
|
-
function start(collector) {
|
|
6720
|
-
let stopped = false;
|
|
6721
|
-
const it = collector.collect();
|
|
6722
|
-
const logger = getLogger();
|
|
6723
|
-
const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
|
|
6724
|
-
const tracer = getTracer(`router.${collectorId}`);
|
|
6725
|
-
const blocks = collector.db.blocks;
|
|
6726
|
-
let initialized = false;
|
|
6727
|
-
(async () => {
|
|
6728
|
-
while (!stopped) try {
|
|
6729
|
-
await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
|
|
6730
|
-
if (!initialized) {
|
|
6731
|
-
await blocks.init({
|
|
6732
|
-
chainId: collector.chain.id,
|
|
6733
|
-
collectorName: collector.name
|
|
6734
|
-
});
|
|
6735
|
-
initialized = true;
|
|
6736
|
-
}
|
|
6737
|
-
const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
|
|
6738
|
-
blockTag: "latest",
|
|
6739
|
-
includeTransactions: false
|
|
6740
|
-
}), blocks.getCollector({
|
|
6741
|
-
collectorName: collector.name,
|
|
6742
|
-
chainId: collector.chain.id
|
|
6743
|
-
})]);
|
|
6744
|
-
const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
|
|
6745
|
-
const waitSpan = tracer.startSpan(`${collectorId}.wait`);
|
|
6746
|
-
try {
|
|
6747
|
-
await wait(delay);
|
|
6748
|
-
} finally {
|
|
6749
|
-
waitSpan.end();
|
|
6750
|
-
}
|
|
6751
|
-
await it.next();
|
|
6752
|
-
});
|
|
6753
|
-
} catch (err) {
|
|
6754
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
6755
|
-
logger.error({
|
|
6756
|
-
msg: "Collector polling error",
|
|
6757
|
-
collector: collector.name,
|
|
6758
|
-
chain_id: collector.chain.id,
|
|
6759
|
-
err: error
|
|
6760
|
-
});
|
|
6761
|
-
}
|
|
6762
|
-
await it.return();
|
|
6763
|
-
})();
|
|
6764
|
-
return () => {
|
|
6765
|
-
stopped = true;
|
|
6766
|
-
};
|
|
6767
|
-
}
|
|
6768
|
-
|
|
6769
|
-
//#endregion
|
|
6770
|
-
//#region src/database/drizzle/VERSION.ts
|
|
6771
|
-
const VERSION = "router_v1.6";
|
|
5084
|
+
//#region src/database/drizzle/VERSION.ts
|
|
5085
|
+
const VERSION = "router_v1.6";
|
|
6772
5086
|
|
|
6773
5087
|
//#endregion
|
|
6774
5088
|
//#region src/database/drizzle/schema.ts
|
|
@@ -7170,6 +5484,1654 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
|
7170
5484
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7171
5485
|
}, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
|
|
7172
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
|
+
|
|
7173
7135
|
//#endregion
|
|
7174
7136
|
//#region src/database/domains/Blocks.ts
|
|
7175
7137
|
/** Postgres implementation. */
|
|
@@ -7601,35 +7563,15 @@ async function _getOffers(db, params) {
|
|
|
7601
7563
|
AND LOWER(pc.contract) = LOWER(c.position_contract)
|
|
7602
7564
|
AND LOWER(pc."user") = LOWER(c.position_user)
|
|
7603
7565
|
),
|
|
7604
|
-
-- Compute contribution per callback in loan terms (
|
|
7566
|
+
-- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
|
|
7605
7567
|
callback_loan_contribution AS (
|
|
7606
7568
|
SELECT
|
|
7607
7569
|
cc.*,
|
|
7608
7570
|
CASE
|
|
7609
|
-
-- No lot exists: contribution is 0
|
|
7610
7571
|
WHEN cc.lot_lower IS NULL THEN 0
|
|
7611
|
-
|
|
7612
|
-
WHEN LOWER(cc.position_asset) = LOWER(cc.loan_token) THEN
|
|
7613
|
-
LEAST(
|
|
7614
|
-
cc.lot_balance,
|
|
7615
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
7616
|
-
)
|
|
7617
|
-
-- Collateral position: convert to loan using (amount * price / 10^36) * lltv / 10^18
|
|
7618
|
-
ELSE
|
|
7619
|
-
(
|
|
7620
|
-
LEAST(
|
|
7621
|
-
cc.lot_balance,
|
|
7622
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
7623
|
-
) * COALESCE(collat_oracle.price::numeric, 0) / 1e36
|
|
7624
|
-
) * COALESCE(collat_info.lltv::numeric, 0) / 1e18
|
|
7572
|
+
ELSE LEAST(cc.lot_balance, COALESCE(cc.callback_amount::numeric, cc.lot_balance))
|
|
7625
7573
|
END AS contribution_in_loan
|
|
7626
7574
|
FROM callback_contributions cc
|
|
7627
|
-
LEFT JOIN ${obligationCollateralsV2} collat_info
|
|
7628
|
-
ON collat_info.obligation_id = cc.obligation_id
|
|
7629
|
-
AND LOWER(collat_info.asset) = LOWER(cc.position_asset)
|
|
7630
|
-
LEFT JOIN ${oracles} collat_oracle
|
|
7631
|
-
ON collat_oracle.chain_id = collat_info.oracle_chain_id
|
|
7632
|
-
AND LOWER(collat_oracle.address) = LOWER(collat_info.oracle_address)
|
|
7633
7575
|
),
|
|
7634
7576
|
-- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
|
|
7635
7577
|
offer_contributions AS (
|
|
@@ -7666,6 +7608,22 @@ async function _getOffers(db, params) {
|
|
|
7666
7608
|
GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
|
|
7667
7609
|
callback_address, callback_data, block_number, group_chain_id, group_maker,
|
|
7668
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
|
+
)
|
|
7669
7627
|
)
|
|
7670
7628
|
-- Final SELECT with inline takeable computation
|
|
7671
7629
|
SELECT
|
|
@@ -7688,18 +7646,24 @@ async function _getOffers(db, params) {
|
|
|
7688
7646
|
oc.block_number,
|
|
7689
7647
|
oc.session,
|
|
7690
7648
|
COALESCE(oc.total_available, 0) AS available,
|
|
7691
|
-
-- takeable
|
|
7692
|
-
|
|
7693
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
7694
|
-
|
|
7695
|
-
|
|
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,
|
|
7696
7657
|
c.collaterals
|
|
7697
7658
|
FROM offer_contributions oc
|
|
7698
7659
|
LEFT JOIN collats c ON c.obligation_id = oc.obligation_id
|
|
7699
|
-
WHERE
|
|
7700
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
7701
|
-
|
|
7702
|
-
|
|
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
|
|
7703
7667
|
ORDER BY
|
|
7704
7668
|
oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
|
|
7705
7669
|
oc.block_number ASC,
|
|
@@ -8266,9 +8230,9 @@ function create$6(db) {
|
|
|
8266
8230
|
blockNumber: r.blockNumber
|
|
8267
8231
|
}));
|
|
8268
8232
|
},
|
|
8269
|
-
upsert: async (oracles$
|
|
8270
|
-
if (oracles$
|
|
8271
|
-
const rows = oracles$
|
|
8233
|
+
upsert: async (oracles$2) => {
|
|
8234
|
+
if (oracles$2.length === 0) return;
|
|
8235
|
+
const rows = oracles$2.map((o) => ({
|
|
8272
8236
|
chainId: o.chainId,
|
|
8273
8237
|
address: o.address.toLowerCase(),
|
|
8274
8238
|
price: o.price !== null ? o.price.toString() : null,
|
|
@@ -10073,6 +10037,7 @@ async function getOffersQuery(db, parameters) {
|
|
|
10073
10037
|
if (cursor !== null && cursor !== void 0) {
|
|
10074
10038
|
if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
|
|
10075
10039
|
}
|
|
10040
|
+
const now = Math.floor((Date.now() - 1) / 1e3);
|
|
10076
10041
|
const collateralsLateral = db.select({ collaterals: sql`COALESCE(
|
|
10077
10042
|
jsonb_agg(
|
|
10078
10043
|
jsonb_build_object(
|
|
@@ -10086,27 +10051,14 @@ async function getOffersQuery(db, parameters) {
|
|
|
10086
10051
|
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
10087
10052
|
const availableLateral = db.select({ available: sql`COALESCE(SUM(
|
|
10088
10053
|
CASE
|
|
10089
|
-
-- If asset is null, position available is 0
|
|
10090
10054
|
WHEN ${positions.asset} IS NULL THEN 0
|
|
10091
|
-
|
|
10092
|
-
-- Position asset matches loan token: no conversion needed
|
|
10093
|
-
WHEN ${positions.asset} = ${obligations.loanToken} THEN
|
|
10055
|
+
ELSE
|
|
10094
10056
|
CASE
|
|
10095
10057
|
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
10096
10058
|
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
10097
10059
|
END
|
|
10098
|
-
|
|
10099
|
-
-- Position asset is collateral: apply oracle price * lltv
|
|
10100
|
-
-- Formula: balance * price / 1e36 * lltv / 1e18
|
|
10101
|
-
ELSE
|
|
10102
|
-
(CASE
|
|
10103
|
-
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
10104
|
-
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
10105
|
-
END)
|
|
10106
|
-
* COALESCE(${oracles.price}, 0)::numeric / 1e36
|
|
10107
|
-
* COALESCE(${obligationCollateralsV2.lltv}, 0)::numeric / 1e18
|
|
10108
10060
|
END
|
|
10109
|
-
), 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");
|
|
10110
10062
|
const rows = (await db.select({
|
|
10111
10063
|
hash: offers.hash,
|
|
10112
10064
|
maker: offers.groupMaker,
|
|
@@ -10128,17 +10080,24 @@ async function getOffersQuery(db, parameters) {
|
|
|
10128
10080
|
collaterals: collateralsLateral.collaterals,
|
|
10129
10081
|
blockNumber: offers.blockNumber,
|
|
10130
10082
|
available: sql`COALESCE(${availableLateral.available}::numeric, 0)`.as("available"),
|
|
10131
|
-
takeable: sql`FLOOR(GREATEST(
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
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
|
|
10137
10091
|
))`.as("takeable")
|
|
10138
|
-
}).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,
|
|
10139
|
-
${offers.
|
|
10140
|
-
|
|
10141
|
-
|
|
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) => {
|
|
10142
10101
|
return {
|
|
10143
10102
|
hash: row.hash,
|
|
10144
10103
|
maker: row.maker,
|
|
@@ -10248,35 +10207,6 @@ async function getUserPositions(queryParameters, db) {
|
|
|
10248
10207
|
}
|
|
10249
10208
|
}
|
|
10250
10209
|
|
|
10251
|
-
//#endregion
|
|
10252
|
-
//#region src/api/Controllers/resolveCallbackTypes.ts
|
|
10253
|
-
/**
|
|
10254
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
10255
|
-
* @param body - Request body with callback addresses. {@link CallbackTypesRequest}
|
|
10256
|
-
* @param chains - Chains to resolve callback types against. {@link Chain.Chain}
|
|
10257
|
-
* @returns Callback types grouped by chain. {@link CallbackTypesPayload}
|
|
10258
|
-
*/
|
|
10259
|
-
async function resolveCallbackTypes$1(body, chains) {
|
|
10260
|
-
const result = safeParse("callback_types", body, (issue) => issue.message);
|
|
10261
|
-
if (!result.success) return failure(result.error);
|
|
10262
|
-
const request = result.data;
|
|
10263
|
-
const chainIds = new Set(chains.map((chain) => chain.id));
|
|
10264
|
-
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
10265
|
-
if (unknown) return failure(new BadRequestError(`Unknown chain id ${unknown.chain_id}`));
|
|
10266
|
-
try {
|
|
10267
|
-
const data = resolveCallbackTypes$2({
|
|
10268
|
-
chains,
|
|
10269
|
-
request
|
|
10270
|
-
});
|
|
10271
|
-
return success({
|
|
10272
|
-
data,
|
|
10273
|
-
cursor: null
|
|
10274
|
-
});
|
|
10275
|
-
} catch (err) {
|
|
10276
|
-
return failure(err);
|
|
10277
|
-
}
|
|
10278
|
-
}
|
|
10279
|
-
|
|
10280
10210
|
//#endregion
|
|
10281
10211
|
//#region src/api/Api.ts
|
|
10282
10212
|
function from(config) {
|
|
@@ -10346,24 +10276,9 @@ function serve$1(parameters) {
|
|
|
10346
10276
|
const { statusCode, body } = await gatekeeper.validate(reqBody);
|
|
10347
10277
|
return c.json(body, statusCode);
|
|
10348
10278
|
} catch (err) {
|
|
10349
|
-
const failure$
|
|
10350
|
-
return c.json(failure$1.body, failure$1.statusCode);
|
|
10351
|
-
}
|
|
10352
|
-
});
|
|
10353
|
-
app.post("/v1/callbacks", async (c) => {
|
|
10354
|
-
let body;
|
|
10355
|
-
try {
|
|
10356
|
-
body = await c.req.json();
|
|
10357
|
-
} catch (err) {
|
|
10358
|
-
const failure$3 = failure(err);
|
|
10359
|
-
return c.json(failure$3.body, failure$3.statusCode);
|
|
10360
|
-
}
|
|
10361
|
-
if (body === null || typeof body !== "object") {
|
|
10362
|
-
const failure$2 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
10279
|
+
const failure$2 = failure(err);
|
|
10363
10280
|
return c.json(failure$2.body, failure$2.statusCode);
|
|
10364
10281
|
}
|
|
10365
|
-
const { statusCode, body: responseBody } = await resolveCallbackTypes$1(body, chainRegistry.list());
|
|
10366
|
-
return c.json(responseBody, statusCode);
|
|
10367
10282
|
});
|
|
10368
10283
|
app.get("/v1/users/:userAddress/positions", async (c) => {
|
|
10369
10284
|
const query = c.req.query();
|
|
@@ -10395,8 +10310,8 @@ function serve$1(parameters) {
|
|
|
10395
10310
|
const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
|
|
10396
10311
|
return c.json(body, statusCode);
|
|
10397
10312
|
} catch (err) {
|
|
10398
|
-
const failure$
|
|
10399
|
-
return c.json(failure$
|
|
10313
|
+
const failure$1 = failure(err);
|
|
10314
|
+
return c.json(failure$1.body, failure$1.statusCode);
|
|
10400
10315
|
}
|
|
10401
10316
|
});
|
|
10402
10317
|
app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
|
|
@@ -10487,23 +10402,11 @@ function createHttpClient(config) {
|
|
|
10487
10402
|
issues: []
|
|
10488
10403
|
};
|
|
10489
10404
|
};
|
|
10490
|
-
const getCallbackTypes = async (requestPayload) => {
|
|
10491
|
-
const response = await request("/v1/callbacks", {
|
|
10492
|
-
method: "POST",
|
|
10493
|
-
headers: { "content-type": "application/json" },
|
|
10494
|
-
body: JSON.stringify(requestPayload)
|
|
10495
|
-
});
|
|
10496
|
-
const json = await response.json();
|
|
10497
|
-
if (!response.ok) throw new Error(`Gatekeeper callbacks request failed: ${extractErrorMessage(json) ?? response.statusText}`);
|
|
10498
|
-
if (!("data" in json) || !Array.isArray(json.data)) throw new Error("Gatekeeper callbacks response is invalid.");
|
|
10499
|
-
return json.data;
|
|
10500
|
-
};
|
|
10501
10405
|
return {
|
|
10502
10406
|
baseUrl,
|
|
10503
10407
|
validate,
|
|
10504
10408
|
getConfigRules,
|
|
10505
|
-
isAllowed
|
|
10506
|
-
getCallbackTypes
|
|
10409
|
+
isAllowed
|
|
10507
10410
|
};
|
|
10508
10411
|
}
|
|
10509
10412
|
function mergeHeaders(base, extra) {
|
|
@@ -10637,15 +10540,33 @@ async function seedMockOffers(parameters) {
|
|
|
10637
10540
|
const { db, gatekeeper, chainRegistry, count } = parameters;
|
|
10638
10541
|
if (count <= 0) return [];
|
|
10639
10542
|
const configuredChains = chainRegistry.list();
|
|
10640
|
-
const
|
|
10641
|
-
"
|
|
10642
|
-
"
|
|
10643
|
-
"
|
|
10644
|
-
"
|
|
10543
|
+
const assetsDecimals = {
|
|
10544
|
+
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": 6,
|
|
10545
|
+
"0x6b175474e89094c44da98b954eedeac495271d0f": 18,
|
|
10546
|
+
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": 18,
|
|
10547
|
+
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 8,
|
|
10548
|
+
"0x1abaea1f7c830bd89acc67ec4af516284b1bc33c": 6,
|
|
10549
|
+
"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0": 18,
|
|
10550
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 6,
|
|
10551
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": 18,
|
|
10645
10552
|
"0x4200000000000000000000000000000000000006": 18,
|
|
10646
|
-
"
|
|
10647
|
-
"
|
|
10553
|
+
"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": 8,
|
|
10554
|
+
"0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": 18,
|
|
10555
|
+
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": 6,
|
|
10556
|
+
"0xce79ddb3152d52ff8fe65a4c7e058b035fcb560a": 18
|
|
10648
10557
|
};
|
|
10558
|
+
const assetsByChainId = {};
|
|
10559
|
+
const oraclesByChainId = {};
|
|
10560
|
+
for (const chain of configuredChains) {
|
|
10561
|
+
const assets$1 = assets[chain.id.toString()]?.map((asset) => asset.toLowerCase()) ?? [];
|
|
10562
|
+
if (assets$1.length === 0) throw new Error(`No gatekeeper assets configured for chain ${chain.id}`);
|
|
10563
|
+
const missingDecimals = assets$1.filter((asset) => assetsDecimals[asset] === void 0);
|
|
10564
|
+
if (missingDecimals.length > 0) throw new Error(`Missing decimals for assets on chain ${chain.id}: ${missingDecimals.join(", ")}`);
|
|
10565
|
+
assetsByChainId[chain.id] = assets$1;
|
|
10566
|
+
const oracles = oracles$1[chain.id.toString()]?.map((oracle) => oracle.toLowerCase()) ?? [];
|
|
10567
|
+
if (oracles.length === 0) throw new Error(`No gatekeeper oracles configured for chain ${chain.id}`);
|
|
10568
|
+
oraclesByChainId[chain.id] = oracles;
|
|
10569
|
+
}
|
|
10649
10570
|
const now = Math.floor(Date.now() / 1e3);
|
|
10650
10571
|
const offerBlockNumber = 1;
|
|
10651
10572
|
const positionBlockNumber = offerBlockNumber + 1;
|
|
@@ -10656,8 +10577,9 @@ async function seedMockOffers(parameters) {
|
|
|
10656
10577
|
const offers = createMockOffers({
|
|
10657
10578
|
count,
|
|
10658
10579
|
chains: configuredChains,
|
|
10659
|
-
|
|
10660
|
-
|
|
10580
|
+
assetsByChainId,
|
|
10581
|
+
oraclesByChainId,
|
|
10582
|
+
assetsDecimals,
|
|
10661
10583
|
start,
|
|
10662
10584
|
expiry,
|
|
10663
10585
|
maturity,
|
|
@@ -10683,43 +10605,54 @@ async function getOffersFromFile(filePath) {
|
|
|
10683
10605
|
return Array.isArray(data) ? data.map(from$12) : [from$12(data)];
|
|
10684
10606
|
}
|
|
10685
10607
|
function createMockOffers(parameters) {
|
|
10686
|
-
const { count, chains,
|
|
10608
|
+
const { count, chains, assetsByChainId, oraclesByChainId, assetsDecimals, start, expiry, maturity, chainRegistry } = parameters;
|
|
10609
|
+
if (chains.length === 0) throw new Error("No chains configured for mock offers");
|
|
10687
10610
|
return Array.from({ length: count }, () => {
|
|
10688
10611
|
const buy = Math.random() < .5;
|
|
10612
|
+
const chain = chains[Math.floor(Math.random() * chains.length)];
|
|
10613
|
+
const allowedAssets = assetsByChainId[chain.id] ?? [];
|
|
10614
|
+
if (allowedAssets.length === 0) throw new Error(`No allowed assets configured for chain ${chain.id}`);
|
|
10615
|
+
const loanToken = allowedAssets[Math.floor(Math.random() * allowedAssets.length)];
|
|
10689
10616
|
const offer = random({
|
|
10690
|
-
chains,
|
|
10691
|
-
loanTokens,
|
|
10617
|
+
chains: [chain],
|
|
10618
|
+
loanTokens: [loanToken],
|
|
10692
10619
|
assetsDecimals,
|
|
10693
10620
|
start,
|
|
10694
10621
|
expiry,
|
|
10695
10622
|
maturity,
|
|
10696
10623
|
buy
|
|
10697
10624
|
});
|
|
10625
|
+
const allowedOracles = oraclesByChainId[chain.id] ?? [];
|
|
10626
|
+
if (allowedOracles.length === 0) throw new Error(`No allowed oracles configured for chain ${chain.id}`);
|
|
10627
|
+
const collateralAssets = allowedAssets.filter((asset) => asset !== loanToken);
|
|
10628
|
+
if (collateralAssets.length === 0) throw new Error(`No collateral assets available for chain ${chain.id}`);
|
|
10629
|
+
const collateralCount = Math.min(collateralAssets.length, 1 + Math.floor(Math.random() * 3));
|
|
10630
|
+
const collateralPool = [...collateralAssets];
|
|
10631
|
+
const collateralSelections = [];
|
|
10632
|
+
while (collateralSelections.length < collateralCount && collateralPool.length > 0) {
|
|
10633
|
+
const index = Math.floor(Math.random() * collateralPool.length);
|
|
10634
|
+
const [value] = collateralPool.splice(index, 1);
|
|
10635
|
+
if (value !== void 0) collateralSelections.push(value);
|
|
10636
|
+
}
|
|
10637
|
+
collateralSelections.sort();
|
|
10638
|
+
const lltv = offer.collaterals[0]?.lltv ?? .86;
|
|
10639
|
+
const collaterals = collateralSelections.map((asset) => from$14({
|
|
10640
|
+
asset,
|
|
10641
|
+
oracle: allowedOracles[Math.floor(Math.random() * allowedOracles.length)],
|
|
10642
|
+
lltv
|
|
10643
|
+
}));
|
|
10698
10644
|
if (!chainRegistry.getById(offer.chainId)) throw new Error(`Missing chain config for id ${offer.chainId}`);
|
|
10699
|
-
const callbackType = buy ? Type$1.BuyVaultV1Callback : Type$1.SellERC20Callback;
|
|
10700
|
-
const callbackAddress = buy ? BUY_CALLBACK_ADDRESS : SELL_CALLBACK_ADDRESS;
|
|
10701
|
-
const callbackData = buildMockCallbackData(callbackType, offer);
|
|
10702
10645
|
return from$12({
|
|
10703
10646
|
...offer,
|
|
10647
|
+
loanToken,
|
|
10648
|
+
collaterals,
|
|
10704
10649
|
callback: {
|
|
10705
|
-
address:
|
|
10706
|
-
data:
|
|
10650
|
+
address: zeroAddress,
|
|
10651
|
+
data: "0x"
|
|
10707
10652
|
}
|
|
10708
10653
|
});
|
|
10709
10654
|
});
|
|
10710
10655
|
}
|
|
10711
|
-
function buildMockCallbackData(callbackType, offer) {
|
|
10712
|
-
const assets = offer.collaterals.map((collateral) => collateral.asset);
|
|
10713
|
-
const amounts = offer.collaterals.map(() => offer.assets);
|
|
10714
|
-
if (callbackType === Type$1.BuyVaultV1Callback) return encodeBuyVaultV1Callback({
|
|
10715
|
-
vaults: assets,
|
|
10716
|
-
amounts
|
|
10717
|
-
});
|
|
10718
|
-
return encodeSellERC20Callback({
|
|
10719
|
-
collaterals: assets,
|
|
10720
|
-
amounts
|
|
10721
|
-
});
|
|
10722
|
-
}
|
|
10723
10656
|
function seedMockOracles(offers, price, blockNumber) {
|
|
10724
10657
|
const oracleMap = /* @__PURE__ */ new Map();
|
|
10725
10658
|
for (const offer of offers) for (const collateral of offer.collaterals) {
|
|
@@ -10734,8 +10667,6 @@ function seedMockOracles(offers, price, blockNumber) {
|
|
|
10734
10667
|
}
|
|
10735
10668
|
return Array.from(oracleMap.values());
|
|
10736
10669
|
}
|
|
10737
|
-
const BUY_CALLBACK_ADDRESS = "0x3333333333333333333333333333333333333333";
|
|
10738
|
-
const SELL_CALLBACK_ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
10739
10670
|
async function seedOffers(parameters) {
|
|
10740
10671
|
const { db, offers, blockNumber } = parameters;
|
|
10741
10672
|
if (offers.length === 0) return;
|
|
@@ -10752,14 +10683,10 @@ async function seedOffers(parameters) {
|
|
|
10752
10683
|
}]);
|
|
10753
10684
|
}
|
|
10754
10685
|
async function seedOfferAssociations(parameters) {
|
|
10755
|
-
const { db,
|
|
10686
|
+
const { db, offers, blockNumber } = parameters;
|
|
10756
10687
|
if (offers.length === 0) return;
|
|
10757
10688
|
const { callbacks, positions, lots } = buildOfferAssociationsFromOffers({
|
|
10758
10689
|
offers,
|
|
10759
|
-
callbackTypes: await resolveCallbackTypes({
|
|
10760
|
-
gatekeeper,
|
|
10761
|
-
offers
|
|
10762
|
-
}),
|
|
10763
10690
|
blockNumber
|
|
10764
10691
|
});
|
|
10765
10692
|
if (positions.length > 0) await db.positions.upsert(positions);
|
|
@@ -10802,83 +10729,40 @@ function buildOfferDependencies(parameters) {
|
|
|
10802
10729
|
groups: Array.from(groupsByKey.values())
|
|
10803
10730
|
};
|
|
10804
10731
|
}
|
|
10805
|
-
async function resolveCallbackTypes(parameters) {
|
|
10806
|
-
const { gatekeeper, offers } = parameters;
|
|
10807
|
-
const addressesByChain = /* @__PURE__ */ new Map();
|
|
10808
|
-
for (const offer of offers) {
|
|
10809
|
-
if (offer.callback.data === "0x") continue;
|
|
10810
|
-
const set = addressesByChain.get(offer.chainId) ?? /* @__PURE__ */ new Set();
|
|
10811
|
-
set.add(offer.callback.address.toLowerCase());
|
|
10812
|
-
addressesByChain.set(offer.chainId, set);
|
|
10813
|
-
}
|
|
10814
|
-
if (addressesByChain.size === 0) return /* @__PURE__ */ new Map();
|
|
10815
|
-
const request = { callbacks: Array.from(addressesByChain.entries()).map(([chainId, addresses]) => ({
|
|
10816
|
-
chain_id: chainId,
|
|
10817
|
-
addresses: Array.from(addresses)
|
|
10818
|
-
})) };
|
|
10819
|
-
const response = await gatekeeper.getCallbackTypes(request);
|
|
10820
|
-
const typeByAddress = /* @__PURE__ */ new Map();
|
|
10821
|
-
for (const entry of response) {
|
|
10822
|
-
const chainId = entry.chain_id;
|
|
10823
|
-
for (const [key, list] of Object.entries(entry)) {
|
|
10824
|
-
if (key === "chain_id" || key === "not_supported") continue;
|
|
10825
|
-
if (!Array.isArray(list)) continue;
|
|
10826
|
-
for (const address of list) {
|
|
10827
|
-
const mapKey = `${chainId}-${address.toLowerCase()}`;
|
|
10828
|
-
typeByAddress.set(mapKey, key);
|
|
10829
|
-
}
|
|
10830
|
-
}
|
|
10831
|
-
}
|
|
10832
|
-
return typeByAddress;
|
|
10833
|
-
}
|
|
10834
10732
|
function buildOfferAssociationsFromOffers(parameters) {
|
|
10835
|
-
const { offers,
|
|
10733
|
+
const { offers, blockNumber } = parameters;
|
|
10836
10734
|
const callbacks = [];
|
|
10837
10735
|
const positions = [];
|
|
10838
10736
|
const lots = [];
|
|
10839
10737
|
for (const offer of offers) {
|
|
10840
|
-
if (offer.
|
|
10841
|
-
const
|
|
10842
|
-
const
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
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
|
+
}));
|
|
10852
10750
|
callbacks.push({
|
|
10853
|
-
offerHash
|
|
10854
|
-
callbacks:
|
|
10751
|
+
offerHash,
|
|
10752
|
+
callbacks: [{
|
|
10855
10753
|
chainId: offer.chainId,
|
|
10856
|
-
contract:
|
|
10754
|
+
contract: loanToken,
|
|
10857
10755
|
user: offer.maker,
|
|
10858
|
-
amount:
|
|
10859
|
-
}
|
|
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
|
|
10860
10765
|
});
|
|
10861
|
-
for (const callback of decoded) {
|
|
10862
|
-
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
10863
|
-
const positionAsset = callbackType === Type$1.BuyVaultV1Callback ? offer.loanToken : callback.contract;
|
|
10864
|
-
positions.push(from$10({
|
|
10865
|
-
chainId: offer.chainId,
|
|
10866
|
-
contract: callback.contract,
|
|
10867
|
-
user: offer.maker,
|
|
10868
|
-
type: positionType,
|
|
10869
|
-
balance: callback.amount * 2n,
|
|
10870
|
-
asset: positionAsset,
|
|
10871
|
-
blockNumber
|
|
10872
|
-
}));
|
|
10873
|
-
const isLoanPosition = positionAsset.toLowerCase() === offer.loanToken.toLowerCase();
|
|
10874
|
-
lots.push({
|
|
10875
|
-
positionChainId: offer.chainId,
|
|
10876
|
-
positionContract: callback.contract,
|
|
10877
|
-
positionUser: offer.maker,
|
|
10878
|
-
group: offer.group,
|
|
10879
|
-
size: isLoanPosition ? offer.assets : callback.amount
|
|
10880
|
-
});
|
|
10881
|
-
}
|
|
10882
10766
|
}
|
|
10883
10767
|
return {
|
|
10884
10768
|
callbacks,
|