@morpho-dev/router 0.7.0 → 0.7.2
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 +1999 -2137
- package/dist/evm/bytecode/erc20.txt +1 -1
- package/dist/evm/bytecode/morpho.txt +1 -1
- package/dist/evm/bytecode/multicall3.txt +1 -1
- package/dist/evm/bytecode/oracle.txt +1 -1
- package/dist/evm/bytecode/vault.txt +1 -1
- package/dist/index.browser.d.mts +1116 -282
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.d.ts +1116 -282
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +193 -456
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +193 -456
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +1154 -329
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.d.ts +1130 -305
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +4342 -4540
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +4345 -4537
- package/dist/index.node.mjs.map +1 -1
- package/docs/integrator.md +5 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -152,7 +152,7 @@ function startActiveSpan(tracer, name, fn) {
|
|
|
152
152
|
//#endregion
|
|
153
153
|
//#region package.json
|
|
154
154
|
var name = "@morpho-dev/router";
|
|
155
|
-
var version = "0.7.
|
|
155
|
+
var version = "0.7.2";
|
|
156
156
|
var description = "Router package for Morpho protocol";
|
|
157
157
|
|
|
158
158
|
//#endregion
|
|
@@ -331,8 +331,8 @@ const chains$2 = {
|
|
|
331
331
|
name: "ethereum-virtual-testnet",
|
|
332
332
|
custom: {
|
|
333
333
|
morpho: {
|
|
334
|
-
address: "
|
|
335
|
-
blockCreated:
|
|
334
|
+
address: "0x634b095371e4e45feed94c1a45c37798e173ea50",
|
|
335
|
+
blockCreated: 23226700
|
|
336
336
|
},
|
|
337
337
|
morphoBlue: {
|
|
338
338
|
address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
|
|
@@ -561,7 +561,7 @@ async function deployContract(client, account, bytecode, name) {
|
|
|
561
561
|
*/
|
|
562
562
|
async function startAnvil(parameters) {
|
|
563
563
|
const args = buildAnvilArgs(parameters);
|
|
564
|
-
const logFd = fs.openSync(parameters.logPath, "
|
|
564
|
+
const logFd = fs.openSync(parameters.logPath, "w");
|
|
565
565
|
const subprocess = spawn("anvil", args, {
|
|
566
566
|
detached: true,
|
|
567
567
|
stdio: [
|
|
@@ -1483,73 +1483,10 @@ function create$20(parameters) {
|
|
|
1483
1483
|
//#region src/core/Callback.ts
|
|
1484
1484
|
let Type$1 = /* @__PURE__ */ function(Type) {
|
|
1485
1485
|
Type["BuyWithEmptyCallback"] = "buy_with_empty_callback";
|
|
1486
|
-
Type["
|
|
1487
|
-
Type["BuyVaultV1Callback"] = "buy_vault_v1_callback";
|
|
1488
|
-
Type["SellERC20Callback"] = "sell_erc20_callback";
|
|
1486
|
+
Type["SellWithEmptyCallback"] = "sell_with_empty_callback";
|
|
1489
1487
|
return Type;
|
|
1490
1488
|
}({});
|
|
1491
1489
|
const isEmptyCallback = (offer) => offer.callback.data === "0x";
|
|
1492
|
-
function decode$1(type, data) {
|
|
1493
|
-
switch (type) {
|
|
1494
|
-
case Type$1.BuyERC20: return decodeBuyERC20(data);
|
|
1495
|
-
case Type$1.BuyVaultV1Callback: return decodeBuyVaultV1Callback(data);
|
|
1496
|
-
case Type$1.SellERC20Callback: return decodeSellERC20Callback(data);
|
|
1497
|
-
default: throw new Error("Invalid callback type");
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
/**
|
|
1501
|
-
* Decodes BuyERC20 callback data into positions.
|
|
1502
|
-
* @param data - The ABI-encoded callback data containing token addresses and amounts.
|
|
1503
|
-
* @returns Array of positions with contract address and amount.
|
|
1504
|
-
* @throws If data is empty, malformed, or arrays have mismatched lengths.
|
|
1505
|
-
*/
|
|
1506
|
-
function decodeBuyERC20(data) {
|
|
1507
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1508
|
-
let tokens;
|
|
1509
|
-
let amounts;
|
|
1510
|
-
try {
|
|
1511
|
-
[tokens, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1512
|
-
} catch (_) {
|
|
1513
|
-
throw new Error("Invalid BuyERC20 callback data");
|
|
1514
|
-
}
|
|
1515
|
-
if (tokens.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1516
|
-
return tokens.map((token, index) => ({
|
|
1517
|
-
contract: token,
|
|
1518
|
-
amount: amounts[index]
|
|
1519
|
-
}));
|
|
1520
|
-
}
|
|
1521
|
-
function decodeBuyVaultV1Callback(data) {
|
|
1522
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1523
|
-
try {
|
|
1524
|
-
const [vaults, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1525
|
-
if (vaults.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1526
|
-
return vaults.map((v, i) => ({
|
|
1527
|
-
contract: v,
|
|
1528
|
-
amount: amounts[i]
|
|
1529
|
-
}));
|
|
1530
|
-
} catch (_) {
|
|
1531
|
-
throw new Error("Invalid BuyVaultV1Callback callback data");
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
function decodeSellERC20Callback(data) {
|
|
1535
|
-
if (!data || data === "0x") throw new Error("Empty callback data");
|
|
1536
|
-
try {
|
|
1537
|
-
const [collaterals, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
|
|
1538
|
-
if (collaterals.length !== amounts.length) throw new Error("Mismatched array lengths");
|
|
1539
|
-
return collaterals.map((c, i) => ({
|
|
1540
|
-
contract: c,
|
|
1541
|
-
amount: amounts[i]
|
|
1542
|
-
}));
|
|
1543
|
-
} catch (_) {
|
|
1544
|
-
throw new Error("Invalid SellERC20Callback callback data");
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
function encodeBuyVaultV1Callback(parameters) {
|
|
1548
|
-
return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.vaults, parameters.amounts]);
|
|
1549
|
-
}
|
|
1550
|
-
function encodeSellERC20Callback(parameters) {
|
|
1551
|
-
return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.collaterals, parameters.amounts]);
|
|
1552
|
-
}
|
|
1553
1490
|
|
|
1554
1491
|
//#endregion
|
|
1555
1492
|
//#region src/core/Maturity.ts
|
|
@@ -1671,26 +1608,6 @@ var InvalidOptionError$1 = class extends BaseError {
|
|
|
1671
1608
|
|
|
1672
1609
|
//#endregion
|
|
1673
1610
|
//#region src/gatekeeper/GateConfig.ts
|
|
1674
|
-
/**
|
|
1675
|
-
* Attempts to infer the configured callback type from a callback address on a chain.
|
|
1676
|
-
* Skips the empty callback type as it does not carry addresses.
|
|
1677
|
-
*
|
|
1678
|
-
* @param chain - Chain name for which to infer the callback type
|
|
1679
|
-
* @param address - Callback contract address
|
|
1680
|
-
* @returns The callback type when found, otherwise undefined
|
|
1681
|
-
*/
|
|
1682
|
-
function getCallbackType(chain, address) {
|
|
1683
|
-
return configs[chain].callbacks?.find((c) => c.type !== Type$1.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
|
|
1684
|
-
}
|
|
1685
|
-
/**
|
|
1686
|
-
* Returns the list of allowed non-empty callback addresses for a chain.
|
|
1687
|
-
*
|
|
1688
|
-
* @param chain - Chain name
|
|
1689
|
-
* @returns Array of allowed callback addresses (lowercased). Empty when none configured
|
|
1690
|
-
*/
|
|
1691
|
-
const getCallbackAddresses = (chain) => {
|
|
1692
|
-
return configs[chain].callbacks?.filter((c) => c.type !== Type$1.BuyWithEmptyCallback).flatMap((c) => c.addresses) ?? [];
|
|
1693
|
-
};
|
|
1694
1611
|
const assets = {
|
|
1695
1612
|
[ChainId.ETHEREUM.toString()]: [
|
|
1696
1613
|
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
@@ -1762,63 +1679,19 @@ const oracles$1 = {
|
|
|
1762
1679
|
};
|
|
1763
1680
|
const configs = {
|
|
1764
1681
|
ethereum: {
|
|
1765
|
-
callbacks: [
|
|
1766
|
-
{
|
|
1767
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1768
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1769
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1770
|
-
},
|
|
1771
|
-
{
|
|
1772
|
-
type: Type$1.SellERC20Callback,
|
|
1773
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1774
|
-
},
|
|
1775
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1776
|
-
],
|
|
1682
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1777
1683
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1778
1684
|
},
|
|
1779
1685
|
base: {
|
|
1780
|
-
callbacks: [
|
|
1781
|
-
{
|
|
1782
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1783
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1784
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0xFf62A7c278C62eD665133147129245053Bbf5918"]
|
|
1785
|
-
},
|
|
1786
|
-
{
|
|
1787
|
-
type: Type$1.SellERC20Callback,
|
|
1788
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1789
|
-
},
|
|
1790
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1791
|
-
],
|
|
1686
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1792
1687
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1793
1688
|
},
|
|
1794
1689
|
"ethereum-virtual-testnet": {
|
|
1795
|
-
callbacks: [
|
|
1796
|
-
{
|
|
1797
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1798
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1799
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1800
|
-
},
|
|
1801
|
-
{
|
|
1802
|
-
type: Type$1.SellERC20Callback,
|
|
1803
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1804
|
-
},
|
|
1805
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1806
|
-
],
|
|
1690
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1807
1691
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1808
1692
|
},
|
|
1809
1693
|
anvil: {
|
|
1810
|
-
callbacks: [
|
|
1811
|
-
{
|
|
1812
|
-
type: Type$1.BuyVaultV1Callback,
|
|
1813
|
-
addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
|
|
1814
|
-
vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
|
|
1815
|
-
},
|
|
1816
|
-
{
|
|
1817
|
-
type: Type$1.SellERC20Callback,
|
|
1818
|
-
addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
|
|
1819
|
-
},
|
|
1820
|
-
{ type: Type$1.BuyWithEmptyCallback }
|
|
1821
|
-
],
|
|
1694
|
+
callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
|
|
1822
1695
|
maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
|
|
1823
1696
|
}
|
|
1824
1697
|
};
|
|
@@ -1841,6 +1714,61 @@ const MetaMorpho = parseAbi([
|
|
|
1841
1714
|
//#region src/core/Abi/MetaMorphoFactory.ts
|
|
1842
1715
|
const MetaMorphoFactory = parseAbi(["event CreateMetaMorpho(address indexed metaMorpho,address indexed caller,address initialOwner,uint256 initialTimelock,address indexed asset,string name,string symbol,bytes32 salt)", "function isMetaMorpho(address) view returns (bool)"]);
|
|
1843
1716
|
|
|
1717
|
+
//#endregion
|
|
1718
|
+
//#region src/core/Abi/MorphoV2.ts
|
|
1719
|
+
const MorphoV2 = parseAbi([
|
|
1720
|
+
"constructor()",
|
|
1721
|
+
"function collateralOf(bytes32 id, address user, address collateralToken) view returns (uint256)",
|
|
1722
|
+
"function consume(bytes32 group, uint256 amount)",
|
|
1723
|
+
"function consumed(address user, bytes32 group) view returns (uint256)",
|
|
1724
|
+
"function debtOf(bytes32 id, address user) view returns (uint256)",
|
|
1725
|
+
"function defaultFees(address loanToken, uint256 index) view returns (uint16)",
|
|
1726
|
+
"function feeSetter() view returns (address)",
|
|
1727
|
+
"function fees(bytes32 id) view returns (uint16[6])",
|
|
1728
|
+
"function flashLoan(address token, uint256 assets, address callback, bytes data)",
|
|
1729
|
+
"function isHealthy((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, bytes32 id, address borrower) view returns (bool)",
|
|
1730
|
+
"function liquidate((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, (uint256 collateralIndex, uint256 repaid, uint256 seized)[] seizures, address borrower, bytes data) returns ((uint256 collateralIndex, uint256 repaid, uint256 seized)[])",
|
|
1731
|
+
"function multicall(bytes[] calls)",
|
|
1732
|
+
"function obligationCreated(bytes32 id) view returns (bool)",
|
|
1733
|
+
"function obligationState(bytes32 id) view returns (uint128 totalUnits, uint128 totalShares, uint256 withdrawable, bool created)",
|
|
1734
|
+
"function owner() view returns (address)",
|
|
1735
|
+
"function repay((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, address onBehalf)",
|
|
1736
|
+
"function session(address user) view returns (bytes32)",
|
|
1737
|
+
"function setDefaultTradingFee(address loanToken, uint256 index, uint256 newTradingFee)",
|
|
1738
|
+
"function setFeeSetter(address newFeeSetter)",
|
|
1739
|
+
"function setObligationTradingFee(bytes32 id, uint256 index, uint256 newTradingFee)",
|
|
1740
|
+
"function setOwner(address newOwner)",
|
|
1741
|
+
"function setTradingFeeRecipient(address recipient)",
|
|
1742
|
+
"function sharesOf(bytes32 id, address user) view returns (uint256)",
|
|
1743
|
+
"function shuffleSession()",
|
|
1744
|
+
"function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
|
|
1745
|
+
"function take(uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, address taker, ((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, bool buy, address maker, uint256 assets, uint256 obligationUnits, uint256 obligationShares, uint256 start, uint256 expiry, uint256 tick, bytes32 group, bytes32 session, address callback, bytes callbackData) offer, (uint8 v, bytes32 r, bytes32 s) sig, bytes32 root, bytes32[] proof, address takerCallback, bytes takerCallbackData) returns (uint256, uint256, uint256, uint256)",
|
|
1746
|
+
"function totalShares(bytes32 id) view returns (uint256)",
|
|
1747
|
+
"function totalUnits(bytes32 id) view returns (uint256)",
|
|
1748
|
+
"function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation) returns (bytes32)",
|
|
1749
|
+
"function tradingFee(bytes32 id, uint256 timeToMaturity) view returns (uint256)",
|
|
1750
|
+
"function tradingFeeRecipient() view returns (address)",
|
|
1751
|
+
"function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf) returns (uint256, uint256)",
|
|
1752
|
+
"function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
|
|
1753
|
+
"function withdrawable(bytes32 id) view returns (uint256)",
|
|
1754
|
+
"event Constructor(address indexed owner)",
|
|
1755
|
+
"event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
|
|
1756
|
+
"event FlashLoan(address indexed caller, address indexed token, uint256 assets)",
|
|
1757
|
+
"event Liquidate(address indexed caller, bytes32 indexed id, (uint256 collateralIndex, uint256 repaid, uint256 seized)[] seizures, address indexed borrower, uint256 totalRepaid, uint256 badDebt)",
|
|
1758
|
+
"event ObligationCreated(bytes32 indexed id, (address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation)",
|
|
1759
|
+
"event Repay(address indexed caller, bytes32 indexed id, uint256 obligationUnits, address indexed onBehalf)",
|
|
1760
|
+
"event SetDefaultTradingFee(address indexed loanToken, uint256 indexed index, uint256 newTradingFee)",
|
|
1761
|
+
"event SetFeeSetter(address indexed feeSetter)",
|
|
1762
|
+
"event SetObligationTradingFee(bytes32 indexed id, uint256 indexed index, uint256 newTradingFee)",
|
|
1763
|
+
"event SetOwner(address indexed owner)",
|
|
1764
|
+
"event SetTradingFeeRecipient(address indexed recipient)",
|
|
1765
|
+
"event ShuffleSession(address indexed user, bytes32 session)",
|
|
1766
|
+
"event SupplyCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)",
|
|
1767
|
+
"event Take(address caller, bytes32 indexed id, address indexed maker, address indexed taker, bool offerIsBuy, uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, bool buyerIsLender, bool sellerIsBorrower, bytes32 group, uint256 consumed)",
|
|
1768
|
+
"event Withdraw(address indexed caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf)",
|
|
1769
|
+
"event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)"
|
|
1770
|
+
]);
|
|
1771
|
+
|
|
1844
1772
|
//#endregion
|
|
1845
1773
|
//#region src/core/Abi/index.ts
|
|
1846
1774
|
const Oracle = [{
|
|
@@ -2300,7 +2228,7 @@ function random(config) {
|
|
|
2300
2228
|
const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
|
|
2301
2229
|
const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
|
|
2302
2230
|
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
|
|
2303
|
-
|
|
2231
|
+
collateralCandidates[int(collateralCandidates.length)];
|
|
2304
2232
|
const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
|
|
2305
2233
|
const maturity = config?.maturity ?? from$16(maturityOption);
|
|
2306
2234
|
const lltv = from$15(weightedChoice([
|
|
@@ -2327,21 +2255,10 @@ function random(config) {
|
|
|
2327
2255
|
const unit = BigInt(10) ** BigInt(loanTokenDecimals);
|
|
2328
2256
|
const amountBase = BigInt(100 + int(999901));
|
|
2329
2257
|
const assetsScaled = config?.assets ?? amountBase * unit;
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
};
|
|
2335
|
-
const sellCallbackAddress = "0x3333333333333333333333333333333333333333";
|
|
2336
|
-
const amount = assetsScaled * 1000000000000000000000n;
|
|
2337
|
-
return {
|
|
2338
|
-
address: sellCallbackAddress,
|
|
2339
|
-
data: encodeSellERC20Callback({
|
|
2340
|
-
collaterals: [collateralAsset],
|
|
2341
|
-
amounts: [amount]
|
|
2342
|
-
})
|
|
2343
|
-
};
|
|
2344
|
-
})();
|
|
2258
|
+
const emptyCallback = {
|
|
2259
|
+
address: zeroAddress,
|
|
2260
|
+
data: "0x"
|
|
2261
|
+
};
|
|
2345
2262
|
return from$12({
|
|
2346
2263
|
maker: config?.maker ?? address(),
|
|
2347
2264
|
assets: assetsScaled,
|
|
@@ -2360,7 +2277,7 @@ function random(config) {
|
|
|
2360
2277
|
...random$1(),
|
|
2361
2278
|
lltv
|
|
2362
2279
|
})).sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
2363
|
-
callback: config?.callback ??
|
|
2280
|
+
callback: config?.callback ?? emptyCallback
|
|
2364
2281
|
});
|
|
2365
2282
|
}
|
|
2366
2283
|
const weightedChoice = (pairs) => {
|
|
@@ -2514,6 +2431,94 @@ function obligationId(offer) {
|
|
|
2514
2431
|
}));
|
|
2515
2432
|
}
|
|
2516
2433
|
/**
|
|
2434
|
+
* ABI for the Take event emitted by the Morpho V2 contract.
|
|
2435
|
+
*/
|
|
2436
|
+
const takeEvent = {
|
|
2437
|
+
type: "event",
|
|
2438
|
+
name: "Take",
|
|
2439
|
+
inputs: [
|
|
2440
|
+
{
|
|
2441
|
+
name: "caller",
|
|
2442
|
+
type: "address",
|
|
2443
|
+
indexed: false,
|
|
2444
|
+
internalType: "address"
|
|
2445
|
+
},
|
|
2446
|
+
{
|
|
2447
|
+
name: "id",
|
|
2448
|
+
type: "bytes32",
|
|
2449
|
+
indexed: true,
|
|
2450
|
+
internalType: "bytes32"
|
|
2451
|
+
},
|
|
2452
|
+
{
|
|
2453
|
+
name: "maker",
|
|
2454
|
+
type: "address",
|
|
2455
|
+
indexed: true,
|
|
2456
|
+
internalType: "address"
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
name: "taker",
|
|
2460
|
+
type: "address",
|
|
2461
|
+
indexed: true,
|
|
2462
|
+
internalType: "address"
|
|
2463
|
+
},
|
|
2464
|
+
{
|
|
2465
|
+
name: "offerIsBuy",
|
|
2466
|
+
type: "bool",
|
|
2467
|
+
indexed: false,
|
|
2468
|
+
internalType: "bool"
|
|
2469
|
+
},
|
|
2470
|
+
{
|
|
2471
|
+
name: "buyerAssets",
|
|
2472
|
+
type: "uint256",
|
|
2473
|
+
indexed: false,
|
|
2474
|
+
internalType: "uint256"
|
|
2475
|
+
},
|
|
2476
|
+
{
|
|
2477
|
+
name: "sellerAssets",
|
|
2478
|
+
type: "uint256",
|
|
2479
|
+
indexed: false,
|
|
2480
|
+
internalType: "uint256"
|
|
2481
|
+
},
|
|
2482
|
+
{
|
|
2483
|
+
name: "obligationUnits",
|
|
2484
|
+
type: "uint256",
|
|
2485
|
+
indexed: false,
|
|
2486
|
+
internalType: "uint256"
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
name: "obligationShares",
|
|
2490
|
+
type: "uint256",
|
|
2491
|
+
indexed: false,
|
|
2492
|
+
internalType: "uint256"
|
|
2493
|
+
},
|
|
2494
|
+
{
|
|
2495
|
+
name: "buyerIsLender",
|
|
2496
|
+
type: "bool",
|
|
2497
|
+
indexed: false,
|
|
2498
|
+
internalType: "bool"
|
|
2499
|
+
},
|
|
2500
|
+
{
|
|
2501
|
+
name: "sellerIsBorrower",
|
|
2502
|
+
type: "bool",
|
|
2503
|
+
indexed: false,
|
|
2504
|
+
internalType: "bool"
|
|
2505
|
+
},
|
|
2506
|
+
{
|
|
2507
|
+
name: "group",
|
|
2508
|
+
type: "bytes32",
|
|
2509
|
+
indexed: false,
|
|
2510
|
+
internalType: "bytes32"
|
|
2511
|
+
},
|
|
2512
|
+
{
|
|
2513
|
+
name: "consumed",
|
|
2514
|
+
type: "uint256",
|
|
2515
|
+
indexed: false,
|
|
2516
|
+
internalType: "uint256"
|
|
2517
|
+
}
|
|
2518
|
+
],
|
|
2519
|
+
anonymous: false
|
|
2520
|
+
};
|
|
2521
|
+
/**
|
|
2517
2522
|
* ABI for the Consume event emitted by the Obligation contract.
|
|
2518
2523
|
*/
|
|
2519
2524
|
const consumedEvent = {
|
|
@@ -2939,12 +2944,10 @@ const maturity = ({ maturities }) => single("maturity", `Validates that offer ma
|
|
|
2939
2944
|
const allowedMaturities = maturities.map((m) => from$16(m));
|
|
2940
2945
|
if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}` };
|
|
2941
2946
|
});
|
|
2942
|
-
const callback = ({ callbacks
|
|
2943
|
-
if (isEmptyCallback(offer)
|
|
2944
|
-
if (isEmptyCallback(offer) &&
|
|
2945
|
-
if (
|
|
2946
|
-
if (!allowedAddresses.includes(offer.callback.address?.toLowerCase())) return { message: `Callback address ${offer.callback.address} is not allowed.` };
|
|
2947
|
-
}
|
|
2947
|
+
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) => {
|
|
2948
|
+
if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
|
|
2949
|
+
if (isEmptyCallback(offer) && offer.buy && !callbacks.includes(Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
|
|
2950
|
+
if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
|
|
2948
2951
|
});
|
|
2949
2952
|
/**
|
|
2950
2953
|
* A validation rule that checks if the offer's tokens are allowed for its chain.
|
|
@@ -3009,12 +3012,8 @@ const morphoRules = (chains) => {
|
|
|
3009
3012
|
chains$1({ chains }),
|
|
3010
3013
|
maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
|
|
3011
3014
|
callback({
|
|
3012
|
-
callbacks: [
|
|
3013
|
-
|
|
3014
|
-
Type$1.BuyVaultV1Callback,
|
|
3015
|
-
Type$1.SellERC20Callback
|
|
3016
|
-
],
|
|
3017
|
-
allowedAddresses: chains.flatMap((c) => getCallbackAddresses(c.name))
|
|
3015
|
+
callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
|
|
3016
|
+
allowedAddresses: []
|
|
3018
3017
|
}),
|
|
3019
3018
|
token({ assetsByChainId }),
|
|
3020
3019
|
oracle({ oraclesByChainId })
|
|
@@ -3031,25 +3030,13 @@ const morphoRules = (chains) => {
|
|
|
3031
3030
|
function buildConfigRules(chains) {
|
|
3032
3031
|
const rules = [];
|
|
3033
3032
|
for (const chain of chains) {
|
|
3034
|
-
const
|
|
3035
|
-
const maturities = config.maturities ?? [];
|
|
3033
|
+
const maturities = configs[chain.name].maturities ?? [];
|
|
3036
3034
|
for (const maturityName of maturities) rules.push({
|
|
3037
3035
|
type: "maturity",
|
|
3038
3036
|
chain_id: chain.id,
|
|
3039
3037
|
name: maturityName,
|
|
3040
3038
|
timestamp: from$16(maturityName)
|
|
3041
3039
|
});
|
|
3042
|
-
const callbacks = config.callbacks ?? [];
|
|
3043
|
-
for (const callback of callbacks) {
|
|
3044
|
-
if (callback.type === Type$1.BuyWithEmptyCallback) continue;
|
|
3045
|
-
if (!("addresses" in callback)) continue;
|
|
3046
|
-
for (const address of callback.addresses) rules.push({
|
|
3047
|
-
type: "callback",
|
|
3048
|
-
chain_id: chain.id,
|
|
3049
|
-
address: normalizeAddress(address),
|
|
3050
|
-
callback_type: callback.type
|
|
3051
|
-
});
|
|
3052
|
-
}
|
|
3053
3040
|
const loanTokens = assets[chain.id.toString()] ?? [];
|
|
3054
3041
|
for (const address of loanTokens) rules.push({
|
|
3055
3042
|
type: "loan_token",
|
|
@@ -3177,6 +3164,16 @@ function from$5(obligation, quote) {
|
|
|
3177
3164
|
|
|
3178
3165
|
//#endregion
|
|
3179
3166
|
//#region src/api/Schema/OfferResponse.ts
|
|
3167
|
+
function normalizeChainId(chainId) {
|
|
3168
|
+
const parsedChainId = Number(chainId);
|
|
3169
|
+
if (!Number.isInteger(parsedChainId) || parsedChainId <= 0) throw new Error(`Invalid chain id: ${String(chainId)}`);
|
|
3170
|
+
return parsedChainId;
|
|
3171
|
+
}
|
|
3172
|
+
function normalizeBlockNumber(blockNumber) {
|
|
3173
|
+
const parsedBlockNumber = Number(blockNumber);
|
|
3174
|
+
if (!Number.isInteger(parsedBlockNumber) || parsedBlockNumber < 0) throw new Error(`Invalid block number: ${String(blockNumber)}`);
|
|
3175
|
+
return parsedBlockNumber;
|
|
3176
|
+
}
|
|
3180
3177
|
/**
|
|
3181
3178
|
* Creates an `OfferResponse` matching the Solidity Offer struct layout.
|
|
3182
3179
|
* @constructor
|
|
@@ -3184,6 +3181,8 @@ function from$5(obligation, quote) {
|
|
|
3184
3181
|
* @returns The created `OfferResponse`. {@link OfferResponse}
|
|
3185
3182
|
*/
|
|
3186
3183
|
function from$4(input) {
|
|
3184
|
+
const chainId = normalizeChainId(input.chainId);
|
|
3185
|
+
const blockNumber = normalizeBlockNumber(input.blockNumber);
|
|
3187
3186
|
const base = {
|
|
3188
3187
|
offer: {
|
|
3189
3188
|
obligation: {
|
|
@@ -3210,15 +3209,15 @@ function from$4(input) {
|
|
|
3210
3209
|
},
|
|
3211
3210
|
offer_hash: input.hash,
|
|
3212
3211
|
obligation_id: id({
|
|
3213
|
-
chainId
|
|
3212
|
+
chainId,
|
|
3214
3213
|
loanToken: input.loanToken,
|
|
3215
3214
|
collaterals: [...input.collaterals],
|
|
3216
3215
|
maturity: input.maturity
|
|
3217
3216
|
}),
|
|
3218
|
-
chain_id:
|
|
3217
|
+
chain_id: chainId,
|
|
3219
3218
|
consumed: input.consumed.toString(),
|
|
3220
3219
|
takeable: input.takeable.toString(),
|
|
3221
|
-
block_number:
|
|
3220
|
+
block_number: blockNumber
|
|
3222
3221
|
};
|
|
3223
3222
|
if (!input.proof || !input.root || !input.signature) return {
|
|
3224
3223
|
...base,
|
|
@@ -3367,8 +3366,8 @@ const offerExample = {
|
|
|
3367
3366
|
price: "2750000000000000000",
|
|
3368
3367
|
group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
|
|
3369
3368
|
session: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
3370
|
-
callback: "
|
|
3371
|
-
callback_data: "
|
|
3369
|
+
callback: "0x0000000000000000000000000000000000000000",
|
|
3370
|
+
callback_data: "0x"
|
|
3372
3371
|
},
|
|
3373
3372
|
offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
|
|
3374
3373
|
obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
|
|
@@ -3420,25 +3419,10 @@ const validateOfferExample = {
|
|
|
3420
3419
|
lltv: "860000000000000000"
|
|
3421
3420
|
}],
|
|
3422
3421
|
callback: {
|
|
3423
|
-
address: "
|
|
3424
|
-
data: "
|
|
3422
|
+
address: "0x0000000000000000000000000000000000000000",
|
|
3423
|
+
data: "0x"
|
|
3425
3424
|
}
|
|
3426
3425
|
};
|
|
3427
|
-
const callbackTypesRequestExample = { callbacks: [{
|
|
3428
|
-
chain_id: 1,
|
|
3429
|
-
addresses: [
|
|
3430
|
-
"0x1111111111111111111111111111111111111111",
|
|
3431
|
-
"0x3333333333333333333333333333333333333333",
|
|
3432
|
-
"0x9999999999999999999999999999999999999999"
|
|
3433
|
-
]
|
|
3434
|
-
}] };
|
|
3435
|
-
const callbackTypesResponseExample = [{
|
|
3436
|
-
chain_id: 1,
|
|
3437
|
-
sell_erc20_callback: ["0x1111111111111111111111111111111111111111"],
|
|
3438
|
-
buy_erc20: ["0x5555555555555555555555555555555555555555"],
|
|
3439
|
-
buy_vault_v1_callback: ["0x3333333333333333333333333333333333333333"],
|
|
3440
|
-
not_supported: ["0x9999999999999999999999999999999999999999"]
|
|
3441
|
-
}];
|
|
3442
3426
|
const routerStatusExample = {
|
|
3443
3427
|
status: "live",
|
|
3444
3428
|
initialized: true,
|
|
@@ -3507,55 +3491,6 @@ __decorate([ApiProperty({
|
|
|
3507
3491
|
type: "string",
|
|
3508
3492
|
example: validateOfferExample.callback.data
|
|
3509
3493
|
})], ValidateCallbackRequest.prototype, "data", void 0);
|
|
3510
|
-
var CallbackTypesChainRequest = class {};
|
|
3511
|
-
__decorate([ApiProperty({
|
|
3512
|
-
type: "number",
|
|
3513
|
-
example: callbackTypesRequestExample.callbacks[0].chain_id
|
|
3514
|
-
})], CallbackTypesChainRequest.prototype, "chain_id", void 0);
|
|
3515
|
-
__decorate([ApiProperty({
|
|
3516
|
-
type: () => [String],
|
|
3517
|
-
example: callbackTypesRequestExample.callbacks[0].addresses
|
|
3518
|
-
})], CallbackTypesChainRequest.prototype, "addresses", void 0);
|
|
3519
|
-
var CallbackTypesRequest = class {};
|
|
3520
|
-
__decorate([ApiProperty({
|
|
3521
|
-
type: () => [CallbackTypesChainRequest],
|
|
3522
|
-
example: callbackTypesRequestExample.callbacks
|
|
3523
|
-
})], CallbackTypesRequest.prototype, "callbacks", void 0);
|
|
3524
|
-
var CallbackTypesChainResponse = class {};
|
|
3525
|
-
__decorate([ApiProperty({
|
|
3526
|
-
type: "number",
|
|
3527
|
-
example: callbackTypesResponseExample[0].chain_id
|
|
3528
|
-
})], CallbackTypesChainResponse.prototype, "chain_id", void 0);
|
|
3529
|
-
__decorate([ApiProperty({
|
|
3530
|
-
type: () => [String],
|
|
3531
|
-
required: false,
|
|
3532
|
-
example: callbackTypesResponseExample[0].buy_vault_v1_callback
|
|
3533
|
-
})], CallbackTypesChainResponse.prototype, "buy_vault_v1_callback", void 0);
|
|
3534
|
-
__decorate([ApiProperty({
|
|
3535
|
-
type: () => [String],
|
|
3536
|
-
required: false,
|
|
3537
|
-
example: callbackTypesResponseExample[0].sell_erc20_callback
|
|
3538
|
-
})], CallbackTypesChainResponse.prototype, "sell_erc20_callback", void 0);
|
|
3539
|
-
__decorate([ApiProperty({
|
|
3540
|
-
type: () => [String],
|
|
3541
|
-
required: false,
|
|
3542
|
-
example: callbackTypesResponseExample[0].buy_erc20
|
|
3543
|
-
})], CallbackTypesChainResponse.prototype, "buy_erc20", void 0);
|
|
3544
|
-
__decorate([ApiProperty({
|
|
3545
|
-
type: () => [String],
|
|
3546
|
-
example: callbackTypesResponseExample[0].not_supported
|
|
3547
|
-
})], CallbackTypesChainResponse.prototype, "not_supported", void 0);
|
|
3548
|
-
var CallbackTypesSuccessResponse = class extends SuccessResponse {};
|
|
3549
|
-
__decorate([ApiProperty({
|
|
3550
|
-
type: "string",
|
|
3551
|
-
nullable: true,
|
|
3552
|
-
example: "maturity:1:1730415600:end_of_next_month"
|
|
3553
|
-
})], CallbackTypesSuccessResponse.prototype, "cursor", void 0);
|
|
3554
|
-
__decorate([ApiProperty({
|
|
3555
|
-
type: () => [CallbackTypesChainResponse],
|
|
3556
|
-
description: "Callback types grouped by chain.",
|
|
3557
|
-
example: callbackTypesResponseExample
|
|
3558
|
-
})], CallbackTypesSuccessResponse.prototype, "data", void 0);
|
|
3559
3494
|
var AskResponse = class {};
|
|
3560
3495
|
__decorate([ApiProperty({
|
|
3561
3496
|
type: "string",
|
|
@@ -4073,7 +4008,7 @@ __decorate([
|
|
|
4073
4008
|
methods: ["post"],
|
|
4074
4009
|
path: "/v1/validate",
|
|
4075
4010
|
summary: "Validate offers",
|
|
4076
|
-
description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
4011
|
+
description: "Validates offers against router validation rules. Only empty callbacks (zero address, 0x data) are accepted. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
4077
4012
|
}),
|
|
4078
4013
|
ApiBody({ type: ValidateOffersRequest }),
|
|
4079
4014
|
ApiResponse({
|
|
@@ -4092,28 +4027,6 @@ ValidateController = __decorate([ApiTags("Make"), ApiResponse({
|
|
|
4092
4027
|
description: "Bad Request",
|
|
4093
4028
|
type: BadRequestResponse
|
|
4094
4029
|
})], ValidateController);
|
|
4095
|
-
let CallbacksController = class CallbacksController {
|
|
4096
|
-
async resolveCallbackTypes() {}
|
|
4097
|
-
};
|
|
4098
|
-
__decorate([
|
|
4099
|
-
ApiOperation({
|
|
4100
|
-
methods: ["post"],
|
|
4101
|
-
path: "/v1/callbacks",
|
|
4102
|
-
summary: "Resolve callback types",
|
|
4103
|
-
description: "Returns callback types for callback addresses grouped by chain."
|
|
4104
|
-
}),
|
|
4105
|
-
ApiBody({ type: CallbackTypesRequest }),
|
|
4106
|
-
ApiResponse({
|
|
4107
|
-
status: 200,
|
|
4108
|
-
description: "Success",
|
|
4109
|
-
type: CallbackTypesSuccessResponse
|
|
4110
|
-
})
|
|
4111
|
-
], CallbacksController.prototype, "resolveCallbackTypes", null);
|
|
4112
|
-
CallbacksController = __decorate([ApiTags("Make"), ApiResponse({
|
|
4113
|
-
status: 400,
|
|
4114
|
-
description: "Bad Request",
|
|
4115
|
-
type: BadRequestResponse
|
|
4116
|
-
})], CallbacksController);
|
|
4117
4030
|
let OffersController = class OffersController {
|
|
4118
4031
|
async getOffers() {}
|
|
4119
4032
|
};
|
|
@@ -4263,12 +4176,6 @@ const configRulesMaturityExample = {
|
|
|
4263
4176
|
name: "end_of_next_month",
|
|
4264
4177
|
timestamp: 1730415600
|
|
4265
4178
|
};
|
|
4266
|
-
const configRulesCallbackExample = {
|
|
4267
|
-
type: "callback",
|
|
4268
|
-
chain_id: 1,
|
|
4269
|
-
address: "0x1111111111111111111111111111111111111111",
|
|
4270
|
-
callback_type: "sell_erc20_callback"
|
|
4271
|
-
};
|
|
4272
4179
|
const configRulesLoanTokenExample = {
|
|
4273
4180
|
type: "loan_token",
|
|
4274
4181
|
chain_id: 1,
|
|
@@ -4282,7 +4189,6 @@ const configRulesOracleExample = {
|
|
|
4282
4189
|
const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
|
|
4283
4190
|
const configRulesPayloadExample = [
|
|
4284
4191
|
configRulesMaturityExample,
|
|
4285
|
-
configRulesCallbackExample,
|
|
4286
4192
|
configRulesLoanTokenExample,
|
|
4287
4193
|
configRulesOracleExample
|
|
4288
4194
|
];
|
|
@@ -4347,14 +4253,9 @@ __decorate([ApiProperty({
|
|
|
4347
4253
|
})], ConfigRulesRuleResponse.prototype, "timestamp", void 0);
|
|
4348
4254
|
__decorate([ApiProperty({
|
|
4349
4255
|
type: "string",
|
|
4350
|
-
example:
|
|
4256
|
+
example: configRulesLoanTokenExample.address,
|
|
4351
4257
|
required: false
|
|
4352
4258
|
})], ConfigRulesRuleResponse.prototype, "address", void 0);
|
|
4353
|
-
__decorate([ApiProperty({
|
|
4354
|
-
type: "string",
|
|
4355
|
-
example: configRulesCallbackExample.callback_type,
|
|
4356
|
-
required: false
|
|
4357
|
-
})], ConfigRulesRuleResponse.prototype, "callback_type", void 0);
|
|
4358
4259
|
var ConfigRulesSuccessResponse = class {};
|
|
4359
4260
|
__decorate([ApiProperty({ type: () => ConfigRulesMeta })], ConfigRulesSuccessResponse.prototype, "meta", void 0);
|
|
4360
4261
|
__decorate([ApiProperty({
|
|
@@ -4415,7 +4316,7 @@ __decorate([
|
|
|
4415
4316
|
methods: ["get"],
|
|
4416
4317
|
path: "/v1/config/rules",
|
|
4417
4318
|
summary: "Get config rules",
|
|
4418
|
-
description: "Returns configured rules for supported chains."
|
|
4319
|
+
description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
|
|
4419
4320
|
}),
|
|
4420
4321
|
ApiQuery({
|
|
4421
4322
|
name: "cursor",
|
|
@@ -4594,8 +4495,7 @@ const OpenApi = async () => {
|
|
|
4594
4495
|
ObligationsController,
|
|
4595
4496
|
HealthController,
|
|
4596
4497
|
UsersController,
|
|
4597
|
-
ValidateController
|
|
4598
|
-
CallbacksController
|
|
4498
|
+
ValidateController
|
|
4599
4499
|
],
|
|
4600
4500
|
document: {
|
|
4601
4501
|
openapi: "3.1.0",
|
|
@@ -4851,16 +4751,6 @@ const GetBookParams = z$2.object({
|
|
|
4851
4751
|
})
|
|
4852
4752
|
});
|
|
4853
4753
|
const ValidateOffersBody = z$2.object({ offers: z$2.array(z$2.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
|
|
4854
|
-
const CallbackTypesBody = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
4855
|
-
chain_id: z$2.number().int().positive().meta({
|
|
4856
|
-
description: "Chain id.",
|
|
4857
|
-
example: 1
|
|
4858
|
-
}),
|
|
4859
|
-
addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Callback address must be a valid 20-byte address" }).transform((val) => val.toLowerCase())).meta({
|
|
4860
|
-
description: "Callback contract addresses.",
|
|
4861
|
-
example: ["0x1111111111111111111111111111111111111111", "0x3333333333333333333333333333333333333333"]
|
|
4862
|
-
})
|
|
4863
|
-
}).strict()) }).strict();
|
|
4864
4754
|
const GetUserPositionsParams = z$2.object({
|
|
4865
4755
|
...PaginationQueryParams.shape,
|
|
4866
4756
|
user_address: z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
|
|
@@ -4879,7 +4769,6 @@ const schemas = {
|
|
|
4879
4769
|
get_obligation: GetObligationParams,
|
|
4880
4770
|
get_book: GetBookParams,
|
|
4881
4771
|
validate_offers: ValidateOffersBody,
|
|
4882
|
-
callback_types: CallbackTypesBody,
|
|
4883
4772
|
get_user_positions: GetUserPositionsParams
|
|
4884
4773
|
};
|
|
4885
4774
|
function safeParse(action, query, error) {
|
|
@@ -5135,35 +5024,6 @@ async function validateOffers(body, gatekeeper) {
|
|
|
5135
5024
|
}
|
|
5136
5025
|
}
|
|
5137
5026
|
|
|
5138
|
-
//#endregion
|
|
5139
|
-
//#region src/gatekeeper/CallbackTypes.ts
|
|
5140
|
-
/**
|
|
5141
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
5142
|
-
* @param parameters - Resolve parameters. {@link resolveCallbackTypes.Parameters}
|
|
5143
|
-
* @returns Callback types grouped by chain. {@link resolveCallbackTypes.ReturnType}
|
|
5144
|
-
* @throws If a chain id is unknown.
|
|
5145
|
-
*/
|
|
5146
|
-
function resolveCallbackTypes$2(parameters) {
|
|
5147
|
-
const { chains, request } = parameters;
|
|
5148
|
-
const chainsById = new Map(chains.map((chain) => [chain.id, chain]));
|
|
5149
|
-
return request.callbacks.map(({ chain_id, addresses }) => {
|
|
5150
|
-
const chain = chainsById.get(chain_id);
|
|
5151
|
-
if (!chain) throw new Error(`Unknown chain id ${chain_id}`);
|
|
5152
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
5153
|
-
const uniqueAddresses = new Set(addresses.map((address) => address.toLowerCase()));
|
|
5154
|
-
for (const address of uniqueAddresses) {
|
|
5155
|
-
const bucketKey = getCallbackType(chain.name, address) ?? "not_supported";
|
|
5156
|
-
const list = buckets.get(bucketKey) ?? [];
|
|
5157
|
-
list.push(address);
|
|
5158
|
-
buckets.set(bucketKey, list);
|
|
5159
|
-
}
|
|
5160
|
-
const response = { chain_id };
|
|
5161
|
-
for (const [type, list] of buckets.entries()) response[type] = list;
|
|
5162
|
-
if (!response.not_supported) response.not_supported = [];
|
|
5163
|
-
return response;
|
|
5164
|
-
});
|
|
5165
|
-
}
|
|
5166
|
-
|
|
5167
5027
|
//#endregion
|
|
5168
5028
|
//#region src/gatekeeper/Service.ts
|
|
5169
5029
|
/**
|
|
@@ -5171,10 +5031,6 @@ function resolveCallbackTypes$2(parameters) {
|
|
|
5171
5031
|
* @param parameters - App parameters including the {@link Gatekeeper} instance.
|
|
5172
5032
|
* @returns Hono app exposing gatekeeper endpoints.
|
|
5173
5033
|
*/
|
|
5174
|
-
const CallbackTypesRequestSchema = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
5175
|
-
chain_id: z$2.number(),
|
|
5176
|
-
addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/))
|
|
5177
|
-
})) });
|
|
5178
5034
|
function createApp(parameters) {
|
|
5179
5035
|
const { gatekeeper, chainRegistry } = parameters;
|
|
5180
5036
|
const app = new Hono();
|
|
@@ -5183,12 +5039,12 @@ function createApp(parameters) {
|
|
|
5183
5039
|
try {
|
|
5184
5040
|
body = await c.req.json();
|
|
5185
5041
|
} catch (err) {
|
|
5186
|
-
const failure$
|
|
5187
|
-
return c.json(failure$
|
|
5042
|
+
const failure$4 = failure(err);
|
|
5043
|
+
return c.json(failure$4.body, failure$4.statusCode);
|
|
5188
5044
|
}
|
|
5189
5045
|
if (body === null || typeof body !== "object") {
|
|
5190
|
-
const failure$
|
|
5191
|
-
return c.json(failure$
|
|
5046
|
+
const failure$3 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
5047
|
+
return c.json(failure$3.body, failure$3.statusCode);
|
|
5192
5048
|
}
|
|
5193
5049
|
const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
|
|
5194
5050
|
return c.json(payload, statusCode);
|
|
@@ -5197,34 +5053,6 @@ function createApp(parameters) {
|
|
|
5197
5053
|
const { statusCode, body } = await getConfigRules(c.req.query(), chainRegistry.list());
|
|
5198
5054
|
return c.json(body, statusCode);
|
|
5199
5055
|
});
|
|
5200
|
-
app.post("/v1/callbacks", async (c) => {
|
|
5201
|
-
let body;
|
|
5202
|
-
try {
|
|
5203
|
-
body = await c.req.json();
|
|
5204
|
-
} catch (err) {
|
|
5205
|
-
const failure$8 = failure(err);
|
|
5206
|
-
return c.json(failure$8.body, failure$8.statusCode);
|
|
5207
|
-
}
|
|
5208
|
-
if (body === null || typeof body !== "object") {
|
|
5209
|
-
const failure$6 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
5210
|
-
return c.json(failure$6.body, failure$6.statusCode);
|
|
5211
|
-
}
|
|
5212
|
-
try {
|
|
5213
|
-
const request = CallbackTypesRequestSchema.parse(body);
|
|
5214
|
-
const chainIds = new Set(chainRegistry.list().map((chain) => chain.id));
|
|
5215
|
-
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
5216
|
-
if (unknown) throw new BadRequestError(`Unknown chain id ${unknown.chain_id}`);
|
|
5217
|
-
const data = resolveCallbackTypes$2({
|
|
5218
|
-
chains: chainRegistry.list(),
|
|
5219
|
-
request
|
|
5220
|
-
});
|
|
5221
|
-
const response = success({ data });
|
|
5222
|
-
return c.json(response.body, response.statusCode);
|
|
5223
|
-
} catch (err) {
|
|
5224
|
-
const failure$7 = failure(err);
|
|
5225
|
-
return c.json(failure$7.body, failure$7.statusCode);
|
|
5226
|
-
}
|
|
5227
|
-
});
|
|
5228
5056
|
return app;
|
|
5229
5057
|
}
|
|
5230
5058
|
/**
|
|
@@ -5320,1559 +5148,8 @@ function now() {
|
|
|
5320
5148
|
}
|
|
5321
5149
|
|
|
5322
5150
|
//#endregion
|
|
5323
|
-
//#region src/
|
|
5324
|
-
|
|
5325
|
-
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5326
|
-
const logger = getLogger();
|
|
5327
|
-
let startBlock = blockNumber;
|
|
5328
|
-
let reorgDetected = false;
|
|
5329
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5330
|
-
const stream = streamLogs({
|
|
5331
|
-
client,
|
|
5332
|
-
contractAddress: client.chain.custom.morpho.address,
|
|
5333
|
-
event: consumedEvent,
|
|
5334
|
-
blockNumberGte: blockNumber,
|
|
5335
|
-
blockNumberLte: latestBlockNumberChain,
|
|
5336
|
-
order: "asc",
|
|
5337
|
-
options: {
|
|
5338
|
-
maxBatchSize,
|
|
5339
|
-
blockWindow
|
|
5340
|
-
}
|
|
5341
|
-
});
|
|
5342
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5343
|
-
const parsedLogs = parseEventLogs({
|
|
5344
|
-
abi: [consumedEvent],
|
|
5345
|
-
logs
|
|
5346
|
-
});
|
|
5347
|
-
const events = [];
|
|
5348
|
-
for (const log of parsedLogs) {
|
|
5349
|
-
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
5350
|
-
logger.debug({
|
|
5351
|
-
collector,
|
|
5352
|
-
chainId: client.chain.id,
|
|
5353
|
-
msg: "Skipping log because it is missing required fields"
|
|
5354
|
-
});
|
|
5355
|
-
continue;
|
|
5356
|
-
}
|
|
5357
|
-
events.push({
|
|
5358
|
-
id: `${log.blockNumber.toString()}-${log.logIndex.toString()}-${client.chain.id}-${log.transactionHash}`,
|
|
5359
|
-
chainId: client.chain.id,
|
|
5360
|
-
maker: log.args.user,
|
|
5361
|
-
group: log.args.group,
|
|
5362
|
-
amount: log.args.amount,
|
|
5363
|
-
blockNumber: Number(log.blockNumber)
|
|
5364
|
-
});
|
|
5365
|
-
}
|
|
5366
|
-
await db.transaction(async (dbTx) => {
|
|
5367
|
-
try {
|
|
5368
|
-
await dbTx.consumed.create(events);
|
|
5369
|
-
if (events.length > 0) logger.info({
|
|
5370
|
-
msg: `Events indexed`,
|
|
5371
|
-
collector,
|
|
5372
|
-
count: events.length,
|
|
5373
|
-
chain_id: client.chain.id,
|
|
5374
|
-
block_range: [startBlock, lastStreamBlockNumber]
|
|
5375
|
-
});
|
|
5376
|
-
} catch (err) {
|
|
5377
|
-
logger.error({
|
|
5378
|
-
err,
|
|
5379
|
-
msg: "Failed to process offer_consumed events"
|
|
5380
|
-
});
|
|
5381
|
-
}
|
|
5382
|
-
blockNumber = lastStreamBlockNumber;
|
|
5383
|
-
try {
|
|
5384
|
-
await dbTx.blocks.advanceCollector({
|
|
5385
|
-
collectorName: collector,
|
|
5386
|
-
chainId: client.chain.id,
|
|
5387
|
-
blockNumber,
|
|
5388
|
-
epoch
|
|
5389
|
-
});
|
|
5390
|
-
} catch (_) {
|
|
5391
|
-
try {
|
|
5392
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
5393
|
-
collectorName: collector,
|
|
5394
|
-
chainId: client.chain.id
|
|
5395
|
-
});
|
|
5396
|
-
blockNumber = ancestor.blockNumber;
|
|
5397
|
-
const deleted = await dbTx.consumed.delete({
|
|
5398
|
-
chainId: client.chain.id,
|
|
5399
|
-
blockNumberGte: blockNumber + 1
|
|
5400
|
-
});
|
|
5401
|
-
logger.info({
|
|
5402
|
-
collector,
|
|
5403
|
-
chain_id: client.chain.id,
|
|
5404
|
-
msg: `Reorg detected, events deleted`,
|
|
5405
|
-
count: deleted,
|
|
5406
|
-
block_number: blockNumber
|
|
5407
|
-
});
|
|
5408
|
-
await dbTx.blocks.advanceCollector({
|
|
5409
|
-
collectorName: collector,
|
|
5410
|
-
chainId: client.chain.id,
|
|
5411
|
-
blockNumber,
|
|
5412
|
-
epoch: ancestor.epoch
|
|
5413
|
-
});
|
|
5414
|
-
reorgDetected = true;
|
|
5415
|
-
} catch (err) {
|
|
5416
|
-
const msg = "Failed to delete consumed events when handling reorg.";
|
|
5417
|
-
logger.error({
|
|
5418
|
-
collector,
|
|
5419
|
-
chainId: client.chain.id,
|
|
5420
|
-
msg,
|
|
5421
|
-
err
|
|
5422
|
-
});
|
|
5423
|
-
throw new Error(msg);
|
|
5424
|
-
}
|
|
5425
|
-
}
|
|
5426
|
-
});
|
|
5427
|
-
if (reorgDetected) return;
|
|
5428
|
-
yield blockNumber;
|
|
5429
|
-
startBlock = blockNumber;
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
|
|
5433
|
-
//#endregion
|
|
5434
|
-
//#region src/indexer/collectors/CollectFunctions/collectOffers.ts
|
|
5435
|
-
async function* collectOffersV2(parameters) {
|
|
5436
|
-
let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5437
|
-
const logger = getLogger();
|
|
5438
|
-
let startBlock = blockNumber;
|
|
5439
|
-
let reorgDetected = false;
|
|
5440
|
-
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
5441
|
-
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
5442
|
-
logger.error({
|
|
5443
|
-
msg,
|
|
5444
|
-
chain_id: client.chain.id
|
|
5445
|
-
});
|
|
5446
|
-
throw new Error(msg);
|
|
5447
|
-
}
|
|
5448
|
-
const signatureDomain = {
|
|
5449
|
-
chainId: client.chain.id,
|
|
5450
|
-
verifyingContract: client.chain.custom.morpho.address
|
|
5451
|
-
};
|
|
5452
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5453
|
-
const stream = streamLogs({
|
|
5454
|
-
client,
|
|
5455
|
-
contractAddress: client.chain.custom.mempool.address,
|
|
5456
|
-
event: {
|
|
5457
|
-
type: "event",
|
|
5458
|
-
name: "Event",
|
|
5459
|
-
inputs: [{
|
|
5460
|
-
name: "data",
|
|
5461
|
-
type: "bytes",
|
|
5462
|
-
indexed: false,
|
|
5463
|
-
internalType: "bytes"
|
|
5464
|
-
}],
|
|
5465
|
-
anonymous: false
|
|
5466
|
-
},
|
|
5467
|
-
blockNumberGte: blockNumber,
|
|
5468
|
-
blockNumberLte: latestBlockNumberChain,
|
|
5469
|
-
order: "asc",
|
|
5470
|
-
options: {
|
|
5471
|
-
maxBatchSize,
|
|
5472
|
-
blockWindow
|
|
5473
|
-
}
|
|
5474
|
-
});
|
|
5475
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5476
|
-
blockNumber = lastStreamBlockNumber;
|
|
5477
|
-
const decodedTrees = [];
|
|
5478
|
-
for (const log of logs) {
|
|
5479
|
-
if (!log) continue;
|
|
5480
|
-
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
5481
|
-
try {
|
|
5482
|
-
const { tree, signature, signer } = await decode(payload, signatureDomain);
|
|
5483
|
-
const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
|
|
5484
|
-
if (signerMismatch) {
|
|
5485
|
-
logger.debug({
|
|
5486
|
-
msg: "Tree rejected: signer mismatch",
|
|
5487
|
-
reason: "signer_mismatch",
|
|
5488
|
-
signer,
|
|
5489
|
-
maker: signerMismatch.maker,
|
|
5490
|
-
chain_id: client.chain.id
|
|
5491
|
-
});
|
|
5492
|
-
continue;
|
|
5493
|
-
}
|
|
5494
|
-
decodedTrees.push({
|
|
5495
|
-
tree,
|
|
5496
|
-
signature,
|
|
5497
|
-
signer,
|
|
5498
|
-
blockNumber: Number(log.blockNumber)
|
|
5499
|
-
});
|
|
5500
|
-
} catch (err) {
|
|
5501
|
-
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
5502
|
-
logger.debug({
|
|
5503
|
-
msg: "Tree decode failed",
|
|
5504
|
-
reason,
|
|
5505
|
-
chain_id: client.chain.id,
|
|
5506
|
-
err: err instanceof Error ? err.message : String(err)
|
|
5507
|
-
});
|
|
5508
|
-
}
|
|
5509
|
-
}
|
|
5510
|
-
await db.transaction(async (dbTx) => {
|
|
5511
|
-
const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
5512
|
-
const treesToInsert = [];
|
|
5513
|
-
let totalValidOffers = 0;
|
|
5514
|
-
const offersWithBlock = [];
|
|
5515
|
-
for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
|
|
5516
|
-
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
5517
|
-
const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
|
|
5518
|
-
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
5519
|
-
if (allowedResults.issues.length > 0) {
|
|
5520
|
-
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
5521
|
-
logger.debug({
|
|
5522
|
-
msg: "Tree offers rejected by gatekeeper",
|
|
5523
|
-
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
5524
|
-
chain_id: client.chain.id,
|
|
5525
|
-
issues_count: allowedResults.issues.length
|
|
5526
|
-
});
|
|
5527
|
-
} else if (hasBlockWindowViolation) logger.debug({
|
|
5528
|
-
msg: "Tree rejected: offers outside block window",
|
|
5529
|
-
reason: "block_window",
|
|
5530
|
-
chain_id: client.chain.id
|
|
5531
|
-
});
|
|
5532
|
-
continue;
|
|
5533
|
-
}
|
|
5534
|
-
treesToInsert.push({
|
|
5535
|
-
tree,
|
|
5536
|
-
signature
|
|
5537
|
-
});
|
|
5538
|
-
totalValidOffers += tree.offers.length;
|
|
5539
|
-
offersWithBlock.push(...tree.offers.map((offer) => ({
|
|
5540
|
-
offer,
|
|
5541
|
-
blockNumber: treeBlockNumber
|
|
5542
|
-
})));
|
|
5543
|
-
} catch (err) {
|
|
5544
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5545
|
-
logger.error({
|
|
5546
|
-
err: error,
|
|
5547
|
-
msg: "Gatekeeper validation failed",
|
|
5548
|
-
chain_id: client.chain.id
|
|
5549
|
-
});
|
|
5550
|
-
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
5551
|
-
}
|
|
5552
|
-
const dependencies = buildOfferDependencies$1(offersWithBlock);
|
|
5553
|
-
await dbTx.oracles.upsert(dependencies.oracles);
|
|
5554
|
-
await dbTx.obligations.create(dependencies.obligations);
|
|
5555
|
-
await dbTx.groups.create(dependencies.groups);
|
|
5556
|
-
const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
|
|
5557
|
-
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
5558
|
-
const insertedOffers = filterInsertedOffers({
|
|
5559
|
-
offers: offersWithBlock,
|
|
5560
|
-
hashes: insertedHashes
|
|
5561
|
-
});
|
|
5562
|
-
const { callbacks, positions, lots } = await decodeCallbacks({
|
|
5563
|
-
chainId: client.chain.id,
|
|
5564
|
-
gatekeeper,
|
|
5565
|
-
offers: insertedOffers
|
|
5566
|
-
});
|
|
5567
|
-
if (positions.length > 0) await dbTx.positions.upsert(positions);
|
|
5568
|
-
if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
|
|
5569
|
-
if (lots.length > 0) await dbTx.lots.create(lots);
|
|
5570
|
-
try {
|
|
5571
|
-
await dbTx.blocks.advanceCollector({
|
|
5572
|
-
collectorName: collector,
|
|
5573
|
-
chainId: client.chain.id,
|
|
5574
|
-
blockNumber,
|
|
5575
|
-
epoch
|
|
5576
|
-
});
|
|
5577
|
-
if (totalValidOffers > 0) logger.info({
|
|
5578
|
-
msg: `New offers`,
|
|
5579
|
-
collector,
|
|
5580
|
-
count: totalValidOffers,
|
|
5581
|
-
trees_count: treesToInsert.length,
|
|
5582
|
-
chain_id: client.chain.id,
|
|
5583
|
-
block_range: [startBlock, lastStreamBlockNumber]
|
|
5584
|
-
});
|
|
5585
|
-
} catch (_) {
|
|
5586
|
-
try {
|
|
5587
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
5588
|
-
collectorName: collector,
|
|
5589
|
-
chainId: client.chain.id
|
|
5590
|
-
});
|
|
5591
|
-
blockNumber = ancestor.blockNumber;
|
|
5592
|
-
const deleted = await dbTx.offers.delete({
|
|
5593
|
-
blockNumberGte: blockNumber + 1,
|
|
5594
|
-
chainId: client.chain.id
|
|
5595
|
-
});
|
|
5596
|
-
logger.info({
|
|
5597
|
-
collector,
|
|
5598
|
-
chain_id: client.chain.id,
|
|
5599
|
-
msg: `Reorg detected, offers deleted`,
|
|
5600
|
-
count: deleted,
|
|
5601
|
-
block_number: blockNumber
|
|
5602
|
-
});
|
|
5603
|
-
await dbTx.blocks.advanceCollector({
|
|
5604
|
-
collectorName: collector,
|
|
5605
|
-
chainId: client.chain.id,
|
|
5606
|
-
blockNumber,
|
|
5607
|
-
epoch: ancestor.epoch
|
|
5608
|
-
});
|
|
5609
|
-
reorgDetected = true;
|
|
5610
|
-
} catch (err) {
|
|
5611
|
-
const msg = "Failed to delete offers when handling reorg.";
|
|
5612
|
-
logger.error({
|
|
5613
|
-
collector,
|
|
5614
|
-
chainId: client.chain.id,
|
|
5615
|
-
msg,
|
|
5616
|
-
err
|
|
5617
|
-
});
|
|
5618
|
-
throw new Error(msg);
|
|
5619
|
-
}
|
|
5620
|
-
}
|
|
5621
|
-
});
|
|
5622
|
-
if (reorgDetected) return;
|
|
5623
|
-
yield blockNumber;
|
|
5624
|
-
startBlock = blockNumber;
|
|
5625
|
-
}
|
|
5626
|
-
}
|
|
5627
|
-
async function decodeCallbacks(parameters) {
|
|
5628
|
-
const { chainId, gatekeeper, offers } = parameters;
|
|
5629
|
-
if (offers.length === 0) return {
|
|
5630
|
-
callbacks: [],
|
|
5631
|
-
positions: [],
|
|
5632
|
-
lots: []
|
|
5633
|
-
};
|
|
5634
|
-
const addresses = offers.filter((entry) => entry.offer.callback.data !== "0x").map((entry) => entry.offer.callback.address);
|
|
5635
|
-
if (addresses.length === 0) return {
|
|
5636
|
-
callbacks: [],
|
|
5637
|
-
positions: [],
|
|
5638
|
-
lots: []
|
|
5639
|
-
};
|
|
5640
|
-
let response;
|
|
5641
|
-
try {
|
|
5642
|
-
response = await gatekeeper.getCallbackTypes({ callbacks: [{
|
|
5643
|
-
chain_id: chainId,
|
|
5644
|
-
addresses
|
|
5645
|
-
}] });
|
|
5646
|
-
} catch (err) {
|
|
5647
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5648
|
-
throw new Error("Failed to resolve callback types", { cause: error });
|
|
5649
|
-
}
|
|
5650
|
-
const entry = response.find((item) => item.chain_id === chainId);
|
|
5651
|
-
const typeByAddress = /* @__PURE__ */ new Map();
|
|
5652
|
-
if (entry) for (const [key, list] of Object.entries(entry)) {
|
|
5653
|
-
if (key === "chain_id" || key === "not_supported") continue;
|
|
5654
|
-
if (!Array.isArray(list)) continue;
|
|
5655
|
-
for (const address of list) typeByAddress.set(address.toLowerCase(), key);
|
|
5656
|
-
}
|
|
5657
|
-
const callbacks = [];
|
|
5658
|
-
const positions = [];
|
|
5659
|
-
const lots = [];
|
|
5660
|
-
for (const { offer, blockNumber: offerBlockNumber } of offers) {
|
|
5661
|
-
if (offer.callback.data === "0x") continue;
|
|
5662
|
-
const callbackType = typeByAddress.get(offer.callback.address.toLowerCase());
|
|
5663
|
-
if (!callbackType) continue;
|
|
5664
|
-
let decoded;
|
|
5665
|
-
try {
|
|
5666
|
-
decoded = decode$1(callbackType, offer.callback.data);
|
|
5667
|
-
} catch (err) {
|
|
5668
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
5669
|
-
throw new Error("Failed to decode callback data", { cause: error });
|
|
5670
|
-
}
|
|
5671
|
-
if (decoded.length === 0) continue;
|
|
5672
|
-
const offerHash = hash(offer);
|
|
5673
|
-
const callbackInputs = decoded.map((callback) => ({
|
|
5674
|
-
chainId: offer.chainId,
|
|
5675
|
-
contract: callback.contract,
|
|
5676
|
-
user: offer.maker,
|
|
5677
|
-
amount: callback.amount
|
|
5678
|
-
}));
|
|
5679
|
-
callbacks.push({
|
|
5680
|
-
offerHash,
|
|
5681
|
-
callbacks: callbackInputs
|
|
5682
|
-
});
|
|
5683
|
-
for (const callback of decoded) {
|
|
5684
|
-
const contract = callback.contract;
|
|
5685
|
-
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
5686
|
-
const asset = callbackType === Type$1.BuyVaultV1Callback ? void 0 : contract;
|
|
5687
|
-
positions.push(from$10({
|
|
5688
|
-
chainId: offer.chainId,
|
|
5689
|
-
contract,
|
|
5690
|
-
user: offer.maker,
|
|
5691
|
-
type: positionType,
|
|
5692
|
-
asset,
|
|
5693
|
-
blockNumber: offerBlockNumber
|
|
5694
|
-
}));
|
|
5695
|
-
const isLoanPosition = offer.loanToken.toLowerCase() === asset?.toLowerCase();
|
|
5696
|
-
lots.push({
|
|
5697
|
-
positionChainId: offer.chainId,
|
|
5698
|
-
positionContract: contract,
|
|
5699
|
-
positionUser: offer.maker,
|
|
5700
|
-
group: offer.group,
|
|
5701
|
-
size: isLoanPosition ? offer.assets : callback.amount
|
|
5702
|
-
});
|
|
5703
|
-
}
|
|
5704
|
-
}
|
|
5705
|
-
return {
|
|
5706
|
-
callbacks,
|
|
5707
|
-
positions,
|
|
5708
|
-
lots
|
|
5709
|
-
};
|
|
5710
|
-
}
|
|
5711
|
-
function buildOfferDependencies$1(offers) {
|
|
5712
|
-
const obligationsById = /* @__PURE__ */ new Map();
|
|
5713
|
-
const oraclesByKey = /* @__PURE__ */ new Map();
|
|
5714
|
-
const groupsByKey = /* @__PURE__ */ new Map();
|
|
5715
|
-
const offersByBlock = /* @__PURE__ */ new Map();
|
|
5716
|
-
for (const { offer, blockNumber } of offers) {
|
|
5717
|
-
const list = offersByBlock.get(blockNumber) ?? [];
|
|
5718
|
-
list.push(offer);
|
|
5719
|
-
offersByBlock.set(blockNumber, list);
|
|
5720
|
-
const obligationId$2 = obligationId(offer);
|
|
5721
|
-
if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
|
|
5722
|
-
chainId: offer.chainId,
|
|
5723
|
-
loanToken: offer.loanToken,
|
|
5724
|
-
maturity: offer.maturity,
|
|
5725
|
-
collaterals: offer.collaterals
|
|
5726
|
-
}));
|
|
5727
|
-
for (const collateral of offer.collaterals) {
|
|
5728
|
-
const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
5729
|
-
if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
|
|
5730
|
-
chainId: offer.chainId,
|
|
5731
|
-
address: collateral.oracle,
|
|
5732
|
-
price: null,
|
|
5733
|
-
blockNumber
|
|
5734
|
-
}));
|
|
5735
|
-
}
|
|
5736
|
-
const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
|
|
5737
|
-
if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
|
|
5738
|
-
chainId: offer.chainId,
|
|
5739
|
-
maker: offer.maker,
|
|
5740
|
-
group: offer.group,
|
|
5741
|
-
blockNumber
|
|
5742
|
-
});
|
|
5743
|
-
}
|
|
5744
|
-
return {
|
|
5745
|
-
obligations: Array.from(obligationsById.values()),
|
|
5746
|
-
oracles: Array.from(oraclesByKey.values()),
|
|
5747
|
-
groups: Array.from(groupsByKey.values()),
|
|
5748
|
-
offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
|
|
5749
|
-
blockNumber,
|
|
5750
|
-
offers: items
|
|
5751
|
-
}))
|
|
5752
|
-
};
|
|
5753
|
-
}
|
|
5754
|
-
function filterInsertedOffers(parameters) {
|
|
5755
|
-
if (parameters.hashes.length === 0) return [];
|
|
5756
|
-
const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
|
|
5757
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5758
|
-
const filtered = [];
|
|
5759
|
-
for (const entry of parameters.offers) {
|
|
5760
|
-
const hash$2 = hash(entry.offer).toLowerCase();
|
|
5761
|
-
if (!inserted.has(hash$2)) continue;
|
|
5762
|
-
if (seen.has(hash$2)) continue;
|
|
5763
|
-
seen.add(hash$2);
|
|
5764
|
-
filtered.push(entry);
|
|
5765
|
-
}
|
|
5766
|
-
return filtered;
|
|
5767
|
-
}
|
|
5768
|
-
|
|
5769
|
-
//#endregion
|
|
5770
|
-
//#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
|
|
5771
|
-
/**
|
|
5772
|
-
* Fetches prices from multiple oracle contracts using multicall.
|
|
5773
|
-
*
|
|
5774
|
-
* - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
|
|
5775
|
-
* - Requires a client supporting {@link PublicActions.multicall | multicall}.
|
|
5776
|
-
*
|
|
5777
|
-
* @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
|
|
5778
|
-
* {@link Address | addresses}, fetch options, and the multicall-enabled client.
|
|
5779
|
-
* @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
|
|
5780
|
-
*/
|
|
5781
|
-
async function fetchOraclePrices(parameters) {
|
|
5782
|
-
const { client, oracles, options } = parameters;
|
|
5783
|
-
if (oracles.length === 0) return /* @__PURE__ */ new Map();
|
|
5784
|
-
const batchSize = Math.max(1, options?.batchSize ?? 5e3);
|
|
5785
|
-
const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
|
|
5786
|
-
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
|
|
5787
|
-
const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
|
|
5788
|
-
const out = /* @__PURE__ */ new Map();
|
|
5789
|
-
for (const oraclesBatch of batch$1(oracles, batchSize)) {
|
|
5790
|
-
const priceCalls = [];
|
|
5791
|
-
for (const oracle of oraclesBatch) priceCalls.push({
|
|
5792
|
-
address: oracle,
|
|
5793
|
-
abi: Oracle,
|
|
5794
|
-
functionName: "price",
|
|
5795
|
-
args: []
|
|
5796
|
-
});
|
|
5797
|
-
const prices = await batchMulticall({
|
|
5798
|
-
client,
|
|
5799
|
-
calls: priceCalls,
|
|
5800
|
-
batchSize,
|
|
5801
|
-
retryAttempts,
|
|
5802
|
-
retryDelayMs,
|
|
5803
|
-
blockNumber
|
|
5804
|
-
});
|
|
5805
|
-
for (let i = 0; i < oraclesBatch.length; i++) {
|
|
5806
|
-
const oracle = oraclesBatch[i];
|
|
5807
|
-
const price = prices[i];
|
|
5808
|
-
out.set(oracle, price);
|
|
5809
|
-
}
|
|
5810
|
-
}
|
|
5811
|
-
return out;
|
|
5812
|
-
}
|
|
5813
|
-
|
|
5814
|
-
//#endregion
|
|
5815
|
-
//#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
|
|
5816
|
-
/**
|
|
5817
|
-
* Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
|
|
5818
|
-
* @notice This method does not mutate positions and returns a new array.
|
|
5819
|
-
*
|
|
5820
|
-
* @param parameters - {@link snapshotERC20Positions.Parameters}
|
|
5821
|
-
* @returns Positions - {@link snapshotERC20Positions.ReturnType}
|
|
5822
|
-
*/
|
|
5823
|
-
async function snapshotERC20Positions(parameters) {
|
|
5824
|
-
const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5825
|
-
if (oldPositions.length === 0) return [];
|
|
5826
|
-
const calls = [];
|
|
5827
|
-
for (const position of oldPositions) calls.push({
|
|
5828
|
-
address: position.contract,
|
|
5829
|
-
abi: erc20Abi,
|
|
5830
|
-
functionName: "balanceOf",
|
|
5831
|
-
args: [position.user]
|
|
5832
|
-
});
|
|
5833
|
-
const balances = await batchMulticall({
|
|
5834
|
-
client: parameters.client,
|
|
5835
|
-
calls,
|
|
5836
|
-
blockNumber: BigInt(blockNumber),
|
|
5837
|
-
batchSize: maxBatchSize,
|
|
5838
|
-
retryAttempts,
|
|
5839
|
-
retryDelayMs
|
|
5840
|
-
});
|
|
5841
|
-
const positions = [];
|
|
5842
|
-
for (let i = 0; i < balances.length; i++) {
|
|
5843
|
-
const oldPosition = oldPositions[i];
|
|
5844
|
-
if (!oldPosition) continue;
|
|
5845
|
-
positions.push({
|
|
5846
|
-
...oldPosition,
|
|
5847
|
-
balance: balances[i],
|
|
5848
|
-
blockNumber
|
|
5849
|
-
});
|
|
5850
|
-
}
|
|
5851
|
-
return positions;
|
|
5852
|
-
}
|
|
5853
|
-
|
|
5854
|
-
//#endregion
|
|
5855
|
-
//#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
|
|
5856
|
-
/**
|
|
5857
|
-
* Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
|
|
5858
|
-
* @notice This method does not mutate positions and returns a new array.
|
|
5859
|
-
*
|
|
5860
|
-
* @param parameters - {@link snapshotVaultPositions.Parameters}
|
|
5861
|
-
* @returns Positions - {@link snapshotVaultPositions.ReturnType}
|
|
5862
|
-
*/
|
|
5863
|
-
async function snapshotVaultPositions(parameters) {
|
|
5864
|
-
const logger = getLogger();
|
|
5865
|
-
const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
5866
|
-
const calls = [];
|
|
5867
|
-
const contracts = /* @__PURE__ */ new Map();
|
|
5868
|
-
const positions = structuredClone(oldPositions);
|
|
5869
|
-
for (const position of positions) {
|
|
5870
|
-
calls.push({
|
|
5871
|
-
address: position.contract,
|
|
5872
|
-
abi: MetaMorpho,
|
|
5873
|
-
functionName: "balanceOf",
|
|
5874
|
-
args: [position.user],
|
|
5875
|
-
convertToAssets: (shares) => {
|
|
5876
|
-
const contract = contracts.get(position.contract.toLowerCase());
|
|
5877
|
-
if (!contract) return;
|
|
5878
|
-
if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
|
|
5879
|
-
try {
|
|
5880
|
-
position.balance = convertToAssets({
|
|
5881
|
-
shares,
|
|
5882
|
-
totalAssets: contract.totalAssets,
|
|
5883
|
-
totalSupply: contract.totalSupply,
|
|
5884
|
-
decimalsOffset: contract.decimalsOffset
|
|
5885
|
-
});
|
|
5886
|
-
position.asset = contract.asset;
|
|
5887
|
-
position.blockNumber = blockNumber;
|
|
5888
|
-
} catch (err) {
|
|
5889
|
-
if (err instanceof DenominatorIsZeroError) {
|
|
5890
|
-
logger.error({
|
|
5891
|
-
msg: "Failed to convert shares to assets",
|
|
5892
|
-
chain_id: client.chain.id,
|
|
5893
|
-
block_number: blockNumber,
|
|
5894
|
-
position_contract: position.contract,
|
|
5895
|
-
position_user: position.user,
|
|
5896
|
-
shares,
|
|
5897
|
-
err
|
|
5898
|
-
});
|
|
5899
|
-
return;
|
|
5900
|
-
}
|
|
5901
|
-
throw err;
|
|
5902
|
-
}
|
|
5903
|
-
}
|
|
5904
|
-
});
|
|
5905
|
-
if (contracts.has(position.contract.toLowerCase())) continue;
|
|
5906
|
-
calls.push({
|
|
5907
|
-
address: position.contract,
|
|
5908
|
-
abi: MetaMorpho,
|
|
5909
|
-
functionName: "DECIMALS_OFFSET",
|
|
5910
|
-
args: []
|
|
5911
|
-
}, {
|
|
5912
|
-
address: position.contract,
|
|
5913
|
-
abi: MetaMorpho,
|
|
5914
|
-
functionName: "totalAssets",
|
|
5915
|
-
args: []
|
|
5916
|
-
}, {
|
|
5917
|
-
address: position.contract,
|
|
5918
|
-
abi: MetaMorpho,
|
|
5919
|
-
functionName: "totalSupply",
|
|
5920
|
-
args: []
|
|
5921
|
-
}, {
|
|
5922
|
-
address: position.contract,
|
|
5923
|
-
abi: MetaMorpho,
|
|
5924
|
-
functionName: "asset",
|
|
5925
|
-
args: []
|
|
5926
|
-
});
|
|
5927
|
-
contracts.set(position.contract.toLowerCase(), {
|
|
5928
|
-
decimalsOffset: void 0,
|
|
5929
|
-
totalAssets: void 0,
|
|
5930
|
-
totalSupply: void 0,
|
|
5931
|
-
asset: void 0
|
|
5932
|
-
});
|
|
5933
|
-
}
|
|
5934
|
-
const results = await batchMulticall({
|
|
5935
|
-
client,
|
|
5936
|
-
calls,
|
|
5937
|
-
blockNumber: BigInt(blockNumber),
|
|
5938
|
-
batchSize: maxBatchSize,
|
|
5939
|
-
retryAttempts,
|
|
5940
|
-
retryDelayMs
|
|
5941
|
-
});
|
|
5942
|
-
const convertToAssetsList = [];
|
|
5943
|
-
for (let i = 0; i < results.length; i++) {
|
|
5944
|
-
const call = calls[i];
|
|
5945
|
-
const value = results[i];
|
|
5946
|
-
const contract = contracts.get(call.address.toLowerCase());
|
|
5947
|
-
if (!contract) continue;
|
|
5948
|
-
switch (call.functionName) {
|
|
5949
|
-
case "balanceOf":
|
|
5950
|
-
convertToAssetsList.push(() => call.convertToAssets(value));
|
|
5951
|
-
break;
|
|
5952
|
-
case "DECIMALS_OFFSET":
|
|
5953
|
-
contract.decimalsOffset = value;
|
|
5954
|
-
break;
|
|
5955
|
-
case "totalAssets":
|
|
5956
|
-
contract.totalAssets = value;
|
|
5957
|
-
break;
|
|
5958
|
-
case "totalSupply":
|
|
5959
|
-
contract.totalSupply = value;
|
|
5960
|
-
break;
|
|
5961
|
-
case "asset":
|
|
5962
|
-
contract.asset = value.toLowerCase();
|
|
5963
|
-
break;
|
|
5964
|
-
}
|
|
5965
|
-
}
|
|
5966
|
-
for (const convertToAssets of convertToAssetsList) convertToAssets();
|
|
5967
|
-
return positions;
|
|
5968
|
-
}
|
|
5969
|
-
|
|
5970
|
-
//#endregion
|
|
5971
|
-
//#region src/indexer/collectors/CollectFunctions/collectPositions.ts
|
|
5972
|
-
async function* collectPositions(parameters) {
|
|
5973
|
-
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
|
|
5974
|
-
const logger = getLogger();
|
|
5975
|
-
let startBlock = blockNumber;
|
|
5976
|
-
let reorgDetected = false;
|
|
5977
|
-
const TransferEvent = {
|
|
5978
|
-
type: "event",
|
|
5979
|
-
name: "Transfer",
|
|
5980
|
-
inputs: [
|
|
5981
|
-
{
|
|
5982
|
-
name: "from",
|
|
5983
|
-
type: "address",
|
|
5984
|
-
indexed: true
|
|
5985
|
-
},
|
|
5986
|
-
{
|
|
5987
|
-
name: "to",
|
|
5988
|
-
type: "address",
|
|
5989
|
-
indexed: true
|
|
5990
|
-
},
|
|
5991
|
-
{
|
|
5992
|
-
name: "value",
|
|
5993
|
-
type: "uint256",
|
|
5994
|
-
indexed: false
|
|
5995
|
-
}
|
|
5996
|
-
]
|
|
5997
|
-
};
|
|
5998
|
-
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5999
|
-
const stream = streamLogs({
|
|
6000
|
-
client,
|
|
6001
|
-
event: TransferEvent,
|
|
6002
|
-
blockNumberGte: blockNumber,
|
|
6003
|
-
blockNumberLte: latestBlockNumberChain,
|
|
6004
|
-
order: "asc",
|
|
6005
|
-
options: {
|
|
6006
|
-
maxBatchSize,
|
|
6007
|
-
blockWindow
|
|
6008
|
-
}
|
|
6009
|
-
});
|
|
6010
|
-
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
6011
|
-
blockNumber = lastStreamBlockNumber;
|
|
6012
|
-
const parsedLogs = parseEventLogs({
|
|
6013
|
-
abi: [TransferEvent],
|
|
6014
|
-
logs
|
|
6015
|
-
});
|
|
6016
|
-
const transfers = [];
|
|
6017
|
-
for (const log of parsedLogs) {
|
|
6018
|
-
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
6019
|
-
logger.debug({
|
|
6020
|
-
collector,
|
|
6021
|
-
chainId: client.chain.id,
|
|
6022
|
-
msg: "Skipping log because it is missing required fields"
|
|
6023
|
-
});
|
|
6024
|
-
continue;
|
|
6025
|
-
}
|
|
6026
|
-
transfers.push(from$8({
|
|
6027
|
-
id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
|
|
6028
|
-
chainId: client.chain.id,
|
|
6029
|
-
contract: log.address,
|
|
6030
|
-
from: log.args.from,
|
|
6031
|
-
to: log.args.to,
|
|
6032
|
-
value: log.args.value,
|
|
6033
|
-
blockNumber: Number(log.blockNumber)
|
|
6034
|
-
}));
|
|
6035
|
-
}
|
|
6036
|
-
const { positions } = await db.positions.get({
|
|
6037
|
-
chainId: client.chain.id,
|
|
6038
|
-
filled: false
|
|
6039
|
-
});
|
|
6040
|
-
const newPositions = [];
|
|
6041
|
-
try {
|
|
6042
|
-
newPositions.push(...(await _snapshot({
|
|
6043
|
-
positions,
|
|
6044
|
-
blockNumber: latestBlockNumberChain,
|
|
6045
|
-
client,
|
|
6046
|
-
maxBatchSize,
|
|
6047
|
-
retryAttempts,
|
|
6048
|
-
retryDelayMs
|
|
6049
|
-
})).map((p) => ({
|
|
6050
|
-
...p,
|
|
6051
|
-
blockNumber: p.blockNumber + 1
|
|
6052
|
-
})));
|
|
6053
|
-
} catch (err) {
|
|
6054
|
-
logger.error({
|
|
6055
|
-
msg: "Failed to snapshot new empty positions",
|
|
6056
|
-
collector,
|
|
6057
|
-
chain_id: client.chain.id,
|
|
6058
|
-
block_number: latestBlockNumberChain,
|
|
6059
|
-
err
|
|
6060
|
-
});
|
|
6061
|
-
yield startBlock;
|
|
6062
|
-
return;
|
|
6063
|
-
}
|
|
6064
|
-
try {
|
|
6065
|
-
await db.transaction(async (dbTx) => {
|
|
6066
|
-
const insertPositions = async () => {
|
|
6067
|
-
if (newPositions.length === 0) return;
|
|
6068
|
-
try {
|
|
6069
|
-
const count = await dbTx.positions.upsert(newPositions);
|
|
6070
|
-
logger.info({
|
|
6071
|
-
msg: `New positions`,
|
|
6072
|
-
collector,
|
|
6073
|
-
count,
|
|
6074
|
-
chain_id: client.chain.id,
|
|
6075
|
-
block_number: latestBlockNumberChain
|
|
6076
|
-
});
|
|
6077
|
-
} catch (err) {
|
|
6078
|
-
throw new InsertPositionsError(err);
|
|
6079
|
-
}
|
|
6080
|
-
};
|
|
6081
|
-
const insertTransfers = async () => {
|
|
6082
|
-
if (transfers.length === 0) return;
|
|
6083
|
-
try {
|
|
6084
|
-
const created = await dbTx.transfers.create(transfers);
|
|
6085
|
-
logger.info({
|
|
6086
|
-
msg: `New transfers`,
|
|
6087
|
-
collector,
|
|
6088
|
-
count: created,
|
|
6089
|
-
chain_id: client.chain.id,
|
|
6090
|
-
block_range: [startBlock, blockNumber]
|
|
6091
|
-
});
|
|
6092
|
-
} catch (err) {
|
|
6093
|
-
throw new InsertTransfersError(err);
|
|
6094
|
-
}
|
|
6095
|
-
};
|
|
6096
|
-
const saveBlockNumber = async () => {
|
|
6097
|
-
try {
|
|
6098
|
-
await dbTx.blocks.advanceCollector({
|
|
6099
|
-
collectorName: collector,
|
|
6100
|
-
chainId: client.chain.id,
|
|
6101
|
-
blockNumber,
|
|
6102
|
-
epoch
|
|
6103
|
-
});
|
|
6104
|
-
} catch (_) {
|
|
6105
|
-
throw new ReorgError(blockNumber);
|
|
6106
|
-
}
|
|
6107
|
-
};
|
|
6108
|
-
await insertPositions();
|
|
6109
|
-
await insertTransfers();
|
|
6110
|
-
await saveBlockNumber();
|
|
6111
|
-
});
|
|
6112
|
-
} catch (err) {
|
|
6113
|
-
if (err instanceof ReorgError) {
|
|
6114
|
-
logger.info({
|
|
6115
|
-
msg: "Reorg detected, positions and transfers insertion aborted",
|
|
6116
|
-
collector,
|
|
6117
|
-
count: newPositions.length,
|
|
6118
|
-
chain_id: client.chain.id,
|
|
6119
|
-
block_number: blockNumber
|
|
6120
|
-
});
|
|
6121
|
-
reorgDetected = true;
|
|
6122
|
-
}
|
|
6123
|
-
if (err instanceof InsertPositionsError) {
|
|
6124
|
-
logger.error({
|
|
6125
|
-
msg: "Failed to insert positions",
|
|
6126
|
-
collector,
|
|
6127
|
-
count: newPositions.length,
|
|
6128
|
-
chain_id: client.chain.id,
|
|
6129
|
-
block_number: latestBlockNumberChain,
|
|
6130
|
-
err
|
|
6131
|
-
});
|
|
6132
|
-
throw err.cause;
|
|
6133
|
-
}
|
|
6134
|
-
if (err instanceof InsertTransfersError) {
|
|
6135
|
-
logger.error({
|
|
6136
|
-
msg: "Failed to insert transfers",
|
|
6137
|
-
collector,
|
|
6138
|
-
count: transfers.length,
|
|
6139
|
-
chain_id: client.chain.id,
|
|
6140
|
-
block_number: blockNumber,
|
|
6141
|
-
err
|
|
6142
|
-
});
|
|
6143
|
-
throw err.cause;
|
|
6144
|
-
}
|
|
6145
|
-
}
|
|
6146
|
-
if (!reorgDetected) {
|
|
6147
|
-
startBlock = blockNumber;
|
|
6148
|
-
if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
|
|
6149
|
-
yield blockNumber;
|
|
6150
|
-
continue;
|
|
6151
|
-
}
|
|
6152
|
-
await db.transaction(async (dbTx) => {
|
|
6153
|
-
try {
|
|
6154
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
6155
|
-
collectorName: collector,
|
|
6156
|
-
chainId: client.chain.id
|
|
6157
|
-
});
|
|
6158
|
-
blockNumber = ancestor.blockNumber;
|
|
6159
|
-
const emptied = await dbTx.positions.setEmptyAfter({
|
|
6160
|
-
chainId: client.chain.id,
|
|
6161
|
-
blockNumber: blockNumber + 1
|
|
6162
|
-
});
|
|
6163
|
-
logger.info({
|
|
6164
|
-
msg: "Reorg detected, positions set to empty",
|
|
6165
|
-
collector,
|
|
6166
|
-
count: emptied,
|
|
6167
|
-
chain_id: client.chain.id,
|
|
6168
|
-
block_number_gte: blockNumber + 1
|
|
6169
|
-
});
|
|
6170
|
-
await dbTx.blocks.advanceCollector({
|
|
6171
|
-
collectorName: collector,
|
|
6172
|
-
chainId: client.chain.id,
|
|
6173
|
-
blockNumber,
|
|
6174
|
-
epoch: ancestor.epoch
|
|
6175
|
-
});
|
|
6176
|
-
} catch (err) {
|
|
6177
|
-
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6178
|
-
logger.error({
|
|
6179
|
-
collector,
|
|
6180
|
-
chainId: client.chain.id,
|
|
6181
|
-
msg,
|
|
6182
|
-
err
|
|
6183
|
-
});
|
|
6184
|
-
throw new Error(msg);
|
|
6185
|
-
}
|
|
6186
|
-
});
|
|
6187
|
-
return;
|
|
6188
|
-
}
|
|
6189
|
-
}
|
|
6190
|
-
/**
|
|
6191
|
-
* @internal
|
|
6192
|
-
*
|
|
6193
|
-
* Snapshots positions and returns the new positions.
|
|
6194
|
-
* @param parameters - {@link _snapshot.Parameters}
|
|
6195
|
-
* @returns The new positions. {@link _snapshot.ReturnType}
|
|
6196
|
-
*/
|
|
6197
|
-
async function _snapshot(parameters) {
|
|
6198
|
-
const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
|
|
6199
|
-
const vaultV1Positions = [];
|
|
6200
|
-
const erc20Positions = [];
|
|
6201
|
-
for (const position of positions) switch (position.type) {
|
|
6202
|
-
case Type.VAULT_V1:
|
|
6203
|
-
vaultV1Positions.push(position);
|
|
6204
|
-
break;
|
|
6205
|
-
case Type.ERC20:
|
|
6206
|
-
erc20Positions.push(position);
|
|
6207
|
-
break;
|
|
6208
|
-
default: throw new Error("Invalid position type");
|
|
6209
|
-
}
|
|
6210
|
-
const promises = [snapshotVaultPositions({
|
|
6211
|
-
client,
|
|
6212
|
-
positions: vaultV1Positions,
|
|
6213
|
-
blockNumber,
|
|
6214
|
-
options: {
|
|
6215
|
-
maxBatchSize,
|
|
6216
|
-
retryAttempts,
|
|
6217
|
-
retryDelayMs
|
|
6218
|
-
}
|
|
6219
|
-
}), snapshotERC20Positions({
|
|
6220
|
-
client,
|
|
6221
|
-
positions: erc20Positions,
|
|
6222
|
-
blockNumber,
|
|
6223
|
-
options: {
|
|
6224
|
-
maxBatchSize,
|
|
6225
|
-
retryAttempts,
|
|
6226
|
-
retryDelayMs
|
|
6227
|
-
}
|
|
6228
|
-
})];
|
|
6229
|
-
return (await Promise.all(promises)).flat();
|
|
6230
|
-
}
|
|
6231
|
-
var InsertPositionsError = class extends BaseError {
|
|
6232
|
-
name = "InsertPositionsError";
|
|
6233
|
-
constructor(err) {
|
|
6234
|
-
super("Failed to insert positions", { cause: err });
|
|
6235
|
-
}
|
|
6236
|
-
};
|
|
6237
|
-
var InsertTransfersError = class extends BaseError {
|
|
6238
|
-
name = "InsertTransfersError";
|
|
6239
|
-
constructor(err) {
|
|
6240
|
-
super("Failed to insert transfers", { cause: err });
|
|
6241
|
-
}
|
|
6242
|
-
};
|
|
6243
|
-
|
|
6244
|
-
//#endregion
|
|
6245
|
-
//#region src/indexer/collectors/CollectFunctions/collectPrices.ts
|
|
6246
|
-
/**
|
|
6247
|
-
* Collects oracle prices from on-chain oracles and persists them.
|
|
6248
|
-
*
|
|
6249
|
-
* - Reads oracle definitions as {@link Oracle.Oracle}.
|
|
6250
|
-
* - Uses chain metadata from {@link Chain.Chain}.
|
|
6251
|
-
*
|
|
6252
|
-
* @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
|
|
6253
|
-
* with a client supporting {@link PublicActions.multicall | multicall}.
|
|
6254
|
-
* @yields Latest processed block number after each successful update.
|
|
6255
|
-
*/
|
|
6256
|
-
async function* collectPrices(parameters) {
|
|
6257
|
-
const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6258
|
-
const logger = getLogger();
|
|
6259
|
-
let blockNumber = parameters.lastBlockNumber;
|
|
6260
|
-
const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
|
|
6261
|
-
const updatedOracles = [];
|
|
6262
|
-
try {
|
|
6263
|
-
const pricesMap = await fetchOraclePrices({
|
|
6264
|
-
client,
|
|
6265
|
-
oracles: oracles.map((oracle) => oracle.address),
|
|
6266
|
-
options: {
|
|
6267
|
-
batchSize: maxBatchSize,
|
|
6268
|
-
blockNumber: latestBlockNumberChain,
|
|
6269
|
-
retryAttempts,
|
|
6270
|
-
retryDelayMs
|
|
6271
|
-
}
|
|
6272
|
-
});
|
|
6273
|
-
for (const oracle of oracles) {
|
|
6274
|
-
const price = pricesMap.get(oracle.address);
|
|
6275
|
-
if (price !== void 0) updatedOracles.push({
|
|
6276
|
-
chainId: client.chain.id,
|
|
6277
|
-
address: oracle.address,
|
|
6278
|
-
price,
|
|
6279
|
-
blockNumber: latestBlockNumberChain
|
|
6280
|
-
});
|
|
6281
|
-
}
|
|
6282
|
-
} catch (err) {
|
|
6283
|
-
logger.error({
|
|
6284
|
-
msg: "Failed to fetch oracle prices",
|
|
6285
|
-
collector,
|
|
6286
|
-
chain_id: client.chain.id,
|
|
6287
|
-
block_number: latestBlockNumberChain,
|
|
6288
|
-
err
|
|
6289
|
-
});
|
|
6290
|
-
yield blockNumber;
|
|
6291
|
-
return;
|
|
6292
|
-
}
|
|
6293
|
-
let reorgDetected = false;
|
|
6294
|
-
try {
|
|
6295
|
-
await db.transaction(async (dbTx) => {
|
|
6296
|
-
if (updatedOracles.length > 0) {
|
|
6297
|
-
await dbTx.oracles.upsert(updatedOracles);
|
|
6298
|
-
logger.info({
|
|
6299
|
-
msg: "Oracle prices updated",
|
|
6300
|
-
collector,
|
|
6301
|
-
count: updatedOracles.length,
|
|
6302
|
-
chain_id: client.chain.id,
|
|
6303
|
-
block_number: latestBlockNumberChain
|
|
6304
|
-
});
|
|
6305
|
-
}
|
|
6306
|
-
try {
|
|
6307
|
-
await dbTx.blocks.advanceCollector({
|
|
6308
|
-
collectorName: collector,
|
|
6309
|
-
chainId: client.chain.id,
|
|
6310
|
-
blockNumber: latestBlockNumberChain,
|
|
6311
|
-
epoch
|
|
6312
|
-
});
|
|
6313
|
-
} catch (_) {
|
|
6314
|
-
throw new ReorgError(latestBlockNumberChain);
|
|
6315
|
-
}
|
|
6316
|
-
blockNumber = latestBlockNumberChain;
|
|
6317
|
-
});
|
|
6318
|
-
} catch (err) {
|
|
6319
|
-
if (err instanceof ReorgError) {
|
|
6320
|
-
logger.info({
|
|
6321
|
-
msg: "Reorg detected, prices update aborted",
|
|
6322
|
-
collector,
|
|
6323
|
-
count: updatedOracles.length,
|
|
6324
|
-
chain_id: client.chain.id,
|
|
6325
|
-
block_number: latestBlockNumberChain
|
|
6326
|
-
});
|
|
6327
|
-
reorgDetected = true;
|
|
6328
|
-
} else throw new Error("Failed to collect oracle prices", { cause: err });
|
|
6329
|
-
}
|
|
6330
|
-
if (!reorgDetected) {
|
|
6331
|
-
yield blockNumber;
|
|
6332
|
-
return;
|
|
6333
|
-
}
|
|
6334
|
-
await db.transaction(async (dbTx) => {
|
|
6335
|
-
try {
|
|
6336
|
-
const ancestor = await dbTx.blocks.getCollector({
|
|
6337
|
-
collectorName: collector,
|
|
6338
|
-
chainId: client.chain.id
|
|
6339
|
-
});
|
|
6340
|
-
blockNumber = ancestor.blockNumber;
|
|
6341
|
-
await dbTx.blocks.advanceCollector({
|
|
6342
|
-
collectorName: collector,
|
|
6343
|
-
chainId: client.chain.id,
|
|
6344
|
-
blockNumber,
|
|
6345
|
-
epoch: ancestor.epoch
|
|
6346
|
-
});
|
|
6347
|
-
} catch (err) {
|
|
6348
|
-
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6349
|
-
logger.error({
|
|
6350
|
-
collector,
|
|
6351
|
-
chainId: client.chain.id,
|
|
6352
|
-
msg,
|
|
6353
|
-
err
|
|
6354
|
-
});
|
|
6355
|
-
throw new Error(msg);
|
|
6356
|
-
}
|
|
6357
|
-
});
|
|
6358
|
-
yield blockNumber;
|
|
6359
|
-
}
|
|
6360
|
-
|
|
6361
|
-
//#endregion
|
|
6362
|
-
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
6363
|
-
function createBuilder(parameters) {
|
|
6364
|
-
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
|
|
6365
|
-
const createCollector = (name, collect) => create$16({
|
|
6366
|
-
name,
|
|
6367
|
-
collect,
|
|
6368
|
-
client,
|
|
6369
|
-
db,
|
|
6370
|
-
options: {
|
|
6371
|
-
maxBlockNumber,
|
|
6372
|
-
interval
|
|
6373
|
-
}
|
|
6374
|
-
});
|
|
6375
|
-
return {
|
|
6376
|
-
buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
|
|
6377
|
-
return createCollector("offers", (p) => collectOffersV2({
|
|
6378
|
-
...p,
|
|
6379
|
-
gatekeeper,
|
|
6380
|
-
collector: "offers",
|
|
6381
|
-
client,
|
|
6382
|
-
options: {
|
|
6383
|
-
maxBatchSize,
|
|
6384
|
-
blockWindow
|
|
6385
|
-
}
|
|
6386
|
-
}));
|
|
6387
|
-
},
|
|
6388
|
-
buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
|
|
6389
|
-
return createCollector("consumed_events", (p) => collectConsumedEvents({
|
|
6390
|
-
...p,
|
|
6391
|
-
collector: "consumed_events",
|
|
6392
|
-
options: {
|
|
6393
|
-
maxBatchSize,
|
|
6394
|
-
blockWindow
|
|
6395
|
-
}
|
|
6396
|
-
}));
|
|
6397
|
-
},
|
|
6398
|
-
buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6399
|
-
return createCollector("prices", (p) => collectPrices({
|
|
6400
|
-
...p,
|
|
6401
|
-
collector: "prices",
|
|
6402
|
-
options: {
|
|
6403
|
-
maxBatchSize,
|
|
6404
|
-
retryAttempts,
|
|
6405
|
-
retryDelayMs
|
|
6406
|
-
}
|
|
6407
|
-
}));
|
|
6408
|
-
},
|
|
6409
|
-
buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6410
|
-
return createCollector("positions", (p) => collectPositions({
|
|
6411
|
-
...p,
|
|
6412
|
-
collector: "positions",
|
|
6413
|
-
options: {
|
|
6414
|
-
maxBatchSize,
|
|
6415
|
-
retryAttempts,
|
|
6416
|
-
retryDelayMs,
|
|
6417
|
-
blockWindow
|
|
6418
|
-
}
|
|
6419
|
-
}));
|
|
6420
|
-
}
|
|
6421
|
-
};
|
|
6422
|
-
}
|
|
6423
|
-
|
|
6424
|
-
//#endregion
|
|
6425
|
-
//#region src/indexer/collectors/Collectors.ts
|
|
6426
|
-
const from$2 = (parameters) => {
|
|
6427
|
-
const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
|
|
6428
|
-
const collectorBuilder = createBuilder({
|
|
6429
|
-
client,
|
|
6430
|
-
db,
|
|
6431
|
-
gatekeeper,
|
|
6432
|
-
options: {
|
|
6433
|
-
maxBlockNumber,
|
|
6434
|
-
blockWindow,
|
|
6435
|
-
interval
|
|
6436
|
-
}
|
|
6437
|
-
});
|
|
6438
|
-
return {
|
|
6439
|
-
offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
|
|
6440
|
-
consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
|
|
6441
|
-
pricesCollector: collectorBuilder.buildPricesCollector({ options: {
|
|
6442
|
-
maxBatchSize,
|
|
6443
|
-
retryAttempts,
|
|
6444
|
-
retryDelayMs
|
|
6445
|
-
} }),
|
|
6446
|
-
positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
|
|
6447
|
-
maxBatchSize,
|
|
6448
|
-
retryAttempts,
|
|
6449
|
-
retryDelayMs
|
|
6450
|
-
} })
|
|
6451
|
-
};
|
|
6452
|
-
};
|
|
6453
|
-
|
|
6454
|
-
//#endregion
|
|
6455
|
-
//#region src/indexer/Indexer.ts
|
|
6456
|
-
function from$1(config) {
|
|
6457
|
-
const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
|
|
6458
|
-
const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
|
|
6459
|
-
client,
|
|
6460
|
-
db,
|
|
6461
|
-
gatekeeper,
|
|
6462
|
-
maxBatchSize,
|
|
6463
|
-
maxBlockNumber,
|
|
6464
|
-
blockWindow,
|
|
6465
|
-
interval,
|
|
6466
|
-
retryAttempts,
|
|
6467
|
-
retryDelayMs
|
|
6468
|
-
});
|
|
6469
|
-
return create$18({
|
|
6470
|
-
client,
|
|
6471
|
-
collectors: [
|
|
6472
|
-
offersCollector,
|
|
6473
|
-
consumedEventsCollector,
|
|
6474
|
-
positionsCollector,
|
|
6475
|
-
pricesCollector
|
|
6476
|
-
]
|
|
6477
|
-
});
|
|
6478
|
-
}
|
|
6479
|
-
function create$18(params) {
|
|
6480
|
-
const { collectors, client } = params;
|
|
6481
|
-
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
6482
|
-
const tracer = getTracer(`router.${indexerId}`);
|
|
6483
|
-
const iterators = collectors.map((collector) => collector.collect());
|
|
6484
|
-
const next = async () => {
|
|
6485
|
-
await startActiveSpan(tracer, `${indexerId}.next`, async () => {
|
|
6486
|
-
await Promise.all(iterators.map((iterator) => iterator.next()));
|
|
6487
|
-
});
|
|
6488
|
-
};
|
|
6489
|
-
const _return = async () => {
|
|
6490
|
-
await Promise.all(iterators.map(async (iterator) => iterator.return()));
|
|
6491
|
-
};
|
|
6492
|
-
return {
|
|
6493
|
-
start: () => {
|
|
6494
|
-
const stops = collectors.map((collector) => start(collector));
|
|
6495
|
-
return () => {
|
|
6496
|
-
stops.forEach((stop) => {
|
|
6497
|
-
stop();
|
|
6498
|
-
});
|
|
6499
|
-
};
|
|
6500
|
-
},
|
|
6501
|
-
next,
|
|
6502
|
-
return: _return
|
|
6503
|
-
};
|
|
6504
|
-
}
|
|
6505
|
-
|
|
6506
|
-
//#endregion
|
|
6507
|
-
//#region src/indexer/collectors/Admin.ts
|
|
6508
|
-
function create$17(parameters) {
|
|
6509
|
-
const collector = "admin";
|
|
6510
|
-
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
6511
|
-
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
6512
|
-
let finalizedBlock = null;
|
|
6513
|
-
let unfinalizedBlocks = [];
|
|
6514
|
-
let initialized = false;
|
|
6515
|
-
const logger = getLogger();
|
|
6516
|
-
let isMaxBlockNumberReached = false;
|
|
6517
|
-
let tick = 0;
|
|
6518
|
-
return { syncBlock: async () => {
|
|
6519
|
-
if (!initialized) {
|
|
6520
|
-
await Promise.all(names.map((collectorName) => db.blocks.init({
|
|
6521
|
-
chainId: client.chain.id,
|
|
6522
|
-
collectorName
|
|
6523
|
-
})));
|
|
6524
|
-
initialized = true;
|
|
6525
|
-
}
|
|
6526
|
-
if (isMaxBlockNumberReached) return true;
|
|
6527
|
-
const head = await client.getBlock({
|
|
6528
|
-
blockTag: "latest",
|
|
6529
|
-
includeTransactions: false
|
|
6530
|
-
});
|
|
6531
|
-
await db.transaction(async (dbTx) => {
|
|
6532
|
-
const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
6533
|
-
if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
|
|
6534
|
-
logger.info({
|
|
6535
|
-
msg: `Head is greater than max block number`,
|
|
6536
|
-
collector,
|
|
6537
|
-
chainId: client.chain.id,
|
|
6538
|
-
block_number: head.number,
|
|
6539
|
-
max_block_number: maxBlockNumber
|
|
6540
|
-
});
|
|
6541
|
-
await dbTx.blocks.handleReorg({
|
|
6542
|
-
chainId: client.chain.id,
|
|
6543
|
-
blockNumber: maxBlockNumber,
|
|
6544
|
-
epoch: epoch + 1n
|
|
6545
|
-
});
|
|
6546
|
-
isMaxBlockNumberReached = true;
|
|
6547
|
-
return isMaxBlockNumberReached;
|
|
6548
|
-
}
|
|
6549
|
-
finalizedBlock = await fetchFinalizedBlock({
|
|
6550
|
-
tick,
|
|
6551
|
-
client,
|
|
6552
|
-
logger,
|
|
6553
|
-
collector,
|
|
6554
|
-
unfinalizedBlocks,
|
|
6555
|
-
previousFinalizedBlock: finalizedBlock
|
|
6556
|
-
});
|
|
6557
|
-
tick++;
|
|
6558
|
-
let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6559
|
-
client,
|
|
6560
|
-
block: head,
|
|
6561
|
-
unfinalizedBlocks,
|
|
6562
|
-
finalizedBlock,
|
|
6563
|
-
logger,
|
|
6564
|
-
collector,
|
|
6565
|
-
maxBatchSize
|
|
6566
|
-
});
|
|
6567
|
-
unfinalizedBlocks = newUnfinalizedBlocks;
|
|
6568
|
-
const blockNumber = Number(returnedBlock.number);
|
|
6569
|
-
didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
|
|
6570
|
-
if (didReorgHappened) await dbTx.blocks.handleReorg({
|
|
6571
|
-
chainId: client.chain.id,
|
|
6572
|
-
blockNumber,
|
|
6573
|
-
epoch: epoch + 1n
|
|
6574
|
-
});
|
|
6575
|
-
else await dbTx.blocks.advanceChain({
|
|
6576
|
-
chainId: client.chain.id,
|
|
6577
|
-
blockNumber,
|
|
6578
|
-
epoch
|
|
6579
|
-
});
|
|
6580
|
-
});
|
|
6581
|
-
return isMaxBlockNumberReached;
|
|
6582
|
-
} };
|
|
6583
|
-
}
|
|
6584
|
-
const commonAncestor = (block, unfinalizedBlocks) => {
|
|
6585
|
-
const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
|
|
6586
|
-
if (parent) return parent;
|
|
6587
|
-
return null;
|
|
6588
|
-
};
|
|
6589
|
-
const fetchFinalizedBlock = async (parameters) => {
|
|
6590
|
-
let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
|
|
6591
|
-
let finalizedBlock = previousFinalizedBlock;
|
|
6592
|
-
if (tick % 20 === 0 || previousFinalizedBlock === null) {
|
|
6593
|
-
finalizedBlock = await client.getBlock({
|
|
6594
|
-
blockTag: "finalized",
|
|
6595
|
-
includeTransactions: false
|
|
6596
|
-
});
|
|
6597
|
-
if (finalizedBlock === null || finalizedBlock.number === null) {
|
|
6598
|
-
const msg = "Failed to get finalized block";
|
|
6599
|
-
logger.fatal({
|
|
6600
|
-
collector,
|
|
6601
|
-
chainId: client.chain.id,
|
|
6602
|
-
msg
|
|
6603
|
-
});
|
|
6604
|
-
throw new Error(msg);
|
|
6605
|
-
}
|
|
6606
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
|
|
6607
|
-
}
|
|
6608
|
-
if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
|
|
6609
|
-
const msg = "Failed to get finalized block";
|
|
6610
|
-
logger.fatal({
|
|
6611
|
-
collector,
|
|
6612
|
-
chainId: client.chain.id,
|
|
6613
|
-
msg
|
|
6614
|
-
});
|
|
6615
|
-
throw new Error(msg);
|
|
6616
|
-
}
|
|
6617
|
-
return {
|
|
6618
|
-
hash: finalizedBlock.hash,
|
|
6619
|
-
number: finalizedBlock.number,
|
|
6620
|
-
parentHash: finalizedBlock.parentHash
|
|
6621
|
-
};
|
|
6622
|
-
};
|
|
6623
|
-
const reconcile = async (parameters) => {
|
|
6624
|
-
let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
|
|
6625
|
-
const chain = client.chain;
|
|
6626
|
-
if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
|
|
6627
|
-
const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
|
|
6628
|
-
if (latestBlock === void 0) {
|
|
6629
|
-
const newBlock = {
|
|
6630
|
-
hash: block.hash,
|
|
6631
|
-
number: block.number,
|
|
6632
|
-
parentHash: block.parentHash
|
|
6633
|
-
};
|
|
6634
|
-
unfinalizedBlocks.push(newBlock);
|
|
6635
|
-
return {
|
|
6636
|
-
block: newBlock,
|
|
6637
|
-
didReorgHappened: false,
|
|
6638
|
-
unfinalizedBlocks
|
|
6639
|
-
};
|
|
6640
|
-
}
|
|
6641
|
-
if (latestBlock.hash === block.hash) return {
|
|
6642
|
-
block: latestBlock,
|
|
6643
|
-
didReorgHappened: false,
|
|
6644
|
-
unfinalizedBlocks
|
|
6645
|
-
};
|
|
6646
|
-
if (latestBlock.number >= block.number) {
|
|
6647
|
-
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6648
|
-
logger.info({
|
|
6649
|
-
msg: `Reorg detected, latestBlock.number >= block.number`,
|
|
6650
|
-
collector,
|
|
6651
|
-
chain_id: chain.id,
|
|
6652
|
-
ancestor: ancestor.number,
|
|
6653
|
-
latest_block_number: latestBlock.number,
|
|
6654
|
-
block_number: block.number
|
|
6655
|
-
});
|
|
6656
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6657
|
-
return {
|
|
6658
|
-
block: ancestor,
|
|
6659
|
-
didReorgHappened: true,
|
|
6660
|
-
unfinalizedBlocks
|
|
6661
|
-
};
|
|
6662
|
-
}
|
|
6663
|
-
if (latestBlock.number + 1n < block.number) {
|
|
6664
|
-
logger.debug({
|
|
6665
|
-
collector,
|
|
6666
|
-
chain_id: chain.id,
|
|
6667
|
-
block_range: [latestBlock.number, block.number],
|
|
6668
|
-
msg: `Missing blocks`
|
|
6669
|
-
});
|
|
6670
|
-
const missingBlockNumbers = (() => {
|
|
6671
|
-
const missingBlockNumbers = [];
|
|
6672
|
-
let start = latestBlock.number + 1n;
|
|
6673
|
-
const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
|
|
6674
|
-
while (start < threshold) {
|
|
6675
|
-
missingBlockNumbers.push(start);
|
|
6676
|
-
start = start + 1n;
|
|
6677
|
-
}
|
|
6678
|
-
return missingBlockNumbers;
|
|
6679
|
-
})();
|
|
6680
|
-
const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
|
|
6681
|
-
blockNumber,
|
|
6682
|
-
includeTransactions: false
|
|
6683
|
-
}))));
|
|
6684
|
-
for (const missingBlock of missingBlocks) {
|
|
6685
|
-
const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6686
|
-
client,
|
|
6687
|
-
block: missingBlock,
|
|
6688
|
-
unfinalizedBlocks,
|
|
6689
|
-
finalizedBlock,
|
|
6690
|
-
logger,
|
|
6691
|
-
collector,
|
|
6692
|
-
maxBatchSize
|
|
6693
|
-
});
|
|
6694
|
-
if (returnedBlock.number !== missingBlock.number) return {
|
|
6695
|
-
block: returnedBlock,
|
|
6696
|
-
didReorgHappened,
|
|
6697
|
-
unfinalizedBlocks: newUnfinalizedBlocks
|
|
6698
|
-
};
|
|
6699
|
-
}
|
|
6700
|
-
return reconcile({
|
|
6701
|
-
client,
|
|
6702
|
-
block,
|
|
6703
|
-
unfinalizedBlocks,
|
|
6704
|
-
finalizedBlock,
|
|
6705
|
-
logger,
|
|
6706
|
-
collector,
|
|
6707
|
-
maxBatchSize
|
|
6708
|
-
});
|
|
6709
|
-
}
|
|
6710
|
-
if (block.parentHash !== latestBlock.hash) {
|
|
6711
|
-
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6712
|
-
logger.info({
|
|
6713
|
-
msg: `Reorg detected, block parent hash !== latest block hash`,
|
|
6714
|
-
collector,
|
|
6715
|
-
chain_id: chain.id,
|
|
6716
|
-
ancestor: ancestor.number,
|
|
6717
|
-
latest_block_number: latestBlock.number,
|
|
6718
|
-
block_number: block.number
|
|
6719
|
-
});
|
|
6720
|
-
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6721
|
-
return {
|
|
6722
|
-
block: ancestor,
|
|
6723
|
-
didReorgHappened: true,
|
|
6724
|
-
unfinalizedBlocks
|
|
6725
|
-
};
|
|
6726
|
-
}
|
|
6727
|
-
const newBlock = {
|
|
6728
|
-
hash: block.hash,
|
|
6729
|
-
number: block.number,
|
|
6730
|
-
parentHash: block.parentHash
|
|
6731
|
-
};
|
|
6732
|
-
unfinalizedBlocks.push(newBlock);
|
|
6733
|
-
return {
|
|
6734
|
-
block: newBlock,
|
|
6735
|
-
didReorgHappened: false,
|
|
6736
|
-
unfinalizedBlocks
|
|
6737
|
-
};
|
|
6738
|
-
};
|
|
6739
|
-
|
|
6740
|
-
//#endregion
|
|
6741
|
-
//#region src/indexer/collectors/Collector.ts
|
|
6742
|
-
const names = [
|
|
6743
|
-
"offers",
|
|
6744
|
-
"consumed_events",
|
|
6745
|
-
"positions",
|
|
6746
|
-
"prices"
|
|
6747
|
-
];
|
|
6748
|
-
function create$16({ name, collect, client, db, options }) {
|
|
6749
|
-
const admin = create$17({
|
|
6750
|
-
client,
|
|
6751
|
-
db,
|
|
6752
|
-
options
|
|
6753
|
-
});
|
|
6754
|
-
return {
|
|
6755
|
-
name,
|
|
6756
|
-
chain: client.chain,
|
|
6757
|
-
client,
|
|
6758
|
-
db,
|
|
6759
|
-
interval: options.interval ?? 1e4,
|
|
6760
|
-
collect: async function* () {
|
|
6761
|
-
const collector = name;
|
|
6762
|
-
const chain = client.chain;
|
|
6763
|
-
const logger = getLogger();
|
|
6764
|
-
const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
|
|
6765
|
-
const tracer = getTracer(`router.${collectorId}`);
|
|
6766
|
-
logger.info({
|
|
6767
|
-
msg: `Collector started`,
|
|
6768
|
-
collector,
|
|
6769
|
-
chain_id: chain.id
|
|
6770
|
-
});
|
|
6771
|
-
let iterator = null;
|
|
6772
|
-
let lastBlockNumber;
|
|
6773
|
-
while (true) try {
|
|
6774
|
-
if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
|
|
6775
|
-
const { collector: collectorBlock } = await db.blocks.init({
|
|
6776
|
-
collectorName: name,
|
|
6777
|
-
chainId: chain.id
|
|
6778
|
-
});
|
|
6779
|
-
lastBlockNumber = collectorBlock.blockNumber;
|
|
6780
|
-
return collect({
|
|
6781
|
-
client,
|
|
6782
|
-
collector: name,
|
|
6783
|
-
epoch: collectorBlock.epoch,
|
|
6784
|
-
lastBlockNumber,
|
|
6785
|
-
db
|
|
6786
|
-
});
|
|
6787
|
-
});
|
|
6788
|
-
if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
|
|
6789
|
-
const isMaxBlockNumberReached = await admin.syncBlock();
|
|
6790
|
-
span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
|
|
6791
|
-
return isMaxBlockNumberReached;
|
|
6792
|
-
}) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
|
|
6793
|
-
const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
|
|
6794
|
-
if (iterator === null) throw new Error("Iterator is not initialized");
|
|
6795
|
-
const { value: blockNumber, done } = await iterator.next();
|
|
6796
|
-
return {
|
|
6797
|
-
blockNumber,
|
|
6798
|
-
done
|
|
6799
|
-
};
|
|
6800
|
-
});
|
|
6801
|
-
if (done) iterator = null;
|
|
6802
|
-
else {
|
|
6803
|
-
lastBlockNumber = blockNumber;
|
|
6804
|
-
yield blockNumber;
|
|
6805
|
-
}
|
|
6806
|
-
} catch (err) {
|
|
6807
|
-
const isError = err instanceof Error;
|
|
6808
|
-
logger.error({
|
|
6809
|
-
msg: "Collector error",
|
|
6810
|
-
collector,
|
|
6811
|
-
chain_id: chain.id,
|
|
6812
|
-
error: isError ? err.message : String(err),
|
|
6813
|
-
stack: isError ? err.stack : void 0
|
|
6814
|
-
});
|
|
6815
|
-
}
|
|
6816
|
-
}
|
|
6817
|
-
};
|
|
6818
|
-
}
|
|
6819
|
-
/** Start a collector with its own polling cadence based on chain head lag.
|
|
6820
|
-
* @param collector - The collector to start.
|
|
6821
|
-
* @returns A function to stop the collector.
|
|
6822
|
-
*/
|
|
6823
|
-
function start(collector) {
|
|
6824
|
-
let stopped = false;
|
|
6825
|
-
const it = collector.collect();
|
|
6826
|
-
const logger = getLogger();
|
|
6827
|
-
const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
|
|
6828
|
-
const tracer = getTracer(`router.${collectorId}`);
|
|
6829
|
-
const blocks = collector.db.blocks;
|
|
6830
|
-
let initialized = false;
|
|
6831
|
-
(async () => {
|
|
6832
|
-
while (!stopped) try {
|
|
6833
|
-
await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
|
|
6834
|
-
if (!initialized) {
|
|
6835
|
-
await blocks.init({
|
|
6836
|
-
chainId: collector.chain.id,
|
|
6837
|
-
collectorName: collector.name
|
|
6838
|
-
});
|
|
6839
|
-
initialized = true;
|
|
6840
|
-
}
|
|
6841
|
-
const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
|
|
6842
|
-
blockTag: "latest",
|
|
6843
|
-
includeTransactions: false
|
|
6844
|
-
}), blocks.getCollector({
|
|
6845
|
-
collectorName: collector.name,
|
|
6846
|
-
chainId: collector.chain.id
|
|
6847
|
-
})]);
|
|
6848
|
-
const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
|
|
6849
|
-
const waitSpan = tracer.startSpan(`${collectorId}.wait`);
|
|
6850
|
-
try {
|
|
6851
|
-
await wait(delay);
|
|
6852
|
-
} finally {
|
|
6853
|
-
waitSpan.end();
|
|
6854
|
-
}
|
|
6855
|
-
await it.next();
|
|
6856
|
-
});
|
|
6857
|
-
} catch (err) {
|
|
6858
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
6859
|
-
logger.error({
|
|
6860
|
-
msg: "Collector polling error",
|
|
6861
|
-
collector: collector.name,
|
|
6862
|
-
chain_id: collector.chain.id,
|
|
6863
|
-
err: error
|
|
6864
|
-
});
|
|
6865
|
-
}
|
|
6866
|
-
await it.return();
|
|
6867
|
-
})();
|
|
6868
|
-
return () => {
|
|
6869
|
-
stopped = true;
|
|
6870
|
-
};
|
|
6871
|
-
}
|
|
6872
|
-
|
|
6873
|
-
//#endregion
|
|
6874
|
-
//#region src/database/drizzle/VERSION.ts
|
|
6875
|
-
const VERSION = "router_v1.6";
|
|
5151
|
+
//#region src/database/drizzle/VERSION.ts
|
|
5152
|
+
const VERSION = "router_v1.6";
|
|
6876
5153
|
|
|
6877
5154
|
//#endregion
|
|
6878
5155
|
//#region src/database/drizzle/schema.ts
|
|
@@ -7274,6 +5551,1654 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
|
7274
5551
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7275
5552
|
}, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
|
|
7276
5553
|
|
|
5554
|
+
//#endregion
|
|
5555
|
+
//#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
|
|
5556
|
+
const buildGroupKey = (parameters) => {
|
|
5557
|
+
return `${parameters.chainId}-${parameters.maker.toLowerCase()}-${parameters.group.toLowerCase()}`;
|
|
5558
|
+
};
|
|
5559
|
+
async function* collectConsumedEvents(parameters) {
|
|
5560
|
+
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5561
|
+
const logger = getLogger();
|
|
5562
|
+
let startBlock = blockNumber;
|
|
5563
|
+
let reorgDetected = false;
|
|
5564
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5565
|
+
const stream = streamLogs({
|
|
5566
|
+
client,
|
|
5567
|
+
contractAddress: client.chain.custom.morpho.address,
|
|
5568
|
+
blockNumberGte: blockNumber,
|
|
5569
|
+
blockNumberLte: latestBlockNumberChain,
|
|
5570
|
+
order: "asc",
|
|
5571
|
+
options: {
|
|
5572
|
+
maxBatchSize,
|
|
5573
|
+
blockWindow
|
|
5574
|
+
}
|
|
5575
|
+
});
|
|
5576
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5577
|
+
const parsedLogs = parseEventLogs({
|
|
5578
|
+
abi: [consumedEvent, takeEvent],
|
|
5579
|
+
logs,
|
|
5580
|
+
strict: false
|
|
5581
|
+
});
|
|
5582
|
+
const normalizedLogs = [];
|
|
5583
|
+
const groups$3 = /* @__PURE__ */ new Map();
|
|
5584
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
5585
|
+
const recordLog = (log) => {
|
|
5586
|
+
normalizedLogs.push(log);
|
|
5587
|
+
eventIds.add(log.id);
|
|
5588
|
+
const groupKey = buildGroupKey({
|
|
5589
|
+
chainId: log.chainId,
|
|
5590
|
+
maker: log.maker,
|
|
5591
|
+
group: log.group
|
|
5592
|
+
});
|
|
5593
|
+
if (!groups$3.has(groupKey)) groups$3.set(groupKey, {
|
|
5594
|
+
chainId: log.chainId,
|
|
5595
|
+
maker: log.maker.toLowerCase(),
|
|
5596
|
+
group: log.group.toLowerCase()
|
|
5597
|
+
});
|
|
5598
|
+
};
|
|
5599
|
+
for (const rawLog of parsedLogs) {
|
|
5600
|
+
if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
|
|
5601
|
+
logger.debug({
|
|
5602
|
+
collector,
|
|
5603
|
+
chainId: client.chain.id,
|
|
5604
|
+
msg: "Skipping log because it is missing required fields"
|
|
5605
|
+
});
|
|
5606
|
+
continue;
|
|
5607
|
+
}
|
|
5608
|
+
if (rawLog.eventName === consumedEvent.name) {
|
|
5609
|
+
const consumeArgs = rawLog.args;
|
|
5610
|
+
if (consumeArgs.user === void 0 || consumeArgs.group === void 0 || consumeArgs.amount === void 0) {
|
|
5611
|
+
logger.debug({
|
|
5612
|
+
collector,
|
|
5613
|
+
chainId: client.chain.id,
|
|
5614
|
+
msg: "Skipping Consume log because it is missing required args"
|
|
5615
|
+
});
|
|
5616
|
+
continue;
|
|
5617
|
+
}
|
|
5618
|
+
recordLog({
|
|
5619
|
+
kind: "consume",
|
|
5620
|
+
id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
|
|
5621
|
+
chainId: client.chain.id,
|
|
5622
|
+
maker: consumeArgs.user,
|
|
5623
|
+
group: consumeArgs.group,
|
|
5624
|
+
amount: consumeArgs.amount,
|
|
5625
|
+
blockNumber: Number(rawLog.blockNumber)
|
|
5626
|
+
});
|
|
5627
|
+
continue;
|
|
5628
|
+
}
|
|
5629
|
+
if (rawLog.eventName === takeEvent.name) {
|
|
5630
|
+
const takeArgs = rawLog.args;
|
|
5631
|
+
if (takeArgs.maker === void 0 || takeArgs.group === void 0 || takeArgs.consumed === void 0) {
|
|
5632
|
+
logger.debug({
|
|
5633
|
+
collector,
|
|
5634
|
+
chainId: client.chain.id,
|
|
5635
|
+
msg: "Skipping Take log because it is missing required args"
|
|
5636
|
+
});
|
|
5637
|
+
continue;
|
|
5638
|
+
}
|
|
5639
|
+
recordLog({
|
|
5640
|
+
kind: "take",
|
|
5641
|
+
id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
|
|
5642
|
+
chainId: client.chain.id,
|
|
5643
|
+
maker: takeArgs.maker,
|
|
5644
|
+
group: takeArgs.group,
|
|
5645
|
+
consumed: takeArgs.consumed,
|
|
5646
|
+
blockNumber: Number(rawLog.blockNumber)
|
|
5647
|
+
});
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
await db.transaction(async (dbTx) => {
|
|
5651
|
+
const existingEventIds = /* @__PURE__ */ new Set();
|
|
5652
|
+
if (eventIds.size > 0) {
|
|
5653
|
+
const ids = Array.from(eventIds);
|
|
5654
|
+
for (let index = 0; index < ids.length; index += 500) {
|
|
5655
|
+
const slice = ids.slice(index, index + 500);
|
|
5656
|
+
const { rows } = await dbTx.execute(sql`
|
|
5657
|
+
SELECT event_id
|
|
5658
|
+
FROM ${consumedEvents}
|
|
5659
|
+
WHERE event_id IN (${sql.join(slice.map((id) => sql`${id}`), sql`,`)});
|
|
5660
|
+
`);
|
|
5661
|
+
for (const row of rows) existingEventIds.add(row.event_id);
|
|
5662
|
+
}
|
|
5663
|
+
}
|
|
5664
|
+
const consumedByGroup = /* @__PURE__ */ new Map();
|
|
5665
|
+
if (groups$3.size > 0) {
|
|
5666
|
+
const groupList = Array.from(groups$3.values());
|
|
5667
|
+
for (let index = 0; index < groupList.length; index += 500) {
|
|
5668
|
+
const slice = groupList.slice(index, index + 500);
|
|
5669
|
+
const { rows } = await dbTx.execute(sql`
|
|
5670
|
+
WITH targets(chain_id, maker, "group") AS (
|
|
5671
|
+
VALUES ${sql.join(slice.map((group) => sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), sql`,`)}
|
|
5672
|
+
)
|
|
5673
|
+
SELECT
|
|
5674
|
+
targets.chain_id,
|
|
5675
|
+
targets.maker,
|
|
5676
|
+
targets."group",
|
|
5677
|
+
COALESCE(g.consumed, 0)::numeric AS consumed
|
|
5678
|
+
FROM targets
|
|
5679
|
+
LEFT JOIN ${groups} g
|
|
5680
|
+
ON g.chain_id = targets.chain_id
|
|
5681
|
+
AND g.maker = targets.maker
|
|
5682
|
+
AND g."group" = targets."group";
|
|
5683
|
+
`);
|
|
5684
|
+
for (const row of rows) {
|
|
5685
|
+
const groupKey = buildGroupKey({
|
|
5686
|
+
chainId: Number(row.chain_id),
|
|
5687
|
+
maker: row.maker,
|
|
5688
|
+
group: row.group
|
|
5689
|
+
});
|
|
5690
|
+
consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
}
|
|
5694
|
+
const events = [];
|
|
5695
|
+
for (const log of normalizedLogs) {
|
|
5696
|
+
if (existingEventIds.has(log.id)) continue;
|
|
5697
|
+
const groupKey = buildGroupKey({
|
|
5698
|
+
chainId: log.chainId,
|
|
5699
|
+
maker: log.maker,
|
|
5700
|
+
group: log.group
|
|
5701
|
+
});
|
|
5702
|
+
const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
|
|
5703
|
+
if (log.kind === "consume") {
|
|
5704
|
+
events.push({
|
|
5705
|
+
id: log.id,
|
|
5706
|
+
chainId: log.chainId,
|
|
5707
|
+
maker: log.maker,
|
|
5708
|
+
group: log.group,
|
|
5709
|
+
amount: log.amount,
|
|
5710
|
+
blockNumber: log.blockNumber
|
|
5711
|
+
});
|
|
5712
|
+
consumedByGroup.set(groupKey, previousConsumed + log.amount);
|
|
5713
|
+
continue;
|
|
5714
|
+
}
|
|
5715
|
+
const delta = log.consumed - previousConsumed;
|
|
5716
|
+
if (delta <= 0n) {
|
|
5717
|
+
logger.debug({
|
|
5718
|
+
collector,
|
|
5719
|
+
chainId: client.chain.id,
|
|
5720
|
+
msg: "Skipping Take log because consumed did not increase",
|
|
5721
|
+
previous_consumed: previousConsumed.toString(),
|
|
5722
|
+
consumed: log.consumed.toString()
|
|
5723
|
+
});
|
|
5724
|
+
continue;
|
|
5725
|
+
}
|
|
5726
|
+
events.push({
|
|
5727
|
+
id: log.id,
|
|
5728
|
+
chainId: log.chainId,
|
|
5729
|
+
maker: log.maker,
|
|
5730
|
+
group: log.group,
|
|
5731
|
+
amount: delta,
|
|
5732
|
+
blockNumber: log.blockNumber
|
|
5733
|
+
});
|
|
5734
|
+
consumedByGroup.set(groupKey, log.consumed);
|
|
5735
|
+
}
|
|
5736
|
+
try {
|
|
5737
|
+
await dbTx.consumed.create(events);
|
|
5738
|
+
if (events.length > 0) logger.info({
|
|
5739
|
+
msg: `Events indexed`,
|
|
5740
|
+
collector,
|
|
5741
|
+
count: events.length,
|
|
5742
|
+
chain_id: client.chain.id,
|
|
5743
|
+
block_range: [startBlock, lastStreamBlockNumber]
|
|
5744
|
+
});
|
|
5745
|
+
} catch (err) {
|
|
5746
|
+
logger.error({
|
|
5747
|
+
err,
|
|
5748
|
+
msg: "Failed to process consumed events"
|
|
5749
|
+
});
|
|
5750
|
+
}
|
|
5751
|
+
blockNumber = lastStreamBlockNumber;
|
|
5752
|
+
try {
|
|
5753
|
+
await dbTx.blocks.advanceCollector({
|
|
5754
|
+
collectorName: collector,
|
|
5755
|
+
chainId: client.chain.id,
|
|
5756
|
+
blockNumber,
|
|
5757
|
+
epoch
|
|
5758
|
+
});
|
|
5759
|
+
} catch (_) {
|
|
5760
|
+
try {
|
|
5761
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
5762
|
+
collectorName: collector,
|
|
5763
|
+
chainId: client.chain.id
|
|
5764
|
+
});
|
|
5765
|
+
blockNumber = ancestor.blockNumber;
|
|
5766
|
+
const deleted = await dbTx.consumed.delete({
|
|
5767
|
+
chainId: client.chain.id,
|
|
5768
|
+
blockNumberGte: blockNumber + 1
|
|
5769
|
+
});
|
|
5770
|
+
logger.info({
|
|
5771
|
+
collector,
|
|
5772
|
+
chain_id: client.chain.id,
|
|
5773
|
+
msg: `Reorg detected, events deleted`,
|
|
5774
|
+
count: deleted,
|
|
5775
|
+
block_number: blockNumber
|
|
5776
|
+
});
|
|
5777
|
+
await dbTx.blocks.advanceCollector({
|
|
5778
|
+
collectorName: collector,
|
|
5779
|
+
chainId: client.chain.id,
|
|
5780
|
+
blockNumber,
|
|
5781
|
+
epoch: ancestor.epoch
|
|
5782
|
+
});
|
|
5783
|
+
reorgDetected = true;
|
|
5784
|
+
} catch (err) {
|
|
5785
|
+
const msg = "Failed to delete consumed events when handling reorg.";
|
|
5786
|
+
logger.error({
|
|
5787
|
+
collector,
|
|
5788
|
+
chainId: client.chain.id,
|
|
5789
|
+
msg,
|
|
5790
|
+
err
|
|
5791
|
+
});
|
|
5792
|
+
throw new Error(msg, { cause: err });
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
});
|
|
5796
|
+
if (reorgDetected) return;
|
|
5797
|
+
yield blockNumber;
|
|
5798
|
+
startBlock = blockNumber;
|
|
5799
|
+
}
|
|
5800
|
+
}
|
|
5801
|
+
|
|
5802
|
+
//#endregion
|
|
5803
|
+
//#region src/indexer/collectors/CollectFunctions/collectOffers.ts
|
|
5804
|
+
async function* collectOffersV2(parameters) {
|
|
5805
|
+
let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
|
|
5806
|
+
const logger = getLogger();
|
|
5807
|
+
let startBlock = blockNumber;
|
|
5808
|
+
let reorgDetected = false;
|
|
5809
|
+
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
5810
|
+
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
5811
|
+
logger.error({
|
|
5812
|
+
msg,
|
|
5813
|
+
chain_id: client.chain.id
|
|
5814
|
+
});
|
|
5815
|
+
throw new Error(msg);
|
|
5816
|
+
}
|
|
5817
|
+
const signatureDomain = {
|
|
5818
|
+
chainId: client.chain.id,
|
|
5819
|
+
verifyingContract: client.chain.custom.morpho.address
|
|
5820
|
+
};
|
|
5821
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
5822
|
+
const stream = streamLogs({
|
|
5823
|
+
client,
|
|
5824
|
+
contractAddress: client.chain.custom.mempool.address,
|
|
5825
|
+
event: {
|
|
5826
|
+
type: "event",
|
|
5827
|
+
name: "Event",
|
|
5828
|
+
inputs: [{
|
|
5829
|
+
name: "data",
|
|
5830
|
+
type: "bytes",
|
|
5831
|
+
indexed: false,
|
|
5832
|
+
internalType: "bytes"
|
|
5833
|
+
}],
|
|
5834
|
+
anonymous: false
|
|
5835
|
+
},
|
|
5836
|
+
blockNumberGte: blockNumber,
|
|
5837
|
+
blockNumberLte: latestBlockNumberChain,
|
|
5838
|
+
order: "asc",
|
|
5839
|
+
options: {
|
|
5840
|
+
maxBatchSize,
|
|
5841
|
+
blockWindow
|
|
5842
|
+
}
|
|
5843
|
+
});
|
|
5844
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
5845
|
+
blockNumber = lastStreamBlockNumber;
|
|
5846
|
+
const decodedTrees = [];
|
|
5847
|
+
for (const log of logs) {
|
|
5848
|
+
if (!log) continue;
|
|
5849
|
+
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
5850
|
+
try {
|
|
5851
|
+
const { tree, signature, signer } = await decode(payload, signatureDomain);
|
|
5852
|
+
const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
|
|
5853
|
+
if (signerMismatch) {
|
|
5854
|
+
logger.debug({
|
|
5855
|
+
msg: "Tree rejected: signer mismatch",
|
|
5856
|
+
reason: "signer_mismatch",
|
|
5857
|
+
signer,
|
|
5858
|
+
maker: signerMismatch.maker,
|
|
5859
|
+
chain_id: client.chain.id
|
|
5860
|
+
});
|
|
5861
|
+
continue;
|
|
5862
|
+
}
|
|
5863
|
+
decodedTrees.push({
|
|
5864
|
+
tree,
|
|
5865
|
+
signature,
|
|
5866
|
+
signer,
|
|
5867
|
+
blockNumber: Number(log.blockNumber)
|
|
5868
|
+
});
|
|
5869
|
+
} catch (err) {
|
|
5870
|
+
const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
|
|
5871
|
+
logger.debug({
|
|
5872
|
+
msg: "Tree decode failed",
|
|
5873
|
+
reason,
|
|
5874
|
+
chain_id: client.chain.id,
|
|
5875
|
+
err: err instanceof Error ? err.message : String(err)
|
|
5876
|
+
});
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
await db.transaction(async (dbTx) => {
|
|
5880
|
+
const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
5881
|
+
const treesToInsert = [];
|
|
5882
|
+
let totalValidOffers = 0;
|
|
5883
|
+
const offersWithBlock = [];
|
|
5884
|
+
for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
|
|
5885
|
+
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
5886
|
+
const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
|
|
5887
|
+
if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
|
|
5888
|
+
if (allowedResults.issues.length > 0) {
|
|
5889
|
+
const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
|
|
5890
|
+
logger.debug({
|
|
5891
|
+
msg: "Tree offers rejected by gatekeeper",
|
|
5892
|
+
reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
|
|
5893
|
+
chain_id: client.chain.id,
|
|
5894
|
+
issues_count: allowedResults.issues.length
|
|
5895
|
+
});
|
|
5896
|
+
} else if (hasBlockWindowViolation) logger.debug({
|
|
5897
|
+
msg: "Tree rejected: offers outside block window",
|
|
5898
|
+
reason: "block_window",
|
|
5899
|
+
chain_id: client.chain.id
|
|
5900
|
+
});
|
|
5901
|
+
continue;
|
|
5902
|
+
}
|
|
5903
|
+
treesToInsert.push({
|
|
5904
|
+
tree,
|
|
5905
|
+
signature
|
|
5906
|
+
});
|
|
5907
|
+
totalValidOffers += tree.offers.length;
|
|
5908
|
+
offersWithBlock.push(...tree.offers.map((offer) => ({
|
|
5909
|
+
offer,
|
|
5910
|
+
blockNumber: treeBlockNumber
|
|
5911
|
+
})));
|
|
5912
|
+
} catch (err) {
|
|
5913
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5914
|
+
logger.error({
|
|
5915
|
+
err: error,
|
|
5916
|
+
msg: "Gatekeeper validation failed",
|
|
5917
|
+
chain_id: client.chain.id
|
|
5918
|
+
});
|
|
5919
|
+
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
5920
|
+
}
|
|
5921
|
+
const dependencies = buildOfferDependencies$1(offersWithBlock);
|
|
5922
|
+
await dbTx.oracles.upsert(dependencies.oracles);
|
|
5923
|
+
await dbTx.obligations.create(dependencies.obligations);
|
|
5924
|
+
await dbTx.groups.create(dependencies.groups);
|
|
5925
|
+
const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
|
|
5926
|
+
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
5927
|
+
const insertedOffers = filterInsertedOffers({
|
|
5928
|
+
offers: offersWithBlock,
|
|
5929
|
+
hashes: insertedHashes
|
|
5930
|
+
});
|
|
5931
|
+
const { callbacks, positions, lots } = decodeCallbacks({
|
|
5932
|
+
chainId: client.chain.id,
|
|
5933
|
+
offers: insertedOffers
|
|
5934
|
+
});
|
|
5935
|
+
if (positions.length > 0) await dbTx.positions.upsert(positions);
|
|
5936
|
+
if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
|
|
5937
|
+
if (lots.length > 0) await dbTx.lots.create(lots);
|
|
5938
|
+
try {
|
|
5939
|
+
await dbTx.blocks.advanceCollector({
|
|
5940
|
+
collectorName: collector,
|
|
5941
|
+
chainId: client.chain.id,
|
|
5942
|
+
blockNumber,
|
|
5943
|
+
epoch
|
|
5944
|
+
});
|
|
5945
|
+
if (totalValidOffers > 0) logger.info({
|
|
5946
|
+
msg: `New offers`,
|
|
5947
|
+
collector,
|
|
5948
|
+
count: totalValidOffers,
|
|
5949
|
+
trees_count: treesToInsert.length,
|
|
5950
|
+
chain_id: client.chain.id,
|
|
5951
|
+
block_range: [startBlock, lastStreamBlockNumber]
|
|
5952
|
+
});
|
|
5953
|
+
} catch (_) {
|
|
5954
|
+
try {
|
|
5955
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
5956
|
+
collectorName: collector,
|
|
5957
|
+
chainId: client.chain.id
|
|
5958
|
+
});
|
|
5959
|
+
blockNumber = ancestor.blockNumber;
|
|
5960
|
+
const deleted = await dbTx.offers.delete({
|
|
5961
|
+
blockNumberGte: blockNumber + 1,
|
|
5962
|
+
chainId: client.chain.id
|
|
5963
|
+
});
|
|
5964
|
+
logger.info({
|
|
5965
|
+
collector,
|
|
5966
|
+
chain_id: client.chain.id,
|
|
5967
|
+
msg: `Reorg detected, offers deleted`,
|
|
5968
|
+
count: deleted,
|
|
5969
|
+
block_number: blockNumber
|
|
5970
|
+
});
|
|
5971
|
+
await dbTx.blocks.advanceCollector({
|
|
5972
|
+
collectorName: collector,
|
|
5973
|
+
chainId: client.chain.id,
|
|
5974
|
+
blockNumber,
|
|
5975
|
+
epoch: ancestor.epoch
|
|
5976
|
+
});
|
|
5977
|
+
reorgDetected = true;
|
|
5978
|
+
} catch (err) {
|
|
5979
|
+
const msg = "Failed to delete offers when handling reorg.";
|
|
5980
|
+
logger.error({
|
|
5981
|
+
collector,
|
|
5982
|
+
chainId: client.chain.id,
|
|
5983
|
+
msg,
|
|
5984
|
+
err
|
|
5985
|
+
});
|
|
5986
|
+
throw new Error(msg);
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
});
|
|
5990
|
+
if (reorgDetected) return;
|
|
5991
|
+
yield blockNumber;
|
|
5992
|
+
startBlock = blockNumber;
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
function decodeCallbacks(parameters) {
|
|
5996
|
+
const { offers } = parameters;
|
|
5997
|
+
if (offers.length === 0) return {
|
|
5998
|
+
callbacks: [],
|
|
5999
|
+
positions: [],
|
|
6000
|
+
lots: []
|
|
6001
|
+
};
|
|
6002
|
+
const callbacks = [];
|
|
6003
|
+
const positions = [];
|
|
6004
|
+
const lots = [];
|
|
6005
|
+
for (const { offer, blockNumber: offerBlockNumber } of offers) {
|
|
6006
|
+
if (!offer.buy) continue;
|
|
6007
|
+
if (!isEmptyCallback(offer)) continue;
|
|
6008
|
+
const loanToken = offer.loanToken.toLowerCase();
|
|
6009
|
+
positions.push(from$10({
|
|
6010
|
+
chainId: offer.chainId,
|
|
6011
|
+
contract: loanToken,
|
|
6012
|
+
user: offer.maker,
|
|
6013
|
+
type: Type.ERC20,
|
|
6014
|
+
asset: loanToken,
|
|
6015
|
+
blockNumber: offerBlockNumber
|
|
6016
|
+
}));
|
|
6017
|
+
lots.push({
|
|
6018
|
+
positionChainId: offer.chainId,
|
|
6019
|
+
positionContract: loanToken,
|
|
6020
|
+
positionUser: offer.maker,
|
|
6021
|
+
group: offer.group,
|
|
6022
|
+
size: offer.assets
|
|
6023
|
+
});
|
|
6024
|
+
callbacks.push({
|
|
6025
|
+
offerHash: hash(offer),
|
|
6026
|
+
callbacks: [{
|
|
6027
|
+
chainId: offer.chainId,
|
|
6028
|
+
contract: loanToken,
|
|
6029
|
+
user: offer.maker,
|
|
6030
|
+
amount: offer.assets
|
|
6031
|
+
}]
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
return {
|
|
6035
|
+
callbacks,
|
|
6036
|
+
positions,
|
|
6037
|
+
lots
|
|
6038
|
+
};
|
|
6039
|
+
}
|
|
6040
|
+
function buildOfferDependencies$1(offers) {
|
|
6041
|
+
const obligationsById = /* @__PURE__ */ new Map();
|
|
6042
|
+
const oraclesByKey = /* @__PURE__ */ new Map();
|
|
6043
|
+
const groupsByKey = /* @__PURE__ */ new Map();
|
|
6044
|
+
const offersByBlock = /* @__PURE__ */ new Map();
|
|
6045
|
+
for (const { offer, blockNumber } of offers) {
|
|
6046
|
+
const list = offersByBlock.get(blockNumber) ?? [];
|
|
6047
|
+
list.push(offer);
|
|
6048
|
+
offersByBlock.set(blockNumber, list);
|
|
6049
|
+
const obligationId$2 = obligationId(offer);
|
|
6050
|
+
if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
|
|
6051
|
+
chainId: offer.chainId,
|
|
6052
|
+
loanToken: offer.loanToken,
|
|
6053
|
+
maturity: offer.maturity,
|
|
6054
|
+
collaterals: offer.collaterals
|
|
6055
|
+
}));
|
|
6056
|
+
for (const collateral of offer.collaterals) {
|
|
6057
|
+
const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
6058
|
+
if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
|
|
6059
|
+
chainId: offer.chainId,
|
|
6060
|
+
address: collateral.oracle,
|
|
6061
|
+
price: null,
|
|
6062
|
+
blockNumber
|
|
6063
|
+
}));
|
|
6064
|
+
}
|
|
6065
|
+
const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
|
|
6066
|
+
if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
|
|
6067
|
+
chainId: offer.chainId,
|
|
6068
|
+
maker: offer.maker,
|
|
6069
|
+
group: offer.group,
|
|
6070
|
+
blockNumber
|
|
6071
|
+
});
|
|
6072
|
+
}
|
|
6073
|
+
return {
|
|
6074
|
+
obligations: Array.from(obligationsById.values()),
|
|
6075
|
+
oracles: Array.from(oraclesByKey.values()),
|
|
6076
|
+
groups: Array.from(groupsByKey.values()),
|
|
6077
|
+
offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
|
|
6078
|
+
blockNumber,
|
|
6079
|
+
offers: items
|
|
6080
|
+
}))
|
|
6081
|
+
};
|
|
6082
|
+
}
|
|
6083
|
+
function filterInsertedOffers(parameters) {
|
|
6084
|
+
if (parameters.hashes.length === 0) return [];
|
|
6085
|
+
const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
|
|
6086
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6087
|
+
const filtered = [];
|
|
6088
|
+
for (const entry of parameters.offers) {
|
|
6089
|
+
const hash$2 = hash(entry.offer).toLowerCase();
|
|
6090
|
+
if (!inserted.has(hash$2)) continue;
|
|
6091
|
+
if (seen.has(hash$2)) continue;
|
|
6092
|
+
seen.add(hash$2);
|
|
6093
|
+
filtered.push(entry);
|
|
6094
|
+
}
|
|
6095
|
+
return filtered;
|
|
6096
|
+
}
|
|
6097
|
+
|
|
6098
|
+
//#endregion
|
|
6099
|
+
//#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
|
|
6100
|
+
/**
|
|
6101
|
+
* Fetches prices from multiple oracle contracts using multicall.
|
|
6102
|
+
*
|
|
6103
|
+
* - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
|
|
6104
|
+
* - Requires a client supporting {@link PublicActions.multicall | multicall}.
|
|
6105
|
+
*
|
|
6106
|
+
* @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
|
|
6107
|
+
* {@link Address | addresses}, fetch options, and the multicall-enabled client.
|
|
6108
|
+
* @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
|
|
6109
|
+
*/
|
|
6110
|
+
async function fetchOraclePrices(parameters) {
|
|
6111
|
+
const { client, oracles, options } = parameters;
|
|
6112
|
+
if (oracles.length === 0) return /* @__PURE__ */ new Map();
|
|
6113
|
+
const batchSize = Math.max(1, options?.batchSize ?? 5e3);
|
|
6114
|
+
const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
|
|
6115
|
+
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
|
|
6116
|
+
const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
|
|
6117
|
+
const out = /* @__PURE__ */ new Map();
|
|
6118
|
+
for (const oraclesBatch of batch$1(oracles, batchSize)) {
|
|
6119
|
+
const priceCalls = [];
|
|
6120
|
+
for (const oracle of oraclesBatch) priceCalls.push({
|
|
6121
|
+
address: oracle,
|
|
6122
|
+
abi: Oracle,
|
|
6123
|
+
functionName: "price",
|
|
6124
|
+
args: []
|
|
6125
|
+
});
|
|
6126
|
+
const prices = await batchMulticall({
|
|
6127
|
+
client,
|
|
6128
|
+
calls: priceCalls,
|
|
6129
|
+
batchSize,
|
|
6130
|
+
retryAttempts,
|
|
6131
|
+
retryDelayMs,
|
|
6132
|
+
blockNumber
|
|
6133
|
+
});
|
|
6134
|
+
for (let i = 0; i < oraclesBatch.length; i++) {
|
|
6135
|
+
const oracle = oraclesBatch[i];
|
|
6136
|
+
const price = prices[i];
|
|
6137
|
+
out.set(oracle, price);
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
return out;
|
|
6141
|
+
}
|
|
6142
|
+
|
|
6143
|
+
//#endregion
|
|
6144
|
+
//#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
|
|
6145
|
+
/**
|
|
6146
|
+
* Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
|
|
6147
|
+
* @notice This method does not mutate positions and returns a new array.
|
|
6148
|
+
*
|
|
6149
|
+
* @param parameters - {@link snapshotERC20Positions.Parameters}
|
|
6150
|
+
* @returns Positions - {@link snapshotERC20Positions.ReturnType}
|
|
6151
|
+
*/
|
|
6152
|
+
async function snapshotERC20Positions(parameters) {
|
|
6153
|
+
const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6154
|
+
if (oldPositions.length === 0) return [];
|
|
6155
|
+
const calls = [];
|
|
6156
|
+
for (const position of oldPositions) calls.push({
|
|
6157
|
+
address: position.contract,
|
|
6158
|
+
abi: erc20Abi,
|
|
6159
|
+
functionName: "balanceOf",
|
|
6160
|
+
args: [position.user]
|
|
6161
|
+
});
|
|
6162
|
+
const balances = await batchMulticall({
|
|
6163
|
+
client: parameters.client,
|
|
6164
|
+
calls,
|
|
6165
|
+
blockNumber: BigInt(blockNumber),
|
|
6166
|
+
batchSize: maxBatchSize,
|
|
6167
|
+
retryAttempts,
|
|
6168
|
+
retryDelayMs
|
|
6169
|
+
});
|
|
6170
|
+
const positions = [];
|
|
6171
|
+
for (let i = 0; i < balances.length; i++) {
|
|
6172
|
+
const oldPosition = oldPositions[i];
|
|
6173
|
+
if (!oldPosition) continue;
|
|
6174
|
+
positions.push({
|
|
6175
|
+
...oldPosition,
|
|
6176
|
+
balance: balances[i],
|
|
6177
|
+
blockNumber
|
|
6178
|
+
});
|
|
6179
|
+
}
|
|
6180
|
+
return positions;
|
|
6181
|
+
}
|
|
6182
|
+
|
|
6183
|
+
//#endregion
|
|
6184
|
+
//#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
|
|
6185
|
+
/**
|
|
6186
|
+
* Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
|
|
6187
|
+
* @notice This method does not mutate positions and returns a new array.
|
|
6188
|
+
*
|
|
6189
|
+
* @param parameters - {@link snapshotVaultPositions.Parameters}
|
|
6190
|
+
* @returns Positions - {@link snapshotVaultPositions.ReturnType}
|
|
6191
|
+
*/
|
|
6192
|
+
async function snapshotVaultPositions(parameters) {
|
|
6193
|
+
const logger = getLogger();
|
|
6194
|
+
const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6195
|
+
const calls = [];
|
|
6196
|
+
const contracts = /* @__PURE__ */ new Map();
|
|
6197
|
+
const positions = structuredClone(oldPositions);
|
|
6198
|
+
for (const position of positions) {
|
|
6199
|
+
calls.push({
|
|
6200
|
+
address: position.contract,
|
|
6201
|
+
abi: MetaMorpho,
|
|
6202
|
+
functionName: "balanceOf",
|
|
6203
|
+
args: [position.user],
|
|
6204
|
+
convertToAssets: (shares) => {
|
|
6205
|
+
const contract = contracts.get(position.contract.toLowerCase());
|
|
6206
|
+
if (!contract) return;
|
|
6207
|
+
if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
|
|
6208
|
+
try {
|
|
6209
|
+
position.balance = convertToAssets({
|
|
6210
|
+
shares,
|
|
6211
|
+
totalAssets: contract.totalAssets,
|
|
6212
|
+
totalSupply: contract.totalSupply,
|
|
6213
|
+
decimalsOffset: contract.decimalsOffset
|
|
6214
|
+
});
|
|
6215
|
+
position.asset = contract.asset;
|
|
6216
|
+
position.blockNumber = blockNumber;
|
|
6217
|
+
} catch (err) {
|
|
6218
|
+
if (err instanceof DenominatorIsZeroError) {
|
|
6219
|
+
logger.error({
|
|
6220
|
+
msg: "Failed to convert shares to assets",
|
|
6221
|
+
chain_id: client.chain.id,
|
|
6222
|
+
block_number: blockNumber,
|
|
6223
|
+
position_contract: position.contract,
|
|
6224
|
+
position_user: position.user,
|
|
6225
|
+
shares,
|
|
6226
|
+
err
|
|
6227
|
+
});
|
|
6228
|
+
return;
|
|
6229
|
+
}
|
|
6230
|
+
throw err;
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
});
|
|
6234
|
+
if (contracts.has(position.contract.toLowerCase())) continue;
|
|
6235
|
+
calls.push({
|
|
6236
|
+
address: position.contract,
|
|
6237
|
+
abi: MetaMorpho,
|
|
6238
|
+
functionName: "DECIMALS_OFFSET",
|
|
6239
|
+
args: []
|
|
6240
|
+
}, {
|
|
6241
|
+
address: position.contract,
|
|
6242
|
+
abi: MetaMorpho,
|
|
6243
|
+
functionName: "totalAssets",
|
|
6244
|
+
args: []
|
|
6245
|
+
}, {
|
|
6246
|
+
address: position.contract,
|
|
6247
|
+
abi: MetaMorpho,
|
|
6248
|
+
functionName: "totalSupply",
|
|
6249
|
+
args: []
|
|
6250
|
+
}, {
|
|
6251
|
+
address: position.contract,
|
|
6252
|
+
abi: MetaMorpho,
|
|
6253
|
+
functionName: "asset",
|
|
6254
|
+
args: []
|
|
6255
|
+
});
|
|
6256
|
+
contracts.set(position.contract.toLowerCase(), {
|
|
6257
|
+
decimalsOffset: void 0,
|
|
6258
|
+
totalAssets: void 0,
|
|
6259
|
+
totalSupply: void 0,
|
|
6260
|
+
asset: void 0
|
|
6261
|
+
});
|
|
6262
|
+
}
|
|
6263
|
+
const results = await batchMulticall({
|
|
6264
|
+
client,
|
|
6265
|
+
calls,
|
|
6266
|
+
blockNumber: BigInt(blockNumber),
|
|
6267
|
+
batchSize: maxBatchSize,
|
|
6268
|
+
retryAttempts,
|
|
6269
|
+
retryDelayMs
|
|
6270
|
+
});
|
|
6271
|
+
const convertToAssetsList = [];
|
|
6272
|
+
for (let i = 0; i < results.length; i++) {
|
|
6273
|
+
const call = calls[i];
|
|
6274
|
+
const value = results[i];
|
|
6275
|
+
const contract = contracts.get(call.address.toLowerCase());
|
|
6276
|
+
if (!contract) continue;
|
|
6277
|
+
switch (call.functionName) {
|
|
6278
|
+
case "balanceOf":
|
|
6279
|
+
convertToAssetsList.push(() => call.convertToAssets(value));
|
|
6280
|
+
break;
|
|
6281
|
+
case "DECIMALS_OFFSET":
|
|
6282
|
+
contract.decimalsOffset = value;
|
|
6283
|
+
break;
|
|
6284
|
+
case "totalAssets":
|
|
6285
|
+
contract.totalAssets = value;
|
|
6286
|
+
break;
|
|
6287
|
+
case "totalSupply":
|
|
6288
|
+
contract.totalSupply = value;
|
|
6289
|
+
break;
|
|
6290
|
+
case "asset":
|
|
6291
|
+
contract.asset = value.toLowerCase();
|
|
6292
|
+
break;
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
for (const convertToAssets of convertToAssetsList) convertToAssets();
|
|
6296
|
+
return positions;
|
|
6297
|
+
}
|
|
6298
|
+
|
|
6299
|
+
//#endregion
|
|
6300
|
+
//#region src/indexer/collectors/CollectFunctions/collectPositions.ts
|
|
6301
|
+
async function* collectPositions(parameters) {
|
|
6302
|
+
let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
|
|
6303
|
+
const logger = getLogger();
|
|
6304
|
+
let startBlock = blockNumber;
|
|
6305
|
+
let reorgDetected = false;
|
|
6306
|
+
const TransferEvent = {
|
|
6307
|
+
type: "event",
|
|
6308
|
+
name: "Transfer",
|
|
6309
|
+
inputs: [
|
|
6310
|
+
{
|
|
6311
|
+
name: "from",
|
|
6312
|
+
type: "address",
|
|
6313
|
+
indexed: true
|
|
6314
|
+
},
|
|
6315
|
+
{
|
|
6316
|
+
name: "to",
|
|
6317
|
+
type: "address",
|
|
6318
|
+
indexed: true
|
|
6319
|
+
},
|
|
6320
|
+
{
|
|
6321
|
+
name: "value",
|
|
6322
|
+
type: "uint256",
|
|
6323
|
+
indexed: false
|
|
6324
|
+
}
|
|
6325
|
+
]
|
|
6326
|
+
};
|
|
6327
|
+
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
6328
|
+
const stream = streamLogs({
|
|
6329
|
+
client,
|
|
6330
|
+
event: TransferEvent,
|
|
6331
|
+
blockNumberGte: blockNumber,
|
|
6332
|
+
blockNumberLte: latestBlockNumberChain,
|
|
6333
|
+
order: "asc",
|
|
6334
|
+
options: {
|
|
6335
|
+
maxBatchSize,
|
|
6336
|
+
blockWindow
|
|
6337
|
+
}
|
|
6338
|
+
});
|
|
6339
|
+
for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
|
|
6340
|
+
blockNumber = lastStreamBlockNumber;
|
|
6341
|
+
const parsedLogs = parseEventLogs({
|
|
6342
|
+
abi: [TransferEvent],
|
|
6343
|
+
logs
|
|
6344
|
+
});
|
|
6345
|
+
const transfers = [];
|
|
6346
|
+
for (const log of parsedLogs) {
|
|
6347
|
+
if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
|
|
6348
|
+
logger.debug({
|
|
6349
|
+
collector,
|
|
6350
|
+
chainId: client.chain.id,
|
|
6351
|
+
msg: "Skipping log because it is missing required fields"
|
|
6352
|
+
});
|
|
6353
|
+
continue;
|
|
6354
|
+
}
|
|
6355
|
+
transfers.push(from$8({
|
|
6356
|
+
id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
|
|
6357
|
+
chainId: client.chain.id,
|
|
6358
|
+
contract: log.address,
|
|
6359
|
+
from: log.args.from,
|
|
6360
|
+
to: log.args.to,
|
|
6361
|
+
value: log.args.value,
|
|
6362
|
+
blockNumber: Number(log.blockNumber)
|
|
6363
|
+
}));
|
|
6364
|
+
}
|
|
6365
|
+
const { positions } = await db.positions.get({
|
|
6366
|
+
chainId: client.chain.id,
|
|
6367
|
+
filled: false
|
|
6368
|
+
});
|
|
6369
|
+
const newPositions = [];
|
|
6370
|
+
try {
|
|
6371
|
+
newPositions.push(...(await _snapshot({
|
|
6372
|
+
positions,
|
|
6373
|
+
blockNumber: latestBlockNumberChain,
|
|
6374
|
+
client,
|
|
6375
|
+
maxBatchSize,
|
|
6376
|
+
retryAttempts,
|
|
6377
|
+
retryDelayMs
|
|
6378
|
+
})).map((p) => ({
|
|
6379
|
+
...p,
|
|
6380
|
+
blockNumber: p.blockNumber + 1
|
|
6381
|
+
})));
|
|
6382
|
+
} catch (err) {
|
|
6383
|
+
logger.error({
|
|
6384
|
+
msg: "Failed to snapshot new empty positions",
|
|
6385
|
+
collector,
|
|
6386
|
+
chain_id: client.chain.id,
|
|
6387
|
+
block_number: latestBlockNumberChain,
|
|
6388
|
+
err
|
|
6389
|
+
});
|
|
6390
|
+
yield startBlock;
|
|
6391
|
+
return;
|
|
6392
|
+
}
|
|
6393
|
+
try {
|
|
6394
|
+
await db.transaction(async (dbTx) => {
|
|
6395
|
+
const insertPositions = async () => {
|
|
6396
|
+
if (newPositions.length === 0) return;
|
|
6397
|
+
try {
|
|
6398
|
+
const count = await dbTx.positions.upsert(newPositions);
|
|
6399
|
+
logger.info({
|
|
6400
|
+
msg: `New positions`,
|
|
6401
|
+
collector,
|
|
6402
|
+
count,
|
|
6403
|
+
chain_id: client.chain.id,
|
|
6404
|
+
block_number: latestBlockNumberChain
|
|
6405
|
+
});
|
|
6406
|
+
} catch (err) {
|
|
6407
|
+
throw new InsertPositionsError(err);
|
|
6408
|
+
}
|
|
6409
|
+
};
|
|
6410
|
+
const insertTransfers = async () => {
|
|
6411
|
+
if (transfers.length === 0) return;
|
|
6412
|
+
try {
|
|
6413
|
+
const created = await dbTx.transfers.create(transfers);
|
|
6414
|
+
logger.info({
|
|
6415
|
+
msg: `New transfers`,
|
|
6416
|
+
collector,
|
|
6417
|
+
count: created,
|
|
6418
|
+
chain_id: client.chain.id,
|
|
6419
|
+
block_range: [startBlock, blockNumber]
|
|
6420
|
+
});
|
|
6421
|
+
} catch (err) {
|
|
6422
|
+
throw new InsertTransfersError(err);
|
|
6423
|
+
}
|
|
6424
|
+
};
|
|
6425
|
+
const saveBlockNumber = async () => {
|
|
6426
|
+
try {
|
|
6427
|
+
await dbTx.blocks.advanceCollector({
|
|
6428
|
+
collectorName: collector,
|
|
6429
|
+
chainId: client.chain.id,
|
|
6430
|
+
blockNumber,
|
|
6431
|
+
epoch
|
|
6432
|
+
});
|
|
6433
|
+
} catch (_) {
|
|
6434
|
+
throw new ReorgError(blockNumber);
|
|
6435
|
+
}
|
|
6436
|
+
};
|
|
6437
|
+
await insertPositions();
|
|
6438
|
+
await insertTransfers();
|
|
6439
|
+
await saveBlockNumber();
|
|
6440
|
+
});
|
|
6441
|
+
} catch (err) {
|
|
6442
|
+
if (err instanceof ReorgError) {
|
|
6443
|
+
logger.info({
|
|
6444
|
+
msg: "Reorg detected, positions and transfers insertion aborted",
|
|
6445
|
+
collector,
|
|
6446
|
+
count: newPositions.length,
|
|
6447
|
+
chain_id: client.chain.id,
|
|
6448
|
+
block_number: blockNumber
|
|
6449
|
+
});
|
|
6450
|
+
reorgDetected = true;
|
|
6451
|
+
}
|
|
6452
|
+
if (err instanceof InsertPositionsError) {
|
|
6453
|
+
logger.error({
|
|
6454
|
+
msg: "Failed to insert positions",
|
|
6455
|
+
collector,
|
|
6456
|
+
count: newPositions.length,
|
|
6457
|
+
chain_id: client.chain.id,
|
|
6458
|
+
block_number: latestBlockNumberChain,
|
|
6459
|
+
err
|
|
6460
|
+
});
|
|
6461
|
+
throw err.cause;
|
|
6462
|
+
}
|
|
6463
|
+
if (err instanceof InsertTransfersError) {
|
|
6464
|
+
logger.error({
|
|
6465
|
+
msg: "Failed to insert transfers",
|
|
6466
|
+
collector,
|
|
6467
|
+
count: transfers.length,
|
|
6468
|
+
chain_id: client.chain.id,
|
|
6469
|
+
block_number: blockNumber,
|
|
6470
|
+
err
|
|
6471
|
+
});
|
|
6472
|
+
throw err.cause;
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
if (!reorgDetected) {
|
|
6476
|
+
startBlock = blockNumber;
|
|
6477
|
+
if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
|
|
6478
|
+
yield blockNumber;
|
|
6479
|
+
continue;
|
|
6480
|
+
}
|
|
6481
|
+
await db.transaction(async (dbTx) => {
|
|
6482
|
+
try {
|
|
6483
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
6484
|
+
collectorName: collector,
|
|
6485
|
+
chainId: client.chain.id
|
|
6486
|
+
});
|
|
6487
|
+
blockNumber = ancestor.blockNumber;
|
|
6488
|
+
const emptied = await dbTx.positions.setEmptyAfter({
|
|
6489
|
+
chainId: client.chain.id,
|
|
6490
|
+
blockNumber: blockNumber + 1
|
|
6491
|
+
});
|
|
6492
|
+
logger.info({
|
|
6493
|
+
msg: "Reorg detected, positions set to empty",
|
|
6494
|
+
collector,
|
|
6495
|
+
count: emptied,
|
|
6496
|
+
chain_id: client.chain.id,
|
|
6497
|
+
block_number_gte: blockNumber + 1
|
|
6498
|
+
});
|
|
6499
|
+
await dbTx.blocks.advanceCollector({
|
|
6500
|
+
collectorName: collector,
|
|
6501
|
+
chainId: client.chain.id,
|
|
6502
|
+
blockNumber,
|
|
6503
|
+
epoch: ancestor.epoch
|
|
6504
|
+
});
|
|
6505
|
+
} catch (err) {
|
|
6506
|
+
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6507
|
+
logger.error({
|
|
6508
|
+
collector,
|
|
6509
|
+
chainId: client.chain.id,
|
|
6510
|
+
msg,
|
|
6511
|
+
err
|
|
6512
|
+
});
|
|
6513
|
+
throw new Error(msg);
|
|
6514
|
+
}
|
|
6515
|
+
});
|
|
6516
|
+
return;
|
|
6517
|
+
}
|
|
6518
|
+
}
|
|
6519
|
+
/**
|
|
6520
|
+
* @internal
|
|
6521
|
+
*
|
|
6522
|
+
* Snapshots positions and returns the new positions.
|
|
6523
|
+
* @param parameters - {@link _snapshot.Parameters}
|
|
6524
|
+
* @returns The new positions. {@link _snapshot.ReturnType}
|
|
6525
|
+
*/
|
|
6526
|
+
async function _snapshot(parameters) {
|
|
6527
|
+
const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
|
|
6528
|
+
const vaultV1Positions = [];
|
|
6529
|
+
const erc20Positions = [];
|
|
6530
|
+
for (const position of positions) switch (position.type) {
|
|
6531
|
+
case Type.VAULT_V1:
|
|
6532
|
+
vaultV1Positions.push(position);
|
|
6533
|
+
break;
|
|
6534
|
+
case Type.ERC20:
|
|
6535
|
+
erc20Positions.push(position);
|
|
6536
|
+
break;
|
|
6537
|
+
default: throw new Error("Invalid position type");
|
|
6538
|
+
}
|
|
6539
|
+
const promises = [snapshotVaultPositions({
|
|
6540
|
+
client,
|
|
6541
|
+
positions: vaultV1Positions,
|
|
6542
|
+
blockNumber,
|
|
6543
|
+
options: {
|
|
6544
|
+
maxBatchSize,
|
|
6545
|
+
retryAttempts,
|
|
6546
|
+
retryDelayMs
|
|
6547
|
+
}
|
|
6548
|
+
}), snapshotERC20Positions({
|
|
6549
|
+
client,
|
|
6550
|
+
positions: erc20Positions,
|
|
6551
|
+
blockNumber,
|
|
6552
|
+
options: {
|
|
6553
|
+
maxBatchSize,
|
|
6554
|
+
retryAttempts,
|
|
6555
|
+
retryDelayMs
|
|
6556
|
+
}
|
|
6557
|
+
})];
|
|
6558
|
+
return (await Promise.all(promises)).flat();
|
|
6559
|
+
}
|
|
6560
|
+
var InsertPositionsError = class extends BaseError {
|
|
6561
|
+
name = "InsertPositionsError";
|
|
6562
|
+
constructor(err) {
|
|
6563
|
+
super("Failed to insert positions", { cause: err });
|
|
6564
|
+
}
|
|
6565
|
+
};
|
|
6566
|
+
var InsertTransfersError = class extends BaseError {
|
|
6567
|
+
name = "InsertTransfersError";
|
|
6568
|
+
constructor(err) {
|
|
6569
|
+
super("Failed to insert transfers", { cause: err });
|
|
6570
|
+
}
|
|
6571
|
+
};
|
|
6572
|
+
|
|
6573
|
+
//#endregion
|
|
6574
|
+
//#region src/indexer/collectors/CollectFunctions/collectPrices.ts
|
|
6575
|
+
/**
|
|
6576
|
+
* Collects oracle prices from on-chain oracles and persists them.
|
|
6577
|
+
*
|
|
6578
|
+
* - Reads oracle definitions as {@link Oracle.Oracle}.
|
|
6579
|
+
* - Uses chain metadata from {@link Chain.Chain}.
|
|
6580
|
+
*
|
|
6581
|
+
* @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
|
|
6582
|
+
* with a client supporting {@link PublicActions.multicall | multicall}.
|
|
6583
|
+
* @yields Latest processed block number after each successful update.
|
|
6584
|
+
*/
|
|
6585
|
+
async function* collectPrices(parameters) {
|
|
6586
|
+
const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
|
|
6587
|
+
const logger = getLogger();
|
|
6588
|
+
let blockNumber = parameters.lastBlockNumber;
|
|
6589
|
+
const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
|
|
6590
|
+
const updatedOracles = [];
|
|
6591
|
+
try {
|
|
6592
|
+
const pricesMap = await fetchOraclePrices({
|
|
6593
|
+
client,
|
|
6594
|
+
oracles: oracles.map((oracle) => oracle.address),
|
|
6595
|
+
options: {
|
|
6596
|
+
batchSize: maxBatchSize,
|
|
6597
|
+
blockNumber: latestBlockNumberChain,
|
|
6598
|
+
retryAttempts,
|
|
6599
|
+
retryDelayMs
|
|
6600
|
+
}
|
|
6601
|
+
});
|
|
6602
|
+
for (const oracle of oracles) {
|
|
6603
|
+
const price = pricesMap.get(oracle.address);
|
|
6604
|
+
if (price !== void 0) updatedOracles.push({
|
|
6605
|
+
chainId: client.chain.id,
|
|
6606
|
+
address: oracle.address,
|
|
6607
|
+
price,
|
|
6608
|
+
blockNumber: latestBlockNumberChain
|
|
6609
|
+
});
|
|
6610
|
+
}
|
|
6611
|
+
} catch (err) {
|
|
6612
|
+
logger.error({
|
|
6613
|
+
msg: "Failed to fetch oracle prices",
|
|
6614
|
+
collector,
|
|
6615
|
+
chain_id: client.chain.id,
|
|
6616
|
+
block_number: latestBlockNumberChain,
|
|
6617
|
+
err
|
|
6618
|
+
});
|
|
6619
|
+
yield blockNumber;
|
|
6620
|
+
return;
|
|
6621
|
+
}
|
|
6622
|
+
let reorgDetected = false;
|
|
6623
|
+
try {
|
|
6624
|
+
await db.transaction(async (dbTx) => {
|
|
6625
|
+
if (updatedOracles.length > 0) {
|
|
6626
|
+
await dbTx.oracles.upsert(updatedOracles);
|
|
6627
|
+
logger.info({
|
|
6628
|
+
msg: "Oracle prices updated",
|
|
6629
|
+
collector,
|
|
6630
|
+
count: updatedOracles.length,
|
|
6631
|
+
chain_id: client.chain.id,
|
|
6632
|
+
block_number: latestBlockNumberChain
|
|
6633
|
+
});
|
|
6634
|
+
}
|
|
6635
|
+
try {
|
|
6636
|
+
await dbTx.blocks.advanceCollector({
|
|
6637
|
+
collectorName: collector,
|
|
6638
|
+
chainId: client.chain.id,
|
|
6639
|
+
blockNumber: latestBlockNumberChain,
|
|
6640
|
+
epoch
|
|
6641
|
+
});
|
|
6642
|
+
} catch (_) {
|
|
6643
|
+
throw new ReorgError(latestBlockNumberChain);
|
|
6644
|
+
}
|
|
6645
|
+
blockNumber = latestBlockNumberChain;
|
|
6646
|
+
});
|
|
6647
|
+
} catch (err) {
|
|
6648
|
+
if (err instanceof ReorgError) {
|
|
6649
|
+
logger.info({
|
|
6650
|
+
msg: "Reorg detected, prices update aborted",
|
|
6651
|
+
collector,
|
|
6652
|
+
count: updatedOracles.length,
|
|
6653
|
+
chain_id: client.chain.id,
|
|
6654
|
+
block_number: latestBlockNumberChain
|
|
6655
|
+
});
|
|
6656
|
+
reorgDetected = true;
|
|
6657
|
+
} else throw new Error("Failed to collect oracle prices", { cause: err });
|
|
6658
|
+
}
|
|
6659
|
+
if (!reorgDetected) {
|
|
6660
|
+
yield blockNumber;
|
|
6661
|
+
return;
|
|
6662
|
+
}
|
|
6663
|
+
await db.transaction(async (dbTx) => {
|
|
6664
|
+
try {
|
|
6665
|
+
const ancestor = await dbTx.blocks.getCollector({
|
|
6666
|
+
collectorName: collector,
|
|
6667
|
+
chainId: client.chain.id
|
|
6668
|
+
});
|
|
6669
|
+
blockNumber = ancestor.blockNumber;
|
|
6670
|
+
await dbTx.blocks.advanceCollector({
|
|
6671
|
+
collectorName: collector,
|
|
6672
|
+
chainId: client.chain.id,
|
|
6673
|
+
blockNumber,
|
|
6674
|
+
epoch: ancestor.epoch
|
|
6675
|
+
});
|
|
6676
|
+
} catch (err) {
|
|
6677
|
+
const msg = "Failed to revert to ancestor block when handling reorg.";
|
|
6678
|
+
logger.error({
|
|
6679
|
+
collector,
|
|
6680
|
+
chainId: client.chain.id,
|
|
6681
|
+
msg,
|
|
6682
|
+
err
|
|
6683
|
+
});
|
|
6684
|
+
throw new Error(msg);
|
|
6685
|
+
}
|
|
6686
|
+
});
|
|
6687
|
+
yield blockNumber;
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6690
|
+
//#endregion
|
|
6691
|
+
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
6692
|
+
function createBuilder(parameters) {
|
|
6693
|
+
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
|
|
6694
|
+
const createCollector = (name, collect) => create$16({
|
|
6695
|
+
name,
|
|
6696
|
+
collect,
|
|
6697
|
+
client,
|
|
6698
|
+
db,
|
|
6699
|
+
options: {
|
|
6700
|
+
maxBlockNumber,
|
|
6701
|
+
interval
|
|
6702
|
+
}
|
|
6703
|
+
});
|
|
6704
|
+
return {
|
|
6705
|
+
buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
|
|
6706
|
+
return createCollector("offers", (p) => collectOffersV2({
|
|
6707
|
+
...p,
|
|
6708
|
+
gatekeeper,
|
|
6709
|
+
collector: "offers",
|
|
6710
|
+
client,
|
|
6711
|
+
options: {
|
|
6712
|
+
maxBatchSize,
|
|
6713
|
+
blockWindow
|
|
6714
|
+
}
|
|
6715
|
+
}));
|
|
6716
|
+
},
|
|
6717
|
+
buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
|
|
6718
|
+
return createCollector("consumed_events", (p) => collectConsumedEvents({
|
|
6719
|
+
...p,
|
|
6720
|
+
collector: "consumed_events",
|
|
6721
|
+
options: {
|
|
6722
|
+
maxBatchSize,
|
|
6723
|
+
blockWindow
|
|
6724
|
+
}
|
|
6725
|
+
}));
|
|
6726
|
+
},
|
|
6727
|
+
buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6728
|
+
return createCollector("prices", (p) => collectPrices({
|
|
6729
|
+
...p,
|
|
6730
|
+
collector: "prices",
|
|
6731
|
+
options: {
|
|
6732
|
+
maxBatchSize,
|
|
6733
|
+
retryAttempts,
|
|
6734
|
+
retryDelayMs
|
|
6735
|
+
}
|
|
6736
|
+
}));
|
|
6737
|
+
},
|
|
6738
|
+
buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
|
|
6739
|
+
return createCollector("positions", (p) => collectPositions({
|
|
6740
|
+
...p,
|
|
6741
|
+
collector: "positions",
|
|
6742
|
+
options: {
|
|
6743
|
+
maxBatchSize,
|
|
6744
|
+
retryAttempts,
|
|
6745
|
+
retryDelayMs,
|
|
6746
|
+
blockWindow
|
|
6747
|
+
}
|
|
6748
|
+
}));
|
|
6749
|
+
}
|
|
6750
|
+
};
|
|
6751
|
+
}
|
|
6752
|
+
|
|
6753
|
+
//#endregion
|
|
6754
|
+
//#region src/indexer/collectors/Collectors.ts
|
|
6755
|
+
const from$2 = (parameters) => {
|
|
6756
|
+
const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
|
|
6757
|
+
const collectorBuilder = createBuilder({
|
|
6758
|
+
client,
|
|
6759
|
+
db,
|
|
6760
|
+
gatekeeper,
|
|
6761
|
+
options: {
|
|
6762
|
+
maxBlockNumber,
|
|
6763
|
+
blockWindow,
|
|
6764
|
+
interval
|
|
6765
|
+
}
|
|
6766
|
+
});
|
|
6767
|
+
return {
|
|
6768
|
+
offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
|
|
6769
|
+
consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
|
|
6770
|
+
pricesCollector: collectorBuilder.buildPricesCollector({ options: {
|
|
6771
|
+
maxBatchSize,
|
|
6772
|
+
retryAttempts,
|
|
6773
|
+
retryDelayMs
|
|
6774
|
+
} }),
|
|
6775
|
+
positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
|
|
6776
|
+
maxBatchSize,
|
|
6777
|
+
retryAttempts,
|
|
6778
|
+
retryDelayMs
|
|
6779
|
+
} })
|
|
6780
|
+
};
|
|
6781
|
+
};
|
|
6782
|
+
|
|
6783
|
+
//#endregion
|
|
6784
|
+
//#region src/indexer/Indexer.ts
|
|
6785
|
+
function from$1(config) {
|
|
6786
|
+
const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
|
|
6787
|
+
const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
|
|
6788
|
+
client,
|
|
6789
|
+
db,
|
|
6790
|
+
gatekeeper,
|
|
6791
|
+
maxBatchSize,
|
|
6792
|
+
maxBlockNumber,
|
|
6793
|
+
blockWindow,
|
|
6794
|
+
interval,
|
|
6795
|
+
retryAttempts,
|
|
6796
|
+
retryDelayMs
|
|
6797
|
+
});
|
|
6798
|
+
return create$18({
|
|
6799
|
+
client,
|
|
6800
|
+
collectors: [
|
|
6801
|
+
offersCollector,
|
|
6802
|
+
consumedEventsCollector,
|
|
6803
|
+
positionsCollector,
|
|
6804
|
+
pricesCollector
|
|
6805
|
+
]
|
|
6806
|
+
});
|
|
6807
|
+
}
|
|
6808
|
+
function create$18(params) {
|
|
6809
|
+
const { collectors, client } = params;
|
|
6810
|
+
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
6811
|
+
const tracer = getTracer(`router.${indexerId}`);
|
|
6812
|
+
const iterators = collectors.map((collector) => collector.collect());
|
|
6813
|
+
const next = async () => {
|
|
6814
|
+
await startActiveSpan(tracer, `${indexerId}.next`, async () => {
|
|
6815
|
+
await Promise.all(iterators.map((iterator) => iterator.next()));
|
|
6816
|
+
});
|
|
6817
|
+
};
|
|
6818
|
+
const _return = async () => {
|
|
6819
|
+
await Promise.all(iterators.map(async (iterator) => iterator.return()));
|
|
6820
|
+
};
|
|
6821
|
+
return {
|
|
6822
|
+
start: () => {
|
|
6823
|
+
const stops = collectors.map((collector) => start(collector));
|
|
6824
|
+
return () => {
|
|
6825
|
+
stops.forEach((stop) => {
|
|
6826
|
+
stop();
|
|
6827
|
+
});
|
|
6828
|
+
};
|
|
6829
|
+
},
|
|
6830
|
+
next,
|
|
6831
|
+
return: _return
|
|
6832
|
+
};
|
|
6833
|
+
}
|
|
6834
|
+
|
|
6835
|
+
//#endregion
|
|
6836
|
+
//#region src/indexer/collectors/Admin.ts
|
|
6837
|
+
function create$17(parameters) {
|
|
6838
|
+
const collector = "admin";
|
|
6839
|
+
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
6840
|
+
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
6841
|
+
let finalizedBlock = null;
|
|
6842
|
+
let unfinalizedBlocks = [];
|
|
6843
|
+
let initialized = false;
|
|
6844
|
+
const logger = getLogger();
|
|
6845
|
+
let isMaxBlockNumberReached = false;
|
|
6846
|
+
let tick = 0;
|
|
6847
|
+
return { syncBlock: async () => {
|
|
6848
|
+
if (!initialized) {
|
|
6849
|
+
await Promise.all(names.map((collectorName) => db.blocks.init({
|
|
6850
|
+
chainId: client.chain.id,
|
|
6851
|
+
collectorName
|
|
6852
|
+
})));
|
|
6853
|
+
initialized = true;
|
|
6854
|
+
}
|
|
6855
|
+
if (isMaxBlockNumberReached) return true;
|
|
6856
|
+
const head = await client.getBlock({
|
|
6857
|
+
blockTag: "latest",
|
|
6858
|
+
includeTransactions: false
|
|
6859
|
+
});
|
|
6860
|
+
await db.transaction(async (dbTx) => {
|
|
6861
|
+
const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
6862
|
+
if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
|
|
6863
|
+
logger.info({
|
|
6864
|
+
msg: `Head is greater than max block number`,
|
|
6865
|
+
collector,
|
|
6866
|
+
chainId: client.chain.id,
|
|
6867
|
+
block_number: head.number,
|
|
6868
|
+
max_block_number: maxBlockNumber
|
|
6869
|
+
});
|
|
6870
|
+
await dbTx.blocks.handleReorg({
|
|
6871
|
+
chainId: client.chain.id,
|
|
6872
|
+
blockNumber: maxBlockNumber,
|
|
6873
|
+
epoch: epoch + 1n
|
|
6874
|
+
});
|
|
6875
|
+
isMaxBlockNumberReached = true;
|
|
6876
|
+
return isMaxBlockNumberReached;
|
|
6877
|
+
}
|
|
6878
|
+
finalizedBlock = await fetchFinalizedBlock({
|
|
6879
|
+
tick,
|
|
6880
|
+
client,
|
|
6881
|
+
logger,
|
|
6882
|
+
collector,
|
|
6883
|
+
unfinalizedBlocks,
|
|
6884
|
+
previousFinalizedBlock: finalizedBlock
|
|
6885
|
+
});
|
|
6886
|
+
tick++;
|
|
6887
|
+
let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
6888
|
+
client,
|
|
6889
|
+
block: head,
|
|
6890
|
+
unfinalizedBlocks,
|
|
6891
|
+
finalizedBlock,
|
|
6892
|
+
logger,
|
|
6893
|
+
collector,
|
|
6894
|
+
maxBatchSize
|
|
6895
|
+
});
|
|
6896
|
+
unfinalizedBlocks = newUnfinalizedBlocks;
|
|
6897
|
+
const blockNumber = Number(returnedBlock.number);
|
|
6898
|
+
didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
|
|
6899
|
+
if (didReorgHappened) await dbTx.blocks.handleReorg({
|
|
6900
|
+
chainId: client.chain.id,
|
|
6901
|
+
blockNumber,
|
|
6902
|
+
epoch: epoch + 1n
|
|
6903
|
+
});
|
|
6904
|
+
else await dbTx.blocks.advanceChain({
|
|
6905
|
+
chainId: client.chain.id,
|
|
6906
|
+
blockNumber,
|
|
6907
|
+
epoch
|
|
6908
|
+
});
|
|
6909
|
+
});
|
|
6910
|
+
return isMaxBlockNumberReached;
|
|
6911
|
+
} };
|
|
6912
|
+
}
|
|
6913
|
+
const commonAncestor = (block, unfinalizedBlocks) => {
|
|
6914
|
+
const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
|
|
6915
|
+
if (parent) return parent;
|
|
6916
|
+
return null;
|
|
6917
|
+
};
|
|
6918
|
+
const fetchFinalizedBlock = async (parameters) => {
|
|
6919
|
+
let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
|
|
6920
|
+
let finalizedBlock = previousFinalizedBlock;
|
|
6921
|
+
if (tick % 20 === 0 || previousFinalizedBlock === null) {
|
|
6922
|
+
finalizedBlock = await client.getBlock({
|
|
6923
|
+
blockTag: "finalized",
|
|
6924
|
+
includeTransactions: false
|
|
6925
|
+
});
|
|
6926
|
+
if (finalizedBlock === null || finalizedBlock.number === null) {
|
|
6927
|
+
const msg = "Failed to get finalized block";
|
|
6928
|
+
logger.fatal({
|
|
6929
|
+
collector,
|
|
6930
|
+
chainId: client.chain.id,
|
|
6931
|
+
msg
|
|
6932
|
+
});
|
|
6933
|
+
throw new Error(msg);
|
|
6934
|
+
}
|
|
6935
|
+
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
|
|
6936
|
+
}
|
|
6937
|
+
if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
|
|
6938
|
+
const msg = "Failed to get finalized block";
|
|
6939
|
+
logger.fatal({
|
|
6940
|
+
collector,
|
|
6941
|
+
chainId: client.chain.id,
|
|
6942
|
+
msg
|
|
6943
|
+
});
|
|
6944
|
+
throw new Error(msg);
|
|
6945
|
+
}
|
|
6946
|
+
return {
|
|
6947
|
+
hash: finalizedBlock.hash,
|
|
6948
|
+
number: finalizedBlock.number,
|
|
6949
|
+
parentHash: finalizedBlock.parentHash
|
|
6950
|
+
};
|
|
6951
|
+
};
|
|
6952
|
+
const reconcile = async (parameters) => {
|
|
6953
|
+
let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
|
|
6954
|
+
const chain = client.chain;
|
|
6955
|
+
if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
|
|
6956
|
+
const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
|
|
6957
|
+
if (latestBlock === void 0) {
|
|
6958
|
+
const newBlock = {
|
|
6959
|
+
hash: block.hash,
|
|
6960
|
+
number: block.number,
|
|
6961
|
+
parentHash: block.parentHash
|
|
6962
|
+
};
|
|
6963
|
+
unfinalizedBlocks.push(newBlock);
|
|
6964
|
+
return {
|
|
6965
|
+
block: newBlock,
|
|
6966
|
+
didReorgHappened: false,
|
|
6967
|
+
unfinalizedBlocks
|
|
6968
|
+
};
|
|
6969
|
+
}
|
|
6970
|
+
if (latestBlock.hash === block.hash) return {
|
|
6971
|
+
block: latestBlock,
|
|
6972
|
+
didReorgHappened: false,
|
|
6973
|
+
unfinalizedBlocks
|
|
6974
|
+
};
|
|
6975
|
+
if (latestBlock.number >= block.number) {
|
|
6976
|
+
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
6977
|
+
logger.info({
|
|
6978
|
+
msg: `Reorg detected, latestBlock.number >= block.number`,
|
|
6979
|
+
collector,
|
|
6980
|
+
chain_id: chain.id,
|
|
6981
|
+
ancestor: ancestor.number,
|
|
6982
|
+
latest_block_number: latestBlock.number,
|
|
6983
|
+
block_number: block.number
|
|
6984
|
+
});
|
|
6985
|
+
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
6986
|
+
return {
|
|
6987
|
+
block: ancestor,
|
|
6988
|
+
didReorgHappened: true,
|
|
6989
|
+
unfinalizedBlocks
|
|
6990
|
+
};
|
|
6991
|
+
}
|
|
6992
|
+
if (latestBlock.number + 1n < block.number) {
|
|
6993
|
+
logger.debug({
|
|
6994
|
+
collector,
|
|
6995
|
+
chain_id: chain.id,
|
|
6996
|
+
block_range: [latestBlock.number, block.number],
|
|
6997
|
+
msg: `Missing blocks`
|
|
6998
|
+
});
|
|
6999
|
+
const missingBlockNumbers = (() => {
|
|
7000
|
+
const missingBlockNumbers = [];
|
|
7001
|
+
let start = latestBlock.number + 1n;
|
|
7002
|
+
const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
|
|
7003
|
+
while (start < threshold) {
|
|
7004
|
+
missingBlockNumbers.push(start);
|
|
7005
|
+
start = start + 1n;
|
|
7006
|
+
}
|
|
7007
|
+
return missingBlockNumbers;
|
|
7008
|
+
})();
|
|
7009
|
+
const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
|
|
7010
|
+
blockNumber,
|
|
7011
|
+
includeTransactions: false
|
|
7012
|
+
}))));
|
|
7013
|
+
for (const missingBlock of missingBlocks) {
|
|
7014
|
+
const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
|
|
7015
|
+
client,
|
|
7016
|
+
block: missingBlock,
|
|
7017
|
+
unfinalizedBlocks,
|
|
7018
|
+
finalizedBlock,
|
|
7019
|
+
logger,
|
|
7020
|
+
collector,
|
|
7021
|
+
maxBatchSize
|
|
7022
|
+
});
|
|
7023
|
+
if (returnedBlock.number !== missingBlock.number) return {
|
|
7024
|
+
block: returnedBlock,
|
|
7025
|
+
didReorgHappened,
|
|
7026
|
+
unfinalizedBlocks: newUnfinalizedBlocks
|
|
7027
|
+
};
|
|
7028
|
+
}
|
|
7029
|
+
return reconcile({
|
|
7030
|
+
client,
|
|
7031
|
+
block,
|
|
7032
|
+
unfinalizedBlocks,
|
|
7033
|
+
finalizedBlock,
|
|
7034
|
+
logger,
|
|
7035
|
+
collector,
|
|
7036
|
+
maxBatchSize
|
|
7037
|
+
});
|
|
7038
|
+
}
|
|
7039
|
+
if (block.parentHash !== latestBlock.hash) {
|
|
7040
|
+
const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
|
|
7041
|
+
logger.info({
|
|
7042
|
+
msg: `Reorg detected, block parent hash !== latest block hash`,
|
|
7043
|
+
collector,
|
|
7044
|
+
chain_id: chain.id,
|
|
7045
|
+
ancestor: ancestor.number,
|
|
7046
|
+
latest_block_number: latestBlock.number,
|
|
7047
|
+
block_number: block.number
|
|
7048
|
+
});
|
|
7049
|
+
unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
|
|
7050
|
+
return {
|
|
7051
|
+
block: ancestor,
|
|
7052
|
+
didReorgHappened: true,
|
|
7053
|
+
unfinalizedBlocks
|
|
7054
|
+
};
|
|
7055
|
+
}
|
|
7056
|
+
const newBlock = {
|
|
7057
|
+
hash: block.hash,
|
|
7058
|
+
number: block.number,
|
|
7059
|
+
parentHash: block.parentHash
|
|
7060
|
+
};
|
|
7061
|
+
unfinalizedBlocks.push(newBlock);
|
|
7062
|
+
return {
|
|
7063
|
+
block: newBlock,
|
|
7064
|
+
didReorgHappened: false,
|
|
7065
|
+
unfinalizedBlocks
|
|
7066
|
+
};
|
|
7067
|
+
};
|
|
7068
|
+
|
|
7069
|
+
//#endregion
|
|
7070
|
+
//#region src/indexer/collectors/Collector.ts
|
|
7071
|
+
const names = [
|
|
7072
|
+
"offers",
|
|
7073
|
+
"consumed_events",
|
|
7074
|
+
"positions",
|
|
7075
|
+
"prices"
|
|
7076
|
+
];
|
|
7077
|
+
function create$16({ name, collect, client, db, options }) {
|
|
7078
|
+
const admin = create$17({
|
|
7079
|
+
client,
|
|
7080
|
+
db,
|
|
7081
|
+
options
|
|
7082
|
+
});
|
|
7083
|
+
return {
|
|
7084
|
+
name,
|
|
7085
|
+
chain: client.chain,
|
|
7086
|
+
client,
|
|
7087
|
+
db,
|
|
7088
|
+
interval: options.interval ?? 1e4,
|
|
7089
|
+
collect: async function* () {
|
|
7090
|
+
const collector = name;
|
|
7091
|
+
const chain = client.chain;
|
|
7092
|
+
const logger = getLogger();
|
|
7093
|
+
const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
|
|
7094
|
+
const tracer = getTracer(`router.${collectorId}`);
|
|
7095
|
+
logger.info({
|
|
7096
|
+
msg: `Collector started`,
|
|
7097
|
+
collector,
|
|
7098
|
+
chain_id: chain.id
|
|
7099
|
+
});
|
|
7100
|
+
let iterator = null;
|
|
7101
|
+
let lastBlockNumber;
|
|
7102
|
+
while (true) try {
|
|
7103
|
+
if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
|
|
7104
|
+
const { collector: collectorBlock } = await db.blocks.init({
|
|
7105
|
+
collectorName: name,
|
|
7106
|
+
chainId: chain.id
|
|
7107
|
+
});
|
|
7108
|
+
lastBlockNumber = collectorBlock.blockNumber;
|
|
7109
|
+
return collect({
|
|
7110
|
+
client,
|
|
7111
|
+
collector: name,
|
|
7112
|
+
epoch: collectorBlock.epoch,
|
|
7113
|
+
lastBlockNumber,
|
|
7114
|
+
db
|
|
7115
|
+
});
|
|
7116
|
+
});
|
|
7117
|
+
if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
|
|
7118
|
+
const isMaxBlockNumberReached = await admin.syncBlock();
|
|
7119
|
+
span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
|
|
7120
|
+
return isMaxBlockNumberReached;
|
|
7121
|
+
}) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
|
|
7122
|
+
const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
|
|
7123
|
+
if (iterator === null) throw new Error("Iterator is not initialized");
|
|
7124
|
+
const { value: blockNumber, done } = await iterator.next();
|
|
7125
|
+
return {
|
|
7126
|
+
blockNumber,
|
|
7127
|
+
done
|
|
7128
|
+
};
|
|
7129
|
+
});
|
|
7130
|
+
if (done) iterator = null;
|
|
7131
|
+
else {
|
|
7132
|
+
lastBlockNumber = blockNumber;
|
|
7133
|
+
yield blockNumber;
|
|
7134
|
+
}
|
|
7135
|
+
} catch (err) {
|
|
7136
|
+
const isError = err instanceof Error;
|
|
7137
|
+
logger.error({
|
|
7138
|
+
msg: "Collector error",
|
|
7139
|
+
collector,
|
|
7140
|
+
chain_id: chain.id,
|
|
7141
|
+
error: isError ? err.message : String(err),
|
|
7142
|
+
stack: isError ? err.stack : void 0
|
|
7143
|
+
});
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
7146
|
+
};
|
|
7147
|
+
}
|
|
7148
|
+
/** Start a collector with its own polling cadence based on chain head lag.
|
|
7149
|
+
* @param collector - The collector to start.
|
|
7150
|
+
* @returns A function to stop the collector.
|
|
7151
|
+
*/
|
|
7152
|
+
function start(collector) {
|
|
7153
|
+
let stopped = false;
|
|
7154
|
+
const it = collector.collect();
|
|
7155
|
+
const logger = getLogger();
|
|
7156
|
+
const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
|
|
7157
|
+
const tracer = getTracer(`router.${collectorId}`);
|
|
7158
|
+
const blocks = collector.db.blocks;
|
|
7159
|
+
let initialized = false;
|
|
7160
|
+
(async () => {
|
|
7161
|
+
while (!stopped) try {
|
|
7162
|
+
await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
|
|
7163
|
+
if (!initialized) {
|
|
7164
|
+
await blocks.init({
|
|
7165
|
+
chainId: collector.chain.id,
|
|
7166
|
+
collectorName: collector.name
|
|
7167
|
+
});
|
|
7168
|
+
initialized = true;
|
|
7169
|
+
}
|
|
7170
|
+
const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
|
|
7171
|
+
blockTag: "latest",
|
|
7172
|
+
includeTransactions: false
|
|
7173
|
+
}), blocks.getCollector({
|
|
7174
|
+
collectorName: collector.name,
|
|
7175
|
+
chainId: collector.chain.id
|
|
7176
|
+
})]);
|
|
7177
|
+
const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
|
|
7178
|
+
const waitSpan = tracer.startSpan(`${collectorId}.wait`);
|
|
7179
|
+
try {
|
|
7180
|
+
await wait(delay);
|
|
7181
|
+
} finally {
|
|
7182
|
+
waitSpan.end();
|
|
7183
|
+
}
|
|
7184
|
+
await it.next();
|
|
7185
|
+
});
|
|
7186
|
+
} catch (err) {
|
|
7187
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
7188
|
+
logger.error({
|
|
7189
|
+
msg: "Collector polling error",
|
|
7190
|
+
collector: collector.name,
|
|
7191
|
+
chain_id: collector.chain.id,
|
|
7192
|
+
err: error
|
|
7193
|
+
});
|
|
7194
|
+
}
|
|
7195
|
+
await it.return();
|
|
7196
|
+
})();
|
|
7197
|
+
return () => {
|
|
7198
|
+
stopped = true;
|
|
7199
|
+
};
|
|
7200
|
+
}
|
|
7201
|
+
|
|
7277
7202
|
//#endregion
|
|
7278
7203
|
//#region src/database/domains/Blocks.ts
|
|
7279
7204
|
/** Postgres implementation. */
|
|
@@ -7705,35 +7630,15 @@ async function _getOffers(db, params) {
|
|
|
7705
7630
|
AND LOWER(pc.contract) = LOWER(c.position_contract)
|
|
7706
7631
|
AND LOWER(pc."user") = LOWER(c.position_user)
|
|
7707
7632
|
),
|
|
7708
|
-
-- Compute contribution per callback in loan terms (
|
|
7633
|
+
-- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
|
|
7709
7634
|
callback_loan_contribution AS (
|
|
7710
7635
|
SELECT
|
|
7711
7636
|
cc.*,
|
|
7712
7637
|
CASE
|
|
7713
|
-
-- No lot exists: contribution is 0
|
|
7714
7638
|
WHEN cc.lot_lower IS NULL THEN 0
|
|
7715
|
-
|
|
7716
|
-
WHEN LOWER(cc.position_asset) = LOWER(cc.loan_token) THEN
|
|
7717
|
-
LEAST(
|
|
7718
|
-
cc.lot_balance,
|
|
7719
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
7720
|
-
)
|
|
7721
|
-
-- Collateral position: convert to loan using (amount * price / 10^36) * lltv / 10^18
|
|
7722
|
-
ELSE
|
|
7723
|
-
(
|
|
7724
|
-
LEAST(
|
|
7725
|
-
cc.lot_balance,
|
|
7726
|
-
COALESCE(cc.callback_amount::numeric, cc.lot_balance)
|
|
7727
|
-
) * COALESCE(collat_oracle.price::numeric, 0) / 1e36
|
|
7728
|
-
) * COALESCE(collat_info.lltv::numeric, 0) / 1e18
|
|
7639
|
+
ELSE LEAST(cc.lot_balance, COALESCE(cc.callback_amount::numeric, cc.lot_balance))
|
|
7729
7640
|
END AS contribution_in_loan
|
|
7730
7641
|
FROM callback_contributions cc
|
|
7731
|
-
LEFT JOIN ${obligationCollateralsV2} collat_info
|
|
7732
|
-
ON collat_info.obligation_id = cc.obligation_id
|
|
7733
|
-
AND LOWER(collat_info.asset) = LOWER(cc.position_asset)
|
|
7734
|
-
LEFT JOIN ${oracles} collat_oracle
|
|
7735
|
-
ON collat_oracle.chain_id = collat_info.oracle_chain_id
|
|
7736
|
-
AND LOWER(collat_oracle.address) = LOWER(collat_info.oracle_address)
|
|
7737
7642
|
),
|
|
7738
7643
|
-- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
|
|
7739
7644
|
offer_contributions AS (
|
|
@@ -7770,6 +7675,22 @@ async function _getOffers(db, params) {
|
|
|
7770
7675
|
GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
|
|
7771
7676
|
callback_address, callback_data, block_number, group_chain_id, group_maker,
|
|
7772
7677
|
consumed, chain_id, loan_token, session
|
|
7678
|
+
UNION ALL
|
|
7679
|
+
-- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
|
|
7680
|
+
SELECT
|
|
7681
|
+
p.hash, p.obligation_id, p.assets, p.price,
|
|
7682
|
+
p.obligation_units, p.obligation_shares,
|
|
7683
|
+
p.maturity, p.expiry, p.start, p.group_group,
|
|
7684
|
+
p.buy, p.callback_address, p.callback_data,
|
|
7685
|
+
p.block_number, p.group_chain_id, p.group_maker,
|
|
7686
|
+
p.consumed, p.chain_id, p.loan_token, p.session,
|
|
7687
|
+
0 AS total_available
|
|
7688
|
+
FROM paged p
|
|
7689
|
+
WHERE p.buy = false
|
|
7690
|
+
AND NOT EXISTS (
|
|
7691
|
+
SELECT 1 FROM ${offersCallbacks} oc2
|
|
7692
|
+
WHERE oc2.offer_hash = p.hash
|
|
7693
|
+
)
|
|
7773
7694
|
)
|
|
7774
7695
|
-- Final SELECT with inline takeable computation
|
|
7775
7696
|
SELECT
|
|
@@ -7792,18 +7713,24 @@ async function _getOffers(db, params) {
|
|
|
7792
7713
|
oc.block_number,
|
|
7793
7714
|
oc.session,
|
|
7794
7715
|
COALESCE(oc.total_available, 0) AS available,
|
|
7795
|
-
-- takeable
|
|
7796
|
-
|
|
7797
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
7798
|
-
|
|
7799
|
-
|
|
7716
|
+
-- takeable: sell offers use assets - consumed directly (collateral positions not indexed yet)
|
|
7717
|
+
CASE WHEN oc.buy = false
|
|
7718
|
+
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
7719
|
+
ELSE GREATEST(0, LEAST(
|
|
7720
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
7721
|
+
COALESCE(oc.total_available, 0)
|
|
7722
|
+
))
|
|
7723
|
+
END AS takeable,
|
|
7800
7724
|
c.collaterals
|
|
7801
7725
|
FROM offer_contributions oc
|
|
7802
7726
|
LEFT JOIN collats c ON c.obligation_id = oc.obligation_id
|
|
7803
|
-
WHERE
|
|
7804
|
-
oc.assets::numeric - oc.consumed::numeric
|
|
7805
|
-
|
|
7806
|
-
|
|
7727
|
+
WHERE CASE WHEN oc.buy = false
|
|
7728
|
+
THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
|
|
7729
|
+
ELSE GREATEST(0, LEAST(
|
|
7730
|
+
oc.assets::numeric - oc.consumed::numeric,
|
|
7731
|
+
COALESCE(oc.total_available, 0)
|
|
7732
|
+
))
|
|
7733
|
+
END > 0
|
|
7807
7734
|
ORDER BY
|
|
7808
7735
|
oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
|
|
7809
7736
|
oc.block_number ASC,
|
|
@@ -9682,12 +9609,34 @@ async function getBook(params, db) {
|
|
|
9682
9609
|
if (!result.success) return failure(result.error);
|
|
9683
9610
|
const query = result.data;
|
|
9684
9611
|
try {
|
|
9612
|
+
logger.debug({
|
|
9613
|
+
service: "api_controller",
|
|
9614
|
+
endpoint: "get_book",
|
|
9615
|
+
msg: "Loading book levels",
|
|
9616
|
+
obligation_id: query.obligation_id,
|
|
9617
|
+
side: query.side,
|
|
9618
|
+
limit: query.limit ?? null,
|
|
9619
|
+
has_cursor: query.cursor != null
|
|
9620
|
+
});
|
|
9685
9621
|
const { levels, nextCursor } = await db.book.get({
|
|
9686
9622
|
side: query.side,
|
|
9687
9623
|
obligationId: query.obligation_id,
|
|
9688
9624
|
cursor: query.cursor,
|
|
9689
9625
|
limit: query.limit
|
|
9690
9626
|
});
|
|
9627
|
+
const firstLevel = levels[0];
|
|
9628
|
+
logger.debug({
|
|
9629
|
+
service: "api_controller",
|
|
9630
|
+
endpoint: "get_book",
|
|
9631
|
+
msg: "Loaded book levels",
|
|
9632
|
+
obligation_id: query.obligation_id,
|
|
9633
|
+
side: query.side,
|
|
9634
|
+
levels_count: levels.length,
|
|
9635
|
+
has_next_cursor: nextCursor != null,
|
|
9636
|
+
first_level_price: firstLevel?.price.toString() ?? null,
|
|
9637
|
+
first_level_assets: firstLevel?.assets.toString() ?? null,
|
|
9638
|
+
first_level_count: firstLevel?.count ?? null
|
|
9639
|
+
});
|
|
9691
9640
|
return success({
|
|
9692
9641
|
data: levels.map(from$6),
|
|
9693
9642
|
cursor: nextCursor
|
|
@@ -10177,6 +10126,7 @@ async function getOffersQuery(db, parameters) {
|
|
|
10177
10126
|
if (cursor !== null && cursor !== void 0) {
|
|
10178
10127
|
if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
|
|
10179
10128
|
}
|
|
10129
|
+
const now = Math.floor((Date.now() - 1) / 1e3);
|
|
10180
10130
|
const collateralsLateral = db.select({ collaterals: sql`COALESCE(
|
|
10181
10131
|
jsonb_agg(
|
|
10182
10132
|
jsonb_build_object(
|
|
@@ -10188,29 +10138,57 @@ async function getOffersQuery(db, parameters) {
|
|
|
10188
10138
|
'[]'::jsonb
|
|
10189
10139
|
)`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
|
|
10190
10140
|
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
10191
|
-
const
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10141
|
+
const lotBalanceExpr = sql`GREATEST(0, LEAST(
|
|
10142
|
+
COALESCE(${positions.balance}, 0)::numeric
|
|
10143
|
+
+ COALESCE((
|
|
10144
|
+
SELECT SUM(${offsets.value}::numeric)
|
|
10145
|
+
FROM ${offsets}
|
|
10146
|
+
WHERE ${offsets.chainId} = ${callbacks.positionChainId}
|
|
10147
|
+
AND LOWER(${offsets.contract}) = LOWER(${callbacks.positionContract})
|
|
10148
|
+
AND LOWER(${offsets.user}) = LOWER(${callbacks.positionUser})
|
|
10149
|
+
), 0)
|
|
10150
|
+
- COALESCE(${lots.lower}::numeric, 0),
|
|
10151
|
+
(COALESCE(${lots.upper}::numeric, 0) - COALESCE(${lots.lower}::numeric, 0))
|
|
10152
|
+
- CASE
|
|
10153
|
+
WHEN ${offers.assets}::numeric > 0
|
|
10154
|
+
THEN COALESCE(${groups.consumed}::numeric, 0)
|
|
10155
|
+
* (COALESCE(${lots.upper}::numeric, 0) - COALESCE(${lots.lower}::numeric, 0))
|
|
10156
|
+
/ ${offers.assets}::numeric
|
|
10157
|
+
ELSE 0
|
|
10158
|
+
END
|
|
10159
|
+
))`;
|
|
10160
|
+
const contributionExpr = sql`CASE
|
|
10161
|
+
WHEN ${positions.asset} IS NULL OR ${lots.lower} IS NULL THEN 0
|
|
10162
|
+
ELSE LEAST(COALESCE(${callbacks.amount}::numeric, ${lotBalanceExpr}), ${lotBalanceExpr})
|
|
10163
|
+
END`;
|
|
10164
|
+
const availableExpr = sql`COALESCE((
|
|
10165
|
+
SELECT SUM(deduped.contribution)
|
|
10166
|
+
FROM (
|
|
10167
|
+
SELECT DISTINCT ON (
|
|
10168
|
+
${callbacks.positionChainId},
|
|
10169
|
+
LOWER(${callbacks.positionContract}),
|
|
10170
|
+
LOWER(${callbacks.positionUser})
|
|
10171
|
+
)
|
|
10172
|
+
${contributionExpr} AS contribution
|
|
10173
|
+
FROM ${offersCallbacks}
|
|
10174
|
+
INNER JOIN ${callbacks} ON ${offersCallbacks.callbackId} = ${callbacks.id}
|
|
10175
|
+
LEFT JOIN ${positions}
|
|
10176
|
+
ON ${positions.chainId} = ${callbacks.positionChainId}
|
|
10177
|
+
AND LOWER(${positions.contract}) = LOWER(${callbacks.positionContract})
|
|
10178
|
+
AND LOWER(${positions.user}) = LOWER(${callbacks.positionUser})
|
|
10179
|
+
LEFT JOIN ${lots}
|
|
10180
|
+
ON ${lots.chainId} = ${callbacks.positionChainId}
|
|
10181
|
+
AND LOWER(${lots.contract}) = LOWER(${callbacks.positionContract})
|
|
10182
|
+
AND LOWER(${lots.user}) = LOWER(${callbacks.positionUser})
|
|
10183
|
+
AND LOWER(${lots.group}) = LOWER(${offers.group})
|
|
10184
|
+
WHERE ${offersCallbacks.offerHash} = ${offers.hash}
|
|
10185
|
+
ORDER BY
|
|
10186
|
+
${callbacks.positionChainId},
|
|
10187
|
+
LOWER(${callbacks.positionContract}),
|
|
10188
|
+
LOWER(${callbacks.positionUser}),
|
|
10189
|
+
${contributionExpr} DESC
|
|
10190
|
+
) deduped
|
|
10191
|
+
), 0)`;
|
|
10214
10192
|
const rows = (await db.select({
|
|
10215
10193
|
hash: offers.hash,
|
|
10216
10194
|
maker: offers.groupMaker,
|
|
@@ -10231,18 +10209,25 @@ async function getOffersQuery(db, parameters) {
|
|
|
10231
10209
|
callbackData: offers.callbackData,
|
|
10232
10210
|
collaterals: collateralsLateral.collaterals,
|
|
10233
10211
|
blockNumber: offers.blockNumber,
|
|
10234
|
-
available: sql
|
|
10235
|
-
takeable: sql`FLOOR(GREATEST(
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10212
|
+
available: sql`${availableExpr}::numeric`.as("available"),
|
|
10213
|
+
takeable: sql`FLOOR(GREATEST(0,
|
|
10214
|
+
CASE WHEN ${offers.buy} = false
|
|
10215
|
+
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
10216
|
+
ELSE LEAST(
|
|
10217
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
10218
|
+
${availableExpr}::numeric
|
|
10219
|
+
)
|
|
10220
|
+
END
|
|
10241
10221
|
))`.as("takeable")
|
|
10242
|
-
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).
|
|
10243
|
-
${offers.
|
|
10244
|
-
|
|
10245
|
-
|
|
10222
|
+
}).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`).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,
|
|
10223
|
+
CASE WHEN ${offers.buy} = false
|
|
10224
|
+
THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
|
|
10225
|
+
ELSE LEAST(
|
|
10226
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
10227
|
+
${availableExpr}::numeric
|
|
10228
|
+
)
|
|
10229
|
+
END
|
|
10230
|
+
) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
|
|
10246
10231
|
return {
|
|
10247
10232
|
hash: row.hash,
|
|
10248
10233
|
maker: row.maker,
|
|
@@ -10352,35 +10337,6 @@ async function getUserPositions(queryParameters, db) {
|
|
|
10352
10337
|
}
|
|
10353
10338
|
}
|
|
10354
10339
|
|
|
10355
|
-
//#endregion
|
|
10356
|
-
//#region src/api/Controllers/resolveCallbackTypes.ts
|
|
10357
|
-
/**
|
|
10358
|
-
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
10359
|
-
* @param body - Request body with callback addresses. {@link CallbackTypesRequest}
|
|
10360
|
-
* @param chains - Chains to resolve callback types against. {@link Chain.Chain}
|
|
10361
|
-
* @returns Callback types grouped by chain. {@link CallbackTypesPayload}
|
|
10362
|
-
*/
|
|
10363
|
-
async function resolveCallbackTypes$1(body, chains) {
|
|
10364
|
-
const result = safeParse("callback_types", body, (issue) => issue.message);
|
|
10365
|
-
if (!result.success) return failure(result.error);
|
|
10366
|
-
const request = result.data;
|
|
10367
|
-
const chainIds = new Set(chains.map((chain) => chain.id));
|
|
10368
|
-
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
10369
|
-
if (unknown) return failure(new BadRequestError(`Unknown chain id ${unknown.chain_id}`));
|
|
10370
|
-
try {
|
|
10371
|
-
const data = resolveCallbackTypes$2({
|
|
10372
|
-
chains,
|
|
10373
|
-
request
|
|
10374
|
-
});
|
|
10375
|
-
return success({
|
|
10376
|
-
data,
|
|
10377
|
-
cursor: null
|
|
10378
|
-
});
|
|
10379
|
-
} catch (err) {
|
|
10380
|
-
return failure(err);
|
|
10381
|
-
}
|
|
10382
|
-
}
|
|
10383
|
-
|
|
10384
10340
|
//#endregion
|
|
10385
10341
|
//#region src/api/Api.ts
|
|
10386
10342
|
function from(config) {
|
|
@@ -10450,24 +10406,9 @@ function serve$1(parameters) {
|
|
|
10450
10406
|
const { statusCode, body } = await gatekeeper.validate(reqBody);
|
|
10451
10407
|
return c.json(body, statusCode);
|
|
10452
10408
|
} catch (err) {
|
|
10453
|
-
const failure$
|
|
10454
|
-
return c.json(failure$1.body, failure$1.statusCode);
|
|
10455
|
-
}
|
|
10456
|
-
});
|
|
10457
|
-
app.post("/v1/callbacks", async (c) => {
|
|
10458
|
-
let body;
|
|
10459
|
-
try {
|
|
10460
|
-
body = await c.req.json();
|
|
10461
|
-
} catch (err) {
|
|
10462
|
-
const failure$3 = failure(err);
|
|
10463
|
-
return c.json(failure$3.body, failure$3.statusCode);
|
|
10464
|
-
}
|
|
10465
|
-
if (body === null || typeof body !== "object") {
|
|
10466
|
-
const failure$2 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
10409
|
+
const failure$2 = failure(err);
|
|
10467
10410
|
return c.json(failure$2.body, failure$2.statusCode);
|
|
10468
10411
|
}
|
|
10469
|
-
const { statusCode, body: responseBody } = await resolveCallbackTypes$1(body, chainRegistry.list());
|
|
10470
|
-
return c.json(responseBody, statusCode);
|
|
10471
10412
|
});
|
|
10472
10413
|
app.get("/v1/users/:userAddress/positions", async (c) => {
|
|
10473
10414
|
const query = c.req.query();
|
|
@@ -10499,8 +10440,8 @@ function serve$1(parameters) {
|
|
|
10499
10440
|
const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
|
|
10500
10441
|
return c.json(body, statusCode);
|
|
10501
10442
|
} catch (err) {
|
|
10502
|
-
const failure$
|
|
10503
|
-
return c.json(failure$
|
|
10443
|
+
const failure$1 = failure(err);
|
|
10444
|
+
return c.json(failure$1.body, failure$1.statusCode);
|
|
10504
10445
|
}
|
|
10505
10446
|
});
|
|
10506
10447
|
app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
|
|
@@ -10591,23 +10532,11 @@ function createHttpClient(config) {
|
|
|
10591
10532
|
issues: []
|
|
10592
10533
|
};
|
|
10593
10534
|
};
|
|
10594
|
-
const getCallbackTypes = async (requestPayload) => {
|
|
10595
|
-
const response = await request("/v1/callbacks", {
|
|
10596
|
-
method: "POST",
|
|
10597
|
-
headers: { "content-type": "application/json" },
|
|
10598
|
-
body: JSON.stringify(requestPayload)
|
|
10599
|
-
});
|
|
10600
|
-
const json = await response.json();
|
|
10601
|
-
if (!response.ok) throw new Error(`Gatekeeper callbacks request failed: ${extractErrorMessage(json) ?? response.statusText}`);
|
|
10602
|
-
if (!("data" in json) || !Array.isArray(json.data)) throw new Error("Gatekeeper callbacks response is invalid.");
|
|
10603
|
-
return json.data;
|
|
10604
|
-
};
|
|
10605
10535
|
return {
|
|
10606
10536
|
baseUrl,
|
|
10607
10537
|
validate,
|
|
10608
10538
|
getConfigRules,
|
|
10609
|
-
isAllowed
|
|
10610
|
-
getCallbackTypes
|
|
10539
|
+
isAllowed
|
|
10611
10540
|
};
|
|
10612
10541
|
}
|
|
10613
10542
|
function mergeHeaders(base, extra) {
|
|
@@ -10843,35 +10772,17 @@ function createMockOffers(parameters) {
|
|
|
10843
10772
|
lltv
|
|
10844
10773
|
}));
|
|
10845
10774
|
if (!chainRegistry.getById(offer.chainId)) throw new Error(`Missing chain config for id ${offer.chainId}`);
|
|
10846
|
-
const callbackType = buy ? Type$1.BuyVaultV1Callback : Type$1.SellERC20Callback;
|
|
10847
|
-
const callbackAddress = buy ? BUY_CALLBACK_ADDRESS : SELL_CALLBACK_ADDRESS;
|
|
10848
|
-
const callbackData = buildMockCallbackData(callbackType, {
|
|
10849
|
-
...offer,
|
|
10850
|
-
collaterals
|
|
10851
|
-
});
|
|
10852
10775
|
return from$12({
|
|
10853
10776
|
...offer,
|
|
10854
10777
|
loanToken,
|
|
10855
10778
|
collaterals,
|
|
10856
10779
|
callback: {
|
|
10857
|
-
address:
|
|
10858
|
-
data:
|
|
10780
|
+
address: zeroAddress,
|
|
10781
|
+
data: "0x"
|
|
10859
10782
|
}
|
|
10860
10783
|
});
|
|
10861
10784
|
});
|
|
10862
10785
|
}
|
|
10863
|
-
function buildMockCallbackData(callbackType, offer) {
|
|
10864
|
-
const assets = offer.collaterals.map((collateral) => collateral.asset);
|
|
10865
|
-
const amounts = offer.collaterals.map(() => offer.assets);
|
|
10866
|
-
if (callbackType === Type$1.BuyVaultV1Callback) return encodeBuyVaultV1Callback({
|
|
10867
|
-
vaults: assets,
|
|
10868
|
-
amounts
|
|
10869
|
-
});
|
|
10870
|
-
return encodeSellERC20Callback({
|
|
10871
|
-
collaterals: assets,
|
|
10872
|
-
amounts
|
|
10873
|
-
});
|
|
10874
|
-
}
|
|
10875
10786
|
function seedMockOracles(offers, price, blockNumber) {
|
|
10876
10787
|
const oracleMap = /* @__PURE__ */ new Map();
|
|
10877
10788
|
for (const offer of offers) for (const collateral of offer.collaterals) {
|
|
@@ -10886,8 +10797,6 @@ function seedMockOracles(offers, price, blockNumber) {
|
|
|
10886
10797
|
}
|
|
10887
10798
|
return Array.from(oracleMap.values());
|
|
10888
10799
|
}
|
|
10889
|
-
const BUY_CALLBACK_ADDRESS = "0x3333333333333333333333333333333333333333";
|
|
10890
|
-
const SELL_CALLBACK_ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
10891
10800
|
async function seedOffers(parameters) {
|
|
10892
10801
|
const { db, offers, blockNumber } = parameters;
|
|
10893
10802
|
if (offers.length === 0) return;
|
|
@@ -10904,14 +10813,10 @@ async function seedOffers(parameters) {
|
|
|
10904
10813
|
}]);
|
|
10905
10814
|
}
|
|
10906
10815
|
async function seedOfferAssociations(parameters) {
|
|
10907
|
-
const { db,
|
|
10816
|
+
const { db, offers, blockNumber } = parameters;
|
|
10908
10817
|
if (offers.length === 0) return;
|
|
10909
10818
|
const { callbacks, positions, lots } = buildOfferAssociationsFromOffers({
|
|
10910
10819
|
offers,
|
|
10911
|
-
callbackTypes: await resolveCallbackTypes({
|
|
10912
|
-
gatekeeper,
|
|
10913
|
-
offers
|
|
10914
|
-
}),
|
|
10915
10820
|
blockNumber
|
|
10916
10821
|
});
|
|
10917
10822
|
if (positions.length > 0) await db.positions.upsert(positions);
|
|
@@ -10954,83 +10859,40 @@ function buildOfferDependencies(parameters) {
|
|
|
10954
10859
|
groups: Array.from(groupsByKey.values())
|
|
10955
10860
|
};
|
|
10956
10861
|
}
|
|
10957
|
-
async function resolveCallbackTypes(parameters) {
|
|
10958
|
-
const { gatekeeper, offers } = parameters;
|
|
10959
|
-
const addressesByChain = /* @__PURE__ */ new Map();
|
|
10960
|
-
for (const offer of offers) {
|
|
10961
|
-
if (offer.callback.data === "0x") continue;
|
|
10962
|
-
const set = addressesByChain.get(offer.chainId) ?? /* @__PURE__ */ new Set();
|
|
10963
|
-
set.add(offer.callback.address.toLowerCase());
|
|
10964
|
-
addressesByChain.set(offer.chainId, set);
|
|
10965
|
-
}
|
|
10966
|
-
if (addressesByChain.size === 0) return /* @__PURE__ */ new Map();
|
|
10967
|
-
const request = { callbacks: Array.from(addressesByChain.entries()).map(([chainId, addresses]) => ({
|
|
10968
|
-
chain_id: chainId,
|
|
10969
|
-
addresses: Array.from(addresses)
|
|
10970
|
-
})) };
|
|
10971
|
-
const response = await gatekeeper.getCallbackTypes(request);
|
|
10972
|
-
const typeByAddress = /* @__PURE__ */ new Map();
|
|
10973
|
-
for (const entry of response) {
|
|
10974
|
-
const chainId = entry.chain_id;
|
|
10975
|
-
for (const [key, list] of Object.entries(entry)) {
|
|
10976
|
-
if (key === "chain_id" || key === "not_supported") continue;
|
|
10977
|
-
if (!Array.isArray(list)) continue;
|
|
10978
|
-
for (const address of list) {
|
|
10979
|
-
const mapKey = `${chainId}-${address.toLowerCase()}`;
|
|
10980
|
-
typeByAddress.set(mapKey, key);
|
|
10981
|
-
}
|
|
10982
|
-
}
|
|
10983
|
-
}
|
|
10984
|
-
return typeByAddress;
|
|
10985
|
-
}
|
|
10986
10862
|
function buildOfferAssociationsFromOffers(parameters) {
|
|
10987
|
-
const { offers,
|
|
10863
|
+
const { offers, blockNumber } = parameters;
|
|
10988
10864
|
const callbacks = [];
|
|
10989
10865
|
const positions = [];
|
|
10990
10866
|
const lots = [];
|
|
10991
10867
|
for (const offer of offers) {
|
|
10992
|
-
if (offer.
|
|
10993
|
-
const
|
|
10994
|
-
const
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
10868
|
+
if (!offer.buy) continue;
|
|
10869
|
+
const loanToken = offer.loanToken.toLowerCase();
|
|
10870
|
+
const offerHash = hash(offer);
|
|
10871
|
+
positions.push(from$10({
|
|
10872
|
+
chainId: offer.chainId,
|
|
10873
|
+
contract: loanToken,
|
|
10874
|
+
user: offer.maker,
|
|
10875
|
+
type: Type.ERC20,
|
|
10876
|
+
asset: loanToken,
|
|
10877
|
+
balance: offer.assets,
|
|
10878
|
+
blockNumber
|
|
10879
|
+
}));
|
|
11004
10880
|
callbacks.push({
|
|
11005
|
-
offerHash
|
|
11006
|
-
callbacks:
|
|
10881
|
+
offerHash,
|
|
10882
|
+
callbacks: [{
|
|
11007
10883
|
chainId: offer.chainId,
|
|
11008
|
-
contract:
|
|
10884
|
+
contract: loanToken,
|
|
11009
10885
|
user: offer.maker,
|
|
11010
|
-
amount:
|
|
11011
|
-
}
|
|
10886
|
+
amount: offer.assets
|
|
10887
|
+
}]
|
|
10888
|
+
});
|
|
10889
|
+
lots.push({
|
|
10890
|
+
positionChainId: offer.chainId,
|
|
10891
|
+
positionContract: loanToken,
|
|
10892
|
+
positionUser: offer.maker,
|
|
10893
|
+
group: offer.group,
|
|
10894
|
+
size: offer.assets
|
|
11012
10895
|
});
|
|
11013
|
-
for (const callback of decoded) {
|
|
11014
|
-
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
11015
|
-
const positionAsset = callbackType === Type$1.BuyVaultV1Callback ? offer.loanToken : callback.contract;
|
|
11016
|
-
positions.push(from$10({
|
|
11017
|
-
chainId: offer.chainId,
|
|
11018
|
-
contract: callback.contract,
|
|
11019
|
-
user: offer.maker,
|
|
11020
|
-
type: positionType,
|
|
11021
|
-
balance: callback.amount * 2n,
|
|
11022
|
-
asset: positionAsset,
|
|
11023
|
-
blockNumber
|
|
11024
|
-
}));
|
|
11025
|
-
const isLoanPosition = positionAsset.toLowerCase() === offer.loanToken.toLowerCase();
|
|
11026
|
-
lots.push({
|
|
11027
|
-
positionChainId: offer.chainId,
|
|
11028
|
-
positionContract: callback.contract,
|
|
11029
|
-
positionUser: offer.maker,
|
|
11030
|
-
group: offer.group,
|
|
11031
|
-
size: isLoanPosition ? offer.assets : callback.amount
|
|
11032
|
-
});
|
|
11033
|
-
}
|
|
11034
10896
|
}
|
|
11035
10897
|
return {
|
|
11036
10898
|
callbacks,
|