@defisaver/positions-sdk 2.1.161-portfolio-api-2-dev → 2.1.161-portfolio-api-3-dev
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/cjs/aaveV2/index.js +2 -0
- package/cjs/aaveV3/index.d.ts +39 -0
- package/cjs/aaveV3/index.js +237 -2
- package/cjs/aaveV4/lend.d.ts +2 -43
- package/cjs/curveUsd/index.d.ts +5 -2
- package/cjs/curveUsd/index.js +30 -15
- package/cjs/fluid/index.d.ts +5 -0
- package/cjs/fluid/index.js +36 -1
- package/cjs/helpers/aaveHelpers/index.d.ts +9 -1
- package/cjs/helpers/aaveHelpers/index.js +26 -6
- package/cjs/helpers/aaveV4Helpers/index.js +1 -0
- package/cjs/helpers/compoundHelpers/index.js +2 -0
- package/cjs/helpers/curveUsdHelpers/index.d.ts +3 -1
- package/cjs/helpers/curveUsdHelpers/index.js +15 -2
- package/cjs/helpers/fluidHelpers/index.js +1 -0
- package/cjs/helpers/liquityV2Helpers/index.js +1 -0
- package/cjs/helpers/llamaLendHelpers/index.js +1 -0
- package/cjs/helpers/makerHelpers/index.d.ts +1 -1
- package/cjs/helpers/makerHelpers/index.js +2 -2
- package/cjs/helpers/morphoBlueHelpers/index.js +2 -0
- package/cjs/helpers/morphoMidnightHelpers/index.js +2 -0
- package/cjs/helpers/sparkHelpers/index.js +1 -0
- package/cjs/liquity/index.js +15 -2
- package/cjs/liquityV2/index.d.ts +5 -0
- package/cjs/liquityV2/index.js +37 -19
- package/cjs/maker/index.d.ts +7 -2
- package/cjs/maker/index.js +44 -12
- package/cjs/markets/index.d.ts +1 -0
- package/cjs/markets/index.js +3 -1
- package/cjs/markets/maker/index.d.ts +1 -0
- package/cjs/markets/maker/index.js +13 -0
- package/cjs/moneymarket/moneymarketCommonService.d.ts +9 -0
- package/cjs/moneymarket/moneymarketCommonService.js +13 -1
- package/cjs/portfolio/index.d.ts +17 -5
- package/cjs/portfolio/index.js +583 -394
- package/cjs/portfolio/lendingProtocols.d.ts +184 -0
- package/cjs/portfolio/lendingProtocols.js +196 -0
- package/cjs/savings/index.d.ts +8 -2
- package/cjs/savings/index.js +37 -5
- package/cjs/savings/morphoVaults/index.d.ts +1 -1
- package/cjs/savings/morphoVaults/index.js +56 -18
- package/cjs/savings/morphoVaults/options.d.ts +2 -0
- package/cjs/savings/morphoVaults/options.js +61 -1
- package/cjs/spark/index.js +1 -0
- package/cjs/staking/staking.d.ts +20 -0
- package/cjs/staking/staking.js +19 -1
- package/cjs/types/aave.d.ts +3 -0
- package/cjs/types/aaveV4.d.ts +45 -0
- package/cjs/types/compound.d.ts +2 -0
- package/cjs/types/curveUsd.d.ts +6 -0
- package/cjs/types/fluid.d.ts +1 -0
- package/cjs/types/liquity.d.ts +4 -0
- package/cjs/types/liquityV2.d.ts +2 -0
- package/cjs/types/maker.d.ts +4 -0
- package/cjs/types/morphoBlue.d.ts +2 -0
- package/cjs/types/morphoMidnight.d.ts +1 -0
- package/cjs/types/portfolio.d.ts +64 -6
- package/cjs/types/savings/index.d.ts +9 -1
- package/cjs/types/savings/morphoVaults.d.ts +10 -2
- package/cjs/types/savings/morphoVaults.js +9 -1
- package/cjs/types/spark.d.ts +2 -0
- package/esm/aaveV2/index.js +2 -0
- package/esm/aaveV3/index.d.ts +39 -0
- package/esm/aaveV3/index.js +231 -2
- package/esm/aaveV4/lend.d.ts +2 -43
- package/esm/curveUsd/index.d.ts +5 -2
- package/esm/curveUsd/index.js +30 -15
- package/esm/fluid/index.d.ts +5 -0
- package/esm/fluid/index.js +33 -0
- package/esm/helpers/aaveHelpers/index.d.ts +9 -1
- package/esm/helpers/aaveHelpers/index.js +23 -5
- package/esm/helpers/aaveV4Helpers/index.js +1 -0
- package/esm/helpers/compoundHelpers/index.js +2 -0
- package/esm/helpers/curveUsdHelpers/index.d.ts +3 -1
- package/esm/helpers/curveUsdHelpers/index.js +16 -3
- package/esm/helpers/fluidHelpers/index.js +1 -0
- package/esm/helpers/liquityV2Helpers/index.js +1 -0
- package/esm/helpers/llamaLendHelpers/index.js +2 -1
- package/esm/helpers/makerHelpers/index.d.ts +1 -1
- package/esm/helpers/makerHelpers/index.js +2 -2
- package/esm/helpers/morphoBlueHelpers/index.js +2 -0
- package/esm/helpers/morphoMidnightHelpers/index.js +2 -0
- package/esm/helpers/sparkHelpers/index.js +1 -0
- package/esm/liquity/index.js +16 -3
- package/esm/liquityV2/index.d.ts +5 -0
- package/esm/liquityV2/index.js +35 -18
- package/esm/maker/index.d.ts +7 -2
- package/esm/maker/index.js +44 -14
- package/esm/markets/index.d.ts +1 -0
- package/esm/markets/index.js +1 -0
- package/esm/markets/maker/index.d.ts +1 -0
- package/esm/markets/maker/index.js +10 -0
- package/esm/moneymarket/moneymarketCommonService.d.ts +9 -0
- package/esm/moneymarket/moneymarketCommonService.js +11 -0
- package/esm/portfolio/index.d.ts +17 -5
- package/esm/portfolio/index.js +584 -398
- package/esm/portfolio/lendingProtocols.d.ts +184 -0
- package/esm/portfolio/lendingProtocols.js +189 -0
- package/esm/savings/index.d.ts +8 -2
- package/esm/savings/index.js +35 -4
- package/esm/savings/morphoVaults/index.d.ts +1 -1
- package/esm/savings/morphoVaults/index.js +57 -19
- package/esm/savings/morphoVaults/options.d.ts +2 -0
- package/esm/savings/morphoVaults/options.js +61 -1
- package/esm/spark/index.js +1 -0
- package/esm/staking/staking.d.ts +20 -0
- package/esm/staking/staking.js +17 -0
- package/esm/types/aave.d.ts +3 -0
- package/esm/types/aaveV4.d.ts +45 -0
- package/esm/types/compound.d.ts +2 -0
- package/esm/types/curveUsd.d.ts +6 -0
- package/esm/types/fluid.d.ts +1 -0
- package/esm/types/liquity.d.ts +4 -0
- package/esm/types/liquityV2.d.ts +2 -0
- package/esm/types/maker.d.ts +4 -0
- package/esm/types/morphoBlue.d.ts +2 -0
- package/esm/types/morphoMidnight.d.ts +1 -0
- package/esm/types/portfolio.d.ts +64 -6
- package/esm/types/savings/index.d.ts +9 -1
- package/esm/types/savings/morphoVaults.d.ts +10 -2
- package/esm/types/savings/morphoVaults.js +8 -0
- package/esm/types/spark.d.ts +2 -0
- package/package.json +3 -2
- package/src/aaveV2/index.ts +2 -0
- package/src/aaveV3/index.ts +291 -1
- package/src/aaveV4/lend.ts +2 -48
- package/src/curveUsd/index.ts +33 -16
- package/src/fluid/index.ts +40 -0
- package/src/helpers/aaveHelpers/index.ts +27 -7
- package/src/helpers/aaveV4Helpers/index.ts +1 -0
- package/src/helpers/compoundHelpers/index.ts +2 -0
- package/src/helpers/curveUsdHelpers/index.ts +23 -3
- package/src/helpers/fluidHelpers/index.ts +1 -0
- package/src/helpers/liquityV2Helpers/index.ts +1 -0
- package/src/helpers/llamaLendHelpers/index.ts +2 -1
- package/src/helpers/makerHelpers/index.ts +2 -1
- package/src/helpers/morphoBlueHelpers/index.ts +2 -0
- package/src/helpers/morphoMidnightHelpers/index.ts +2 -0
- package/src/helpers/sparkHelpers/index.ts +1 -0
- package/src/liquity/index.ts +17 -3
- package/src/liquityV2/index.ts +45 -19
- package/src/maker/index.ts +77 -30
- package/src/markets/index.ts +2 -1
- package/src/markets/maker/index.ts +10 -0
- package/src/moneymarket/moneymarketCommonService.ts +15 -0
- package/src/portfolio/index.ts +428 -215
- package/src/portfolio/lendingProtocols.ts +281 -0
- package/src/savings/index.ts +45 -5
- package/src/savings/morphoVaults/index.ts +73 -21
- package/src/savings/morphoVaults/options.ts +67 -1
- package/src/spark/index.ts +1 -0
- package/src/staking/staking.ts +28 -0
- package/src/types/aave.ts +4 -0
- package/src/types/aaveV4.ts +50 -0
- package/src/types/compound.ts +2 -0
- package/src/types/curveUsd.ts +7 -0
- package/src/types/fluid.ts +1 -0
- package/src/types/liquity.ts +6 -0
- package/src/types/liquityV2.ts +2 -0
- package/src/types/maker.ts +6 -0
- package/src/types/morphoBlue.ts +2 -0
- package/src/types/morphoMidnight.ts +1 -0
- package/src/types/portfolio.ts +71 -6
- package/src/types/savings/index.ts +12 -1
- package/src/types/savings/morphoVaults.ts +11 -1
- package/src/types/spark.ts +2 -0
package/src/portfolio/index.ts
CHANGED
|
@@ -7,141 +7,155 @@ import {
|
|
|
7
7
|
CrvUsdMarkets,
|
|
8
8
|
LiquityV2Markets,
|
|
9
9
|
LlamaLendMarkets,
|
|
10
|
+
MakerActiveIlks,
|
|
10
11
|
MorphoBlueMarkets,
|
|
11
|
-
MorphoMidnightMarkets,
|
|
12
12
|
SparkMarkets,
|
|
13
13
|
} from '../markets';
|
|
14
14
|
import { _getMorphoBlueAccountData, _getMorphoBluePortfolioMarketData, getMorphoEarn } from '../morphoBlue';
|
|
15
|
-
import { _getMorphoMidnightAccountData, _getMorphoMidnightMarketData } from '../morphoMidnight';
|
|
16
15
|
import {
|
|
17
|
-
AaveV2MarketData,
|
|
18
|
-
AaveV3MarketData,
|
|
19
|
-
AaveV4SpokeData,
|
|
20
16
|
AaveVersions,
|
|
21
17
|
CdpInfo,
|
|
22
|
-
CompoundV2MarketsData,
|
|
23
|
-
CompoundV3MarketsData,
|
|
24
18
|
CompoundVersions,
|
|
25
|
-
CrvUSDGlobalMarketData,
|
|
26
19
|
LiquityV2MarketData,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
MorphoMidnightMarketInfo,
|
|
20
|
+
LiquityV2Versions,
|
|
21
|
+
PortfolioData,
|
|
30
22
|
PortfolioDataOptions,
|
|
23
|
+
PortfolioLiquityV2Troves,
|
|
31
24
|
PortfolioMarketsData,
|
|
25
|
+
PortfolioMarketsErrors,
|
|
26
|
+
PortfolioMarketsResult,
|
|
32
27
|
PortfolioPositionsData,
|
|
28
|
+
PortfolioPositionsDataForAddress,
|
|
33
29
|
PortfolioRewardsData,
|
|
34
30
|
PortfolioStakingPositionsData,
|
|
35
|
-
|
|
31
|
+
PortfolioUserData,
|
|
32
|
+
ShifterPortfolioMarketsData,
|
|
36
33
|
} from '../types';
|
|
37
34
|
import { _getCompoundV3AccountData, _getCompoundV3MarketsData } from '../compoundV3';
|
|
38
35
|
import { _getSparkAccountData, _getSparkMarketsData } from '../spark';
|
|
39
36
|
import { _getCurveUsdGlobalData, _getCurveUsdUserData } from '../curveUsd';
|
|
40
37
|
import { _getLlamaLendGlobalData, _getLlamaLendUserData } from '../llamaLend';
|
|
41
38
|
import { _getAaveV3AccountData, _getAaveV3MarketData, getStakeAaveData } from '../aaveV3';
|
|
39
|
+
import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
|
|
42
40
|
import { ZERO_ADDRESS } from '../constants';
|
|
43
|
-
import { _getMakerCdpData, _getUserCdps } from '../maker';
|
|
41
|
+
import { _getMakerCdpData, _getMakerIlksData, _getUserCdps } from '../maker';
|
|
44
42
|
import { _getAaveV2AccountData, _getAaveV2MarketsData } from '../aaveV2';
|
|
45
43
|
import { _getCompoundV2AccountData, _getCompoundV2MarketsData } from '../compoundV2';
|
|
46
44
|
import { getViemProvider } from '../services/viem';
|
|
47
45
|
import { _getLiquityTroveInfo, getLiquityStakingData } from '../liquity';
|
|
48
|
-
import {
|
|
49
|
-
|
|
46
|
+
import {
|
|
47
|
+
_getLiquityV2MarketData, _getLiquityV2TroveData, _getLiquityV2UsersTroveIds, getLiquitySAndYBold, getLiquityV2Staking,
|
|
48
|
+
} from '../liquityV2';
|
|
49
|
+
import { _getAllFluidMarketDataPortfolio, _getAllUserEarnPositionsWithFTokens, _getUserPositionsPortfolio } from '../fluid';
|
|
50
50
|
import { getUmbrellaData } from '../umbrella';
|
|
51
51
|
import { getMerklUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
|
|
52
52
|
import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
|
|
53
53
|
import { getKingRewards } from '../claiming/king';
|
|
54
54
|
import { fetchEthenaAirdropRewards } from '../claiming/ethena';
|
|
55
|
-
import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
|
|
56
55
|
import { getUniswapRewards } from '../claiming/uniswap';
|
|
56
|
+
import {
|
|
57
|
+
aaveV3Lending, LENDING_PROTOCOLS, LendingKey, morphoBlueLending, requireMarket, sparkLending,
|
|
58
|
+
} from './lendingProtocols';
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
positions: PortfolioPositionsData;
|
|
60
|
-
stakingPositions: PortfolioStakingPositionsData;
|
|
61
|
-
rewardsData: PortfolioRewardsData;
|
|
62
|
-
markets: PortfolioMarketsData;
|
|
63
|
-
}> {
|
|
64
|
-
const isMainnet = network === NetworkNumber.Eth;
|
|
65
|
-
const includeStaking = options.staking !== false;
|
|
66
|
-
const includeRewards = options.rewards !== false;
|
|
67
|
-
// The addresses staking and rewards are read for: none when left out, so their fetches below make no calls.
|
|
68
|
-
const stakingAddresses = includeStaking ? addresses : [];
|
|
69
|
-
const rewardsAddresses = includeRewards ? addresses : [];
|
|
70
|
-
const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
|
|
71
|
-
|
|
72
|
-
const morphoMarkets = Object.values(MorphoBlueMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
73
|
-
const morphoMidnightMarkets = Object.values(MorphoMidnightMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
74
|
-
const compoundV3Markets = Object.values(CompoundMarkets(network)).filter((market) => market.chainIds.includes(network) && market.value !== CompoundVersions.CompoundV2);
|
|
75
|
-
const sparkMarkets = Object.values(SparkMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
76
|
-
const aaveV3Markets = [AaveVersions.AaveV3, AaveVersions.AaveV3Lido, AaveVersions.AaveV3Etherfi].map((version) => AaveMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
77
|
-
const aaveV2Markets = [AaveVersions.AaveV2].map((version) => AaveMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
78
|
-
const compoundV2Markets = [CompoundVersions.CompoundV2].map((version) => CompoundMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
79
|
-
const crvUsdMarkets = Object.values(CrvUsdMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
80
|
-
const llamaLendMarkets = [NetworkNumber.Eth, NetworkNumber.Arb].includes(network) ? Object.values(LlamaLendMarkets(network)).filter((market) => market.chainIds.includes(network)) : [];
|
|
81
|
-
const liquityV2Markets = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)) : [];
|
|
82
|
-
const liquityV2MarketsStaking = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)).filter(market => !market.isLegacy) : [];
|
|
83
|
-
const aaveV4Spokes = Object.values(AaveV4Spokes(network)).filter((market) => market.chainIds.includes(network));
|
|
60
|
+
type PortfolioClient = ReturnType<typeof getViemProvider>;
|
|
84
61
|
|
|
62
|
+
/** The markets of the protocols read by their own code below; LENDING_PROTOCOLS has the others'. */
|
|
63
|
+
const getPortfolioMarkets = (network: NetworkNumber) => ({
|
|
64
|
+
liquityV2Markets: [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)) : [],
|
|
65
|
+
liquityV2MarketsStaking: [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)).filter(market => !market.isLegacy) : [],
|
|
66
|
+
});
|
|
85
67
|
|
|
68
|
+
const getPortfolioClients = (provider: EthereumProvider, defaultProvider: EthereumProvider, network: NetworkNumber, isSim: boolean) => {
|
|
86
69
|
// batchSize is viem's cap on a batch's raw subcall calldata (bytes); the JSON-RPC body ends up
|
|
87
70
|
// ~4x larger (hex + aggregate3 ABI + JSON overhead) and RPC providers reject bodies over
|
|
88
71
|
// ~2.5MB with HTTP 413, so keep this small enough that no single body gets near that.
|
|
89
72
|
// 250k gives the largest body of around 1.26MB - if we bump, we can save maybe 2-3 rpc calls but scaling takes a hit
|
|
90
73
|
// (e.g. adding new markets may result in body size going over alchemy body size limit)
|
|
91
74
|
const args: [NetworkNumber, any?] = [network, { batch: { multicall: { batchSize: isSim ? 2_000 : 250_000 } } }];
|
|
92
|
-
|
|
93
|
-
|
|
75
|
+
return {
|
|
76
|
+
client: getViemProvider(provider, ...args),
|
|
77
|
+
defaultClient: getViemProvider(defaultProvider, ...args),
|
|
78
|
+
};
|
|
79
|
+
};
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const compoundV3MarketsData: Record<string, CompoundV3MarketsData> = {};
|
|
98
|
-
const sparkMarketsData: Record<string, SparkMarketsData> = {};
|
|
99
|
-
const aaveV3MarketsData: Record<string, AaveV3MarketData> = {};
|
|
100
|
-
const makerCdps: Record<string, CdpInfo[]> = {};
|
|
101
|
-
const aaveV2MarketsData: Record<string, AaveV2MarketData> = {};
|
|
102
|
-
const compoundV2MarketsData: Record<string, CompoundV2MarketsData> = {};
|
|
103
|
-
const crvUsdMarketsData: Record<string, CrvUSDGlobalMarketData> = {};
|
|
104
|
-
const llamaLendMarketsData: Record<string, LlamaLendGlobalMarketData> = {};
|
|
105
|
-
const liquityV2MarketsData: Record<string, LiquityV2MarketData> = {};
|
|
106
|
-
const aaveV4SpokesData: Record<string, AaveV4SpokeData> = {};
|
|
81
|
+
const _getPortfolioMarketsData = async (client: PortfolioClient, defaultClient: PortfolioClient, network: NetworkNumber): Promise<PortfolioMarketsResult> => {
|
|
82
|
+
const { liquityV2Markets } = getPortfolioMarkets(network);
|
|
107
83
|
|
|
108
84
|
const markets: PortfolioMarketsData = {
|
|
109
|
-
morphoMarketsData,
|
|
110
|
-
morphoMidnightMarketsData,
|
|
111
|
-
compoundV3MarketsData,
|
|
112
|
-
sparkMarketsData,
|
|
113
|
-
aaveV3MarketsData,
|
|
114
|
-
aaveV2MarketsData,
|
|
115
|
-
compoundV2MarketsData,
|
|
116
|
-
crvUsdMarketsData,
|
|
117
|
-
llamaLendMarketsData,
|
|
118
|
-
liquityV2MarketsData,
|
|
119
|
-
aaveV4SpokesData,
|
|
85
|
+
morphoMarketsData: {},
|
|
86
|
+
morphoMidnightMarketsData: {},
|
|
87
|
+
compoundV3MarketsData: {},
|
|
88
|
+
sparkMarketsData: {},
|
|
89
|
+
aaveV3MarketsData: {},
|
|
90
|
+
aaveV2MarketsData: {},
|
|
91
|
+
compoundV2MarketsData: {},
|
|
92
|
+
crvUsdMarketsData: {},
|
|
93
|
+
llamaLendMarketsData: {},
|
|
94
|
+
liquityV2MarketsData: {},
|
|
95
|
+
aaveV4SpokesData: {},
|
|
120
96
|
};
|
|
97
|
+
const liquityV2Failed: Record<string, string> = {};
|
|
98
|
+
|
|
99
|
+
const [lendingFailed] = await Promise.all([
|
|
100
|
+
Promise.all(LENDING_PROTOCOLS.map(async (protocol) => [protocol.marketsKey, await protocol.readMarkets({ client, defaultClient, network }, markets)] as const)),
|
|
101
|
+
// Liquity V2's markets: its troves are read by their own code below.
|
|
102
|
+
...liquityV2Markets.map(async (market) => {
|
|
103
|
+
try {
|
|
104
|
+
markets.liquityV2MarketsData[market.value] = await _getLiquityV2MarketData(client, network, market);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`Error fetching liquityV2MarketsData for market ${market.value}:`, error);
|
|
107
|
+
liquityV2Failed[market.value] = `Error fetching liquityV2MarketsData for market ${market.value}`;
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// A market that failed is left out of markets and named here, under its protocol's key.
|
|
113
|
+
const errors: PortfolioMarketsErrors = {};
|
|
114
|
+
for (const [key, failed] of [...lendingFailed, ['liquityV2MarketsData', liquityV2Failed] as const]) {
|
|
115
|
+
if (Object.keys(failed).length) errors[key] = failed;
|
|
116
|
+
}
|
|
117
|
+
return { markets, errors };
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const _getPortfolioUserData = async (
|
|
121
|
+
client: PortfolioClient,
|
|
122
|
+
network: NetworkNumber,
|
|
123
|
+
addresses: EthAddress[],
|
|
124
|
+
marketsData: Promise<PortfolioMarketsData>,
|
|
125
|
+
isSim: boolean,
|
|
126
|
+
options: PortfolioDataOptions,
|
|
127
|
+
): Promise<PortfolioUserData> => {
|
|
128
|
+
const isMainnet = network === NetworkNumber.Eth;
|
|
129
|
+
const includePositions = options.positions !== false;
|
|
130
|
+
const includeStaking = options.staking !== false;
|
|
131
|
+
const includeRewards = options.rewards !== false;
|
|
132
|
+
const includeLiquityV2 = includePositions && options.liquityV2 === true;
|
|
133
|
+
// The addresses positions, staking, rewards and Liquity V2 troves are read for: none when left out, so their fetches
|
|
134
|
+
// below make no calls.
|
|
135
|
+
const positionsAddresses = includePositions ? addresses : [];
|
|
136
|
+
const stakingAddresses = includeStaking ? addresses : [];
|
|
137
|
+
const rewardsAddresses = includeRewards ? addresses : [];
|
|
138
|
+
const liquityV2Addresses = includeLiquityV2 ? addresses : [];
|
|
139
|
+
const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
|
|
140
|
+
|
|
141
|
+
const { liquityV2Markets, liquityV2MarketsStaking } = getPortfolioMarkets(network);
|
|
121
142
|
|
|
122
143
|
const positions: PortfolioPositionsData = {};
|
|
123
144
|
const stakingPositions: PortfolioStakingPositionsData = {};
|
|
124
145
|
const rewardsData: PortfolioRewardsData = {};
|
|
125
|
-
const
|
|
146
|
+
const makerCdpsErrors: Record<EthAddress, string> = {};
|
|
126
147
|
|
|
127
|
-
for (const address of
|
|
148
|
+
for (const address of positionsAddresses) {
|
|
128
149
|
positions[address.toLowerCase() as EthAddress] = {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
morphoBlue: {},
|
|
132
|
-
morphoMidnight: {},
|
|
133
|
-
compoundV3: {},
|
|
134
|
-
spark: {},
|
|
150
|
+
// Every registry protocol, keyed by market as its reads fill it in.
|
|
151
|
+
...Object.fromEntries(LENDING_PROTOCOLS.map((protocol) => [protocol.key, {}])) as Pick<PortfolioPositionsDataForAddress, LendingKey>,
|
|
135
152
|
maker: {},
|
|
136
|
-
aaveV2: {},
|
|
137
|
-
compoundV2: {},
|
|
138
153
|
liquity: {},
|
|
139
|
-
crvUsd: {},
|
|
140
|
-
llamaLend: {},
|
|
141
154
|
fluid: {
|
|
142
155
|
error: '',
|
|
143
156
|
data: {},
|
|
144
157
|
},
|
|
158
|
+
...(includeLiquityV2 ? { liquityV2: {} } : {}),
|
|
145
159
|
};
|
|
146
160
|
}
|
|
147
161
|
|
|
@@ -178,60 +192,34 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
178
192
|
};
|
|
179
193
|
}
|
|
180
194
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
...morphoMidnightMarkets.map(async (market) => {
|
|
188
|
-
const marketData = await _getMorphoMidnightMarketData(client, network, market);
|
|
189
|
-
morphoMidnightMarketsData[market.value] = marketData;
|
|
190
|
-
}),
|
|
191
|
-
...compoundV3Markets.map(async (market) => {
|
|
192
|
-
const marketData = await _getCompoundV3MarketsData(client, network, market, defaultClient);
|
|
193
|
-
compoundV3MarketsData[market.value] = marketData;
|
|
194
|
-
}),
|
|
195
|
-
...sparkMarkets.map(async (market) => {
|
|
196
|
-
const marketData = await _getSparkMarketsData(client, network, market);
|
|
197
|
-
sparkMarketsData[market.value] = marketData;
|
|
198
|
-
}),
|
|
199
|
-
...aaveV3Markets.map(async (market) => {
|
|
200
|
-
const marketData = await _getAaveV3MarketData(client, network, market);
|
|
201
|
-
aaveV3MarketsData[market.value] = marketData;
|
|
202
|
-
}),
|
|
203
|
-
...aaveV4Spokes.map(async (spoke) => {
|
|
204
|
-
const spokeData = await _getAaveV4SpokeData(client, network, spoke);
|
|
205
|
-
aaveV4SpokesData[spoke.value] = spokeData;
|
|
206
|
-
}),
|
|
207
|
-
...aaveV2Markets.map(async (market) => {
|
|
208
|
-
const marketData = await _getAaveV2MarketsData(client, network, market);
|
|
209
|
-
aaveV2MarketsData[market.value] = marketData;
|
|
210
|
-
}),
|
|
211
|
-
...compoundV2Markets.map(async (market) => {
|
|
212
|
-
const marketData = await _getCompoundV2MarketsData(client, network);
|
|
213
|
-
compoundV2MarketsData[market.value] = marketData;
|
|
214
|
-
}),
|
|
215
|
-
...crvUsdMarkets.map(async (market) => {
|
|
216
|
-
const marketData = await _getCurveUsdGlobalData(client, network, market);
|
|
217
|
-
crvUsdMarketsData[market.value] = marketData;
|
|
218
|
-
}),
|
|
219
|
-
...llamaLendMarkets.map(async (market) => {
|
|
220
|
-
const marketData = await _getLlamaLendGlobalData(client, network, market);
|
|
221
|
-
llamaLendMarketsData[market.value] = marketData;
|
|
222
|
-
}),
|
|
223
|
-
...liquityV2Markets.map(async (market) => {
|
|
224
|
-
const marketData = await _getLiquityV2MarketData(client, network, market);
|
|
225
|
-
liquityV2MarketsData[market.value] = marketData;
|
|
226
|
-
}),
|
|
227
|
-
|
|
228
|
-
// === INDEPENDENT USER DATA (doesn't depend on market data) ===
|
|
229
|
-
...addresses.map(async (address) => {
|
|
195
|
+
// Everything starts at once; a read that needs a market's data waits for marketsData.
|
|
196
|
+
await Promise.all([
|
|
197
|
+
// === POSITIONS THAT DO NOT NEED MARKET DATA ===
|
|
198
|
+
// Maker: the address's vault list, then each vault. A failed list is reported in errors.makerCdps: maker stays {},
|
|
199
|
+
// which would otherwise read as "no vaults".
|
|
200
|
+
...positionsAddresses.map(async (address) => {
|
|
230
201
|
if (!isMainnet) return; // Maker CDPs are only available on mainnet
|
|
231
|
-
|
|
232
|
-
|
|
202
|
+
let cdps: CdpInfo[];
|
|
203
|
+
try {
|
|
204
|
+
cdps = await _getUserCdps(client, network, address);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`Error fetching Maker CDPs for address ${address}:`, error);
|
|
207
|
+
makerCdpsErrors[address.toLowerCase() as EthAddress] = `Error fetching Maker CDPs for address ${address}`;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
await Promise.all(cdps.map(async (cdpInfo) => {
|
|
211
|
+
try {
|
|
212
|
+
const cdpData = await _getMakerCdpData(client, network, cdpInfo);
|
|
213
|
+
if (cdpData) {
|
|
214
|
+
positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: '', data: cdpData };
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(`Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}:`, error);
|
|
218
|
+
positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: `Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}`, data: null };
|
|
219
|
+
}
|
|
220
|
+
}));
|
|
233
221
|
}),
|
|
234
|
-
...
|
|
222
|
+
...positionsAddresses.map(async (address) => {
|
|
235
223
|
try {
|
|
236
224
|
if (!isFluidSupported) return; // Fluid is not available on Optimism
|
|
237
225
|
const userPositions = (await _getUserPositionsPortfolio(client, network, address));
|
|
@@ -248,8 +236,18 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
248
236
|
};
|
|
249
237
|
}
|
|
250
238
|
}),
|
|
239
|
+
...positionsAddresses.map(async (address) => {
|
|
240
|
+
try {
|
|
241
|
+
if (!isMainnet) return; // Liquity trove info is only available on mainnet
|
|
242
|
+
const troveInfo = await _getLiquityTroveInfo(client, network, address);
|
|
243
|
+
if (new Dec(troveInfo.collateral).gt(0)) positions[address.toLowerCase() as EthAddress].liquity = { error: '', data: troveInfo };
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(`Error fetching Liquity trove info for address ${address}:`, error);
|
|
246
|
+
positions[address.toLowerCase() as EthAddress].liquity = { error: `Error fetching Liquity trove info for address ${address}`, data: null };
|
|
247
|
+
}
|
|
248
|
+
}),
|
|
251
249
|
|
|
252
|
-
// === STAKING DATA (independent of market data) ===
|
|
250
|
+
// === STAKING DATA (independent of market data; Morpho Earn is with the positions below) ===
|
|
253
251
|
...stakingAddresses.map(async (address) => {
|
|
254
252
|
try {
|
|
255
253
|
if (!isFluidSupported) return;
|
|
@@ -286,20 +284,33 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
286
284
|
stakingPositions[address.toLowerCase() as EthAddress].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
|
|
287
285
|
}
|
|
288
286
|
}),
|
|
289
|
-
// Liquity V2
|
|
290
|
-
...
|
|
287
|
+
// Liquity V2 stability pools, then sBOLD/yBOLD, which reads them.
|
|
288
|
+
...stakingAddresses.map(async (address) => {
|
|
289
|
+
await Promise.all(liquityV2MarketsStaking.map(async (market) => {
|
|
290
|
+
try {
|
|
291
|
+
if (!isMainnet) {
|
|
292
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: null };
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const liquityV2StakingData = await getLiquityV2Staking(client, network, market.value, address);
|
|
296
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error(`Error fetching Liquity V2 staking data for address ${address}, market ${market.value}:`, error);
|
|
299
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: `Error fetching Liquity V2 staking data for address ${address}`, data: null };
|
|
300
|
+
}
|
|
301
|
+
}));
|
|
291
302
|
try {
|
|
292
303
|
if (!isMainnet) {
|
|
293
|
-
stakingPositions[address.toLowerCase() as EthAddress].
|
|
304
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
|
|
294
305
|
return;
|
|
295
306
|
}
|
|
296
|
-
const
|
|
297
|
-
stakingPositions[address.toLowerCase() as EthAddress].
|
|
307
|
+
const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase() as EthAddress].liquityV2, address);
|
|
308
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
|
|
298
309
|
} catch (error) {
|
|
299
|
-
console.error(`Error fetching
|
|
300
|
-
stakingPositions[address.toLowerCase() as EthAddress].
|
|
310
|
+
console.error(`Error fetching SBold/YBold data for address ${address}:`, error);
|
|
311
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: `Error fetching sBold/yBold data for address ${address}`, data: null };
|
|
301
312
|
}
|
|
302
|
-
})
|
|
313
|
+
}),
|
|
303
314
|
|
|
304
315
|
// === REWARDS DATA (independent of market data) ===
|
|
305
316
|
// Batch King rewards
|
|
@@ -356,7 +367,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
356
367
|
}
|
|
357
368
|
}
|
|
358
369
|
})(),
|
|
359
|
-
...
|
|
370
|
+
...sparkLending.markets(network).map((market) => rewardsAddresses.map(async address => {
|
|
360
371
|
try {
|
|
361
372
|
if (!isMainnet) {
|
|
362
373
|
rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: [] };
|
|
@@ -378,7 +389,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
378
389
|
rewardsData[address.toLowerCase() as EthAddress].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
|
|
379
390
|
}
|
|
380
391
|
}),
|
|
381
|
-
...
|
|
392
|
+
...aaveV3Lending.markets(network).map(market => rewardsAddresses.map(async (address) => {
|
|
382
393
|
try {
|
|
383
394
|
const aaveData = await getUnclaimedRewardsForAllMarkets(client, network, address, market.providerAddress);
|
|
384
395
|
rewardsData[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: aaveData };
|
|
@@ -440,21 +451,274 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
440
451
|
}
|
|
441
452
|
}
|
|
442
453
|
})(),
|
|
454
|
+
|
|
455
|
+
// === POSITIONS ON MARKETS (each waits for its market's data) ===
|
|
456
|
+
...LENDING_PROTOCOLS.flatMap((protocol) => protocol.readPositions({ client, network }, positionsAddresses, marketsData, positions)),
|
|
457
|
+
// Morpho Blue Earn: a supply without a borrow, kept as staking, read on the same markets as Morpho Blue's positions.
|
|
458
|
+
...morphoBlueLending.markets(network).flatMap((market) => stakingAddresses.map(async (address) => {
|
|
459
|
+
try {
|
|
460
|
+
const marketData = requireMarket((await marketsData).morphoMarketsData, market.value);
|
|
461
|
+
const earnData = await getMorphoEarn(client, network, address, market, marketData);
|
|
462
|
+
if (earnData && new Dec(earnData.amount).gt(0)) {
|
|
463
|
+
stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: '', data: earnData };
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error(`Error fetching MorphoBlue earn data for address ${address} on market ${market.value}:`, error);
|
|
467
|
+
stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue earn data for address ${address} on market ${market.value}`, data: null };
|
|
468
|
+
}
|
|
469
|
+
})),
|
|
470
|
+
// Liquity V2 troves: one market's trove ids for every address at once, then each trove's data. A failure is the
|
|
471
|
+
// market's error; the troves that did load stay in its data.
|
|
472
|
+
...(liquityV2Addresses.length ? liquityV2Markets : []).map(async (market) => {
|
|
473
|
+
const setEntry = (address: EthAddress, entry: PortfolioLiquityV2Troves) => {
|
|
474
|
+
positions[address.toLowerCase() as EthAddress].liquityV2![market.value] = entry;
|
|
475
|
+
};
|
|
476
|
+
const failAll = (error: string) => liquityV2Addresses.forEach((address) => setEntry(address, { error, data: {} }));
|
|
477
|
+
|
|
478
|
+
const allMarketsData = (await marketsData).liquityV2MarketsData;
|
|
479
|
+
const marketData = allMarketsData[market.value];
|
|
480
|
+
if (!marketData) {
|
|
481
|
+
failAll(`Error fetching Liquity V2 market data for market ${market.value}`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
let troveIds: Record<EthAddress, string[]>;
|
|
485
|
+
try {
|
|
486
|
+
troveIds = await _getLiquityV2UsersTroveIds(client, network, market, marketData.marketData.troveNFTAddress, isSim, liquityV2Addresses);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
console.error(`Error fetching Liquity V2 trove ids on market ${market.value}:`, error);
|
|
489
|
+
failAll(`Error fetching Liquity V2 troves on market ${market.value}`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
await Promise.all(liquityV2Addresses.map(async (address) => {
|
|
493
|
+
const entry: PortfolioLiquityV2Troves = { error: '', data: {} };
|
|
494
|
+
const failed: string[] = [];
|
|
495
|
+
await Promise.all((troveIds[address] ?? []).map(async (troveId) => {
|
|
496
|
+
try {
|
|
497
|
+
entry.data[troveId] = await _getLiquityV2TroveData(client, network, {
|
|
498
|
+
selectedMarket: market,
|
|
499
|
+
assetsData: marketData.assetsData,
|
|
500
|
+
troveId,
|
|
501
|
+
allMarketsData: allMarketsData as Record<LiquityV2Versions, LiquityV2MarketData>,
|
|
502
|
+
});
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error(`Error fetching Liquity V2 trove ${troveId} for address ${address} on market ${market.value}:`, error);
|
|
505
|
+
failed.push(troveId);
|
|
506
|
+
}
|
|
507
|
+
}));
|
|
508
|
+
if (failed.length) entry.error = `Error fetching Liquity V2 troves ${failed.join(', ')} for address ${address} on market ${market.value}`;
|
|
509
|
+
setEntry(address, entry);
|
|
510
|
+
}));
|
|
511
|
+
}),
|
|
512
|
+
]);
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
positions,
|
|
516
|
+
stakingPositions,
|
|
517
|
+
rewardsData,
|
|
518
|
+
errors: Object.keys(makerCdpsErrors).length ? { makerCdps: makerCdpsErrors } : {},
|
|
519
|
+
};
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Every market the portfolio reads on a network — the markets half of getPortfolioData, for a caller that keeps
|
|
524
|
+
* markets apart from what it reads per address (a cache shared by many addresses). A market that fails is left out
|
|
525
|
+
* of `markets` and named in `errors`.
|
|
526
|
+
*/
|
|
527
|
+
export async function getPortfolioMarketsData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, isSim = false): Promise<PortfolioMarketsResult> {
|
|
528
|
+
const { client, defaultClient } = getPortfolioClients(provider, defaultProvider, network, isSim);
|
|
529
|
+
return _getPortfolioMarketsData(client, defaultClient, network);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* What the portfolio reads per address — the other half of getPortfolioData — against `markets` from
|
|
534
|
+
* getPortfolioMarketsData. `markets` can be a promise: reads that do not need a market (Maker, Fluid, Liquity, staking,
|
|
535
|
+
* rewards) start at once, and each read on a market waits for it. A position on a market missing from `markets` is an
|
|
536
|
+
* entry with an error.
|
|
537
|
+
*/
|
|
538
|
+
export async function getPortfolioUserData(
|
|
539
|
+
provider: EthereumProvider,
|
|
540
|
+
network: NetworkNumber,
|
|
541
|
+
addresses: EthAddress[],
|
|
542
|
+
markets: PortfolioMarketsData | Promise<PortfolioMarketsData>,
|
|
543
|
+
isSim = false,
|
|
544
|
+
options: PortfolioDataOptions = {},
|
|
545
|
+
): Promise<PortfolioUserData> {
|
|
546
|
+
const { client } = getPortfolioClients(provider, provider, network, isSim);
|
|
547
|
+
return _getPortfolioUserData(client, network, addresses, Promise.resolve(markets), isSim, options);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export async function getPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false, options: PortfolioDataOptions = {}): Promise<PortfolioData> {
|
|
551
|
+
// One pair of clients for both halves, so their calls batch together.
|
|
552
|
+
const { client, defaultClient } = getPortfolioClients(provider, defaultProvider, network, isSim);
|
|
553
|
+
const marketsResult = _getPortfolioMarketsData(client, defaultClient, network);
|
|
554
|
+
const [{ markets, errors: marketsErrors }, userData] = await Promise.all([
|
|
555
|
+
marketsResult,
|
|
556
|
+
_getPortfolioUserData(client, network, addresses, marketsResult.then((result) => result.markets), isSim, options),
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
positions: userData.positions,
|
|
561
|
+
stakingPositions: userData.stakingPositions,
|
|
562
|
+
rewardsData: userData.rewardsData,
|
|
563
|
+
markets,
|
|
564
|
+
errors: {
|
|
565
|
+
...(Object.keys(marketsErrors).length ? { markets: marketsErrors } : {}),
|
|
566
|
+
...userData.errors,
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
export async function getShifterPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false): Promise<{
|
|
573
|
+
positions: PortfolioPositionsData;
|
|
574
|
+
markets: ShifterPortfolioMarketsData;
|
|
575
|
+
}> {
|
|
576
|
+
const isMainnet = network === NetworkNumber.Eth;
|
|
577
|
+
const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
|
|
578
|
+
|
|
579
|
+
const morphoMarkets = Object.values(MorphoBlueMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
580
|
+
const compoundV3Markets = Object.values(CompoundMarkets(network)).filter((market) => market.chainIds.includes(network) && market.value !== CompoundVersions.CompoundV2);
|
|
581
|
+
const sparkMarkets = Object.values(SparkMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
582
|
+
const aaveV3Markets = [AaveVersions.AaveV3, AaveVersions.AaveV3Lido, AaveVersions.AaveV3Etherfi].map((version) => AaveMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
583
|
+
const aaveV2Markets = [AaveVersions.AaveV2].map((version) => AaveMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
584
|
+
const compoundV2Markets = [CompoundVersions.CompoundV2].map((version) => CompoundMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
585
|
+
const crvUsdMarkets = Object.values(CrvUsdMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
586
|
+
const llamaLendMarkets = [NetworkNumber.Eth, NetworkNumber.Arb].includes(network) ? Object.values(LlamaLendMarkets(network)).filter((market) => market.chainIds.includes(network)) : [];
|
|
587
|
+
const liquityV2Markets = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)) : [];
|
|
588
|
+
const aaveV4Spokes = Object.values(AaveV4Spokes(network)).filter((market) => market.chainIds.includes(network));
|
|
589
|
+
|
|
590
|
+
const args: [NetworkNumber, any?] = [network, { batch: { multicall: { batchSize: isSim ? 2_000 : 2_500_000 } } }];
|
|
591
|
+
const client = getViemProvider(provider, ...args);
|
|
592
|
+
const defaultClient = getViemProvider(defaultProvider, ...args);
|
|
593
|
+
|
|
594
|
+
const markets: ShifterPortfolioMarketsData = {
|
|
595
|
+
morphoMarketsData: {},
|
|
596
|
+
morphoMidnightMarketsData: {}, // Morpho Midnight is not fetched for the shifter; key kept so the payload shape is stable
|
|
597
|
+
compoundV3MarketsData: {},
|
|
598
|
+
sparkMarketsData: {},
|
|
599
|
+
aaveV3MarketsData: {},
|
|
600
|
+
aaveV2MarketsData: {},
|
|
601
|
+
compoundV2MarketsData: {},
|
|
602
|
+
crvUsdMarketsData: {},
|
|
603
|
+
llamaLendMarketsData: {},
|
|
604
|
+
liquityV2MarketsData: {},
|
|
605
|
+
aaveV4SpokesData: {},
|
|
606
|
+
fluidMarketsData: {},
|
|
607
|
+
makerMarketsData: {},
|
|
608
|
+
};
|
|
609
|
+
const makerCdps: Record<string, CdpInfo[]> = {};
|
|
610
|
+
|
|
611
|
+
const positions: PortfolioPositionsData = {};
|
|
612
|
+
|
|
613
|
+
for (const address of addresses) {
|
|
614
|
+
positions[address.toLowerCase() as EthAddress] = {
|
|
615
|
+
aaveV3: {},
|
|
616
|
+
aaveV4: {},
|
|
617
|
+
morphoBlue: {},
|
|
618
|
+
morphoMidnight: {}, // not fetched for the shifter (see markets above)
|
|
619
|
+
compoundV3: {},
|
|
620
|
+
spark: {},
|
|
621
|
+
maker: {},
|
|
622
|
+
aaveV2: {},
|
|
623
|
+
compoundV2: {},
|
|
624
|
+
liquity: {},
|
|
625
|
+
crvUsd: {},
|
|
626
|
+
llamaLend: {},
|
|
627
|
+
fluid: {
|
|
628
|
+
error: '',
|
|
629
|
+
data: {},
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
await Promise.allSettled([
|
|
635
|
+
// === MARKET DATA (needs to be fetched first) ===
|
|
636
|
+
...morphoMarkets.map(async (market) => {
|
|
637
|
+
markets.morphoMarketsData[market.value] = await _getMorphoBluePortfolioMarketData(client, network, market);
|
|
638
|
+
}),
|
|
639
|
+
...compoundV3Markets.map(async (market) => {
|
|
640
|
+
markets.compoundV3MarketsData[market.value] = await _getCompoundV3MarketsData(client, network, market, defaultClient);
|
|
641
|
+
}),
|
|
642
|
+
...sparkMarkets.map(async (market) => {
|
|
643
|
+
markets.sparkMarketsData[market.value] = await _getSparkMarketsData(client, network, market);
|
|
644
|
+
}),
|
|
645
|
+
...aaveV3Markets.map(async (market) => {
|
|
646
|
+
markets.aaveV3MarketsData[market.value] = await _getAaveV3MarketData(client, network, market);
|
|
647
|
+
}),
|
|
648
|
+
...aaveV4Spokes.map(async (spoke) => {
|
|
649
|
+
markets.aaveV4SpokesData[spoke.value] = await _getAaveV4SpokeData(client, network, spoke);
|
|
650
|
+
}),
|
|
651
|
+
...aaveV2Markets.map(async (market) => {
|
|
652
|
+
markets.aaveV2MarketsData[market.value] = await _getAaveV2MarketsData(client, network, market);
|
|
653
|
+
}),
|
|
654
|
+
...compoundV2Markets.map(async (market) => {
|
|
655
|
+
markets.compoundV2MarketsData[market.value] = await _getCompoundV2MarketsData(client, network);
|
|
656
|
+
}),
|
|
657
|
+
...crvUsdMarkets.map(async (market) => {
|
|
658
|
+
markets.crvUsdMarketsData[market.value] = await _getCurveUsdGlobalData(client, network, market);
|
|
659
|
+
}),
|
|
660
|
+
...llamaLendMarkets.map(async (market) => {
|
|
661
|
+
markets.llamaLendMarketsData[market.value] = await _getLlamaLendGlobalData(client, network, market);
|
|
662
|
+
}),
|
|
663
|
+
...liquityV2Markets.map(async (market) => {
|
|
664
|
+
markets.liquityV2MarketsData[market.value] = await _getLiquityV2MarketData(client, network, market);
|
|
665
|
+
}),
|
|
666
|
+
(async () => {
|
|
667
|
+
if (!isFluidSupported) return;
|
|
668
|
+
try {
|
|
669
|
+
markets.fluidMarketsData = await _getAllFluidMarketDataPortfolio(client, network);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
console.error('Error fetching Fluid markets data:', error);
|
|
672
|
+
}
|
|
673
|
+
})(),
|
|
674
|
+
(async () => {
|
|
675
|
+
if (!isMainnet) return; // Maker CDPs are only available on mainnet
|
|
676
|
+
try {
|
|
677
|
+
markets.makerMarketsData = await _getMakerIlksData(client, network, MakerActiveIlks);
|
|
678
|
+
} catch (error) {
|
|
679
|
+
console.error('Error fetching Maker ilks data:', error);
|
|
680
|
+
}
|
|
681
|
+
})(),
|
|
682
|
+
|
|
683
|
+
// === INDEPENDENT USER DATA (doesn't depend on market data) ===
|
|
684
|
+
...addresses.map(async (address) => {
|
|
685
|
+
if (!isMainnet) return; // Maker CDPs are only available on mainnet
|
|
686
|
+
const makerCdp = await _getUserCdps(client, network, address);
|
|
687
|
+
makerCdps[address.toLowerCase() as EthAddress] = makerCdp;
|
|
688
|
+
}),
|
|
689
|
+
...addresses.map(async (address) => {
|
|
690
|
+
try {
|
|
691
|
+
if (!isFluidSupported) return; // Fluid is not available on Optimism
|
|
692
|
+
const userPositions = (await _getUserPositionsPortfolio(client, network, address));
|
|
693
|
+
for (const position of userPositions) {
|
|
694
|
+
if (position.userData && new Dec(position.userData.suppliedUsd).gt(0)) {
|
|
695
|
+
positions[address.toLowerCase() as EthAddress].fluid.data[position.userData.nftId] = position.userData;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
} catch (error) {
|
|
699
|
+
console.error(`Error fetching Fluid positions for address ${address}:`, error);
|
|
700
|
+
positions[address.toLowerCase() as EthAddress].fluid = {
|
|
701
|
+
error: `Error fetching Fluid positions for address ${address}`,
|
|
702
|
+
data: {},
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}),
|
|
443
706
|
]);
|
|
444
707
|
|
|
445
708
|
await Promise.all([
|
|
446
|
-
...aaveV3Markets.map((market) =>
|
|
709
|
+
...aaveV3Markets.map((market) => addresses.map(async (address) => {
|
|
447
710
|
try {
|
|
448
|
-
const accData = await _getAaveV3AccountData(client, network, address, { selectedMarket: market, ...aaveV3MarketsData[market.value] });
|
|
449
|
-
|
|
711
|
+
const accData = await _getAaveV3AccountData(client, network, address, { selectedMarket: market, ...markets.aaveV3MarketsData[market.value] });
|
|
712
|
+
// Always store the position (even with no supply) so the eMode category is available to the shifter
|
|
713
|
+
positions[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: accData };
|
|
450
714
|
} catch (error) {
|
|
451
715
|
console.error(`Error fetching AaveV3 account data for address ${address} on market ${market.value}:`, error);
|
|
452
716
|
positions[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: `Error fetching AaveV3 account data for address ${address} on market ${market.value}`, data: null };
|
|
453
717
|
}
|
|
454
718
|
})).flat(),
|
|
455
|
-
...aaveV4Spokes.map((spoke) =>
|
|
719
|
+
...aaveV4Spokes.map((spoke) => addresses.map(async (address) => {
|
|
456
720
|
try {
|
|
457
|
-
const accData = await _getAaveV4AccountData(client, network, aaveV4SpokesData[spoke.value], address);
|
|
721
|
+
const accData = await _getAaveV4AccountData(client, network, markets.aaveV4SpokesData[spoke.value], address);
|
|
458
722
|
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].aaveV4[spoke.value] = { error: '', data: accData };
|
|
459
723
|
} catch (error) {
|
|
460
724
|
console.error(`Error fetching AaveV4 account data for address ${address} on spoke ${spoke.value}:`, error);
|
|
@@ -463,71 +727,36 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
463
727
|
})).flat(),
|
|
464
728
|
...morphoMarkets.map((market) => addresses.map(async (address) => {
|
|
465
729
|
try {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
includeStaking ? getMorphoEarn(client, network, address, market, morphoMarketsData[market.value]) : null,
|
|
469
|
-
]);
|
|
470
|
-
if (accDataPromise.status === 'rejected') {
|
|
471
|
-
console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, accDataPromise.reason);
|
|
472
|
-
positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
|
|
473
|
-
}
|
|
474
|
-
if (earnDataPromise.status === 'rejected') {
|
|
475
|
-
// An Earn failure belongs to the staking entry: the lending position is not the one that failed.
|
|
476
|
-
console.error(`Error fetching MorphoBlue earn data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
|
|
477
|
-
stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue earn data for address ${address} on market ${market.value}`, data: null };
|
|
478
|
-
}
|
|
479
|
-
if (accDataPromise.status !== 'rejected') {
|
|
480
|
-
const accData = accDataPromise.value;
|
|
481
|
-
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: '', data: accData };
|
|
482
|
-
}
|
|
483
|
-
if (earnDataPromise.status !== 'rejected') {
|
|
484
|
-
const earnData = earnDataPromise.value;
|
|
485
|
-
if (earnData && new Dec(earnData.amount).gt(0)) {
|
|
486
|
-
stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = {
|
|
487
|
-
error: '',
|
|
488
|
-
data: earnData,
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
}
|
|
730
|
+
const accData = await _getMorphoBlueAccountData(client, network, address, market, markets.morphoMarketsData[market.value]);
|
|
731
|
+
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: '', data: accData };
|
|
492
732
|
} catch (error) {
|
|
493
733
|
console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, error);
|
|
494
734
|
positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
|
|
495
735
|
}
|
|
496
736
|
})).flat(),
|
|
497
|
-
...morphoMidnightMarkets.map((market) => addresses.map(async (address) => {
|
|
498
|
-
try {
|
|
499
|
-
// Markets are created lazily on the first position, so an uncreated one can hold no position.
|
|
500
|
-
if (!morphoMidnightMarketsData[market.value]?.isCreated) return;
|
|
501
|
-
const accData = await _getMorphoMidnightAccountData(client, network, address, market, morphoMidnightMarketsData[market.value]);
|
|
502
|
-
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: '', data: accData };
|
|
503
|
-
} catch (error) {
|
|
504
|
-
console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, error);
|
|
505
|
-
positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: `Error fetching MorphoMidnight account data for address ${address} on market ${market.value}`, data: null };
|
|
506
|
-
}
|
|
507
|
-
})).flat(),
|
|
508
737
|
...compoundV3Markets.map((market) => addresses.map(async (address) => {
|
|
509
738
|
try {
|
|
510
|
-
const accData = await _getCompoundV3AccountData(client, network, address, ZERO_ADDRESS, { selectedMarket: market, assetsData: compoundV3MarketsData[market.value].assetsData });
|
|
739
|
+
const accData = await _getCompoundV3AccountData(client, network, address, ZERO_ADDRESS, { selectedMarket: market, assetsData: markets.compoundV3MarketsData[market.value].assetsData });
|
|
511
740
|
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].compoundV3[market.value] = { error: '', data: accData };
|
|
512
741
|
} catch (error) {
|
|
513
742
|
console.error(`Error fetching CompoundV3 account data for address ${address} on market ${market.value}:`, error);
|
|
514
743
|
positions[address.toLowerCase() as EthAddress].compoundV3[market.value] = { error: `Error fetching CompoundV3 account data for address ${address} on market ${market.value}`, data: null };
|
|
515
744
|
}
|
|
516
745
|
})).flat(),
|
|
517
|
-
...sparkMarkets.map((market) =>
|
|
746
|
+
...sparkMarkets.map((market) => addresses.map(async (address) => {
|
|
518
747
|
try {
|
|
519
|
-
const accData = await _getSparkAccountData(client, network, address, { selectedMarket: market, assetsData: sparkMarketsData[market.value].assetsData, eModeCategoriesData: sparkMarketsData[market.value].eModeCategoriesData });
|
|
520
|
-
|
|
748
|
+
const accData = await _getSparkAccountData(client, network, address, { selectedMarket: market, assetsData: markets.sparkMarketsData[market.value].assetsData, eModeCategoriesData: markets.sparkMarketsData[market.value].eModeCategoriesData });
|
|
749
|
+
// Always store the position (even with no supply) so the eMode category is available to the shifter
|
|
750
|
+
positions[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: accData };
|
|
521
751
|
} catch (error) {
|
|
522
752
|
console.error(`Error fetching Spark account data for address ${address} on market ${market.value}:`, error);
|
|
523
753
|
positions[address.toLowerCase() as EthAddress].spark[market.value] = { error: `Error fetching Spark account data for address ${address} on market ${market.value}`, data: null };
|
|
524
754
|
}
|
|
525
755
|
})).flat(),
|
|
526
|
-
|
|
527
|
-
// before a vault's data lands and the vault is missing from the result.
|
|
528
|
-
...addresses.flatMap((address) => (makerCdps[address.toLowerCase() as EthAddress] ?? []).map(async (cdpInfo) => {
|
|
756
|
+
...addresses.map((address) => (makerCdps[address.toLowerCase() as EthAddress] || []).map(async (cdpInfo) => {
|
|
529
757
|
try {
|
|
530
|
-
|
|
758
|
+
// reuse ilk data fetched for the markets payload; ilks outside the active set are fetched on demand
|
|
759
|
+
const cdpData = await _getMakerCdpData(client, network, cdpInfo, markets.makerMarketsData[cdpInfo.ilkLabel]);
|
|
531
760
|
if (cdpData) {
|
|
532
761
|
positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: '', data: cdpData };
|
|
533
762
|
}
|
|
@@ -535,10 +764,10 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
535
764
|
console.error(`Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}:`, error);
|
|
536
765
|
positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: `Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}`, data: null };
|
|
537
766
|
}
|
|
538
|
-
})),
|
|
767
|
+
})).flat(),
|
|
539
768
|
...aaveV2Markets.map((market) => addresses.map(async (address) => {
|
|
540
769
|
try {
|
|
541
|
-
const accData = await _getAaveV2AccountData(client, network, address, aaveV2MarketsData[market.value].assetsData, market);
|
|
770
|
+
const accData = await _getAaveV2AccountData(client, network, address, markets.aaveV2MarketsData[market.value].assetsData, market);
|
|
542
771
|
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].aaveV2[market.value] = { error: '', data: accData };
|
|
543
772
|
} catch (error) {
|
|
544
773
|
console.error(`Error fetching AaveV2 account data for address ${address}:`, error);
|
|
@@ -547,7 +776,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
547
776
|
})).flat(),
|
|
548
777
|
...compoundV2Markets.map((market) => addresses.map(async (address) => {
|
|
549
778
|
try {
|
|
550
|
-
const accData = await _getCompoundV2AccountData(client, network, address, compoundV2MarketsData[market.value].assetsData);
|
|
779
|
+
const accData = await _getCompoundV2AccountData(client, network, address, markets.compoundV2MarketsData[market.value].assetsData);
|
|
551
780
|
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].compoundV2[market.value] = { error: '', data: accData };
|
|
552
781
|
} catch (error) {
|
|
553
782
|
console.error(`Error fetching CompoundV2 account data for address ${address}:`, error);
|
|
@@ -566,9 +795,9 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
566
795
|
}),
|
|
567
796
|
...crvUsdMarkets.map((market) => addresses.map(async (address) => {
|
|
568
797
|
try {
|
|
569
|
-
const accData = await _getCurveUsdUserData(client, network, address, market, crvUsdMarketsData[market.value].activeBand);
|
|
798
|
+
const accData = await _getCurveUsdUserData(client, network, address, market, markets.crvUsdMarketsData[market.value].activeBand, markets.crvUsdMarketsData[market.value].borrowRate);
|
|
570
799
|
if (new Dec(accData.suppliedUsd).gt(0) || new Dec(accData.borrowedUsd).gt(0)) {
|
|
571
|
-
positions[address.toLowerCase() as EthAddress].crvUsd[market.value] = { error: '', data:
|
|
800
|
+
positions[address.toLowerCase() as EthAddress].crvUsd[market.value] = { error: '', data: accData };
|
|
572
801
|
}
|
|
573
802
|
} catch (error) {
|
|
574
803
|
console.error(`Error fetching Curve USD account data for address ${address} on market ${market.value}:`, error);
|
|
@@ -577,35 +806,19 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
577
806
|
})).flat(),
|
|
578
807
|
...llamaLendMarkets.map((market) => addresses.map(async (address) => {
|
|
579
808
|
try {
|
|
580
|
-
const accData = await _getLlamaLendUserData(client, network, address, market, llamaLendMarketsData[market.value]);
|
|
809
|
+
const accData = await _getLlamaLendUserData(client, network, address, market, markets.llamaLendMarketsData[market.value]);
|
|
581
810
|
if (new Dec(accData.suppliedUsd).gt(0) || new Dec(accData.borrowedUsd).gt(0)) {
|
|
582
|
-
positions[address.toLowerCase() as EthAddress].llamaLend[market.value] = { error: '', data: { ...accData, borrowRate: llamaLendMarketsData[market.value].borrowRate } };
|
|
811
|
+
positions[address.toLowerCase() as EthAddress].llamaLend[market.value] = { error: '', data: { ...accData, borrowRate: markets.llamaLendMarketsData[market.value].borrowRate } };
|
|
583
812
|
}
|
|
584
813
|
} catch (error) {
|
|
585
814
|
console.error(`Error fetching LlamaLend account data for address ${address} on market ${market.value}:`, error);
|
|
586
815
|
positions[address.toLowerCase() as EthAddress].llamaLend[market.value] = { error: `Error fetching LlamaLend account data for address ${address} on market ${market.value}`, data: null };
|
|
587
816
|
}
|
|
588
817
|
})).flat(),
|
|
589
|
-
// liquity sBold/yBold and staking options
|
|
590
|
-
...stakingAddresses.map(async (address) => {
|
|
591
|
-
try {
|
|
592
|
-
if (!isMainnet) {
|
|
593
|
-
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase() as EthAddress].liquityV2, address);
|
|
597
|
-
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
|
|
598
|
-
} catch (error) {
|
|
599
|
-
console.error(`Error fetching SBold/YBold data for address ${address}:`, error);
|
|
600
|
-
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: `Error fetching sBold/yBold data for address ${address}`, data: null };
|
|
601
|
-
}
|
|
602
|
-
}),
|
|
603
818
|
]);
|
|
604
819
|
|
|
605
820
|
return {
|
|
606
821
|
positions,
|
|
607
|
-
stakingPositions,
|
|
608
|
-
rewardsData,
|
|
609
822
|
markets,
|
|
610
823
|
};
|
|
611
824
|
}
|