@defisaver/positions-sdk 2.1.159 → 2.1.160-pos-history-2-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/aaveV3/index.d.ts +39 -0
- package/cjs/aaveV3/index.js +230 -1
- package/cjs/constants/index.d.ts +1 -0
- package/cjs/constants/index.js +4 -1
- package/cjs/helpers/morphoBlueHelpers/index.js +3 -4
- package/cjs/savings/morphoVaults/index.js +2 -2
- package/esm/aaveV3/index.d.ts +39 -0
- package/esm/aaveV3/index.js +224 -1
- package/esm/constants/index.d.ts +1 -0
- package/esm/constants/index.js +3 -0
- package/esm/helpers/morphoBlueHelpers/index.js +4 -5
- package/esm/savings/morphoVaults/index.js +2 -2
- package/package.json +1 -1
- package/src/aaveV3/index.ts +285 -0
- package/src/constants/index.ts +5 -1
- package/src/helpers/morphoBlueHelpers/index.ts +6 -5
- package/src/savings/morphoVaults/index.ts +2 -3
package/cjs/aaveV3/index.d.ts
CHANGED
|
@@ -32,6 +32,45 @@ export declare const EMPTY_AAVE_DATA: {
|
|
|
32
32
|
};
|
|
33
33
|
export declare const _getAaveV3AccountBalances: (provider: Client, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
|
|
34
34
|
export declare const getAaveV3AccountBalances: (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
|
|
35
|
+
export interface AaveV3ReserveTokenAddresses {
|
|
36
|
+
[symbol: string]: {
|
|
37
|
+
symbol: string;
|
|
38
|
+
underlyingAddress: EthAddress;
|
|
39
|
+
aTokenAddress: EthAddress;
|
|
40
|
+
stableDebtTokenAddress: EthAddress;
|
|
41
|
+
variableDebtTokenAddress: EthAddress;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface AaveV3HistoricalBalance {
|
|
45
|
+
block: number;
|
|
46
|
+
suppliedUsd: string;
|
|
47
|
+
borrowedUsd: string;
|
|
48
|
+
netUsd: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
|
|
52
|
+
* These are effectively immutable per reserve, so fetch once and reuse across all history points.
|
|
53
|
+
*/
|
|
54
|
+
export declare const _getAaveV3ReserveTokenAddresses: (provider: Client, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
|
|
55
|
+
export declare const getAaveV3ReserveTokenAddresses: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
|
|
56
|
+
/**
|
|
57
|
+
* Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
|
|
58
|
+
* Aave can rotate the oracle over time, so a caller hoisting one address across many historical
|
|
59
|
+
* points must resolve it at both ends of its block range and only reuse it when the two agree —
|
|
60
|
+
* otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
|
|
61
|
+
*/
|
|
62
|
+
export declare const _getAaveV3OracleAddress: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
|
|
63
|
+
export declare const getAaveV3OracleAddress: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
|
|
64
|
+
/**
|
|
65
|
+
* Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
|
|
66
|
+
* without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
|
|
67
|
+
* to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
|
|
68
|
+
* verified identical at both ends of the sampled range) to collapse the point into a single multicall —
|
|
69
|
+
* with it the balances and all prices ride one request; without it the oracle is resolved at the block
|
|
70
|
+
* inside the balance multicall and the active assets are priced in a second one.
|
|
71
|
+
*/
|
|
72
|
+
export declare const _getAaveV3HistoricalBalance: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
|
|
73
|
+
export declare const getAaveV3HistoricalBalance: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
|
|
35
74
|
export declare const _getAaveV3AccountData: (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
|
|
36
75
|
export declare const getAaveV3AccountData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
|
|
37
76
|
export declare const getAaveV3FullPositionData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, market: AaveMarketInfo) => Promise<AaveV3PositionData>;
|
package/cjs/aaveV3/index.js
CHANGED
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.getSghoData = exports.getMerkleCampaigns = exports.getMeritCampaigns = exports.getStakeAaveData = exports.fetchYearlyMeritApyForStakingGho = exports.REWARDABLE_ASSETS = exports.getAaveV3FullPositionData = exports.getAaveV3AccountData = exports._getAaveV3AccountData = exports.getAaveV3AccountBalances = exports._getAaveV3AccountBalances = exports.EMPTY_AAVE_DATA = exports.aaveV3EmodeCategoriesMapping = void 0;
|
|
15
|
+
exports.getSghoData = exports.getMerkleCampaigns = exports.getMeritCampaigns = exports.getStakeAaveData = exports.fetchYearlyMeritApyForStakingGho = exports.REWARDABLE_ASSETS = exports.getAaveV3FullPositionData = exports.getAaveV3AccountData = exports._getAaveV3AccountData = exports.getAaveV3HistoricalBalance = exports._getAaveV3HistoricalBalance = exports.getAaveV3OracleAddress = exports._getAaveV3OracleAddress = exports.getAaveV3ReserveTokenAddresses = exports._getAaveV3ReserveTokenAddresses = exports.getAaveV3AccountBalances = exports._getAaveV3AccountBalances = exports.EMPTY_AAVE_DATA = exports.aaveV3EmodeCategoriesMapping = void 0;
|
|
16
16
|
exports._getAaveV3MarketData = _getAaveV3MarketData;
|
|
17
17
|
exports.getAaveV3MarketData = getAaveV3MarketData;
|
|
18
18
|
const tokens_1 = require("@defisaver/tokens");
|
|
@@ -375,6 +375,235 @@ const _getAaveV3AccountBalances = (provider, network, block, addressMapping, add
|
|
|
375
375
|
exports._getAaveV3AccountBalances = _getAaveV3AccountBalances;
|
|
376
376
|
const getAaveV3AccountBalances = (provider, network, block, addressMapping, address) => __awaiter(void 0, void 0, void 0, function* () { return (0, exports._getAaveV3AccountBalances)((0, viem_1.getViemProvider)(provider, network), network, block, addressMapping, address); });
|
|
377
377
|
exports.getAaveV3AccountBalances = getAaveV3AccountBalances;
|
|
378
|
+
/**
|
|
379
|
+
* Historical net-balance helpers that bypass the AaveV3View contract.
|
|
380
|
+
*
|
|
381
|
+
* The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
|
|
382
|
+
* queried from its deployment block onwards, so it cannot read balances for positions older than that.
|
|
383
|
+
* The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
|
|
384
|
+
* launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
|
|
385
|
+
* and costs ~1 multicall per point. Used to build a position balance-history chart.
|
|
386
|
+
*/
|
|
387
|
+
// matches morphoVaults' batchSize; stays under the 500k portfolio discovery allows on simulation fork RPCs
|
|
388
|
+
const HISTORICAL_MULTICALL_BATCH_SIZE = 250000;
|
|
389
|
+
// Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
|
|
390
|
+
const AAVE_ORACLE_ABI = [
|
|
391
|
+
{
|
|
392
|
+
inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
|
|
393
|
+
name: 'getAssetPrice',
|
|
394
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
395
|
+
stateMutability: 'view',
|
|
396
|
+
type: 'function',
|
|
397
|
+
},
|
|
398
|
+
];
|
|
399
|
+
/**
|
|
400
|
+
* Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
|
|
401
|
+
* These are effectively immutable per reserve, so fetch once and reuse across all history points.
|
|
402
|
+
*/
|
|
403
|
+
const _getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () {
|
|
404
|
+
const symbols = market.assets;
|
|
405
|
+
const underlyingAddresses = symbols.map((a) => (0, tokens_1.getAssetInfo)((0, utils_1.getWrappedNativeAssetFromUnwrapped)(a), network).address);
|
|
406
|
+
// @ts-ignore market.protocolData is a valid config key at runtime
|
|
407
|
+
const dataProviderAbi = (0, contracts_1.getConfigContractAbi)(market.protocolData, network);
|
|
408
|
+
const contracts = underlyingAddresses.map((underlying) => ({
|
|
409
|
+
address: market.protocolDataAddress,
|
|
410
|
+
abi: dataProviderAbi,
|
|
411
|
+
functionName: 'getReserveTokensAddresses',
|
|
412
|
+
args: [underlying],
|
|
413
|
+
}));
|
|
414
|
+
// @ts-ignore
|
|
415
|
+
const results = yield provider.multicall({ contracts, allowFailure: true, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE });
|
|
416
|
+
const mapping = {};
|
|
417
|
+
results.forEach((res, i) => {
|
|
418
|
+
if (res.status !== 'success' || !res.result)
|
|
419
|
+
return;
|
|
420
|
+
// outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
|
|
421
|
+
const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result;
|
|
422
|
+
mapping[symbols[i]] = {
|
|
423
|
+
symbol: symbols[i],
|
|
424
|
+
underlyingAddress: underlyingAddresses[i],
|
|
425
|
+
aTokenAddress,
|
|
426
|
+
stableDebtTokenAddress,
|
|
427
|
+
variableDebtTokenAddress,
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
return mapping;
|
|
431
|
+
});
|
|
432
|
+
exports._getAaveV3ReserveTokenAddresses = _getAaveV3ReserveTokenAddresses;
|
|
433
|
+
const getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () { return (0, exports._getAaveV3ReserveTokenAddresses)((0, viem_1.getViemProvider)(provider, network, { batch: { multicall: true } }), network, market); });
|
|
434
|
+
exports.getAaveV3ReserveTokenAddresses = getAaveV3ReserveTokenAddresses;
|
|
435
|
+
/**
|
|
436
|
+
* Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
|
|
437
|
+
* Aave can rotate the oracle over time, so a caller hoisting one address across many historical
|
|
438
|
+
* points must resolve it at both ends of its block range and only reuse it when the two agree —
|
|
439
|
+
* otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
|
|
440
|
+
*/
|
|
441
|
+
const _getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') {
|
|
442
|
+
// @ts-ignore market.provider is a valid config key at runtime
|
|
443
|
+
const providerAbi = (0, contracts_1.getConfigContractAbi)(market.provider, network);
|
|
444
|
+
// @ts-ignore readContract exists on the public client returned by getViemProvider
|
|
445
|
+
const oracleAddress = yield provider.readContract(Object.assign({ address: market.providerAddress, abi: providerAbi, functionName: 'getPriceOracle' }, (0, viem_1.setViemBlockNumber)(block)));
|
|
446
|
+
if (!oracleAddress)
|
|
447
|
+
throw new Error(`AaveV3 oracle unavailable at block ${block}`);
|
|
448
|
+
return oracleAddress;
|
|
449
|
+
});
|
|
450
|
+
exports._getAaveV3OracleAddress = _getAaveV3OracleAddress;
|
|
451
|
+
const getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') { return (0, exports._getAaveV3OracleAddress)((0, viem_1.getViemProvider)(provider, network, { batch: { multicall: true } }), network, market, block); });
|
|
452
|
+
exports.getAaveV3OracleAddress = getAaveV3OracleAddress;
|
|
453
|
+
// supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf — three reads per reserve
|
|
454
|
+
const getHistoricalBalanceContracts = (entries, address) => {
|
|
455
|
+
const erc20Abi = (0, contracts_1.getConfigContractAbi)('Erc20');
|
|
456
|
+
return entries.flatMap((e) => ([
|
|
457
|
+
{
|
|
458
|
+
address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
465
|
+
},
|
|
466
|
+
]));
|
|
467
|
+
};
|
|
468
|
+
const getAssetPriceContract = (oracle, underlyingAddress) => ({
|
|
469
|
+
address: oracle,
|
|
470
|
+
abi: AAVE_ORACLE_ABI,
|
|
471
|
+
functionName: 'getAssetPrice',
|
|
472
|
+
args: [underlyingAddress],
|
|
473
|
+
});
|
|
474
|
+
/**
|
|
475
|
+
* Pairs each reserve with its three balance reads and keeps the assets the wallet actually held.
|
|
476
|
+
* Throws when every read failed — a totally failed fetch must not masquerade as a real $0 balance,
|
|
477
|
+
* so the caller can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
|
|
478
|
+
*/
|
|
479
|
+
const toActiveHistoricalAssets = (entries, balanceResults, block) => {
|
|
480
|
+
const anyBalanceRead = balanceResults.some((r) => (r === null || r === void 0 ? void 0 : r.status) === 'success');
|
|
481
|
+
if (!anyBalanceRead)
|
|
482
|
+
throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
|
|
483
|
+
return entries.map((e, i) => {
|
|
484
|
+
const supplyRes = balanceResults[i * 3];
|
|
485
|
+
const varDebtRes = balanceResults[(i * 3) + 1];
|
|
486
|
+
const stableDebtRes = balanceResults[(i * 3) + 2];
|
|
487
|
+
const supplied = (supplyRes === null || supplyRes === void 0 ? void 0 : supplyRes.status) === 'success' ? supplyRes.result.toString() : '0';
|
|
488
|
+
const varDebt = (varDebtRes === null || varDebtRes === void 0 ? void 0 : varDebtRes.status) === 'success' ? varDebtRes.result.toString() : '0';
|
|
489
|
+
const stableDebt = (stableDebtRes === null || stableDebtRes === void 0 ? void 0 : stableDebtRes.status) === 'success' ? stableDebtRes.result.toString() : '0';
|
|
490
|
+
const debt = new decimal_js_1.default(varDebt).add(stableDebt).toString();
|
|
491
|
+
return Object.assign(Object.assign({}, e), { supplied, debt });
|
|
492
|
+
}).filter((a) => a.supplied !== '0' || a.debt !== '0');
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* Sums the active assets into the point's USD totals. Throws when every price read failed — the
|
|
496
|
+
* oracle couldn't serve this block (pruned state, or a hoisted oracle that wasn't deployed yet back
|
|
497
|
+
* then), and totalling it up anyway would return a fake near-$0 balance. A single asset's price
|
|
498
|
+
* failure still just skips that asset, matching the live data path.
|
|
499
|
+
*/
|
|
500
|
+
const sumHistoricalUsd = (activeAssets, priceResBySymbol, block) => {
|
|
501
|
+
const anyPriceRead = activeAssets.some((a) => { var _a; return ((_a = priceResBySymbol[a.symbol]) === null || _a === void 0 ? void 0 : _a.status) === 'success'; });
|
|
502
|
+
if (!anyPriceRead)
|
|
503
|
+
throw new Error(`AaveV3 historical balance: all price reads failed at block ${block}`);
|
|
504
|
+
let suppliedUsd = new decimal_js_1.default(0);
|
|
505
|
+
let borrowedUsd = new decimal_js_1.default(0);
|
|
506
|
+
activeAssets.forEach((a) => {
|
|
507
|
+
const priceRes = priceResBySymbol[a.symbol];
|
|
508
|
+
if ((priceRes === null || priceRes === void 0 ? void 0 : priceRes.status) !== 'success')
|
|
509
|
+
return;
|
|
510
|
+
const priceUsd = new decimal_js_1.default(priceRes.result.toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
|
|
511
|
+
if (a.supplied !== '0')
|
|
512
|
+
suppliedUsd = suppliedUsd.add(new decimal_js_1.default((0, tokens_1.assetAmountInEth)(a.supplied, a.symbol)).mul(priceUsd));
|
|
513
|
+
if (a.debt !== '0')
|
|
514
|
+
borrowedUsd = borrowedUsd.add(new decimal_js_1.default((0, tokens_1.assetAmountInEth)(a.debt, a.symbol)).mul(priceUsd));
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
block,
|
|
518
|
+
suppliedUsd: suppliedUsd.toString(),
|
|
519
|
+
borrowedUsd: borrowedUsd.toString(),
|
|
520
|
+
netUsd: suppliedUsd.minus(borrowedUsd).toString(),
|
|
521
|
+
};
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* Fast path, ONE multicall: balances plus a price read for every reserve through the caller-hoisted
|
|
525
|
+
* oracle. Prices are read for all reserves because which assets are active isn't known until the
|
|
526
|
+
* balances come back — extra reads inside an already-paid multicall are nearly free.
|
|
527
|
+
*/
|
|
528
|
+
const fetchHistoricalPointWithOracle = (provider, entries, address, block, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
|
|
529
|
+
const balanceContracts = getHistoricalBalanceContracts(entries, address);
|
|
530
|
+
const priceContracts = entries.map((e) => getAssetPriceContract(oracleAddress, e.underlyingAddress));
|
|
531
|
+
// @ts-ignore
|
|
532
|
+
const results = yield provider.multicall({
|
|
533
|
+
contracts: [...balanceContracts, ...priceContracts], allowFailure: true, blockNumber: BigInt(block), batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
534
|
+
});
|
|
535
|
+
const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
|
|
536
|
+
const priceResBySymbol = {};
|
|
537
|
+
results.slice(balanceContracts.length).forEach((res, i) => {
|
|
538
|
+
priceResBySymbol[entries[i].symbol] = res;
|
|
539
|
+
});
|
|
540
|
+
return { activeAssets, priceResBySymbol };
|
|
541
|
+
});
|
|
542
|
+
/**
|
|
543
|
+
* Fallback path, TWO multicalls: balances plus getPriceOracle (resolving the oracle that was active
|
|
544
|
+
* AT the block — Aave can rotate it over time), then prices for just the active assets. The oracle
|
|
545
|
+
* read has no fallback: if it fails (bad archive node, rate limit, ...) the failure must surface
|
|
546
|
+
* rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
|
|
547
|
+
*/
|
|
548
|
+
const fetchHistoricalPointResolvingOracle = (provider, network, market, entries, address, block) => __awaiter(void 0, void 0, void 0, function* () {
|
|
549
|
+
const balanceContracts = getHistoricalBalanceContracts(entries, address);
|
|
550
|
+
// @ts-ignore market.provider is a valid config key at runtime
|
|
551
|
+
const providerAbi = (0, contracts_1.getConfigContractAbi)(market.provider, network);
|
|
552
|
+
const oracleContract = {
|
|
553
|
+
address: market.providerAddress,
|
|
554
|
+
abi: providerAbi,
|
|
555
|
+
functionName: 'getPriceOracle',
|
|
556
|
+
};
|
|
557
|
+
const blockNumber = BigInt(block);
|
|
558
|
+
// @ts-ignore
|
|
559
|
+
const results = yield provider.multicall({
|
|
560
|
+
contracts: [...balanceContracts, oracleContract], allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
561
|
+
});
|
|
562
|
+
const oracleRes = results[balanceContracts.length];
|
|
563
|
+
if ((oracleRes === null || oracleRes === void 0 ? void 0 : oracleRes.status) !== 'success' || !oracleRes.result)
|
|
564
|
+
throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
|
|
565
|
+
const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
|
|
566
|
+
if (!activeAssets.length)
|
|
567
|
+
return { activeAssets, priceResBySymbol: {} };
|
|
568
|
+
const priceContracts = activeAssets.map((a) => getAssetPriceContract(oracleRes.result, a.underlyingAddress));
|
|
569
|
+
// @ts-ignore
|
|
570
|
+
const priceResults = yield provider.multicall({
|
|
571
|
+
contracts: priceContracts, allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
572
|
+
});
|
|
573
|
+
const priceResBySymbol = {};
|
|
574
|
+
priceResults.forEach((res, i) => {
|
|
575
|
+
priceResBySymbol[activeAssets[i].symbol] = res;
|
|
576
|
+
});
|
|
577
|
+
return { activeAssets, priceResBySymbol };
|
|
578
|
+
});
|
|
579
|
+
/**
|
|
580
|
+
* Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
|
|
581
|
+
* without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
|
|
582
|
+
* to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
|
|
583
|
+
* verified identical at both ends of the sampled range) to collapse the point into a single multicall —
|
|
584
|
+
* with it the balances and all prices ride one request; without it the oracle is resolved at the block
|
|
585
|
+
* inside the balance multicall and the active assets are priced in a second one.
|
|
586
|
+
*/
|
|
587
|
+
const _getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
|
|
588
|
+
const empty = {
|
|
589
|
+
block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
|
|
590
|
+
};
|
|
591
|
+
if (!address)
|
|
592
|
+
return empty;
|
|
593
|
+
const tokens = reserveTokens || (yield (0, exports._getAaveV3ReserveTokenAddresses)(provider, network, market));
|
|
594
|
+
const entries = Object.values(tokens);
|
|
595
|
+
if (!entries.length)
|
|
596
|
+
return empty;
|
|
597
|
+
const { activeAssets, priceResBySymbol } = oracleAddress
|
|
598
|
+
? yield fetchHistoricalPointWithOracle(provider, entries, address, block, oracleAddress)
|
|
599
|
+
: yield fetchHistoricalPointResolvingOracle(provider, network, market, entries, address, block);
|
|
600
|
+
if (!activeAssets.length)
|
|
601
|
+
return empty;
|
|
602
|
+
return sumHistoricalUsd(activeAssets, priceResBySymbol, block);
|
|
603
|
+
});
|
|
604
|
+
exports._getAaveV3HistoricalBalance = _getAaveV3HistoricalBalance;
|
|
605
|
+
const getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () { return (0, exports._getAaveV3HistoricalBalance)((0, viem_1.getViemProvider)(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens, oracleAddress); });
|
|
606
|
+
exports.getAaveV3HistoricalBalance = getAaveV3HistoricalBalance;
|
|
378
607
|
const _getAaveV3AccountData = (provider_1, network_1, address_1, extractedState_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, address_1, extractedState_1, ...args_1], void 0, function* (provider, network, address, extractedState, blockNumber = 'latest') {
|
|
379
608
|
const { selectedMarket: market, assetsData, eModeCategoriesData, } = extractedState;
|
|
380
609
|
let payload = Object.assign(Object.assign({}, exports.EMPTY_AAVE_DATA), { lastUpdated: Date.now() });
|
package/cjs/constants/index.d.ts
CHANGED
|
@@ -10,3 +10,4 @@ export declare const ZERO_BYTES32 = "0x00000000000000000000000000000000000000000
|
|
|
10
10
|
export declare const WAD = 1000000000000000000;
|
|
11
11
|
export declare const USD_QUOTE = "0x0000000000000000000000000000000000000348";
|
|
12
12
|
export declare const borrowOperations: string[];
|
|
13
|
+
export declare const MORPHO_API_URL = "https://fe.defisaver.com/api/morpho/graphql";
|
package/cjs/constants/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.borrowOperations = exports.USD_QUOTE = exports.WAD = exports.ZERO_BYTES32 = exports.ZERO_ADDRESS = exports.BLOCKS_IN_A_YEAR = exports.AVG_BLOCK_TIME = exports.SECONDS_PER_WEEK = exports.SECONDS_PER_YEAR = exports.SECONDS_PER_DAY = exports.SECONDS_PER_HOUR = exports.SECONDS_PER_MINUTE = void 0;
|
|
3
|
+
exports.MORPHO_API_URL = exports.borrowOperations = exports.USD_QUOTE = exports.WAD = exports.ZERO_BYTES32 = exports.ZERO_ADDRESS = exports.BLOCKS_IN_A_YEAR = exports.AVG_BLOCK_TIME = exports.SECONDS_PER_WEEK = exports.SECONDS_PER_YEAR = exports.SECONDS_PER_DAY = exports.SECONDS_PER_HOUR = exports.SECONDS_PER_MINUTE = void 0;
|
|
4
4
|
exports.SECONDS_PER_MINUTE = 60;
|
|
5
5
|
exports.SECONDS_PER_HOUR = 60 * exports.SECONDS_PER_MINUTE;
|
|
6
6
|
exports.SECONDS_PER_DAY = 24 * exports.SECONDS_PER_HOUR;
|
|
@@ -13,3 +13,6 @@ exports.ZERO_BYTES32 = '0x000000000000000000000000000000000000000000000000000000
|
|
|
13
13
|
exports.WAD = 1e18;
|
|
14
14
|
exports.USD_QUOTE = '0x0000000000000000000000000000000000000348';
|
|
15
15
|
exports.borrowOperations = ['borrow', 'payback'];
|
|
16
|
+
// Proxies Morpho's GraphQL API through the DFS backend, which attaches the private API key
|
|
17
|
+
// for better rate limits (DEV-13673)
|
|
18
|
+
exports.MORPHO_API_URL = 'https://fe.defisaver.com/api/morpho/graphql';
|
|
@@ -139,7 +139,6 @@ const getApyAfterValuesEstimation = (selectedMarket, actions, provider, network)
|
|
|
139
139
|
return { borrowRate, supplyRate };
|
|
140
140
|
});
|
|
141
141
|
exports.getApyAfterValuesEstimation = getApyAfterValuesEstimation;
|
|
142
|
-
const API_URL = 'https://api.morpho.org/graphql';
|
|
143
142
|
// Morpho Blue ACRM (Adaptive Curve IRM) always targets 90% utilization — protocol constant
|
|
144
143
|
const ACRM_TARGET_UTILIZATION = '900000000000000000';
|
|
145
144
|
const MARKET_QUERY = `
|
|
@@ -216,7 +215,7 @@ const REWARDS_QUERY = `
|
|
|
216
215
|
const getReallocatableLiquidity = (marketId_1, ...args_1) => __awaiter(void 0, [marketId_1, ...args_1], void 0, function* (marketId, network = common_1.NetworkNumber.Eth) {
|
|
217
216
|
var _a;
|
|
218
217
|
try {
|
|
219
|
-
const response = yield fetch(
|
|
218
|
+
const response = yield fetch(constants_1.MORPHO_API_URL, {
|
|
220
219
|
method: 'POST',
|
|
221
220
|
headers: { 'Content-Type': 'application/json' },
|
|
222
221
|
body: JSON.stringify({
|
|
@@ -280,7 +279,7 @@ const getReallocation = (market_1, assetsData_1, amountToBorrow_1, ...args_1) =>
|
|
|
280
279
|
var _a, _b, _c;
|
|
281
280
|
try {
|
|
282
281
|
const { marketId, loanToken } = market;
|
|
283
|
-
const response = yield fetch(
|
|
282
|
+
const response = yield fetch(constants_1.MORPHO_API_URL, {
|
|
284
283
|
method: 'POST',
|
|
285
284
|
headers: { 'Content-Type': 'application/json' },
|
|
286
285
|
body: JSON.stringify({
|
|
@@ -354,7 +353,7 @@ const getReallocation = (market_1, assetsData_1, amountToBorrow_1, ...args_1) =>
|
|
|
354
353
|
exports.getReallocation = getReallocation;
|
|
355
354
|
const getRewardsForMarket = (marketId_1, ...args_1) => __awaiter(void 0, [marketId_1, ...args_1], void 0, function* (marketId, network = common_1.NetworkNumber.Eth) {
|
|
356
355
|
var _a;
|
|
357
|
-
const response = yield fetch(
|
|
356
|
+
const response = yield fetch(constants_1.MORPHO_API_URL, {
|
|
358
357
|
method: 'POST',
|
|
359
358
|
headers: { 'Content-Type': 'application/json' },
|
|
360
359
|
body: JSON.stringify({
|
|
@@ -55,7 +55,7 @@ const morphoVaultsOptions = __importStar(require("./options"));
|
|
|
55
55
|
exports.morphoVaultsOptions = morphoVaultsOptions;
|
|
56
56
|
const viem_1 = require("../../services/viem");
|
|
57
57
|
const contracts_1 = require("../../contracts");
|
|
58
|
-
const
|
|
58
|
+
const constants_1 = require("../../constants");
|
|
59
59
|
// Morpho API caps list page size at 100 items
|
|
60
60
|
const MORPHO_API_PAGE_SIZE = 100;
|
|
61
61
|
const vaultsLiquidityQuery = `
|
|
@@ -80,7 +80,7 @@ const fetchMorphoVaultsLiquidity = (network, vaults) => __awaiter(void 0, void 0
|
|
|
80
80
|
chunks.push(vaults.slice(i, i + MORPHO_API_PAGE_SIZE));
|
|
81
81
|
const liquidityByAddress = {};
|
|
82
82
|
yield Promise.all(chunks.map((chunk) => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
-
const data = yield (0, graphql_request_1.request)(
|
|
83
|
+
const data = yield (0, graphql_request_1.request)(constants_1.MORPHO_API_URL, vaultsLiquidityQuery, {
|
|
84
84
|
addresses: chunk.map((vault) => vault.address),
|
|
85
85
|
chainIds: [network],
|
|
86
86
|
// the BigInt scalar serializes as a JSON number when it fits in Number.MAX_SAFE_INTEGER, a string otherwise
|
package/esm/aaveV3/index.d.ts
CHANGED
|
@@ -32,6 +32,45 @@ export declare const EMPTY_AAVE_DATA: {
|
|
|
32
32
|
};
|
|
33
33
|
export declare const _getAaveV3AccountBalances: (provider: Client, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
|
|
34
34
|
export declare const getAaveV3AccountBalances: (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
|
|
35
|
+
export interface AaveV3ReserveTokenAddresses {
|
|
36
|
+
[symbol: string]: {
|
|
37
|
+
symbol: string;
|
|
38
|
+
underlyingAddress: EthAddress;
|
|
39
|
+
aTokenAddress: EthAddress;
|
|
40
|
+
stableDebtTokenAddress: EthAddress;
|
|
41
|
+
variableDebtTokenAddress: EthAddress;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface AaveV3HistoricalBalance {
|
|
45
|
+
block: number;
|
|
46
|
+
suppliedUsd: string;
|
|
47
|
+
borrowedUsd: string;
|
|
48
|
+
netUsd: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
|
|
52
|
+
* These are effectively immutable per reserve, so fetch once and reuse across all history points.
|
|
53
|
+
*/
|
|
54
|
+
export declare const _getAaveV3ReserveTokenAddresses: (provider: Client, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
|
|
55
|
+
export declare const getAaveV3ReserveTokenAddresses: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
|
|
56
|
+
/**
|
|
57
|
+
* Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
|
|
58
|
+
* Aave can rotate the oracle over time, so a caller hoisting one address across many historical
|
|
59
|
+
* points must resolve it at both ends of its block range and only reuse it when the two agree —
|
|
60
|
+
* otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
|
|
61
|
+
*/
|
|
62
|
+
export declare const _getAaveV3OracleAddress: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
|
|
63
|
+
export declare const getAaveV3OracleAddress: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
|
|
64
|
+
/**
|
|
65
|
+
* Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
|
|
66
|
+
* without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
|
|
67
|
+
* to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
|
|
68
|
+
* verified identical at both ends of the sampled range) to collapse the point into a single multicall —
|
|
69
|
+
* with it the balances and all prices ride one request; without it the oracle is resolved at the block
|
|
70
|
+
* inside the balance multicall and the active assets are priced in a second one.
|
|
71
|
+
*/
|
|
72
|
+
export declare const _getAaveV3HistoricalBalance: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
|
|
73
|
+
export declare const getAaveV3HistoricalBalance: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
|
|
35
74
|
export declare const _getAaveV3AccountData: (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
|
|
36
75
|
export declare const getAaveV3AccountData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
|
|
37
76
|
export declare const getAaveV3FullPositionData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, market: AaveMarketInfo) => Promise<AaveV3PositionData>;
|
package/esm/aaveV3/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { assetAmountInEth, assetAmountInWei, getAssetInfo } from '@defisaver/tokens';
|
|
11
11
|
import Dec from 'decimal.js';
|
|
12
|
-
import { AaveIncentiveDataProviderV3ContractViem, AaveIncentivesControllerViem, AaveV3ViewContractViem, createViemContractFromConfigFunc, StkAAVEViem, } from '../contracts';
|
|
12
|
+
import { AaveIncentiveDataProviderV3ContractViem, AaveIncentivesControllerViem, AaveV3ViewContractViem, createViemContractFromConfigFunc, getConfigContractAbi, StkAAVEViem, } from '../contracts';
|
|
13
13
|
import { aaveAnyGetAggregatedPositionData, aaveV3IsInIsolationMode, aaveV3IsInSiloedMode } from '../helpers/aaveHelpers';
|
|
14
14
|
import { AAVE_V3 } from '../markets/aave';
|
|
15
15
|
import { aprToApy, calculateBorrowingAssetLimit } from '../moneymarket';
|
|
@@ -362,6 +362,229 @@ export const _getAaveV3AccountBalances = (provider, network, block, addressMappi
|
|
|
362
362
|
return balances;
|
|
363
363
|
});
|
|
364
364
|
export const getAaveV3AccountBalances = (provider, network, block, addressMapping, address) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3AccountBalances(getViemProvider(provider, network), network, block, addressMapping, address); });
|
|
365
|
+
/**
|
|
366
|
+
* Historical net-balance helpers that bypass the AaveV3View contract.
|
|
367
|
+
*
|
|
368
|
+
* The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
|
|
369
|
+
* queried from its deployment block onwards, so it cannot read balances for positions older than that.
|
|
370
|
+
* The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
|
|
371
|
+
* launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
|
|
372
|
+
* and costs ~1 multicall per point. Used to build a position balance-history chart.
|
|
373
|
+
*/
|
|
374
|
+
// matches morphoVaults' batchSize; stays under the 500k portfolio discovery allows on simulation fork RPCs
|
|
375
|
+
const HISTORICAL_MULTICALL_BATCH_SIZE = 250000;
|
|
376
|
+
// Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
|
|
377
|
+
const AAVE_ORACLE_ABI = [
|
|
378
|
+
{
|
|
379
|
+
inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
|
|
380
|
+
name: 'getAssetPrice',
|
|
381
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
382
|
+
stateMutability: 'view',
|
|
383
|
+
type: 'function',
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
/**
|
|
387
|
+
* Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
|
|
388
|
+
* These are effectively immutable per reserve, so fetch once and reuse across all history points.
|
|
389
|
+
*/
|
|
390
|
+
export const _getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () {
|
|
391
|
+
const symbols = market.assets;
|
|
392
|
+
const underlyingAddresses = symbols.map((a) => getAssetInfo(getWrappedNativeAssetFromUnwrapped(a), network).address);
|
|
393
|
+
// @ts-ignore market.protocolData is a valid config key at runtime
|
|
394
|
+
const dataProviderAbi = getConfigContractAbi(market.protocolData, network);
|
|
395
|
+
const contracts = underlyingAddresses.map((underlying) => ({
|
|
396
|
+
address: market.protocolDataAddress,
|
|
397
|
+
abi: dataProviderAbi,
|
|
398
|
+
functionName: 'getReserveTokensAddresses',
|
|
399
|
+
args: [underlying],
|
|
400
|
+
}));
|
|
401
|
+
// @ts-ignore
|
|
402
|
+
const results = yield provider.multicall({ contracts, allowFailure: true, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE });
|
|
403
|
+
const mapping = {};
|
|
404
|
+
results.forEach((res, i) => {
|
|
405
|
+
if (res.status !== 'success' || !res.result)
|
|
406
|
+
return;
|
|
407
|
+
// outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
|
|
408
|
+
const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result;
|
|
409
|
+
mapping[symbols[i]] = {
|
|
410
|
+
symbol: symbols[i],
|
|
411
|
+
underlyingAddress: underlyingAddresses[i],
|
|
412
|
+
aTokenAddress,
|
|
413
|
+
stableDebtTokenAddress,
|
|
414
|
+
variableDebtTokenAddress,
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
return mapping;
|
|
418
|
+
});
|
|
419
|
+
export const getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3ReserveTokenAddresses(getViemProvider(provider, network, { batch: { multicall: true } }), network, market); });
|
|
420
|
+
/**
|
|
421
|
+
* Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
|
|
422
|
+
* Aave can rotate the oracle over time, so a caller hoisting one address across many historical
|
|
423
|
+
* points must resolve it at both ends of its block range and only reuse it when the two agree —
|
|
424
|
+
* otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
|
|
425
|
+
*/
|
|
426
|
+
export const _getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') {
|
|
427
|
+
// @ts-ignore market.provider is a valid config key at runtime
|
|
428
|
+
const providerAbi = getConfigContractAbi(market.provider, network);
|
|
429
|
+
// @ts-ignore readContract exists on the public client returned by getViemProvider
|
|
430
|
+
const oracleAddress = yield provider.readContract(Object.assign({ address: market.providerAddress, abi: providerAbi, functionName: 'getPriceOracle' }, setViemBlockNumber(block)));
|
|
431
|
+
if (!oracleAddress)
|
|
432
|
+
throw new Error(`AaveV3 oracle unavailable at block ${block}`);
|
|
433
|
+
return oracleAddress;
|
|
434
|
+
});
|
|
435
|
+
export const getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') { return _getAaveV3OracleAddress(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, block); });
|
|
436
|
+
// supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf — three reads per reserve
|
|
437
|
+
const getHistoricalBalanceContracts = (entries, address) => {
|
|
438
|
+
const erc20Abi = getConfigContractAbi('Erc20');
|
|
439
|
+
return entries.flatMap((e) => ([
|
|
440
|
+
{
|
|
441
|
+
address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
448
|
+
},
|
|
449
|
+
]));
|
|
450
|
+
};
|
|
451
|
+
const getAssetPriceContract = (oracle, underlyingAddress) => ({
|
|
452
|
+
address: oracle,
|
|
453
|
+
abi: AAVE_ORACLE_ABI,
|
|
454
|
+
functionName: 'getAssetPrice',
|
|
455
|
+
args: [underlyingAddress],
|
|
456
|
+
});
|
|
457
|
+
/**
|
|
458
|
+
* Pairs each reserve with its three balance reads and keeps the assets the wallet actually held.
|
|
459
|
+
* Throws when every read failed — a totally failed fetch must not masquerade as a real $0 balance,
|
|
460
|
+
* so the caller can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
|
|
461
|
+
*/
|
|
462
|
+
const toActiveHistoricalAssets = (entries, balanceResults, block) => {
|
|
463
|
+
const anyBalanceRead = balanceResults.some((r) => (r === null || r === void 0 ? void 0 : r.status) === 'success');
|
|
464
|
+
if (!anyBalanceRead)
|
|
465
|
+
throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
|
|
466
|
+
return entries.map((e, i) => {
|
|
467
|
+
const supplyRes = balanceResults[i * 3];
|
|
468
|
+
const varDebtRes = balanceResults[(i * 3) + 1];
|
|
469
|
+
const stableDebtRes = balanceResults[(i * 3) + 2];
|
|
470
|
+
const supplied = (supplyRes === null || supplyRes === void 0 ? void 0 : supplyRes.status) === 'success' ? supplyRes.result.toString() : '0';
|
|
471
|
+
const varDebt = (varDebtRes === null || varDebtRes === void 0 ? void 0 : varDebtRes.status) === 'success' ? varDebtRes.result.toString() : '0';
|
|
472
|
+
const stableDebt = (stableDebtRes === null || stableDebtRes === void 0 ? void 0 : stableDebtRes.status) === 'success' ? stableDebtRes.result.toString() : '0';
|
|
473
|
+
const debt = new Dec(varDebt).add(stableDebt).toString();
|
|
474
|
+
return Object.assign(Object.assign({}, e), { supplied, debt });
|
|
475
|
+
}).filter((a) => a.supplied !== '0' || a.debt !== '0');
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* Sums the active assets into the point's USD totals. Throws when every price read failed — the
|
|
479
|
+
* oracle couldn't serve this block (pruned state, or a hoisted oracle that wasn't deployed yet back
|
|
480
|
+
* then), and totalling it up anyway would return a fake near-$0 balance. A single asset's price
|
|
481
|
+
* failure still just skips that asset, matching the live data path.
|
|
482
|
+
*/
|
|
483
|
+
const sumHistoricalUsd = (activeAssets, priceResBySymbol, block) => {
|
|
484
|
+
const anyPriceRead = activeAssets.some((a) => { var _a; return ((_a = priceResBySymbol[a.symbol]) === null || _a === void 0 ? void 0 : _a.status) === 'success'; });
|
|
485
|
+
if (!anyPriceRead)
|
|
486
|
+
throw new Error(`AaveV3 historical balance: all price reads failed at block ${block}`);
|
|
487
|
+
let suppliedUsd = new Dec(0);
|
|
488
|
+
let borrowedUsd = new Dec(0);
|
|
489
|
+
activeAssets.forEach((a) => {
|
|
490
|
+
const priceRes = priceResBySymbol[a.symbol];
|
|
491
|
+
if ((priceRes === null || priceRes === void 0 ? void 0 : priceRes.status) !== 'success')
|
|
492
|
+
return;
|
|
493
|
+
const priceUsd = new Dec(priceRes.result.toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
|
|
494
|
+
if (a.supplied !== '0')
|
|
495
|
+
suppliedUsd = suppliedUsd.add(new Dec(assetAmountInEth(a.supplied, a.symbol)).mul(priceUsd));
|
|
496
|
+
if (a.debt !== '0')
|
|
497
|
+
borrowedUsd = borrowedUsd.add(new Dec(assetAmountInEth(a.debt, a.symbol)).mul(priceUsd));
|
|
498
|
+
});
|
|
499
|
+
return {
|
|
500
|
+
block,
|
|
501
|
+
suppliedUsd: suppliedUsd.toString(),
|
|
502
|
+
borrowedUsd: borrowedUsd.toString(),
|
|
503
|
+
netUsd: suppliedUsd.minus(borrowedUsd).toString(),
|
|
504
|
+
};
|
|
505
|
+
};
|
|
506
|
+
/**
|
|
507
|
+
* Fast path, ONE multicall: balances plus a price read for every reserve through the caller-hoisted
|
|
508
|
+
* oracle. Prices are read for all reserves because which assets are active isn't known until the
|
|
509
|
+
* balances come back — extra reads inside an already-paid multicall are nearly free.
|
|
510
|
+
*/
|
|
511
|
+
const fetchHistoricalPointWithOracle = (provider, entries, address, block, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
|
|
512
|
+
const balanceContracts = getHistoricalBalanceContracts(entries, address);
|
|
513
|
+
const priceContracts = entries.map((e) => getAssetPriceContract(oracleAddress, e.underlyingAddress));
|
|
514
|
+
// @ts-ignore
|
|
515
|
+
const results = yield provider.multicall({
|
|
516
|
+
contracts: [...balanceContracts, ...priceContracts], allowFailure: true, blockNumber: BigInt(block), batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
517
|
+
});
|
|
518
|
+
const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
|
|
519
|
+
const priceResBySymbol = {};
|
|
520
|
+
results.slice(balanceContracts.length).forEach((res, i) => {
|
|
521
|
+
priceResBySymbol[entries[i].symbol] = res;
|
|
522
|
+
});
|
|
523
|
+
return { activeAssets, priceResBySymbol };
|
|
524
|
+
});
|
|
525
|
+
/**
|
|
526
|
+
* Fallback path, TWO multicalls: balances plus getPriceOracle (resolving the oracle that was active
|
|
527
|
+
* AT the block — Aave can rotate it over time), then prices for just the active assets. The oracle
|
|
528
|
+
* read has no fallback: if it fails (bad archive node, rate limit, ...) the failure must surface
|
|
529
|
+
* rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
|
|
530
|
+
*/
|
|
531
|
+
const fetchHistoricalPointResolvingOracle = (provider, network, market, entries, address, block) => __awaiter(void 0, void 0, void 0, function* () {
|
|
532
|
+
const balanceContracts = getHistoricalBalanceContracts(entries, address);
|
|
533
|
+
// @ts-ignore market.provider is a valid config key at runtime
|
|
534
|
+
const providerAbi = getConfigContractAbi(market.provider, network);
|
|
535
|
+
const oracleContract = {
|
|
536
|
+
address: market.providerAddress,
|
|
537
|
+
abi: providerAbi,
|
|
538
|
+
functionName: 'getPriceOracle',
|
|
539
|
+
};
|
|
540
|
+
const blockNumber = BigInt(block);
|
|
541
|
+
// @ts-ignore
|
|
542
|
+
const results = yield provider.multicall({
|
|
543
|
+
contracts: [...balanceContracts, oracleContract], allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
544
|
+
});
|
|
545
|
+
const oracleRes = results[balanceContracts.length];
|
|
546
|
+
if ((oracleRes === null || oracleRes === void 0 ? void 0 : oracleRes.status) !== 'success' || !oracleRes.result)
|
|
547
|
+
throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
|
|
548
|
+
const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
|
|
549
|
+
if (!activeAssets.length)
|
|
550
|
+
return { activeAssets, priceResBySymbol: {} };
|
|
551
|
+
const priceContracts = activeAssets.map((a) => getAssetPriceContract(oracleRes.result, a.underlyingAddress));
|
|
552
|
+
// @ts-ignore
|
|
553
|
+
const priceResults = yield provider.multicall({
|
|
554
|
+
contracts: priceContracts, allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
555
|
+
});
|
|
556
|
+
const priceResBySymbol = {};
|
|
557
|
+
priceResults.forEach((res, i) => {
|
|
558
|
+
priceResBySymbol[activeAssets[i].symbol] = res;
|
|
559
|
+
});
|
|
560
|
+
return { activeAssets, priceResBySymbol };
|
|
561
|
+
});
|
|
562
|
+
/**
|
|
563
|
+
* Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
|
|
564
|
+
* without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
|
|
565
|
+
* to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
|
|
566
|
+
* verified identical at both ends of the sampled range) to collapse the point into a single multicall —
|
|
567
|
+
* with it the balances and all prices ride one request; without it the oracle is resolved at the block
|
|
568
|
+
* inside the balance multicall and the active assets are priced in a second one.
|
|
569
|
+
*/
|
|
570
|
+
export const _getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
|
|
571
|
+
const empty = {
|
|
572
|
+
block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
|
|
573
|
+
};
|
|
574
|
+
if (!address)
|
|
575
|
+
return empty;
|
|
576
|
+
const tokens = reserveTokens || (yield _getAaveV3ReserveTokenAddresses(provider, network, market));
|
|
577
|
+
const entries = Object.values(tokens);
|
|
578
|
+
if (!entries.length)
|
|
579
|
+
return empty;
|
|
580
|
+
const { activeAssets, priceResBySymbol } = oracleAddress
|
|
581
|
+
? yield fetchHistoricalPointWithOracle(provider, entries, address, block, oracleAddress)
|
|
582
|
+
: yield fetchHistoricalPointResolvingOracle(provider, network, market, entries, address, block);
|
|
583
|
+
if (!activeAssets.length)
|
|
584
|
+
return empty;
|
|
585
|
+
return sumHistoricalUsd(activeAssets, priceResBySymbol, block);
|
|
586
|
+
});
|
|
587
|
+
export const getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3HistoricalBalance(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens, oracleAddress); });
|
|
365
588
|
export const _getAaveV3AccountData = (provider_1, network_1, address_1, extractedState_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, address_1, extractedState_1, ...args_1], void 0, function* (provider, network, address, extractedState, blockNumber = 'latest') {
|
|
366
589
|
const { selectedMarket: market, assetsData, eModeCategoriesData, } = extractedState;
|
|
367
590
|
let payload = Object.assign(Object.assign({}, EMPTY_AAVE_DATA), { lastUpdated: Date.now() });
|
package/esm/constants/index.d.ts
CHANGED
|
@@ -10,3 +10,4 @@ export declare const ZERO_BYTES32 = "0x00000000000000000000000000000000000000000
|
|
|
10
10
|
export declare const WAD = 1000000000000000000;
|
|
11
11
|
export declare const USD_QUOTE = "0x0000000000000000000000000000000000000348";
|
|
12
12
|
export declare const borrowOperations: string[];
|
|
13
|
+
export declare const MORPHO_API_URL = "https://fe.defisaver.com/api/morpho/graphql";
|
package/esm/constants/index.js
CHANGED
|
@@ -10,3 +10,6 @@ export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000
|
|
|
10
10
|
export const WAD = 1e18;
|
|
11
11
|
export const USD_QUOTE = '0x0000000000000000000000000000000000000348';
|
|
12
12
|
export const borrowOperations = ['borrow', 'payback'];
|
|
13
|
+
// Proxies Morpho's GraphQL API through the DFS backend, which attaches the private API key
|
|
14
|
+
// for better rate limits (DEV-13673)
|
|
15
|
+
export const MORPHO_API_URL = 'https://fe.defisaver.com/api/morpho/graphql';
|
|
@@ -12,7 +12,7 @@ import { assetAmountInWei, getAssetInfo, getAssetInfoByAddress } from '@defisave
|
|
|
12
12
|
import { aprToApy, calcLeverageLiqPrice, getAssetsTotal, getExposure, isLeveragedPos, } from '../../moneymarket';
|
|
13
13
|
import { calculateNetApy } from '../../staking';
|
|
14
14
|
import { LeverageType, NetworkNumber, } from '../../types/common';
|
|
15
|
-
import { borrowOperations, SECONDS_PER_YEAR, WAD } from '../../constants';
|
|
15
|
+
import { borrowOperations, MORPHO_API_URL, SECONDS_PER_YEAR, WAD, } from '../../constants';
|
|
16
16
|
import { MorphoBlueViewContractViem } from '../../contracts';
|
|
17
17
|
import { compareAddresses, LONGER_TIMEOUT } from '../../services/utils';
|
|
18
18
|
import { getViemProvider } from '../../services/viem';
|
|
@@ -129,7 +129,6 @@ export const getApyAfterValuesEstimation = (selectedMarket, actions, provider, n
|
|
|
129
129
|
const supplyRate = getSupplyRate(data[1].totalSupplyAssets.toString(), data[1].totalBorrowAssets.toString(), data[0].toString(), data[1].fee.toString());
|
|
130
130
|
return { borrowRate, supplyRate };
|
|
131
131
|
});
|
|
132
|
-
const API_URL = 'https://api.morpho.org/graphql';
|
|
133
132
|
// Morpho Blue ACRM (Adaptive Curve IRM) always targets 90% utilization — protocol constant
|
|
134
133
|
const ACRM_TARGET_UTILIZATION = '900000000000000000';
|
|
135
134
|
const MARKET_QUERY = `
|
|
@@ -206,7 +205,7 @@ const REWARDS_QUERY = `
|
|
|
206
205
|
export const getReallocatableLiquidity = (marketId_1, ...args_1) => __awaiter(void 0, [marketId_1, ...args_1], void 0, function* (marketId, network = NetworkNumber.Eth) {
|
|
207
206
|
var _a;
|
|
208
207
|
try {
|
|
209
|
-
const response = yield fetch(
|
|
208
|
+
const response = yield fetch(MORPHO_API_URL, {
|
|
210
209
|
method: 'POST',
|
|
211
210
|
headers: { 'Content-Type': 'application/json' },
|
|
212
211
|
body: JSON.stringify({
|
|
@@ -268,7 +267,7 @@ export const getReallocation = (market_1, assetsData_1, amountToBorrow_1, ...arg
|
|
|
268
267
|
var _a, _b, _c;
|
|
269
268
|
try {
|
|
270
269
|
const { marketId, loanToken } = market;
|
|
271
|
-
const response = yield fetch(
|
|
270
|
+
const response = yield fetch(MORPHO_API_URL, {
|
|
272
271
|
method: 'POST',
|
|
273
272
|
headers: { 'Content-Type': 'application/json' },
|
|
274
273
|
body: JSON.stringify({
|
|
@@ -341,7 +340,7 @@ export const getReallocation = (market_1, assetsData_1, amountToBorrow_1, ...arg
|
|
|
341
340
|
});
|
|
342
341
|
export const getRewardsForMarket = (marketId_1, ...args_1) => __awaiter(void 0, [marketId_1, ...args_1], void 0, function* (marketId, network = NetworkNumber.Eth) {
|
|
343
342
|
var _a;
|
|
344
|
-
const response = yield fetch(
|
|
343
|
+
const response = yield fetch(MORPHO_API_URL, {
|
|
345
344
|
method: 'POST',
|
|
346
345
|
headers: { 'Content-Type': 'application/json' },
|
|
347
346
|
body: JSON.stringify({
|
|
@@ -13,8 +13,8 @@ import { assetAmountInEth } from '@defisaver/tokens';
|
|
|
13
13
|
import * as morphoVaultsOptions from './options';
|
|
14
14
|
import { getViemProvider } from '../../services/viem';
|
|
15
15
|
import { getMorphoVaultContractViem } from '../../contracts';
|
|
16
|
+
import { MORPHO_API_URL } from '../../constants';
|
|
16
17
|
export { morphoVaultsOptions, };
|
|
17
|
-
const MORPHO_BLUE_API = 'https://api.morpho.org/graphql';
|
|
18
18
|
// Morpho API caps list page size at 100 items
|
|
19
19
|
const MORPHO_API_PAGE_SIZE = 100;
|
|
20
20
|
const vaultsLiquidityQuery = `
|
|
@@ -39,7 +39,7 @@ export const fetchMorphoVaultsLiquidity = (network, vaults) => __awaiter(void 0,
|
|
|
39
39
|
chunks.push(vaults.slice(i, i + MORPHO_API_PAGE_SIZE));
|
|
40
40
|
const liquidityByAddress = {};
|
|
41
41
|
yield Promise.all(chunks.map((chunk) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
-
const data = yield graphqlRequest(
|
|
42
|
+
const data = yield graphqlRequest(MORPHO_API_URL, vaultsLiquidityQuery, {
|
|
43
43
|
addresses: chunk.map((vault) => vault.address),
|
|
44
44
|
chainIds: [network],
|
|
45
45
|
// the BigInt scalar serializes as a JSON number when it fits in Number.MAX_SAFE_INTEGER, a string otherwise
|
package/package.json
CHANGED
package/src/aaveV3/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
AaveIncentivesControllerViem,
|
|
7
7
|
AaveV3ViewContractViem,
|
|
8
8
|
createViemContractFromConfigFunc,
|
|
9
|
+
getConfigContractAbi,
|
|
9
10
|
StkAAVEViem,
|
|
10
11
|
} from '../contracts';
|
|
11
12
|
import { aaveAnyGetAggregatedPositionData, aaveV3IsInIsolationMode, aaveV3IsInSiloedMode } from '../helpers/aaveHelpers';
|
|
@@ -430,6 +431,290 @@ export const _getAaveV3AccountBalances = async (provider: Client, network: Netwo
|
|
|
430
431
|
|
|
431
432
|
export const getAaveV3AccountBalances = async (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress): Promise<PositionBalances> => _getAaveV3AccountBalances(getViemProvider(provider, network), network, block, addressMapping, address);
|
|
432
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Historical net-balance helpers that bypass the AaveV3View contract.
|
|
436
|
+
*
|
|
437
|
+
* The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
|
|
438
|
+
* queried from its deployment block onwards, so it cannot read balances for positions older than that.
|
|
439
|
+
* The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
|
|
440
|
+
* launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
|
|
441
|
+
* and costs ~1 multicall per point. Used to build a position balance-history chart.
|
|
442
|
+
*/
|
|
443
|
+
|
|
444
|
+
// matches morphoVaults' batchSize; stays under the 500k portfolio discovery allows on simulation fork RPCs
|
|
445
|
+
const HISTORICAL_MULTICALL_BATCH_SIZE = 250_000;
|
|
446
|
+
|
|
447
|
+
// Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
|
|
448
|
+
const AAVE_ORACLE_ABI = [
|
|
449
|
+
{
|
|
450
|
+
inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
|
|
451
|
+
name: 'getAssetPrice',
|
|
452
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
453
|
+
stateMutability: 'view',
|
|
454
|
+
type: 'function',
|
|
455
|
+
},
|
|
456
|
+
] as const;
|
|
457
|
+
|
|
458
|
+
export interface AaveV3ReserveTokenAddresses {
|
|
459
|
+
[symbol: string]: {
|
|
460
|
+
symbol: string,
|
|
461
|
+
underlyingAddress: EthAddress,
|
|
462
|
+
aTokenAddress: EthAddress,
|
|
463
|
+
stableDebtTokenAddress: EthAddress,
|
|
464
|
+
variableDebtTokenAddress: EthAddress,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export interface AaveV3HistoricalBalance {
|
|
469
|
+
block: number,
|
|
470
|
+
suppliedUsd: string,
|
|
471
|
+
borrowedUsd: string,
|
|
472
|
+
netUsd: string,
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
|
|
477
|
+
* These are effectively immutable per reserve, so fetch once and reuse across all history points.
|
|
478
|
+
*/
|
|
479
|
+
export const _getAaveV3ReserveTokenAddresses = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => {
|
|
480
|
+
const symbols = market.assets;
|
|
481
|
+
const underlyingAddresses = symbols.map((a: string) => getAssetInfo(getWrappedNativeAssetFromUnwrapped(a), network).address as EthAddress);
|
|
482
|
+
// @ts-ignore market.protocolData is a valid config key at runtime
|
|
483
|
+
const dataProviderAbi = getConfigContractAbi(market.protocolData, network);
|
|
484
|
+
|
|
485
|
+
const contracts = underlyingAddresses.map((underlying) => ({
|
|
486
|
+
address: market.protocolDataAddress as EthAddress,
|
|
487
|
+
abi: dataProviderAbi,
|
|
488
|
+
functionName: 'getReserveTokensAddresses',
|
|
489
|
+
args: [underlying],
|
|
490
|
+
}));
|
|
491
|
+
|
|
492
|
+
// @ts-ignore
|
|
493
|
+
const results = await provider.multicall({ contracts, allowFailure: true, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE });
|
|
494
|
+
|
|
495
|
+
const mapping: AaveV3ReserveTokenAddresses = {};
|
|
496
|
+
results.forEach((res: any, i: number) => {
|
|
497
|
+
if (res.status !== 'success' || !res.result) return;
|
|
498
|
+
// outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
|
|
499
|
+
const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result as [EthAddress, EthAddress, EthAddress];
|
|
500
|
+
mapping[symbols[i]] = {
|
|
501
|
+
symbol: symbols[i],
|
|
502
|
+
underlyingAddress: underlyingAddresses[i],
|
|
503
|
+
aTokenAddress,
|
|
504
|
+
stableDebtTokenAddress,
|
|
505
|
+
variableDebtTokenAddress,
|
|
506
|
+
};
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
return mapping;
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
export const getAaveV3ReserveTokenAddresses = async (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => _getAaveV3ReserveTokenAddresses(getViemProvider(provider, network, { batch: { multicall: true } }), network, market);
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
|
|
516
|
+
* Aave can rotate the oracle over time, so a caller hoisting one address across many historical
|
|
517
|
+
* points must resolve it at both ends of its block range and only reuse it when the two agree —
|
|
518
|
+
* otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
|
|
519
|
+
*/
|
|
520
|
+
export const _getAaveV3OracleAddress = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo, block: Blockish = 'latest'): Promise<EthAddress> => {
|
|
521
|
+
// @ts-ignore market.provider is a valid config key at runtime
|
|
522
|
+
const providerAbi = getConfigContractAbi(market.provider, network);
|
|
523
|
+
// @ts-ignore readContract exists on the public client returned by getViemProvider
|
|
524
|
+
const oracleAddress = await provider.readContract({
|
|
525
|
+
address: market.providerAddress as EthAddress,
|
|
526
|
+
abi: providerAbi as any,
|
|
527
|
+
functionName: 'getPriceOracle',
|
|
528
|
+
...setViemBlockNumber(block),
|
|
529
|
+
});
|
|
530
|
+
if (!oracleAddress) throw new Error(`AaveV3 oracle unavailable at block ${block}`);
|
|
531
|
+
return oracleAddress as EthAddress;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
export const getAaveV3OracleAddress = async (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, block: Blockish = 'latest'): Promise<EthAddress> => _getAaveV3OracleAddress(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, block);
|
|
535
|
+
|
|
536
|
+
type AaveV3ReserveTokenEntry = AaveV3ReserveTokenAddresses[string];
|
|
537
|
+
type AaveV3ActiveHistoricalAsset = AaveV3ReserveTokenEntry & { supplied: string, debt: string };
|
|
538
|
+
// raw getAssetPrice multicall result per asset symbol
|
|
539
|
+
type AaveV3PriceResultsBySymbol = Record<string, any>;
|
|
540
|
+
|
|
541
|
+
// supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf — three reads per reserve
|
|
542
|
+
const getHistoricalBalanceContracts = (entries: AaveV3ReserveTokenEntry[], address: EthAddress) => {
|
|
543
|
+
const erc20Abi = getConfigContractAbi('Erc20');
|
|
544
|
+
return entries.flatMap((e) => ([
|
|
545
|
+
{
|
|
546
|
+
address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
553
|
+
},
|
|
554
|
+
]));
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const getAssetPriceContract = (oracle: EthAddress, underlyingAddress: EthAddress) => ({
|
|
558
|
+
address: oracle,
|
|
559
|
+
abi: AAVE_ORACLE_ABI,
|
|
560
|
+
functionName: 'getAssetPrice',
|
|
561
|
+
args: [underlyingAddress],
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Pairs each reserve with its three balance reads and keeps the assets the wallet actually held.
|
|
566
|
+
* Throws when every read failed — a totally failed fetch must not masquerade as a real $0 balance,
|
|
567
|
+
* so the caller can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
|
|
568
|
+
*/
|
|
569
|
+
const toActiveHistoricalAssets = (entries: AaveV3ReserveTokenEntry[], balanceResults: any[], block: number): AaveV3ActiveHistoricalAsset[] => {
|
|
570
|
+
const anyBalanceRead = balanceResults.some((r) => r?.status === 'success');
|
|
571
|
+
if (!anyBalanceRead) throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
|
|
572
|
+
|
|
573
|
+
return entries.map((e, i) => {
|
|
574
|
+
const supplyRes = balanceResults[i * 3];
|
|
575
|
+
const varDebtRes = balanceResults[(i * 3) + 1];
|
|
576
|
+
const stableDebtRes = balanceResults[(i * 3) + 2];
|
|
577
|
+
const supplied = supplyRes?.status === 'success' ? (supplyRes.result as bigint).toString() : '0';
|
|
578
|
+
const varDebt = varDebtRes?.status === 'success' ? (varDebtRes.result as bigint).toString() : '0';
|
|
579
|
+
const stableDebt = stableDebtRes?.status === 'success' ? (stableDebtRes.result as bigint).toString() : '0';
|
|
580
|
+
const debt = new Dec(varDebt).add(stableDebt).toString();
|
|
581
|
+
return { ...e, supplied, debt };
|
|
582
|
+
}).filter((a) => a.supplied !== '0' || a.debt !== '0');
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Sums the active assets into the point's USD totals. Throws when every price read failed — the
|
|
587
|
+
* oracle couldn't serve this block (pruned state, or a hoisted oracle that wasn't deployed yet back
|
|
588
|
+
* then), and totalling it up anyway would return a fake near-$0 balance. A single asset's price
|
|
589
|
+
* failure still just skips that asset, matching the live data path.
|
|
590
|
+
*/
|
|
591
|
+
const sumHistoricalUsd = (activeAssets: AaveV3ActiveHistoricalAsset[], priceResBySymbol: AaveV3PriceResultsBySymbol, block: number): AaveV3HistoricalBalance => {
|
|
592
|
+
const anyPriceRead = activeAssets.some((a) => priceResBySymbol[a.symbol]?.status === 'success');
|
|
593
|
+
if (!anyPriceRead) throw new Error(`AaveV3 historical balance: all price reads failed at block ${block}`);
|
|
594
|
+
|
|
595
|
+
let suppliedUsd = new Dec(0);
|
|
596
|
+
let borrowedUsd = new Dec(0);
|
|
597
|
+
activeAssets.forEach((a) => {
|
|
598
|
+
const priceRes = priceResBySymbol[a.symbol];
|
|
599
|
+
if (priceRes?.status !== 'success') return;
|
|
600
|
+
const priceUsd = new Dec((priceRes.result as bigint).toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
|
|
601
|
+
if (a.supplied !== '0') suppliedUsd = suppliedUsd.add(new Dec(assetAmountInEth(a.supplied, a.symbol)).mul(priceUsd));
|
|
602
|
+
if (a.debt !== '0') borrowedUsd = borrowedUsd.add(new Dec(assetAmountInEth(a.debt, a.symbol)).mul(priceUsd));
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
block,
|
|
607
|
+
suppliedUsd: suppliedUsd.toString(),
|
|
608
|
+
borrowedUsd: borrowedUsd.toString(),
|
|
609
|
+
netUsd: suppliedUsd.minus(borrowedUsd).toString(),
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Fast path, ONE multicall: balances plus a price read for every reserve through the caller-hoisted
|
|
615
|
+
* oracle. Prices are read for all reserves because which assets are active isn't known until the
|
|
616
|
+
* balances come back — extra reads inside an already-paid multicall are nearly free.
|
|
617
|
+
*/
|
|
618
|
+
const fetchHistoricalPointWithOracle = async (provider: Client, entries: AaveV3ReserveTokenEntry[], address: EthAddress, block: number, oracleAddress: EthAddress) => {
|
|
619
|
+
const balanceContracts = getHistoricalBalanceContracts(entries, address);
|
|
620
|
+
const priceContracts = entries.map((e) => getAssetPriceContract(oracleAddress, e.underlyingAddress));
|
|
621
|
+
|
|
622
|
+
// @ts-ignore
|
|
623
|
+
const results = await provider.multicall({
|
|
624
|
+
contracts: [...balanceContracts, ...priceContracts], allowFailure: true, blockNumber: BigInt(block), batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
|
|
628
|
+
const priceResBySymbol: AaveV3PriceResultsBySymbol = {};
|
|
629
|
+
results.slice(balanceContracts.length).forEach((res: any, i: number) => {
|
|
630
|
+
priceResBySymbol[entries[i].symbol] = res;
|
|
631
|
+
});
|
|
632
|
+
return { activeAssets, priceResBySymbol };
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Fallback path, TWO multicalls: balances plus getPriceOracle (resolving the oracle that was active
|
|
637
|
+
* AT the block — Aave can rotate it over time), then prices for just the active assets. The oracle
|
|
638
|
+
* read has no fallback: if it fails (bad archive node, rate limit, ...) the failure must surface
|
|
639
|
+
* rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
|
|
640
|
+
*/
|
|
641
|
+
const fetchHistoricalPointResolvingOracle = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo, entries: AaveV3ReserveTokenEntry[], address: EthAddress, block: number) => {
|
|
642
|
+
const balanceContracts = getHistoricalBalanceContracts(entries, address);
|
|
643
|
+
// @ts-ignore market.provider is a valid config key at runtime
|
|
644
|
+
const providerAbi = getConfigContractAbi(market.provider, network);
|
|
645
|
+
const oracleContract = {
|
|
646
|
+
address: market.providerAddress as EthAddress,
|
|
647
|
+
abi: providerAbi,
|
|
648
|
+
functionName: 'getPriceOracle',
|
|
649
|
+
};
|
|
650
|
+
const blockNumber = BigInt(block);
|
|
651
|
+
|
|
652
|
+
// @ts-ignore
|
|
653
|
+
const results = await provider.multicall({
|
|
654
|
+
contracts: [...balanceContracts, oracleContract], allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
655
|
+
});
|
|
656
|
+
const oracleRes = results[balanceContracts.length];
|
|
657
|
+
if (oracleRes?.status !== 'success' || !oracleRes.result) throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
|
|
658
|
+
|
|
659
|
+
const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
|
|
660
|
+
if (!activeAssets.length) return { activeAssets, priceResBySymbol: {} };
|
|
661
|
+
|
|
662
|
+
const priceContracts = activeAssets.map((a) => getAssetPriceContract(oracleRes.result as EthAddress, a.underlyingAddress));
|
|
663
|
+
// @ts-ignore
|
|
664
|
+
const priceResults = await provider.multicall({
|
|
665
|
+
contracts: priceContracts, allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
|
|
666
|
+
});
|
|
667
|
+
const priceResBySymbol: AaveV3PriceResultsBySymbol = {};
|
|
668
|
+
priceResults.forEach((res: any, i: number) => {
|
|
669
|
+
priceResBySymbol[activeAssets[i].symbol] = res;
|
|
670
|
+
});
|
|
671
|
+
return { activeAssets, priceResBySymbol };
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
|
|
676
|
+
* without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
|
|
677
|
+
* to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
|
|
678
|
+
* verified identical at both ends of the sampled range) to collapse the point into a single multicall —
|
|
679
|
+
* with it the balances and all prices ride one request; without it the oracle is resolved at the block
|
|
680
|
+
* inside the balance multicall and the active assets are priced in a second one.
|
|
681
|
+
*/
|
|
682
|
+
export const _getAaveV3HistoricalBalance = async (
|
|
683
|
+
provider: Client,
|
|
684
|
+
network: NetworkNumber,
|
|
685
|
+
market: AaveMarketInfo,
|
|
686
|
+
address: EthAddress,
|
|
687
|
+
block: number,
|
|
688
|
+
reserveTokens?: AaveV3ReserveTokenAddresses,
|
|
689
|
+
oracleAddress?: EthAddress,
|
|
690
|
+
): Promise<AaveV3HistoricalBalance> => {
|
|
691
|
+
const empty: AaveV3HistoricalBalance = {
|
|
692
|
+
block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
|
|
693
|
+
};
|
|
694
|
+
if (!address) return empty;
|
|
695
|
+
|
|
696
|
+
const tokens = reserveTokens || await _getAaveV3ReserveTokenAddresses(provider, network, market);
|
|
697
|
+
const entries = Object.values(tokens);
|
|
698
|
+
if (!entries.length) return empty;
|
|
699
|
+
|
|
700
|
+
const { activeAssets, priceResBySymbol } = oracleAddress
|
|
701
|
+
? await fetchHistoricalPointWithOracle(provider, entries, address, block, oracleAddress)
|
|
702
|
+
: await fetchHistoricalPointResolvingOracle(provider, network, market, entries, address, block);
|
|
703
|
+
|
|
704
|
+
if (!activeAssets.length) return empty;
|
|
705
|
+
return sumHistoricalUsd(activeAssets, priceResBySymbol, block);
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
export const getAaveV3HistoricalBalance = async (
|
|
709
|
+
provider: EthereumProvider,
|
|
710
|
+
network: NetworkNumber,
|
|
711
|
+
market: AaveMarketInfo,
|
|
712
|
+
address: EthAddress,
|
|
713
|
+
block: number,
|
|
714
|
+
reserveTokens?: AaveV3ReserveTokenAddresses,
|
|
715
|
+
oracleAddress?: EthAddress,
|
|
716
|
+
): Promise<AaveV3HistoricalBalance> => _getAaveV3HistoricalBalance(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens, oracleAddress);
|
|
717
|
+
|
|
433
718
|
export const _getAaveV3AccountData = async (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber: 'latest' | number = 'latest'): Promise<AaveV3PositionData> => {
|
|
434
719
|
const {
|
|
435
720
|
selectedMarket: market, assetsData, eModeCategoriesData,
|
package/src/constants/index.ts
CHANGED
|
@@ -9,4 +9,8 @@ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
|
9
9
|
export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
10
10
|
export const WAD = 1e18;
|
|
11
11
|
export const USD_QUOTE = '0x0000000000000000000000000000000000000348';
|
|
12
|
-
export const borrowOperations = ['borrow', 'payback'];
|
|
12
|
+
export const borrowOperations = ['borrow', 'payback'];
|
|
13
|
+
|
|
14
|
+
// Proxies Morpho's GraphQL API through the DFS backend, which attaches the private API key
|
|
15
|
+
// for better rate limits (DEV-13673)
|
|
16
|
+
export const MORPHO_API_URL = 'https://fe.defisaver.com/api/morpho/graphql';
|
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
MorphoBluePublicAllocatorItem,
|
|
13
13
|
MorphoBlueRealloactionMarketData,
|
|
14
14
|
} from '../../types';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
borrowOperations, MORPHO_API_URL, SECONDS_PER_YEAR, WAD,
|
|
17
|
+
} from '../../constants';
|
|
16
18
|
import { MorphoBlueViewContractViem } from '../../contracts';
|
|
17
19
|
import { compareAddresses, LONGER_TIMEOUT, wethToEth } from '../../services/utils';
|
|
18
20
|
import { getViemProvider } from '../../services/viem';
|
|
@@ -147,7 +149,6 @@ export const getApyAfterValuesEstimation = async (selectedMarket: MorphoBlueMark
|
|
|
147
149
|
return { borrowRate, supplyRate };
|
|
148
150
|
};
|
|
149
151
|
|
|
150
|
-
const API_URL = 'https://api.morpho.org/graphql';
|
|
151
152
|
// Morpho Blue ACRM (Adaptive Curve IRM) always targets 90% utilization — protocol constant
|
|
152
153
|
const ACRM_TARGET_UTILIZATION = '900000000000000000';
|
|
153
154
|
|
|
@@ -226,7 +227,7 @@ const REWARDS_QUERY = `
|
|
|
226
227
|
*/
|
|
227
228
|
export const getReallocatableLiquidity = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ reallocatableLiquidity: string, targetBorrowUtilization: string }> => {
|
|
228
229
|
try {
|
|
229
|
-
const response = await fetch(
|
|
230
|
+
const response = await fetch(MORPHO_API_URL, {
|
|
230
231
|
method: 'POST',
|
|
231
232
|
headers: { 'Content-Type': 'application/json' },
|
|
232
233
|
body: JSON.stringify({
|
|
@@ -291,7 +292,7 @@ export const getLiquidityToAllocate = (amountToBorrow: string, totalBorrow: stri
|
|
|
291
292
|
export const getReallocation = async (market: MorphoBlueMarketData, assetsData: MorphoBlueAssetsData, amountToBorrow: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ vaults: string[], withdrawals: (string | string[])[][][] }> => {
|
|
292
293
|
try {
|
|
293
294
|
const { marketId, loanToken } = market;
|
|
294
|
-
const response = await fetch(
|
|
295
|
+
const response = await fetch(MORPHO_API_URL, {
|
|
295
296
|
method: 'POST',
|
|
296
297
|
headers: { 'Content-Type': 'application/json' },
|
|
297
298
|
body: JSON.stringify({
|
|
@@ -382,7 +383,7 @@ export const getReallocation = async (market: MorphoBlueMarketData, assetsData:
|
|
|
382
383
|
};
|
|
383
384
|
|
|
384
385
|
export const getRewardsForMarket = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth) => {
|
|
385
|
-
const response = await fetch(
|
|
386
|
+
const response = await fetch(MORPHO_API_URL, {
|
|
386
387
|
method: 'POST',
|
|
387
388
|
headers: { 'Content-Type': 'application/json' },
|
|
388
389
|
body: JSON.stringify({
|
|
@@ -8,13 +8,12 @@ import {
|
|
|
8
8
|
} from '../../types';
|
|
9
9
|
import { getViemProvider } from '../../services/viem';
|
|
10
10
|
import { getMorphoVaultContractViem } from '../../contracts';
|
|
11
|
+
import { MORPHO_API_URL } from '../../constants';
|
|
11
12
|
|
|
12
13
|
export {
|
|
13
14
|
morphoVaultsOptions,
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
const MORPHO_BLUE_API = 'https://api.morpho.org/graphql';
|
|
17
|
-
|
|
18
17
|
// Morpho API caps list page size at 100 items
|
|
19
18
|
const MORPHO_API_PAGE_SIZE = 100;
|
|
20
19
|
|
|
@@ -41,7 +40,7 @@ export const fetchMorphoVaultsLiquidity = async (network: NetworkNumber, vaults:
|
|
|
41
40
|
|
|
42
41
|
const liquidityByAddress: Record<string, string> = {};
|
|
43
42
|
await Promise.all(chunks.map(async (chunk) => {
|
|
44
|
-
const data = await graphqlRequest(
|
|
43
|
+
const data = await graphqlRequest(MORPHO_API_URL, vaultsLiquidityQuery, {
|
|
45
44
|
addresses: chunk.map((vault) => vault.address),
|
|
46
45
|
chainIds: [network],
|
|
47
46
|
// the BigInt scalar serializes as a JSON number when it fits in Number.MAX_SAFE_INTEGER, a string otherwise
|