@kasufinance/kasu-sdk 2.14.0 → 2.16.0
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/README.md +45 -5
- package/dist/bundle.cjs.js +1484 -228
- package/dist/bundle.esm.js +1476 -229
- package/dist/domain/index.d.ts +6 -1
- package/dist/domain/index.js +6 -2
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/loan-contract.d.ts +16 -5
- package/dist/domain/loan-contract.js +16 -5
- package/dist/domain/loan-contract.js.map +1 -1
- package/dist/domain/pool-tvl.js +179 -0
- package/dist/domain/pool-tvl.js.map +1 -1
- package/dist/domain/requests.js +29 -19
- package/dist/domain/requests.js.map +1 -1
- package/dist/domain/strategy-details.d.ts +262 -0
- package/dist/domain/strategy-details.js +375 -0
- package/dist/domain/strategy-details.js.map +1 -0
- package/dist/domain/yield-history.d.ts +25 -1
- package/dist/domain/yield-history.js +41 -11
- package/dist/domain/yield-history.js.map +1 -1
- package/dist/facade/index.d.ts +1 -1
- package/dist/facade/index.js +2 -0
- package/dist/facade/index.js.map +1 -1
- package/dist/facade/strategies.d.ts +85 -3
- package/dist/facade/strategies.js +182 -6
- package/dist/facade/strategies.js.map +1 -1
- package/dist/facade/strategy-deployments.js +97 -0
- package/dist/facade/strategy-deployments.js.map +1 -0
- package/dist/facade/types.d.ts +82 -0
- package/dist/flows/deposit-flow.d.ts +19 -5
- package/dist/flows/deposit-flow.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/DataService/data-service.js +80 -9
- package/dist/services/DataService/data-service.js.map +1 -1
- package/dist/services/DataService/tvl-history.js +89 -0
- package/dist/services/DataService/tvl-history.js.map +1 -1
- package/dist/services/Portfolio/portfolio.d.ts +54 -1
- package/dist/services/Portfolio/portfolio.js +357 -80
- package/dist/services/Portfolio/portfolio.js.map +1 -1
- package/dist/services/Portfolio/queries.d.ts +16 -0
- package/dist/services/Portfolio/queries.js +119 -2
- package/dist/services/Portfolio/queries.js.map +1 -1
- package/dist/services/Portfolio/types.d.ts +94 -6
- package/dist/services/Portfolio/yield-history.d.ts +148 -10
- package/dist/services/Portfolio/yield-history.js +241 -31
- package/dist/services/Portfolio/yield-history.js.map +1 -1
- package/dist/services/UserLending/queries.js +2 -7
- package/dist/services/UserLending/queries.js.map +1 -1
- package/dist/services/UserLending/subgraph-types.js.map +1 -1
- package/dist/services/UserLending/types.js.map +1 -1
- package/dist/services/UserLending/user-lending.d.ts +16 -0
- package/dist/services/UserLending/user-lending.js +96 -16
- package/dist/services/UserLending/user-lending.js.map +1 -1
- package/dist/services/shared.d.ts +7 -0
- package/dist/services/shared.js +48 -0
- package/dist/services/shared.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/index.ts +27 -1
- package/src/domain/loan-contract.ts +16 -5
- package/src/domain/strategy-details.ts +528 -0
- package/src/domain/yield-history.ts +47 -11
- package/src/facade/index.ts +3 -0
- package/src/facade/strategies.ts +205 -10
- package/src/facade/types.ts +85 -0
- package/src/flows/deposit-flow.ts +19 -5
- package/src/services/Portfolio/portfolio.ts +559 -126
- package/src/services/Portfolio/queries.ts +119 -2
- package/src/services/Portfolio/types.ts +91 -6
- package/src/services/Portfolio/yield-history.ts +405 -56
- package/src/services/UserLending/user-lending.ts +46 -0
- package/src/services/shared.ts +48 -0
|
@@ -8,18 +8,69 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { BigNumber } from 'ethers';
|
|
11
|
-
import { formatEther, parseEther, parseUnits } from 'ethers/lib/utils';
|
|
11
|
+
import { formatEther, formatUnits, parseEther, parseUnits, } from 'ethers/lib/utils';
|
|
12
12
|
import { GraphQLClient } from 'graphql-request';
|
|
13
13
|
import { IKasuNFTsAbi__factory, ISystemVariablesAbi__factory, IUserLoyaltyRewardsAbi__factory, } from '../../contracts';
|
|
14
|
-
import { epochTrancheYield, } from '../../domain/yield-history';
|
|
15
14
|
import { isLiteDeployment } from '../../utils/deployment-mode';
|
|
16
15
|
import { DataService } from '../DataService/data-service';
|
|
17
16
|
import { getAllTrancheConfigurationsQuery, getAllTranchesQuery, } from '../DataService/queries';
|
|
18
17
|
import { KSULocking } from '../Locking/locking';
|
|
19
18
|
import { getSystemVariablesQuery, lockingSummariesQuery, } from '../Locking/queries';
|
|
19
|
+
import { isBlockUnavailableError } from '../shared';
|
|
20
20
|
import { UserLending } from '../UserLending/user-lending';
|
|
21
21
|
import { lendingPortfolioQuery } from './queries';
|
|
22
|
-
import { buildYieldHistory, fetchYieldHistorySeries, isEmptyHistoryError, resolveYieldHistoryWindow, validateYieldHistoryOptions, YIELD_HISTORY_PAGE_SIZE, yieldHistoryPageQuery, yieldHistoryPositionsQuery, } from './yield-history';
|
|
22
|
+
import { buildYieldHistory, clearingStarts, fetchYieldHistorySeries, isEmptyHistoryError, positionEpochYield, positionSeriesFrom, resolveYieldHistoryWindow, validateYieldHistoryOptions, YIELD_HISTORY_PAGE_SIZE, yieldHistoryPageQuery, yieldHistoryPositionsQuery, } from './yield-history';
|
|
23
|
+
/**
|
|
24
|
+
* How many epochs back the portfolio read pairs a tranche's newest interest
|
|
25
|
+
* with the shares in force at its clearing (`lendingPortfolioQuery`): the last
|
|
26
|
+
* six settled epochs, so a pool whose clearings run late or lag a few weeks
|
|
27
|
+
* behind the others (Base has one) is paired too. A pool whose newest
|
|
28
|
+
* interest is older (pools clear independently; some only when the
|
|
29
|
+
* originator repays) is priced from a history read of its own, at that
|
|
30
|
+
* epoch. The extra rows are a few per lender and tranche.
|
|
31
|
+
*/
|
|
32
|
+
const RECENT_PAIRING_EPOCHS = 6;
|
|
33
|
+
/**
|
|
34
|
+
* The ledger (subgraph) and the chain agree on a balance AT THE SAME BLOCK
|
|
35
|
+
* within a cent, or a millionth of it — the rounding of two share-price
|
|
36
|
+
* conversions.
|
|
37
|
+
*/
|
|
38
|
+
const BALANCE_RECONCILE_TOLERANCE = 0.01;
|
|
39
|
+
const BALANCE_RECONCILE_RELATIVE = 1e-6;
|
|
40
|
+
function ledgerAgrees(ledger, chain) {
|
|
41
|
+
return (Math.abs(ledger - chain) <=
|
|
42
|
+
Math.max(BALANCE_RECONCILE_TOLERANCE, Math.abs(chain) * BALANCE_RECONCILE_RELATIVE));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A list read with `first: $first` that comes back FULL may have been cut:
|
|
46
|
+
* refuse it rather than price from a truncated read.
|
|
47
|
+
*/
|
|
48
|
+
function refuseTruncated(data) {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
const full = (rows) => { var _a; return ((_a = rows === null || rows === void 0 ? void 0 : rows.length) !== null && _a !== void 0 ? _a : 0) >= YIELD_HISTORY_PAGE_SIZE; };
|
|
51
|
+
const lists = [
|
|
52
|
+
['lendingPoolClearings', full(data.lendingPoolClearings)],
|
|
53
|
+
['recentWithdrawalsAccepted', full(data.recentWithdrawalsAccepted)],
|
|
54
|
+
];
|
|
55
|
+
for (const pool of (_b = (_a = data.user) === null || _a === void 0 ? void 0 : _a.lendingPoolUserDetails) !== null && _b !== void 0 ? _b : []) {
|
|
56
|
+
for (const position of pool.lendingPoolTrancheUserDetails) {
|
|
57
|
+
lists.push([
|
|
58
|
+
'recentUserShareUpdates',
|
|
59
|
+
full(position.recentUserShareUpdates),
|
|
60
|
+
], [
|
|
61
|
+
'recentTrancheShareUpdates',
|
|
62
|
+
full(position.tranche.recentTrancheShareUpdates),
|
|
63
|
+
], ...position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => [
|
|
64
|
+
'lockSharesRecent',
|
|
65
|
+
full(lock.lockSharesRecent),
|
|
66
|
+
]));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const cut = lists.find(([, isFull]) => isFull);
|
|
70
|
+
if (cut) {
|
|
71
|
+
throw new Error(`getPortfolioLendingData: ${cut[0]} reached ${String(YIELD_HISTORY_PAGE_SIZE)} rows; refusing a possibly truncated read`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
23
74
|
export class Portfolio {
|
|
24
75
|
constructor(_kasuConfig, signerOrProvider) {
|
|
25
76
|
this._kasuConfig = _kasuConfig;
|
|
@@ -237,23 +288,52 @@ export class Portfolio {
|
|
|
237
288
|
}
|
|
238
289
|
getPortfolioLendingData(userAddress, poolOverviews, currentEpoch, provider) {
|
|
239
290
|
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
291
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
241
292
|
const SECONDS_PER_EPOCH = 604800; // seconds in a week
|
|
242
293
|
const portfolioLendingPoolsPromise = [];
|
|
243
294
|
const previousEpoch = BigNumber.from(currentEpoch).sub(BigNumber.from(1));
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.
|
|
254
|
-
|
|
295
|
+
const recentEpoch = Math.max(0, previousEpoch.toNumber() - (RECENT_PAIRING_EPOCHS - 1));
|
|
296
|
+
const unused = new Set(this._kasuConfig.UNUSED_LENDING_POOL_IDS.map((id) => id.toLowerCase()));
|
|
297
|
+
const listedPools = poolOverviews.filter(({ id }) => !unused.has(id.toLowerCase()));
|
|
298
|
+
const lastEpochData = yield this._graph.request(lendingPortfolioQuery, {
|
|
299
|
+
userAddress: userAddress.toLowerCase(),
|
|
300
|
+
epochId: parseFloat(currentEpoch),
|
|
301
|
+
lastEpochId: previousEpoch.toNumber(),
|
|
302
|
+
recentEpoch,
|
|
303
|
+
lockRecentEpoch: Math.max(0, recentEpoch - 1),
|
|
304
|
+
unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
|
|
305
|
+
poolIds: listedPools.map(({ id }) => id.toLowerCase()),
|
|
306
|
+
trancheIds: listedPools.flatMap(({ tranches }) => tranches.map(({ id }) => id.toLowerCase())),
|
|
307
|
+
first: YIELD_HISTORY_PAGE_SIZE,
|
|
308
|
+
});
|
|
255
309
|
if (!lastEpochData.user)
|
|
256
310
|
return [];
|
|
311
|
+
refuseTruncated(lastEpochData);
|
|
312
|
+
// The pools the lender has a ledger in AND that are listed: the only
|
|
313
|
+
// ones read on chain, and the only ones reported.
|
|
314
|
+
const userPools = listedPools.flatMap((overview) => {
|
|
315
|
+
var _a;
|
|
316
|
+
const details = (_a = lastEpochData.user) === null || _a === void 0 ? void 0 : _a.lendingPoolUserDetails.find(({ lendingPool }) => lendingPool.id.toLowerCase() === overview.id.toLowerCase());
|
|
317
|
+
return details ? [{ overview, details }] : [];
|
|
318
|
+
});
|
|
319
|
+
const recentStarts = clearingStarts(lastEpochData.lendingPoolClearings);
|
|
320
|
+
const recentExits = new Map();
|
|
321
|
+
for (const exit of lastEpochData.recentWithdrawalsAccepted) {
|
|
322
|
+
const list = (_a = recentExits.get(exit.tranche.id)) !== null && _a !== void 0 ? _a : [];
|
|
323
|
+
list.push(exit);
|
|
324
|
+
recentExits.set(exit.tranche.id, list);
|
|
325
|
+
}
|
|
326
|
+
const [chain, staleYields] = yield Promise.all([
|
|
327
|
+
this.readChainAt((_b = lastEpochData._meta) === null || _b === void 0 ? void 0 : _b.block.number, (blockTag) => this._userLendingService.getUserPoolBalancesAt(userAddress, userPools.map(({ overview }) => overview.id), provider, blockTag)),
|
|
328
|
+
this.staleLastEpochYields(userPools.flatMap(({ overview, details }) => details.lendingPoolTrancheUserDetails.map((position) => ({
|
|
329
|
+
poolId: overview.id,
|
|
330
|
+
position,
|
|
331
|
+
}))), recentEpoch),
|
|
332
|
+
]);
|
|
333
|
+
const reconciled = yield Promise.all(userPools.map(({ overview, details }, index) => {
|
|
334
|
+
var _a;
|
|
335
|
+
return this.reconcilePool(userAddress, overview, details, (_a = chain.values[index]) !== null && _a !== void 0 ? _a : BigNumber.from(0), chain.blockTag, provider);
|
|
336
|
+
}));
|
|
257
337
|
const userRequestsMap = new Map();
|
|
258
338
|
for (const userRequest of lastEpochData.userRequests) {
|
|
259
339
|
const trancheId = userRequest.tranche.id;
|
|
@@ -279,28 +359,22 @@ export class Portfolio {
|
|
|
279
359
|
continue;
|
|
280
360
|
}
|
|
281
361
|
userRequestsMap.set(trancheId, Object.assign(Object.assign({}, mappedUserRequest), { [ftdId]: [
|
|
282
|
-
...((
|
|
362
|
+
...((_c = mappedUserRequest[ftdId]) !== null && _c !== void 0 ? _c : []),
|
|
283
363
|
userRequestFtdItem,
|
|
284
364
|
] }));
|
|
285
365
|
}
|
|
286
366
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const userPoolBalance = userPoolBalances.find(({ poolId }) => poolId === poolOverview.id);
|
|
290
|
-
if (!userPoolBalance) {
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
totalInvestments = userPoolBalance.balance.add(totalInvestments);
|
|
367
|
+
for (const [poolIndex, { overview: poolOverview, details },] of userPools.entries()) {
|
|
368
|
+
const pool = reconciled[poolIndex];
|
|
294
369
|
const tranches = [];
|
|
295
370
|
let poolYieldEarnedLastEpoch = 0;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const portfolioUserTrancheBalance = portfolioUserTrancheBalances.get(poolOverview.id);
|
|
371
|
+
// A ledger crediting shares the wallet does not hold earns
|
|
372
|
+
// nothing we can vouch for: its last-cycle figures are withheld.
|
|
373
|
+
const withheld = ((_d = pool.mismatch) === null || _d === void 0 ? void 0 : _d.kind) === 'ledger-exceeds-chain';
|
|
300
374
|
for (const tranche of poolOverview.tranches) {
|
|
301
|
-
const lastEpochUserTrancheDetails =
|
|
375
|
+
const lastEpochUserTrancheDetails = details.lendingPoolTrancheUserDetails.find((lendingPool) => lendingPool.tranche.id === tranche.id);
|
|
302
376
|
const userRequest = userRequestsMap.get(tranche.id);
|
|
303
|
-
const depositDetails = (
|
|
377
|
+
const depositDetails = (_e = userRequest === null || userRequest === void 0 ? void 0 : userRequest['0']) !== null && _e !== void 0 ? _e : [];
|
|
304
378
|
if (!lastEpochUserTrancheDetails) {
|
|
305
379
|
tranches.push(Object.assign(Object.assign({}, tranche), { investedAmount: {
|
|
306
380
|
balance: '0',
|
|
@@ -311,55 +385,61 @@ export class Portfolio {
|
|
|
311
385
|
}, depositDetails, fixedLoans: [] }));
|
|
312
386
|
continue;
|
|
313
387
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
388
|
+
// The tranche's newest interest (epoch E), priced with the
|
|
389
|
+
// shares in force when clearing E applied it — the one
|
|
390
|
+
// selection `getYieldHistory` makes (`positionEpochYield`).
|
|
391
|
+
// Inside the recent window the candidates below are complete.
|
|
392
|
+
// An older E (a pool that clears rarely — some only when the
|
|
393
|
+
// originator repays) is priced from a history read of its own,
|
|
394
|
+
// and is 0 for a lender who has left the tranche.
|
|
395
|
+
const newestInterest = (_f = lastEpochUserTrancheDetails.tranche
|
|
396
|
+
.lendingPoolTrancheEpochInterest) === null || _f === void 0 ? void 0 : _f[0];
|
|
397
|
+
const interestEpoch = (newestInterest === null || newestInterest === void 0 ? void 0 : newestInterest.epochId) === undefined
|
|
398
|
+
? undefined
|
|
399
|
+
: Number(newestInterest.epochId);
|
|
317
400
|
const fixedTermDeposits = lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
epochInterest: parseFloat((_f = (_e = (_d = lastEpochUserTrancheDetails.tranche
|
|
323
|
-
.lendingPoolTrancheEpochInterest) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.epochInterestAmount) !== null && _f !== void 0 ? _f : '0'),
|
|
324
|
-
userShares: parseFloat((_j = (_h = (_g = lastEpochUserTrancheDetails
|
|
325
|
-
.lendingPoolTrancheUserEpochSharesUpdates) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.shares) !== null && _j !== void 0 ? _j : '0'),
|
|
326
|
-
trancheShares: parseFloat((_m = (_l = (_k = lastEpochUserTrancheDetails.tranche
|
|
327
|
-
.lendingPoolTrancheShareUpdates) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.shares) !== null && _m !== void 0 ? _m : '0'),
|
|
328
|
-
trancheBalanceNow: parseFloat(lastEpochUserTrancheDetails.tranche.balance),
|
|
329
|
-
trancheSharesNow: parseFloat(lastEpochUserTrancheDetails.tranche.shares),
|
|
330
|
-
locks: fixedTermDeposits.map((fixedTermDeposit) => {
|
|
331
|
-
var _a, _b, _c, _d, _e, _f;
|
|
332
|
-
return ({
|
|
333
|
-
active: parseFloat(fixedTermDeposit.epochLockStart) <
|
|
334
|
-
lastEpoch &&
|
|
335
|
-
parseFloat(fixedTermDeposit.epochLockEnd) >=
|
|
336
|
-
lastEpoch &&
|
|
337
|
-
userHasLastEpochShares,
|
|
338
|
-
// No snapshot = the lock's opening shares, exactly
|
|
339
|
-
// as `getYieldHistory` reads it: a lock made after
|
|
340
|
-
// clearing E starts at E with its first update
|
|
341
|
-
// tagged E + 1. Never the CURRENT shares, which
|
|
342
|
-
// would carry a top-up from a clearing that ran
|
|
343
|
-
// after the last settled epoch.
|
|
344
|
-
sharesAtEpoch: parseFloat((_c = (_b = (_a = fixedTermDeposit.lockSharesAtLastEpoch) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.shares) !== null && _c !== void 0 ? _c : fixedTermDeposit.initialTrancheShares),
|
|
345
|
-
sharesBeforeEpoch: parseFloat((_f = (_e = (_d = fixedTermDeposit
|
|
346
|
-
.userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : fixedTermDeposit.initialTrancheShares),
|
|
347
|
-
});
|
|
348
|
-
}),
|
|
349
|
-
});
|
|
350
|
-
const userTrancheBalance = {
|
|
351
|
-
balance: '0',
|
|
352
|
-
yieldEarned: 0,
|
|
401
|
+
const noYield = {
|
|
402
|
+
total: 0,
|
|
403
|
+
variable: 0,
|
|
404
|
+
locks: fixedTermDeposits.map(() => 0),
|
|
353
405
|
};
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
406
|
+
let lastEpochYield = noYield;
|
|
407
|
+
if (interestEpoch !== undefined &&
|
|
408
|
+
interestEpoch < recentEpoch &&
|
|
409
|
+
!withheld) {
|
|
410
|
+
lastEpochYield =
|
|
411
|
+
(_g = staleYields.get(lastEpochUserTrancheDetails.id)) !== null && _g !== void 0 ? _g : noYield;
|
|
412
|
+
}
|
|
413
|
+
else if (interestEpoch !== undefined && !withheld) {
|
|
414
|
+
lastEpochYield = positionEpochYield({
|
|
415
|
+
trancheBalanceNow: parseFloat(lastEpochUserTrancheDetails.tranche.balance),
|
|
416
|
+
trancheSharesNow: parseFloat(lastEpochUserTrancheDetails.tranche.shares),
|
|
417
|
+
userUpdates: [
|
|
418
|
+
...((_h = lastEpochUserTrancheDetails.lendingPoolTrancheUserEpochSharesUpdates) !== null && _h !== void 0 ? _h : []),
|
|
419
|
+
...((_j = lastEpochUserTrancheDetails.recentUserShareUpdates) !== null && _j !== void 0 ? _j : []),
|
|
420
|
+
],
|
|
421
|
+
trancheUpdates: [
|
|
422
|
+
...((_k = lastEpochUserTrancheDetails.tranche
|
|
423
|
+
.lendingPoolTrancheShareUpdates) !== null && _k !== void 0 ? _k : []),
|
|
424
|
+
...((_l = lastEpochUserTrancheDetails.tranche
|
|
425
|
+
.recentTrancheShareUpdates) !== null && _l !== void 0 ? _l : []),
|
|
426
|
+
],
|
|
427
|
+
exits: (_m = recentExits.get(tranche.id)) !== null && _m !== void 0 ? _m : [],
|
|
428
|
+
locks: fixedTermDeposits.map((lock) => {
|
|
429
|
+
var _a, _b;
|
|
430
|
+
return (Object.assign(Object.assign({}, lock), { updates: [
|
|
431
|
+
...((_a = lock.lockSharesBase) !== null && _a !== void 0 ? _a : []),
|
|
432
|
+
...((_b = lock.lockSharesRecent) !== null && _b !== void 0 ? _b : []),
|
|
433
|
+
] }));
|
|
434
|
+
}),
|
|
435
|
+
}, interestEpoch, parseFloat((_o = newestInterest === null || newestInterest === void 0 ? void 0 : newestInterest.epochInterestAmount) !== null && _o !== void 0 ? _o : '0'), (_p = recentStarts
|
|
436
|
+
.get(poolOverview.id.toLowerCase())) === null || _p === void 0 ? void 0 : _p.get(interestEpoch)).yield;
|
|
362
437
|
}
|
|
438
|
+
const ledger = pool.tranches.get(tranche.id);
|
|
439
|
+
const userTrancheBalance = {
|
|
440
|
+
balance: (_q = ledger === null || ledger === void 0 ? void 0 : ledger.balance) !== null && _q !== void 0 ? _q : '0',
|
|
441
|
+
yieldEarned: (_r = ledger === null || ledger === void 0 ? void 0 : ledger.yieldEarned) !== null && _r !== void 0 ? _r : 0,
|
|
442
|
+
};
|
|
363
443
|
let totalLifetimeFtdYield = 0;
|
|
364
444
|
const fixedLoans = fixedTermDeposits.map((fixedTermDeposit, lockIndex) => {
|
|
365
445
|
var _a, _b;
|
|
@@ -407,7 +487,9 @@ export class Portfolio {
|
|
|
407
487
|
}, fixedLoans,
|
|
408
488
|
depositDetails }));
|
|
409
489
|
}
|
|
410
|
-
portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: this.precisionToString(poolYieldEarnedLastEpoch), totalInvestedAmount:
|
|
490
|
+
portfolioLendingPoolsPromise.push(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: this.precisionToString(poolYieldEarnedLastEpoch), totalInvestedAmount: pool.chainBalance.toString(), totalYieldEarningsLifetime: pool.lifetime.toString() }), (pool.mismatch ? { ledgerMismatch: pool.mismatch } : {})), (chain.blockTag === undefined
|
|
491
|
+
? { chainPinned: false }
|
|
492
|
+
: {})), { tranches }));
|
|
411
493
|
}
|
|
412
494
|
const portfolioLendingPools = portfolioLendingPoolsPromise.map((pool) => (Object.assign(Object.assign({}, pool), { tranches: pool.tranches.map((tranche) => {
|
|
413
495
|
const trancheBalance = tranche.investedAmount;
|
|
@@ -418,7 +500,14 @@ export class Portfolio {
|
|
|
418
500
|
}, BigNumber.from(0));
|
|
419
501
|
const totalTrancheYieldEarned = trancheBalance.yieldEarned.toFixed(6);
|
|
420
502
|
const totalTrancheFtdYieldEarned = parseFloat(tranche.yieldEarnings.lifetime).toFixed(6);
|
|
421
|
-
|
|
503
|
+
// Never negative: a variable balance below the locked
|
|
504
|
+
// amount is a ledger the chain does not back (the pool
|
|
505
|
+
// carries `ledgerMismatch`), not money owed — flagged.
|
|
506
|
+
const variableBalance = parseEther(trancheBalance.balance).sub(lockedAmount);
|
|
507
|
+
const floored = variableBalance.isNegative();
|
|
508
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, tranche), { investedAmount: formatEther(floored ? BigNumber.from(0) : variableBalance) }), (floored
|
|
509
|
+
? { investedAmountFloored: true }
|
|
510
|
+
: {})), { yieldEarnings: Object.assign(Object.assign({}, tranche.yieldEarnings), { lifetime: this.precisionToString(parseFloat(totalTrancheYieldEarned) -
|
|
422
511
|
parseFloat(totalTrancheFtdYieldEarned)) }) });
|
|
423
512
|
}) })));
|
|
424
513
|
return portfolioLendingPools.filter((pool) => !parseUnits(pool.totalInvestedAmount).isZero() ||
|
|
@@ -444,7 +533,12 @@ export class Portfolio {
|
|
|
444
533
|
* Cost: three parallel `SystemVariables` reads and the positions query in
|
|
445
534
|
* one round, then one subgraph request per page of the longest series
|
|
446
535
|
* (1000 rows a page) — normally two subgraph round trips in all, never one
|
|
447
|
-
* per epoch.
|
|
536
|
+
* per epoch — alongside one `LendingPool.userBalance` call per pool at
|
|
537
|
+
* the subgraph's block. A pool whose ledger credits more than the wallet
|
|
538
|
+
* holds at that block (shares moved away outside a withdrawal) is left
|
|
539
|
+
* out; if the node says it cannot serve that block, `latest` is read
|
|
540
|
+
* and nothing is. Any other failure of those reads (a rate limit, a
|
|
541
|
+
* network error) THROWS: the history needs a working RPC.
|
|
448
542
|
*
|
|
449
543
|
* On a LITE deployment, a subgraph that lacks the entities (a validation
|
|
450
544
|
* error naming them) yields `[]`; on a full deployment that error throws,
|
|
@@ -455,6 +549,7 @@ export class Portfolio {
|
|
|
455
549
|
*/
|
|
456
550
|
getYieldHistory(userAddress_1) {
|
|
457
551
|
return __awaiter(this, arguments, void 0, function* (userAddress, options = {}) {
|
|
552
|
+
var _a;
|
|
458
553
|
validateYieldHistoryOptions(options);
|
|
459
554
|
try {
|
|
460
555
|
const [currentEpoch, epochZeroStart, epochDuration, positions] = yield Promise.all([
|
|
@@ -473,7 +568,9 @@ export class Portfolio {
|
|
|
473
568
|
const userTranches = [];
|
|
474
569
|
const tranches = new Set();
|
|
475
570
|
const locks = [];
|
|
571
|
+
const pools = [];
|
|
476
572
|
for (const pool of positions.user.lendingPoolUserDetails) {
|
|
573
|
+
pools.push(pool.lendingPool.id);
|
|
477
574
|
for (const position of pool.lendingPoolTrancheUserDetails) {
|
|
478
575
|
userTranches.push(position.id);
|
|
479
576
|
tranches.add(position.tranche.id);
|
|
@@ -484,11 +581,33 @@ export class Portfolio {
|
|
|
484
581
|
}
|
|
485
582
|
if (userTranches.length === 0)
|
|
486
583
|
return [];
|
|
487
|
-
const { rows } = yield
|
|
584
|
+
const [{ rows }, chain] = yield Promise.all([
|
|
585
|
+
fetchYieldHistorySeries((variables) => this._graph.request(yieldHistoryPageQuery, variables), { pools, tranches: [...tranches], userTranches, locks }, window),
|
|
586
|
+
this.readChainAt((_a = positions._meta) === null || _a === void 0 ? void 0 : _a.block.number, (blockTag) => this._userLendingService.getUserPoolBalancesAt(userAddress, pools, undefined, blockTag)),
|
|
587
|
+
]);
|
|
588
|
+
// A pool whose ledger credits shares the wallet does not hold at
|
|
589
|
+
// the same block (shares moved away outside a withdrawal) has no
|
|
590
|
+
// history we can vouch for: it is left out. Unpinned, nothing is.
|
|
591
|
+
const decimals = this._kasuConfig.stableAssetDecimals;
|
|
592
|
+
const withheld = new Set();
|
|
593
|
+
if (chain.blockTag !== undefined) {
|
|
594
|
+
positions.user.lendingPoolUserDetails.forEach((pool, index) => {
|
|
595
|
+
var _a;
|
|
596
|
+
const ledger = pool.lendingPoolTrancheUserDetails.reduce((sum, position) => {
|
|
597
|
+
var _a;
|
|
598
|
+
return sum +
|
|
599
|
+
parseFloat(this._userLendingService.convertSharesToAssets((_a = position.shares) !== null && _a !== void 0 ? _a : '0', position.tranche.balance, position.tranche.shares));
|
|
600
|
+
}, 0);
|
|
601
|
+
const held = parseFloat(formatUnits((_a = chain.values[index]) !== null && _a !== void 0 ? _a : BigNumber.from(0), decimals));
|
|
602
|
+
if (ledger > held && !ledgerAgrees(ledger, held)) {
|
|
603
|
+
withheld.add(pool.lendingPool.id.toLowerCase());
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
488
607
|
return buildYieldHistory(Object.assign({ positions }, rows), window, {
|
|
489
608
|
epochZeroStart: epochZeroStart.toNumber(),
|
|
490
609
|
epochDuration: epochDuration.toNumber(),
|
|
491
|
-
});
|
|
610
|
+
}).filter((entry) => !withheld.has(entry.poolId.toLowerCase()));
|
|
492
611
|
}
|
|
493
612
|
catch (error) {
|
|
494
613
|
if (isEmptyHistoryError(error, this._isLiteDeployment))
|
|
@@ -497,6 +616,164 @@ export class Portfolio {
|
|
|
497
616
|
}
|
|
498
617
|
});
|
|
499
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Read the chain at the subgraph's block, so the chain and the ledger
|
|
621
|
+
* describe the same state: after every clearing the subgraph runs a few
|
|
622
|
+
* blocks behind, and a `latest` read would then disagree with an
|
|
623
|
+
* honest ledger (interest moved every balance, deposits were accepted)
|
|
624
|
+
* and book that difference as a ledger fault.
|
|
625
|
+
*
|
|
626
|
+
* Only when the node says it cannot serve that block (pruned, or behind
|
|
627
|
+
* the subgraph: `isBlockUnavailableError`) does it fall back to `latest`,
|
|
628
|
+
* and it says so (`blockTag: undefined`): unpinned figures are never
|
|
629
|
+
* compared with the ledger, and `getPortfolioLendingData` marks the pools
|
|
630
|
+
* `chainPinned: false`. Any other failure — a rate limit, a timeout, a
|
|
631
|
+
* network error — is thrown, never turned into an unpinned read.
|
|
632
|
+
*/
|
|
633
|
+
readChainAt(block, read) {
|
|
634
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
635
|
+
if (block !== undefined) {
|
|
636
|
+
try {
|
|
637
|
+
return { values: yield read(block), blockTag: block };
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
if (!isBlockUnavailableError(error))
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return { values: yield read(undefined), blockTag: undefined };
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* One pool's balances: the ledger's (the subgraph counts a lender's
|
|
649
|
+
* shares as accepted deposits − accepted withdrawals ± top-ups) checked
|
|
650
|
+
* against the chain's `LendingPool.userBalance` at the SAME block.
|
|
651
|
+
*
|
|
652
|
+
* The ledger does not follow shares that move between wallets any other
|
|
653
|
+
* way (an administrative move of a position is the case seen on chain):
|
|
654
|
+
* the receiving wallet goes negative as it withdraws, the sending one
|
|
655
|
+
* keeps shares it no longer holds. Where the two disagree at that block —
|
|
656
|
+
* or any ledger balance is negative — the tranche balances are read from
|
|
657
|
+
* the chain at that block (`userActiveAssets`, one call per tranche), the
|
|
658
|
+
* tranche's ledger `yieldEarned` moves by the same difference, and the
|
|
659
|
+
* pool carries `ledgerMismatch`. Unpinned (the node could not serve the
|
|
660
|
+
* block), only a negative ledger balance is corrected. A pool that agrees
|
|
661
|
+
* — every ordinary lender — costs nothing extra.
|
|
662
|
+
*
|
|
663
|
+
* Lifetime figures stay ledger figures: they cannot tell moved principal
|
|
664
|
+
* from interest.
|
|
665
|
+
*/
|
|
666
|
+
reconcilePool(userAddress, overview, details, chainBalance, blockTag, provider) {
|
|
667
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
668
|
+
var _a, _b, _c;
|
|
669
|
+
const decimals = this._kasuConfig.stableAssetDecimals;
|
|
670
|
+
const chainTotal = parseFloat(formatUnits(chainBalance, decimals));
|
|
671
|
+
const lifetime = chainTotal -
|
|
672
|
+
parseFloat(details.totalAcceptedDeposits) +
|
|
673
|
+
parseFloat(details.totalAcceptedWithdrawnAmount);
|
|
674
|
+
const tranches = new Map();
|
|
675
|
+
for (const position of details.lendingPoolTrancheUserDetails) {
|
|
676
|
+
const balance = this._userLendingService.convertSharesToAssets(position.shares, position.tranche.balance, position.tranche.shares);
|
|
677
|
+
tranches.set(position.tranche.id, {
|
|
678
|
+
balance,
|
|
679
|
+
yieldEarned: parseFloat(balance) -
|
|
680
|
+
parseFloat(position.totalAcceptedDeposits) +
|
|
681
|
+
parseFloat(position.totalAcceptedWithdrawnAmount),
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
const ledgerTotal = [...tranches.values()].reduce((sum, { balance }) => sum + parseFloat(balance), 0);
|
|
685
|
+
const negative = [...tranches.values()].some(({ balance }) => parseFloat(balance) < 0);
|
|
686
|
+
const disagrees = blockTag !== undefined && !ledgerAgrees(ledgerTotal, chainTotal);
|
|
687
|
+
if (!negative && !disagrees) {
|
|
688
|
+
return { chainBalance, lifetime, tranches };
|
|
689
|
+
}
|
|
690
|
+
const trancheIds = [
|
|
691
|
+
...new Set([
|
|
692
|
+
...overview.tranches.map(({ id }) => id),
|
|
693
|
+
...tranches.keys(),
|
|
694
|
+
]),
|
|
695
|
+
];
|
|
696
|
+
const onChain = yield this._userLendingService.getUserTrancheActiveAssets(userAddress, trancheIds, provider, blockTag);
|
|
697
|
+
let phantom = false;
|
|
698
|
+
const reconciled = new Map();
|
|
699
|
+
for (const [index, trancheId] of trancheIds.entries()) {
|
|
700
|
+
const fromLedger = tranches.get(trancheId);
|
|
701
|
+
const ledgerBalance = parseFloat((_a = fromLedger === null || fromLedger === void 0 ? void 0 : fromLedger.balance) !== null && _a !== void 0 ? _a : '0');
|
|
702
|
+
const balance = formatUnits((_b = onChain[index]) !== null && _b !== void 0 ? _b : BigNumber.from(0), decimals);
|
|
703
|
+
if (blockTag !== undefined &&
|
|
704
|
+
ledgerBalance > parseFloat(balance) &&
|
|
705
|
+
!ledgerAgrees(ledgerBalance, parseFloat(balance))) {
|
|
706
|
+
phantom = true;
|
|
707
|
+
}
|
|
708
|
+
reconciled.set(trancheId, {
|
|
709
|
+
balance,
|
|
710
|
+
yieldEarned: ((_c = fromLedger === null || fromLedger === void 0 ? void 0 : fromLedger.yieldEarned) !== null && _c !== void 0 ? _c : 0) +
|
|
711
|
+
parseFloat(balance) -
|
|
712
|
+
ledgerBalance,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
chainBalance,
|
|
717
|
+
lifetime,
|
|
718
|
+
tranches: reconciled,
|
|
719
|
+
mismatch: {
|
|
720
|
+
kind: phantom ? 'ledger-exceeds-chain' : 'chain-exceeds-ledger',
|
|
721
|
+
ledgerBalance: String(ledgerTotal),
|
|
722
|
+
chainBalance: String(chainTotal),
|
|
723
|
+
},
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* The last-epoch figures of positions whose newest interest is OLDER than
|
|
729
|
+
* the recent window. Pools clear independently, and some clear only when
|
|
730
|
+
* the originator repays, so a pool's last cycle can be weeks back: it is
|
|
731
|
+
* priced at THAT epoch, from a history read of just those positions over
|
|
732
|
+
* just those epochs, through the same `positionEpochYield` — so it equals
|
|
733
|
+
* the history's newest bar.
|
|
734
|
+
*
|
|
735
|
+
* A lender who has since left the tranche (no ledger shares now) gets no
|
|
736
|
+
* figure: an old pool's last cycle is not theirs to show. Costs nothing
|
|
737
|
+
* when no position qualifies.
|
|
738
|
+
*/
|
|
739
|
+
staleLastEpochYields(positions, recentEpoch) {
|
|
740
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
741
|
+
var _a;
|
|
742
|
+
const stale = positions.flatMap(({ poolId, position }) => {
|
|
743
|
+
var _a;
|
|
744
|
+
const interest = (_a = position.tranche.lendingPoolTrancheEpochInterest) === null || _a === void 0 ? void 0 : _a[0];
|
|
745
|
+
if ((interest === null || interest === void 0 ? void 0 : interest.epochId) === undefined)
|
|
746
|
+
return [];
|
|
747
|
+
if (!(parseFloat(position.shares) > 0))
|
|
748
|
+
return [];
|
|
749
|
+
const epoch = Number(interest.epochId);
|
|
750
|
+
return epoch < recentEpoch
|
|
751
|
+
? [{ poolId, position, epoch, interest }]
|
|
752
|
+
: [];
|
|
753
|
+
});
|
|
754
|
+
const yields = new Map();
|
|
755
|
+
if (stale.length === 0)
|
|
756
|
+
return yields;
|
|
757
|
+
const lockId = (poolId, lock) => { var _a; return (_a = lock.id) !== null && _a !== void 0 ? _a : `${poolId.toLowerCase()}-${lock.lockId}`; };
|
|
758
|
+
const { rows } = yield fetchYieldHistorySeries((variables) => this._graph.request(yieldHistoryPageQuery, variables), {
|
|
759
|
+
pools: [...new Set(stale.map(({ poolId }) => poolId))].map((id) => id.toLowerCase()),
|
|
760
|
+
tranches: [
|
|
761
|
+
...new Set(stale.map(({ position }) => position.tranche.id)),
|
|
762
|
+
],
|
|
763
|
+
userTranches: stale.map(({ position }) => position.id),
|
|
764
|
+
locks: stale.flatMap(({ poolId, position }) => position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => lockId(poolId, lock))),
|
|
765
|
+
}, {
|
|
766
|
+
fromEpoch: Math.min(...stale.map(({ epoch }) => epoch)),
|
|
767
|
+
toEpoch: Math.max(...stale.map(({ epoch }) => epoch)),
|
|
768
|
+
});
|
|
769
|
+
const starts = clearingStarts(rows.clearings);
|
|
770
|
+
for (const { poolId, position, epoch, interest } of stale) {
|
|
771
|
+
const series = positionSeriesFrom(rows, Object.assign(Object.assign({}, position), { userLendingPoolTrancheFixedTermDepositLocks: position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => (Object.assign(Object.assign({}, lock), { id: lockId(poolId, lock) }))) }));
|
|
772
|
+
yields.set(position.id, positionEpochYield(series, epoch, parseFloat(interest.epochInterestAmount), (_a = starts.get(poolId.toLowerCase())) === null || _a === void 0 ? void 0 : _a.get(epoch)).yield);
|
|
773
|
+
}
|
|
774
|
+
return yields;
|
|
775
|
+
});
|
|
776
|
+
}
|
|
500
777
|
/**
|
|
501
778
|
* A money figure as a plain decimal string. Below a millionth of a token
|
|
502
779
|
* (the stable asset's own precision) is float noise, never money, so it
|