@kasufinance/kasu-sdk 2.10.3 → 2.12.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 +28 -1
- package/dist/bundle.cjs.js +752 -59
- package/dist/bundle.esm.js +750 -60
- package/dist/domain/index.d.ts +10 -2
- package/dist/domain/index.js +6 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/strategy-summary.d.ts +47 -0
- package/dist/domain/strategy-summary.js +76 -0
- package/dist/domain/strategy-summary.js.map +1 -0
- package/dist/domain/yield-history.d.ts +122 -0
- package/dist/domain/yield-history.js +95 -0
- package/dist/domain/yield-history.js.map +1 -0
- package/dist/facade/strategies.js +2 -0
- package/dist/facade/strategies.js.map +1 -1
- package/dist/facade/types.d.ts +13 -0
- package/dist/facade/user-portfolio.d.ts +32 -0
- package/dist/facade/user-portfolio.js +34 -0
- package/dist/facade/user-portfolio.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/dist/services/Portfolio/portfolio.d.ts +37 -0
- package/dist/services/Portfolio/portfolio.js +136 -56
- package/dist/services/Portfolio/portfolio.js.map +1 -1
- package/dist/services/Portfolio/queries.js +15 -3
- package/dist/services/Portfolio/queries.js.map +1 -1
- package/dist/services/Portfolio/types.d.ts +8 -0
- package/dist/services/Portfolio/yield-history.d.ts +177 -0
- package/dist/services/Portfolio/yield-history.js +405 -0
- package/dist/services/Portfolio/yield-history.js.map +1 -0
- package/package.json +2 -1
- package/src/domain/index.ts +19 -10
- package/src/domain/strategy-summary.ts +88 -0
- package/src/domain/yield-history.ts +194 -0
- package/src/facade/strategies.ts +2 -0
- package/src/facade/types.ts +13 -0
- package/src/facade/user-portfolio.ts +40 -3
- package/src/index.ts +2 -0
- package/src/services/Portfolio/portfolio.ts +263 -153
- package/src/services/Portfolio/queries.ts +15 -3
- package/src/services/Portfolio/types.ts +10 -0
- package/src/services/Portfolio/yield-history.ts +574 -0
package/dist/bundle.cjs.js
CHANGED
|
@@ -16943,6 +16943,101 @@ class KSULocking {
|
|
|
16943
16943
|
}
|
|
16944
16944
|
}
|
|
16945
16945
|
|
|
16946
|
+
/**
|
|
16947
|
+
* Settled interest per cycle (epoch): what a lender was credited at each close,
|
|
16948
|
+
* per pool and tranche, and the one formula that turns a tranche's credited
|
|
16949
|
+
* interest into one lender's share of it.
|
|
16950
|
+
*
|
|
16951
|
+
* **The figures are NET, and only net.** `LendingPool._applyTrancheInterest`
|
|
16952
|
+
* takes the performance fee off first and emits `InterestApplied` with the
|
|
16953
|
+
* lender side only (`interest × (1 − performanceFee)`); a fixed-term top-up
|
|
16954
|
+
* (`_applyFixedRateInterests`) is net of the fee the same way. So the figure
|
|
16955
|
+
* this module computes from those records IS what the lender was credited —
|
|
16956
|
+
* never take the fee off it again. No gross is offered: the subgraph keeps
|
|
16957
|
+
* only TODAY's fee, so a historical gross would be a guess.
|
|
16958
|
+
*
|
|
16959
|
+
* Units: amounts are display units of the DEPLOYMENT'S OWN stable token (USDC
|
|
16960
|
+
* on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
|
|
16961
|
+
* shares are display units of tranche shares, as the subgraph's BigDecimals.
|
|
16962
|
+
*/
|
|
16963
|
+
/**
|
|
16964
|
+
* The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
|
|
16965
|
+
* the last settled epoch and `getYieldHistory` for every epoch of a window, so
|
|
16966
|
+
* the two figures cannot drift apart.
|
|
16967
|
+
*
|
|
16968
|
+
* ```
|
|
16969
|
+
* base = epochInterest × userShares / trancheShares
|
|
16970
|
+
* lockBase = base × lock.sharesBeforeEpoch / userShares (active locks)
|
|
16971
|
+
* topUp = (lock.sharesAtEpoch − lock.sharesBeforeEpoch) × trancheBalanceNow / trancheSharesNow
|
|
16972
|
+
* variable = base − Σ lockBase
|
|
16973
|
+
* total = variable + Σ (lockBase + topUp)
|
|
16974
|
+
* ```
|
|
16975
|
+
*
|
|
16976
|
+
* A zero denominator contributes 0 (a tranche or a lender with no shares
|
|
16977
|
+
* earned nothing at that close), never `NaN`.
|
|
16978
|
+
*/
|
|
16979
|
+
function epochTrancheYield(input) {
|
|
16980
|
+
const base = input.trancheShares === 0
|
|
16981
|
+
? 0
|
|
16982
|
+
: (input.epochInterest * input.userShares) / input.trancheShares;
|
|
16983
|
+
let variable = base;
|
|
16984
|
+
const locks = input.locks.map((lock) => {
|
|
16985
|
+
if (!lock.active)
|
|
16986
|
+
return 0;
|
|
16987
|
+
const topUp = input.trancheSharesNow === 0
|
|
16988
|
+
? 0
|
|
16989
|
+
: ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
|
|
16990
|
+
input.trancheBalanceNow) /
|
|
16991
|
+
input.trancheSharesNow;
|
|
16992
|
+
const lockBase = input.userShares === 0
|
|
16993
|
+
? 0
|
|
16994
|
+
: (base * lock.sharesBeforeEpoch) / input.userShares;
|
|
16995
|
+
variable -= lockBase;
|
|
16996
|
+
return lockBase + topUp;
|
|
16997
|
+
});
|
|
16998
|
+
const total = locks.reduce((sum, lockYield) => sum + lockYield, variable);
|
|
16999
|
+
return { total, variable, locks };
|
|
17000
|
+
}
|
|
17001
|
+
/**
|
|
17002
|
+
* Pool/tranche entries → one point per epoch, ascending by `epochId`. With no
|
|
17003
|
+
* filter it sums every strategy (the Lend tab's bars); with `poolId` it is one
|
|
17004
|
+
* strategy (a strategy page's bars).
|
|
17005
|
+
*
|
|
17006
|
+
* An epoch appears only if a kept entry has it — there is no zero padding, so
|
|
17007
|
+
* an empty input (a new lender) is an empty output.
|
|
17008
|
+
*
|
|
17009
|
+
* For a fixed-term lock holder the bars can sum to slightly MORE than the
|
|
17010
|
+
* lender's lifetime interest (up to about 0.2 %): each top-up is valued at the
|
|
17011
|
+
* tranche's CURRENT share price, which has grown since. Show "earned to date"
|
|
17012
|
+
* from the lifetime figure, never from this sum.
|
|
17013
|
+
*/
|
|
17014
|
+
function aggregateYieldHistory(entries, filter = {}) {
|
|
17015
|
+
var _a, _b;
|
|
17016
|
+
const poolId = (_a = filter.poolId) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
17017
|
+
const trancheId = (_b = filter.trancheId) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
17018
|
+
const byEpoch = new Map();
|
|
17019
|
+
for (const entry of entries) {
|
|
17020
|
+
if (poolId !== undefined && entry.poolId.toLowerCase() !== poolId) {
|
|
17021
|
+
continue;
|
|
17022
|
+
}
|
|
17023
|
+
if (trancheId !== undefined &&
|
|
17024
|
+
entry.trancheId.toLowerCase() !== trancheId) {
|
|
17025
|
+
continue;
|
|
17026
|
+
}
|
|
17027
|
+
const point = byEpoch.get(entry.epochId);
|
|
17028
|
+
if (point) {
|
|
17029
|
+
point.netYield += entry.netYield;
|
|
17030
|
+
continue;
|
|
17031
|
+
}
|
|
17032
|
+
byEpoch.set(entry.epochId, {
|
|
17033
|
+
epochId: entry.epochId,
|
|
17034
|
+
epochEnd: entry.epochEnd,
|
|
17035
|
+
netYield: entry.netYield,
|
|
17036
|
+
});
|
|
17037
|
+
}
|
|
17038
|
+
return [...byEpoch.values()].sort((a, b) => a.epochId - b.epochId);
|
|
17039
|
+
}
|
|
17040
|
+
|
|
16946
17041
|
/**
|
|
16947
17042
|
* Check if the SDK is configured for a Lite deployment.
|
|
16948
17043
|
* Lite deployments don't have KSU token, locking, or loyalty features.
|
|
@@ -17726,9 +17821,11 @@ const lendingPortfolioQuery = gql `
|
|
|
17726
17821
|
$epochId: BigInt!
|
|
17727
17822
|
$lastEpochId: BigInt!
|
|
17728
17823
|
$unusedPools: [String!]!
|
|
17824
|
+
$first: Int!
|
|
17729
17825
|
) {
|
|
17730
17826
|
user(id: $userAddress) {
|
|
17731
17827
|
lendingPoolUserDetails(
|
|
17828
|
+
first: $first
|
|
17732
17829
|
where: { lendingPool_not_in: $unusedPools }
|
|
17733
17830
|
) {
|
|
17734
17831
|
id
|
|
@@ -17736,11 +17833,11 @@ const lendingPortfolioQuery = gql `
|
|
|
17736
17833
|
id
|
|
17737
17834
|
name
|
|
17738
17835
|
balance
|
|
17739
|
-
tranches {
|
|
17836
|
+
tranches(first: $first) {
|
|
17740
17837
|
shares
|
|
17741
17838
|
}
|
|
17742
17839
|
}
|
|
17743
|
-
lendingPoolTrancheUserDetails {
|
|
17840
|
+
lendingPoolTrancheUserDetails(first: $first) {
|
|
17744
17841
|
shares
|
|
17745
17842
|
lendingPoolTrancheUserEpochSharesUpdates(
|
|
17746
17843
|
orderDirection: desc
|
|
@@ -17750,7 +17847,7 @@ const lendingPortfolioQuery = gql `
|
|
|
17750
17847
|
) {
|
|
17751
17848
|
shares
|
|
17752
17849
|
}
|
|
17753
|
-
userLendingPoolTrancheFixedTermDepositLocks {
|
|
17850
|
+
userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
|
|
17754
17851
|
isLocked
|
|
17755
17852
|
unlockAmount
|
|
17756
17853
|
initialAmount
|
|
@@ -17774,6 +17871,14 @@ const lendingPortfolioQuery = gql `
|
|
|
17774
17871
|
) {
|
|
17775
17872
|
shares
|
|
17776
17873
|
}
|
|
17874
|
+
lockSharesAtLastEpoch: userLendingPoolTrancheFixedTermDepositLockShareUpdate(
|
|
17875
|
+
orderBy: shareUpdatesIndex
|
|
17876
|
+
orderDirection: desc
|
|
17877
|
+
first: 1
|
|
17878
|
+
where: { epochId_lt: $epochId }
|
|
17879
|
+
) {
|
|
17880
|
+
shares
|
|
17881
|
+
}
|
|
17777
17882
|
}
|
|
17778
17883
|
tranche {
|
|
17779
17884
|
id
|
|
@@ -17800,6 +17905,7 @@ const lendingPortfolioQuery = gql `
|
|
|
17800
17905
|
}
|
|
17801
17906
|
}
|
|
17802
17907
|
userRequests(
|
|
17908
|
+
first: $first
|
|
17803
17909
|
where: {
|
|
17804
17910
|
user: $userAddress
|
|
17805
17911
|
userRequestEvents_: {
|
|
@@ -17815,6 +17921,7 @@ const lendingPortfolioQuery = gql `
|
|
|
17815
17921
|
}
|
|
17816
17922
|
fixedTermConfigId
|
|
17817
17923
|
userRequestEvents(
|
|
17924
|
+
first: $first
|
|
17818
17925
|
where: { type_in: [DepositInitiated, DepositIncreased] }
|
|
17819
17926
|
) {
|
|
17820
17927
|
id
|
|
@@ -17824,6 +17931,400 @@ const lendingPortfolioQuery = gql `
|
|
|
17824
17931
|
}
|
|
17825
17932
|
`;
|
|
17826
17933
|
|
|
17934
|
+
/** The subgraph returns at most this many rows per collection per request. */
|
|
17935
|
+
const YIELD_HISTORY_PAGE_SIZE = 1000;
|
|
17936
|
+
/**
|
|
17937
|
+
* A runaway guard, not a working limit: a collection this many pages long
|
|
17938
|
+
* (`MAX × PAGE_SIZE` rows for one lender) is a broken response, not a history.
|
|
17939
|
+
*/
|
|
17940
|
+
const YIELD_HISTORY_MAX_PAGES = 100;
|
|
17941
|
+
/**
|
|
17942
|
+
* Throws `RangeError` on a malformed `fromEpoch` / `count`. `getYieldHistory`
|
|
17943
|
+
* calls it BEFORE any network read.
|
|
17944
|
+
*/
|
|
17945
|
+
function validateYieldHistoryOptions(options) {
|
|
17946
|
+
const { fromEpoch, count } = options;
|
|
17947
|
+
if (fromEpoch !== undefined &&
|
|
17948
|
+
(!Number.isSafeInteger(fromEpoch) || fromEpoch < 0)) {
|
|
17949
|
+
throw new RangeError(`getYieldHistory: fromEpoch must be a non-negative integer, got ${String(fromEpoch)}`);
|
|
17950
|
+
}
|
|
17951
|
+
if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) {
|
|
17952
|
+
throw new RangeError(`getYieldHistory: count must be a positive integer, got ${String(count)}`);
|
|
17953
|
+
}
|
|
17954
|
+
}
|
|
17955
|
+
function resolveYieldHistoryWindow(options, currentEpoch) {
|
|
17956
|
+
validateYieldHistoryOptions(options);
|
|
17957
|
+
const { fromEpoch, count } = options;
|
|
17958
|
+
const newest = currentEpoch - 1;
|
|
17959
|
+
if (newest < 0)
|
|
17960
|
+
return null;
|
|
17961
|
+
let from;
|
|
17962
|
+
let to = newest;
|
|
17963
|
+
if (fromEpoch !== undefined) {
|
|
17964
|
+
from = fromEpoch;
|
|
17965
|
+
if (count !== undefined)
|
|
17966
|
+
to = Math.min(newest, fromEpoch + count - 1);
|
|
17967
|
+
}
|
|
17968
|
+
else {
|
|
17969
|
+
from = count !== undefined ? Math.max(0, newest - count + 1) : 0;
|
|
17970
|
+
}
|
|
17971
|
+
return from > to ? null : { fromEpoch: from, toEpoch: to };
|
|
17972
|
+
}
|
|
17973
|
+
// ---------------------------------------------------------------------------
|
|
17974
|
+
// Subgraph reads
|
|
17975
|
+
// ---------------------------------------------------------------------------
|
|
17976
|
+
/**
|
|
17977
|
+
* Round trip 1: the lender's tranche positions and fixed-term locks. Same
|
|
17978
|
+
* pool exclusion and the same nested-list size (`$first`) as
|
|
17979
|
+
* `lendingPortfolioQuery`, so both read the same lock set.
|
|
17980
|
+
*/
|
|
17981
|
+
const yieldHistoryPositionsQuery = gql `
|
|
17982
|
+
query YieldHistoryPositions(
|
|
17983
|
+
$userAddress: ID!
|
|
17984
|
+
$unusedPools: [String!]!
|
|
17985
|
+
$first: Int!
|
|
17986
|
+
) {
|
|
17987
|
+
user(id: $userAddress) {
|
|
17988
|
+
lendingPoolUserDetails(
|
|
17989
|
+
first: $first
|
|
17990
|
+
where: { lendingPool_not_in: $unusedPools }
|
|
17991
|
+
) {
|
|
17992
|
+
lendingPool {
|
|
17993
|
+
id
|
|
17994
|
+
}
|
|
17995
|
+
lendingPoolTrancheUserDetails(first: $first) {
|
|
17996
|
+
id
|
|
17997
|
+
tranche {
|
|
17998
|
+
id
|
|
17999
|
+
balance
|
|
18000
|
+
shares
|
|
18001
|
+
}
|
|
18002
|
+
userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
|
|
18003
|
+
id
|
|
18004
|
+
epochLockStart
|
|
18005
|
+
epochLockEnd
|
|
18006
|
+
initialTrancheShares
|
|
18007
|
+
}
|
|
18008
|
+
}
|
|
18009
|
+
}
|
|
18010
|
+
}
|
|
18011
|
+
}
|
|
18012
|
+
`;
|
|
18013
|
+
/**
|
|
18014
|
+
* Round trip 2 onwards: the four per-epoch series, one page of each per
|
|
18015
|
+
* request, cursor-paginated by `id`. A series that is exhausted is skipped with
|
|
18016
|
+
* `@include`, so a history costs as many requests as its LONGEST series has
|
|
18017
|
+
* pages — normally one — never one per epoch.
|
|
18018
|
+
*
|
|
18019
|
+
* Share snapshots are read up to the window's end with no lower bound: the
|
|
18020
|
+
* value AS OF the first epoch may come from an update long before it.
|
|
18021
|
+
*/
|
|
18022
|
+
const yieldHistoryPageQuery = gql `
|
|
18023
|
+
query YieldHistoryPage(
|
|
18024
|
+
$tranches: [String!]!
|
|
18025
|
+
$userTranches: [String!]!
|
|
18026
|
+
$locks: [String!]!
|
|
18027
|
+
$fromEpoch: BigInt!
|
|
18028
|
+
$toEpoch: BigInt!
|
|
18029
|
+
$interestCursor: ID!
|
|
18030
|
+
$trancheSharesCursor: ID!
|
|
18031
|
+
$userSharesCursor: ID!
|
|
18032
|
+
$lockSharesCursor: ID!
|
|
18033
|
+
$withInterest: Boolean!
|
|
18034
|
+
$withTrancheShares: Boolean!
|
|
18035
|
+
$withUserShares: Boolean!
|
|
18036
|
+
$withLockShares: Boolean!
|
|
18037
|
+
$first: Int!
|
|
18038
|
+
) {
|
|
18039
|
+
interest: lendingPoolTrancheEpochInterests(
|
|
18040
|
+
first: $first
|
|
18041
|
+
orderBy: id
|
|
18042
|
+
orderDirection: asc
|
|
18043
|
+
where: {
|
|
18044
|
+
id_gt: $interestCursor
|
|
18045
|
+
lendingPoolTranche_in: $tranches
|
|
18046
|
+
epochId_gte: $fromEpoch
|
|
18047
|
+
epochId_lte: $toEpoch
|
|
18048
|
+
}
|
|
18049
|
+
) @include(if: $withInterest) {
|
|
18050
|
+
id
|
|
18051
|
+
epochId
|
|
18052
|
+
epochInterestAmount
|
|
18053
|
+
lendingPoolTranche {
|
|
18054
|
+
id
|
|
18055
|
+
}
|
|
18056
|
+
}
|
|
18057
|
+
trancheShares: lendingPoolTrancheShareUpdates(
|
|
18058
|
+
first: $first
|
|
18059
|
+
orderBy: id
|
|
18060
|
+
orderDirection: asc
|
|
18061
|
+
where: {
|
|
18062
|
+
id_gt: $trancheSharesCursor
|
|
18063
|
+
lendingPoolTranche_in: $tranches
|
|
18064
|
+
epochId_lte: $toEpoch
|
|
18065
|
+
}
|
|
18066
|
+
) @include(if: $withTrancheShares) {
|
|
18067
|
+
id
|
|
18068
|
+
epochId
|
|
18069
|
+
shareUpdatesIndex
|
|
18070
|
+
shares
|
|
18071
|
+
lendingPoolTranche {
|
|
18072
|
+
id
|
|
18073
|
+
}
|
|
18074
|
+
}
|
|
18075
|
+
userShares: lendingPoolTrancheUserEpochSharesUpdates(
|
|
18076
|
+
first: $first
|
|
18077
|
+
orderBy: id
|
|
18078
|
+
orderDirection: asc
|
|
18079
|
+
where: {
|
|
18080
|
+
id_gt: $userSharesCursor
|
|
18081
|
+
lendingPoolTrancheUserDetails_in: $userTranches
|
|
18082
|
+
epochId_lte: $toEpoch
|
|
18083
|
+
}
|
|
18084
|
+
) @include(if: $withUserShares) {
|
|
18085
|
+
id
|
|
18086
|
+
epochId
|
|
18087
|
+
shareUpdatesIndex
|
|
18088
|
+
shares
|
|
18089
|
+
lendingPoolTrancheUserDetails {
|
|
18090
|
+
id
|
|
18091
|
+
}
|
|
18092
|
+
}
|
|
18093
|
+
lockShares: userLendingPoolTrancheFtdLockShareUpdates(
|
|
18094
|
+
first: $first
|
|
18095
|
+
orderBy: id
|
|
18096
|
+
orderDirection: asc
|
|
18097
|
+
where: {
|
|
18098
|
+
id_gt: $lockSharesCursor
|
|
18099
|
+
userLendingPoolTrancheFixedTermDepositLock_in: $locks
|
|
18100
|
+
epochId_lte: $toEpoch
|
|
18101
|
+
}
|
|
18102
|
+
) @include(if: $withLockShares) {
|
|
18103
|
+
id
|
|
18104
|
+
epochId
|
|
18105
|
+
shareUpdatesIndex
|
|
18106
|
+
shares
|
|
18107
|
+
userLendingPoolTrancheFixedTermDepositLock {
|
|
18108
|
+
id
|
|
18109
|
+
}
|
|
18110
|
+
}
|
|
18111
|
+
}
|
|
18112
|
+
`;
|
|
18113
|
+
/**
|
|
18114
|
+
* Fetch the four series for the window, paginating each by `id` until a short
|
|
18115
|
+
* page. Returns the rows and how many requests it took.
|
|
18116
|
+
*/
|
|
18117
|
+
function fetchYieldHistorySeries(request, ids, window) {
|
|
18118
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
18119
|
+
var _a;
|
|
18120
|
+
const rows = {
|
|
18121
|
+
interest: [],
|
|
18122
|
+
trancheShares: [],
|
|
18123
|
+
userShares: [],
|
|
18124
|
+
lockShares: [],
|
|
18125
|
+
};
|
|
18126
|
+
const series = [
|
|
18127
|
+
{ key: 'interest', flag: 'withInterest', cursor: 'interestCursor' },
|
|
18128
|
+
{
|
|
18129
|
+
key: 'trancheShares',
|
|
18130
|
+
flag: 'withTrancheShares',
|
|
18131
|
+
cursor: 'trancheSharesCursor',
|
|
18132
|
+
},
|
|
18133
|
+
{
|
|
18134
|
+
key: 'userShares',
|
|
18135
|
+
flag: 'withUserShares',
|
|
18136
|
+
cursor: 'userSharesCursor',
|
|
18137
|
+
},
|
|
18138
|
+
{
|
|
18139
|
+
key: 'lockShares',
|
|
18140
|
+
flag: 'withLockShares',
|
|
18141
|
+
cursor: 'lockSharesCursor',
|
|
18142
|
+
},
|
|
18143
|
+
];
|
|
18144
|
+
const cursors = {
|
|
18145
|
+
interest: '',
|
|
18146
|
+
trancheShares: '',
|
|
18147
|
+
userShares: '',
|
|
18148
|
+
lockShares: '',
|
|
18149
|
+
};
|
|
18150
|
+
const open = {
|
|
18151
|
+
interest: ids.tranches.length > 0,
|
|
18152
|
+
trancheShares: ids.tranches.length > 0,
|
|
18153
|
+
userShares: ids.userTranches.length > 0,
|
|
18154
|
+
lockShares: ids.locks.length > 0,
|
|
18155
|
+
};
|
|
18156
|
+
let requests = 0;
|
|
18157
|
+
while (series.some(({ key }) => open[key])) {
|
|
18158
|
+
if (requests >= YIELD_HISTORY_MAX_PAGES) {
|
|
18159
|
+
throw new Error(`getYieldHistory: more than ${String(YIELD_HISTORY_MAX_PAGES)} pages for one lender`);
|
|
18160
|
+
}
|
|
18161
|
+
const variables = {
|
|
18162
|
+
tranches: ids.tranches,
|
|
18163
|
+
userTranches: ids.userTranches,
|
|
18164
|
+
locks: ids.locks,
|
|
18165
|
+
fromEpoch: String(window.fromEpoch),
|
|
18166
|
+
toEpoch: String(window.toEpoch),
|
|
18167
|
+
first: YIELD_HISTORY_PAGE_SIZE,
|
|
18168
|
+
};
|
|
18169
|
+
for (const { key, flag, cursor } of series) {
|
|
18170
|
+
variables[flag] = open[key];
|
|
18171
|
+
variables[cursor] = cursors[key];
|
|
18172
|
+
}
|
|
18173
|
+
const page = yield request(variables);
|
|
18174
|
+
requests += 1;
|
|
18175
|
+
for (const { key } of series) {
|
|
18176
|
+
if (!open[key])
|
|
18177
|
+
continue;
|
|
18178
|
+
const got = (_a = page[key]) !== null && _a !== void 0 ? _a : [];
|
|
18179
|
+
rows[key].push(...got);
|
|
18180
|
+
const last = got.at(-1);
|
|
18181
|
+
if (got.length < YIELD_HISTORY_PAGE_SIZE || last === undefined) {
|
|
18182
|
+
open[key] = false;
|
|
18183
|
+
continue;
|
|
18184
|
+
}
|
|
18185
|
+
if (last.id <= cursors[key]) {
|
|
18186
|
+
throw new Error(`getYieldHistory: the ${key} cursor did not advance`);
|
|
18187
|
+
}
|
|
18188
|
+
cursors[key] = last.id;
|
|
18189
|
+
}
|
|
18190
|
+
}
|
|
18191
|
+
return { rows, requests };
|
|
18192
|
+
});
|
|
18193
|
+
}
|
|
18194
|
+
/**
|
|
18195
|
+
* The snapshot in force at `epoch`: among updates with `epochId ≤ epoch`, the
|
|
18196
|
+
* one with the highest `shareUpdatesIndex` — exactly what
|
|
18197
|
+
* `lendingPortfolioQuery` selects with `orderBy: shareUpdatesIndex, desc,
|
|
18198
|
+
* first: 1, where: { epochId_lt: epoch + 1 }`. `undefined` when none.
|
|
18199
|
+
*/
|
|
18200
|
+
function sharesAsOf(updates, epoch) {
|
|
18201
|
+
let found;
|
|
18202
|
+
for (const update of updates) {
|
|
18203
|
+
if (Number(update.epochId) > epoch)
|
|
18204
|
+
continue;
|
|
18205
|
+
if (!found || update.shareUpdatesIndex > found.shareUpdatesIndex) {
|
|
18206
|
+
found = update;
|
|
18207
|
+
}
|
|
18208
|
+
}
|
|
18209
|
+
return found;
|
|
18210
|
+
}
|
|
18211
|
+
function groupBy(rows, key) {
|
|
18212
|
+
const map = new Map();
|
|
18213
|
+
for (const row of rows) {
|
|
18214
|
+
const k = key(row);
|
|
18215
|
+
const list = map.get(k);
|
|
18216
|
+
if (list)
|
|
18217
|
+
list.push(row);
|
|
18218
|
+
else
|
|
18219
|
+
map.set(k, [row]);
|
|
18220
|
+
}
|
|
18221
|
+
return map;
|
|
18222
|
+
}
|
|
18223
|
+
/**
|
|
18224
|
+
* Pure: the fetched series → one entry per pool, tranche and settled epoch in
|
|
18225
|
+
* which the lender held shares (or a lock earned a top-up). Each entry runs the
|
|
18226
|
+
* same `epochTrancheYield` as the portfolio's last-epoch figure, with the same
|
|
18227
|
+
* share selection, so the newest entry equals that figure.
|
|
18228
|
+
*
|
|
18229
|
+
* Sorted by `epochId`, then `poolId`, then `trancheId`.
|
|
18230
|
+
*/
|
|
18231
|
+
function buildYieldHistory(read, window, clock) {
|
|
18232
|
+
var _a, _b, _c, _d, _e, _f;
|
|
18233
|
+
const user = read.positions.user;
|
|
18234
|
+
if (!user)
|
|
18235
|
+
return [];
|
|
18236
|
+
const interestByTranche = groupBy(read.interest, (row) => row.lendingPoolTranche.id);
|
|
18237
|
+
const trancheSharesByTranche = groupBy(read.trancheShares, (row) => row.lendingPoolTranche.id);
|
|
18238
|
+
const userSharesByPosition = groupBy(read.userShares, (row) => row.lendingPoolTrancheUserDetails.id);
|
|
18239
|
+
const lockSharesByLock = groupBy(read.lockShares, (row) => row.userLendingPoolTrancheFixedTermDepositLock.id);
|
|
18240
|
+
const entries = [];
|
|
18241
|
+
for (const pool of user.lendingPoolUserDetails) {
|
|
18242
|
+
for (const position of pool.lendingPoolTrancheUserDetails) {
|
|
18243
|
+
const trancheId = position.tranche.id;
|
|
18244
|
+
const trancheUpdates = (_a = trancheSharesByTranche.get(trancheId)) !== null && _a !== void 0 ? _a : [];
|
|
18245
|
+
const userUpdates = (_b = userSharesByPosition.get(position.id)) !== null && _b !== void 0 ? _b : [];
|
|
18246
|
+
const trancheBalanceNow = parseFloat(position.tranche.balance);
|
|
18247
|
+
const trancheSharesNow = parseFloat(position.tranche.shares);
|
|
18248
|
+
for (const record of (_c = interestByTranche.get(trancheId)) !== null && _c !== void 0 ? _c : []) {
|
|
18249
|
+
const epoch = Number(record.epochId);
|
|
18250
|
+
if (epoch < window.fromEpoch || epoch > window.toEpoch) {
|
|
18251
|
+
continue;
|
|
18252
|
+
}
|
|
18253
|
+
const userSnapshot = sharesAsOf(userUpdates, epoch);
|
|
18254
|
+
const userShares = parseFloat((_d = userSnapshot === null || userSnapshot === void 0 ? void 0 : userSnapshot.shares) !== null && _d !== void 0 ? _d : '0');
|
|
18255
|
+
const locks = position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => {
|
|
18256
|
+
var _a, _b, _c, _d, _e;
|
|
18257
|
+
const lockUpdates = (_a = lockSharesByLock.get(lock.id)) !== null && _a !== void 0 ? _a : [];
|
|
18258
|
+
return {
|
|
18259
|
+
active: parseFloat(lock.epochLockStart) < epoch &&
|
|
18260
|
+
parseFloat(lock.epochLockEnd) >= epoch &&
|
|
18261
|
+
userSnapshot !== undefined,
|
|
18262
|
+
// No snapshot yet = the lock's opening
|
|
18263
|
+
// shares: a lock made after clearing E
|
|
18264
|
+
// starts at E but its first update is
|
|
18265
|
+
// tagged E + 1, and reading that gap as 0
|
|
18266
|
+
// would book its principal as a top-up.
|
|
18267
|
+
sharesAtEpoch: parseFloat((_c = (_b = sharesAsOf(lockUpdates, epoch)) === null || _b === void 0 ? void 0 : _b.shares) !== null && _c !== void 0 ? _c : lock.initialTrancheShares),
|
|
18268
|
+
sharesBeforeEpoch: parseFloat((_e = (_d = sharesAsOf(lockUpdates, epoch - 1)) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : lock.initialTrancheShares),
|
|
18269
|
+
};
|
|
18270
|
+
});
|
|
18271
|
+
const { total } = epochTrancheYield({
|
|
18272
|
+
epochInterest: parseFloat(record.epochInterestAmount),
|
|
18273
|
+
userShares,
|
|
18274
|
+
trancheShares: parseFloat((_f = (_e = sharesAsOf(trancheUpdates, epoch)) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : '0'),
|
|
18275
|
+
trancheBalanceNow,
|
|
18276
|
+
trancheSharesNow,
|
|
18277
|
+
locks,
|
|
18278
|
+
});
|
|
18279
|
+
// Nothing held and nothing topped up: not part of this
|
|
18280
|
+
// lender's history (a new lender's history is empty).
|
|
18281
|
+
if (userShares <= 0 && total === 0)
|
|
18282
|
+
continue;
|
|
18283
|
+
entries.push({
|
|
18284
|
+
epochId: epoch,
|
|
18285
|
+
epochEnd: clock.epochZeroStart +
|
|
18286
|
+
(epoch + 1) * clock.epochDuration,
|
|
18287
|
+
poolId: pool.lendingPool.id,
|
|
18288
|
+
trancheId,
|
|
18289
|
+
netYield: total,
|
|
18290
|
+
});
|
|
18291
|
+
}
|
|
18292
|
+
}
|
|
18293
|
+
}
|
|
18294
|
+
return entries.sort((a, b) => a.epochId - b.epochId ||
|
|
18295
|
+
a.poolId.localeCompare(b.poolId) ||
|
|
18296
|
+
a.trancheId.localeCompare(b.trancheId));
|
|
18297
|
+
}
|
|
18298
|
+
/**
|
|
18299
|
+
* A GraphQL VALIDATION error naming an entity, field or argument this
|
|
18300
|
+
* deployment's subgraph does not have. A network error, a timeout or an
|
|
18301
|
+
* indexing error is not one.
|
|
18302
|
+
*/
|
|
18303
|
+
function isMissingEntityError(error) {
|
|
18304
|
+
if (typeof error !== 'object' || error === null)
|
|
18305
|
+
return false;
|
|
18306
|
+
const response = error.response;
|
|
18307
|
+
const errors = response === null || response === void 0 ? void 0 : response.errors;
|
|
18308
|
+
if (!Array.isArray(errors) || errors.length === 0)
|
|
18309
|
+
return false;
|
|
18310
|
+
return errors.every((e) => {
|
|
18311
|
+
const message = e === null || e === void 0 ? void 0 : e.message;
|
|
18312
|
+
return (typeof message === 'string' &&
|
|
18313
|
+
/has no field|cannot query field|unknown (type|field|argument)|no such (type|field)/i.test(message));
|
|
18314
|
+
});
|
|
18315
|
+
}
|
|
18316
|
+
/**
|
|
18317
|
+
* The only failure `getYieldHistory` turns into `[]`: a missing-schema error
|
|
18318
|
+
* on a LITE deployment, whose subgraph may predate the entities. On a full
|
|
18319
|
+
* deployment the entities are part of the schema, so the same error means a
|
|
18320
|
+
* broken query or subgraph and THROWS — an empty chart there would hide it.
|
|
18321
|
+
* Everything else (network, timeout, indexing) throws on every deployment, so
|
|
18322
|
+
* a consumer can tell "no history here" from "history unavailable".
|
|
18323
|
+
*/
|
|
18324
|
+
function isEmptyHistoryError(error, liteDeployment) {
|
|
18325
|
+
return liteDeployment && isMissingEntityError(error);
|
|
18326
|
+
}
|
|
18327
|
+
|
|
17827
18328
|
class Portfolio {
|
|
17828
18329
|
constructor(_kasuConfig, signerOrProvider) {
|
|
17829
18330
|
this._kasuConfig = _kasuConfig;
|
|
@@ -17924,7 +18425,7 @@ class Portfolio {
|
|
|
17924
18425
|
performanceFee *
|
|
17925
18426
|
ecosystemFeeRate;
|
|
17926
18427
|
}
|
|
17927
|
-
return (
|
|
18428
|
+
return (totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount;
|
|
17928
18429
|
}
|
|
17929
18430
|
/**
|
|
17930
18431
|
* Calculate the user's weekly KASU bonus rewards (KASU) based on current deposits,
|
|
@@ -17932,7 +18433,7 @@ class Portfolio {
|
|
|
17932
18433
|
*/
|
|
17933
18434
|
calculateWeeklyKsuBonusRewards(params) {
|
|
17934
18435
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17935
|
-
const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice } = params;
|
|
18436
|
+
const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice, } = params;
|
|
17936
18437
|
const totalUserDeposits = userDepositAmounts[0].add(userDepositAmounts[1]);
|
|
17937
18438
|
if (totalUserDeposits.isZero())
|
|
17938
18439
|
return 0;
|
|
@@ -18041,7 +18542,7 @@ class Portfolio {
|
|
|
18041
18542
|
}
|
|
18042
18543
|
getPortfolioLendingData(userAddress, poolOverviews, currentEpoch, provider) {
|
|
18043
18544
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18044
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
18545
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
18045
18546
|
const SECONDS_PER_EPOCH = 604800; // seconds in a week
|
|
18046
18547
|
const portfolioLendingPoolsPromise = [];
|
|
18047
18548
|
const previousEpoch = ethers.BigNumber.from(currentEpoch).sub(ethers.BigNumber.from(1));
|
|
@@ -18051,6 +18552,7 @@ class Portfolio {
|
|
|
18051
18552
|
epochId: parseFloat(currentEpoch),
|
|
18052
18553
|
lastEpochId: previousEpoch.toNumber(),
|
|
18053
18554
|
unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
|
|
18555
|
+
first: YIELD_HISTORY_PAGE_SIZE,
|
|
18054
18556
|
}),
|
|
18055
18557
|
this._userLendingService.getPortfolioUserTrancheBalances(userAddress),
|
|
18056
18558
|
this._userLendingService.getUserPoolBalance(userAddress, poolOverviews.map(({ id }) => id), provider),
|
|
@@ -18088,7 +18590,6 @@ class Portfolio {
|
|
|
18088
18590
|
}
|
|
18089
18591
|
}
|
|
18090
18592
|
let totalInvestments = ethers.BigNumber.from(0);
|
|
18091
|
-
let totalYieldEarnedLastEpoch = 0;
|
|
18092
18593
|
for (const poolOverview of poolOverviews) {
|
|
18093
18594
|
const userPoolBalance = userPoolBalances.find(({ poolId }) => poolId === poolOverview.id);
|
|
18094
18595
|
if (!userPoolBalance) {
|
|
@@ -18096,6 +18597,7 @@ class Portfolio {
|
|
|
18096
18597
|
}
|
|
18097
18598
|
totalInvestments = userPoolBalance.balance.add(totalInvestments);
|
|
18098
18599
|
const tranches = [];
|
|
18600
|
+
let poolYieldEarnedLastEpoch = 0;
|
|
18099
18601
|
const lendingPoolUserDetails = lastEpochData.user.lendingPoolUserDetails.find(({ lendingPool }) => lendingPool.id === poolOverview.id);
|
|
18100
18602
|
if (!lendingPoolUserDetails)
|
|
18101
18603
|
continue;
|
|
@@ -18114,18 +18616,42 @@ class Portfolio {
|
|
|
18114
18616
|
}, depositDetails, fixedLoans: [] }));
|
|
18115
18617
|
continue;
|
|
18116
18618
|
}
|
|
18117
|
-
const
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
const
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
: (
|
|
18128
|
-
|
|
18619
|
+
const lastEpoch = previousEpoch.toNumber();
|
|
18620
|
+
const userHasLastEpochShares = Boolean((_c = lastEpochUserTrancheDetails
|
|
18621
|
+
.lendingPoolTrancheUserEpochSharesUpdates) === null || _c === void 0 ? void 0 : _c.length);
|
|
18622
|
+
const fixedTermDeposits = lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks;
|
|
18623
|
+
// The one per-epoch formula `getYieldHistory` runs too, with
|
|
18624
|
+
// the same share selection — so the last bar of a history
|
|
18625
|
+
// equals these figures.
|
|
18626
|
+
const lastEpochYield = epochTrancheYield({
|
|
18627
|
+
epochInterest: parseFloat((_f = (_e = (_d = lastEpochUserTrancheDetails.tranche
|
|
18628
|
+
.lendingPoolTrancheEpochInterest) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.epochInterestAmount) !== null && _f !== void 0 ? _f : '0'),
|
|
18629
|
+
userShares: parseFloat((_j = (_h = (_g = lastEpochUserTrancheDetails
|
|
18630
|
+
.lendingPoolTrancheUserEpochSharesUpdates) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.shares) !== null && _j !== void 0 ? _j : '0'),
|
|
18631
|
+
trancheShares: parseFloat((_m = (_l = (_k = lastEpochUserTrancheDetails.tranche
|
|
18632
|
+
.lendingPoolTrancheShareUpdates) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.shares) !== null && _m !== void 0 ? _m : '0'),
|
|
18633
|
+
trancheBalanceNow: parseFloat(lastEpochUserTrancheDetails.tranche.balance),
|
|
18634
|
+
trancheSharesNow: parseFloat(lastEpochUserTrancheDetails.tranche.shares),
|
|
18635
|
+
locks: fixedTermDeposits.map((fixedTermDeposit) => {
|
|
18636
|
+
var _a, _b, _c, _d, _e, _f;
|
|
18637
|
+
return ({
|
|
18638
|
+
active: parseFloat(fixedTermDeposit.epochLockStart) <
|
|
18639
|
+
lastEpoch &&
|
|
18640
|
+
parseFloat(fixedTermDeposit.epochLockEnd) >=
|
|
18641
|
+
lastEpoch &&
|
|
18642
|
+
userHasLastEpochShares,
|
|
18643
|
+
// No snapshot = the lock's opening shares, exactly
|
|
18644
|
+
// as `getYieldHistory` reads it: a lock made after
|
|
18645
|
+
// clearing E starts at E with its first update
|
|
18646
|
+
// tagged E + 1. Never the CURRENT shares, which
|
|
18647
|
+
// would carry a top-up from a clearing that ran
|
|
18648
|
+
// after the last settled epoch.
|
|
18649
|
+
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),
|
|
18650
|
+
sharesBeforeEpoch: parseFloat((_f = (_e = (_d = fixedTermDeposit
|
|
18651
|
+
.userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : fixedTermDeposit.initialTrancheShares),
|
|
18652
|
+
});
|
|
18653
|
+
}),
|
|
18654
|
+
});
|
|
18129
18655
|
const userTrancheBalance = {
|
|
18130
18656
|
balance: '0',
|
|
18131
18657
|
yieldEarned: 0,
|
|
@@ -18140,47 +18666,20 @@ class Portfolio {
|
|
|
18140
18666
|
}
|
|
18141
18667
|
}
|
|
18142
18668
|
let totalLifetimeFtdYield = 0;
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
var _a, _b, _c, _d, _e;
|
|
18669
|
+
const fixedLoans = fixedTermDeposits.map((fixedTermDeposit, lockIndex) => {
|
|
18670
|
+
var _a, _b;
|
|
18146
18671
|
const fixedLoanDepositDetails = (_a = userRequest === null || userRequest === void 0 ? void 0 : userRequest[fixedTermDeposit
|
|
18147
|
-
.lendingPoolTrancheFixedTermConfig
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
let lifetimeYieldEarned = 0;
|
|
18151
|
-
if (parseFloat(fixedTermDeposit.epochLockStart) <
|
|
18152
|
-
previousEpoch.toNumber() &&
|
|
18153
|
-
parseFloat(fixedTermDeposit.epochLockEnd) >=
|
|
18154
|
-
previousEpoch.toNumber() &&
|
|
18155
|
-
Boolean((_b = lastEpochUserTrancheDetails
|
|
18156
|
-
.lendingPoolTrancheUserEpochSharesUpdates) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
18157
|
-
const currentEpochFtdShares = parseFloat(fixedTermDeposit.trancheShares);
|
|
18158
|
-
const lastEpochFtdShares = parseFloat((_e = (_d = (_c = fixedTermDeposit
|
|
18159
|
-
.userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : '0');
|
|
18160
|
-
const additionalFtdYield = ((currentEpochFtdShares -
|
|
18161
|
-
lastEpochFtdShares) *
|
|
18162
|
-
currentTotalAmount) /
|
|
18163
|
-
currentTotalShares;
|
|
18164
|
-
const lastEpochFtdBaseYield = (lastEpochTotalBaseYield *
|
|
18165
|
-
lastEpochFtdShares) /
|
|
18166
|
-
lastEpochUserShares;
|
|
18167
|
-
lastEpochVariableBaseYield -=
|
|
18168
|
-
lastEpochFtdBaseYield;
|
|
18169
|
-
lastEpochYieldEarned =
|
|
18170
|
-
lastEpochFtdBaseYield + additionalFtdYield;
|
|
18171
|
-
}
|
|
18172
|
-
lifetimeYieldEarned =
|
|
18173
|
-
this.calculateFixedTermDepositEarnings(fixedTermDeposit, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares);
|
|
18672
|
+
.lendingPoolTrancheFixedTermConfig.configId]) !== null && _a !== void 0 ? _a : [];
|
|
18673
|
+
const lastEpochYieldEarned = (_b = lastEpochYield.locks[lockIndex]) !== null && _b !== void 0 ? _b : 0;
|
|
18674
|
+
const lifetimeYieldEarned = this.calculateFixedTermDepositEarnings(fixedTermDeposit, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares);
|
|
18174
18675
|
totalLifetimeFtdYield += lifetimeYieldEarned;
|
|
18175
|
-
totalYieldEarnedLastEpoch += lastEpochYieldEarned;
|
|
18176
18676
|
const lockDuration = parseFloat(fixedTermDeposit
|
|
18177
18677
|
.lendingPoolTrancheFixedTermConfig
|
|
18178
18678
|
.epochLockDuration) * SECONDS_PER_EPOCH;
|
|
18179
18679
|
return {
|
|
18180
18680
|
lockId: fixedTermDeposit.lockId,
|
|
18181
18681
|
configId: fixedTermDeposit
|
|
18182
|
-
.lendingPoolTrancheFixedTermConfig
|
|
18183
|
-
.configId,
|
|
18682
|
+
.lendingPoolTrancheFixedTermConfig.configId,
|
|
18184
18683
|
epochLockDuration: fixedTermDeposit
|
|
18185
18684
|
.lendingPoolTrancheFixedTermConfig
|
|
18186
18685
|
.epochLockDuration,
|
|
@@ -18190,9 +18689,7 @@ class Portfolio {
|
|
|
18190
18689
|
.epochInterestRate,
|
|
18191
18690
|
epochLockStart: fixedTermDeposit.epochLockStart,
|
|
18192
18691
|
amount: this._userLendingService
|
|
18193
|
-
.convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche
|
|
18194
|
-
.balance, lastEpochUserTrancheDetails.tranche
|
|
18195
|
-
.shares)
|
|
18692
|
+
.convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares)
|
|
18196
18693
|
.toString(),
|
|
18197
18694
|
endTime: parseInt(fixedTermDeposit.createdOn) +
|
|
18198
18695
|
lockDuration,
|
|
@@ -18200,20 +18697,22 @@ class Portfolio {
|
|
|
18200
18697
|
isWithdrawalRequested: fixedTermDeposit.isWithdrawalRequested,
|
|
18201
18698
|
startTime: parseInt(fixedTermDeposit.createdOn),
|
|
18202
18699
|
yieldEarnings: {
|
|
18203
|
-
lastEpoch:
|
|
18700
|
+
lastEpoch: this.precisionToString(lastEpochYieldEarned),
|
|
18204
18701
|
lifetime: this.precisionToString(lifetimeYieldEarned),
|
|
18205
18702
|
},
|
|
18206
18703
|
depositDetails: fixedLoanDepositDetails,
|
|
18207
18704
|
};
|
|
18208
18705
|
});
|
|
18209
|
-
|
|
18706
|
+
// This POOL's last-epoch total: the tranche totals summed, the
|
|
18707
|
+
// locks counted once (inside `total`).
|
|
18708
|
+
poolYieldEarnedLastEpoch += lastEpochYield.total;
|
|
18210
18709
|
tranches.push(Object.assign(Object.assign({}, tranche), { investedAmount: userTrancheBalance, yieldEarnings: {
|
|
18211
|
-
lastEpoch:
|
|
18710
|
+
lastEpoch: this.precisionToString(lastEpochYield.variable),
|
|
18212
18711
|
lifetime: this.precisionToString(totalLifetimeFtdYield), // passing in total FTD yield to be subtracted by total trache lifetime later down
|
|
18213
18712
|
}, fixedLoans,
|
|
18214
18713
|
depositDetails }));
|
|
18215
18714
|
}
|
|
18216
|
-
portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch:
|
|
18715
|
+
portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: this.precisionToString(poolYieldEarnedLastEpoch), totalInvestedAmount: userPoolBalance.balance.toString(), totalYieldEarningsLifetime: userPoolBalance.yieldEarned.toString(), tranches }));
|
|
18217
18716
|
}
|
|
18218
18717
|
const portfolioLendingPools = portfolioLendingPoolsPromise.map((pool) => (Object.assign(Object.assign({}, pool), { tranches: pool.tranches.map((tranche) => {
|
|
18219
18718
|
const trancheBalance = tranche.investedAmount;
|
|
@@ -18231,7 +18730,87 @@ class Portfolio {
|
|
|
18231
18730
|
!utils$2.parseUnits(pool.totalYieldEarningsLifetime).isZero());
|
|
18232
18731
|
});
|
|
18233
18732
|
}
|
|
18733
|
+
/**
|
|
18734
|
+
* The lender's settled yield per cycle (epoch), per pool and tranche —
|
|
18735
|
+
* what the chain credited at each close, NET of the performance fee
|
|
18736
|
+
* (`domain/yield-history.ts`), in the deployment's own stable token. Each
|
|
18737
|
+
* entry runs the same `epochTrancheYield` as `getPortfolioLendingData`'s
|
|
18738
|
+
* last-epoch figures: a tranche's `yieldEarnings.lastEpoch` plus its
|
|
18739
|
+
* locks' equals its entry for the epoch of the tranche's most recently
|
|
18740
|
+
* APPLIED interest (older than the last cycle for a pool that has not
|
|
18741
|
+
* cleared since; no entry, and a figure of 0, when the lender held
|
|
18742
|
+
* nothing at that epoch).
|
|
18743
|
+
*
|
|
18744
|
+
* Sum entries with `aggregateYieldHistory` (all strategies, or one with
|
|
18745
|
+
* `{ poolId }`). Only epochs in which the lender held shares appear — a
|
|
18746
|
+
* new lender gets `[]`. Pools in `UNUSED_LENDING_POOL_IDS` are left out,
|
|
18747
|
+
* as in `lendingPortfolioQuery`; Directus enablement is NOT applied.
|
|
18748
|
+
*
|
|
18749
|
+
* Cost: three parallel `SystemVariables` reads and the positions query in
|
|
18750
|
+
* one round, then one subgraph request per page of the longest series
|
|
18751
|
+
* (1000 rows a page) — normally two subgraph round trips in all, never one
|
|
18752
|
+
* per epoch.
|
|
18753
|
+
*
|
|
18754
|
+
* On a LITE deployment, a subgraph that lacks the entities (a validation
|
|
18755
|
+
* error naming them) yields `[]`; on a full deployment that error throws,
|
|
18756
|
+
* like every other failure (`isEmptyHistoryError`).
|
|
18757
|
+
*
|
|
18758
|
+
* @throws RangeError on a malformed `fromEpoch` / `count`, before any
|
|
18759
|
+
* network read.
|
|
18760
|
+
*/
|
|
18761
|
+
getYieldHistory(userAddress_1) {
|
|
18762
|
+
return __awaiter(this, arguments, void 0, function* (userAddress, options = {}) {
|
|
18763
|
+
validateYieldHistoryOptions(options);
|
|
18764
|
+
try {
|
|
18765
|
+
const [currentEpoch, epochZeroStart, epochDuration, positions] = yield Promise.all([
|
|
18766
|
+
this._systemVariablesAbi.currentEpochNumber(),
|
|
18767
|
+
this._systemVariablesAbi.epochStartTimestamp(0),
|
|
18768
|
+
this._systemVariablesAbi.epochDuration(),
|
|
18769
|
+
this._graph.request(yieldHistoryPositionsQuery, {
|
|
18770
|
+
userAddress: userAddress.toLowerCase(),
|
|
18771
|
+
unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
|
|
18772
|
+
first: YIELD_HISTORY_PAGE_SIZE,
|
|
18773
|
+
}),
|
|
18774
|
+
]);
|
|
18775
|
+
const window = resolveYieldHistoryWindow(options, currentEpoch.toNumber());
|
|
18776
|
+
if (!window || !positions.user)
|
|
18777
|
+
return [];
|
|
18778
|
+
const userTranches = [];
|
|
18779
|
+
const tranches = new Set();
|
|
18780
|
+
const locks = [];
|
|
18781
|
+
for (const pool of positions.user.lendingPoolUserDetails) {
|
|
18782
|
+
for (const position of pool.lendingPoolTrancheUserDetails) {
|
|
18783
|
+
userTranches.push(position.id);
|
|
18784
|
+
tranches.add(position.tranche.id);
|
|
18785
|
+
for (const lock of position.userLendingPoolTrancheFixedTermDepositLocks) {
|
|
18786
|
+
locks.push(lock.id);
|
|
18787
|
+
}
|
|
18788
|
+
}
|
|
18789
|
+
}
|
|
18790
|
+
if (userTranches.length === 0)
|
|
18791
|
+
return [];
|
|
18792
|
+
const { rows } = yield fetchYieldHistorySeries((variables) => this._graph.request(yieldHistoryPageQuery, variables), { tranches: [...tranches], userTranches, locks }, window);
|
|
18793
|
+
return buildYieldHistory(Object.assign({ positions }, rows), window, {
|
|
18794
|
+
epochZeroStart: epochZeroStart.toNumber(),
|
|
18795
|
+
epochDuration: epochDuration.toNumber(),
|
|
18796
|
+
});
|
|
18797
|
+
}
|
|
18798
|
+
catch (error) {
|
|
18799
|
+
if (isEmptyHistoryError(error, this._isLiteDeployment))
|
|
18800
|
+
return [];
|
|
18801
|
+
throw error;
|
|
18802
|
+
}
|
|
18803
|
+
});
|
|
18804
|
+
}
|
|
18805
|
+
/**
|
|
18806
|
+
* A money figure as a plain decimal string. Below a millionth of a token
|
|
18807
|
+
* (the stable asset's own precision) is float noise, never money, so it
|
|
18808
|
+
* is `'0'` — not `'1e-7'`, which `parseUnits` rejects, nor `'-3e-12'`. A
|
|
18809
|
+
* small negative remainder (above −0.0001) is `'0'` as well.
|
|
18810
|
+
*/
|
|
18234
18811
|
precisionToString(val) {
|
|
18812
|
+
if (Math.abs(val) < 1e-6)
|
|
18813
|
+
return '0';
|
|
18235
18814
|
if (val < 0 && val > -1e-4) {
|
|
18236
18815
|
return '0';
|
|
18237
18816
|
}
|
|
@@ -27545,6 +28124,82 @@ function maxNetRateCeiling(pools, feePercent) {
|
|
|
27545
28124
|
return rate;
|
|
27546
28125
|
}
|
|
27547
28126
|
|
|
28127
|
+
/**
|
|
28128
|
+
* A strategy's SHORT blurb, as plain text: the CMS `liteDescription` first,
|
|
28129
|
+
* else the first paragraph of the long `description`, else nothing.
|
|
28130
|
+
*
|
|
28131
|
+
* Lifted from kasu-ui (`pickShortDescription`, its deposit dialog and landing
|
|
28132
|
+
* strategy card) so every app shows the same line, and pinned to its output
|
|
28133
|
+
* for every live CMS row. The rules are facts about how the CMS is authored,
|
|
28134
|
+
* not formatting preferences:
|
|
28135
|
+
*
|
|
28136
|
+
* - `liteDescription` IS THE SHORT ONE. `description` is the credit
|
|
28137
|
+
* originator's several-paragraph profile; on a card it is a wall.
|
|
28138
|
+
* - `"Not Available"` IS A PLACEHOLDER, not editorial. Several rows carry it
|
|
28139
|
+
* verbatim, and it must read as missing, not print. Matched on the whole
|
|
28140
|
+
* trimmed value, case-insensitively. It applies to `liteDescription` only.
|
|
28141
|
+
* - EMPTY OR BLANK IS MISSING. The service maps a pool with no CMS record to
|
|
28142
|
+
* `''`.
|
|
28143
|
+
* - THE FIELDS HOLD MARKDOWN. `liteDescription` can open with a `**bold**`
|
|
28144
|
+
* lead, and the surfaces this feeds render plain text, so the markers are
|
|
28145
|
+
* stripped (bold, then italic, then inline code).
|
|
28146
|
+
* - AND HARD NEWLINES. Every run of whitespace becomes one space, so a
|
|
28147
|
+
* paragraph break inside the value is not a gap in a one-line slot.
|
|
28148
|
+
* - NOT SANITISED. "Plain text" means text to render AS text. HTML tags,
|
|
28149
|
+
* entities and link syntax (`<script>`, `<`, `[a](javascript:…)`) pass
|
|
28150
|
+
* through unchanged, and removing markers can join fragments into a tag
|
|
28151
|
+
* (`<scr**i**pt>` becomes `<script>`). Render it as a text node, never as
|
|
28152
|
+
* HTML or markdown.
|
|
28153
|
+
*
|
|
28154
|
+
* Two refinements over the kasu-ui original, neither reachable by any live
|
|
28155
|
+
* row: the cleaned text is trimmed (a CRLF value no longer ends in a space),
|
|
28156
|
+
* and a `liteDescription` that cleans to nothing (`** **`) counts as missing,
|
|
28157
|
+
* so the result is never a blank string.
|
|
28158
|
+
*
|
|
28159
|
+
* This authors no string of its own. It selects and cleans editorial the CMS
|
|
28160
|
+
* already holds, and an absent blurb is `undefined`, never a fallback sentence:
|
|
28161
|
+
* what "no blurb" means is the render site's decision.
|
|
28162
|
+
*
|
|
28163
|
+
* Pure. A value that is not a string reads as missing.
|
|
28164
|
+
*/
|
|
28165
|
+
function pickShortDescription(source) {
|
|
28166
|
+
var _a;
|
|
28167
|
+
const lite = asTrimmed(source.liteDescription);
|
|
28168
|
+
if (lite && !NOT_AVAILABLE.test(lite)) {
|
|
28169
|
+
const summary = toOneLine(lite);
|
|
28170
|
+
if (summary)
|
|
28171
|
+
return summary;
|
|
28172
|
+
}
|
|
28173
|
+
const long = asTrimmed(source.description);
|
|
28174
|
+
if (long) {
|
|
28175
|
+
const firstParagraph = (_a = long.split(PARAGRAPH_BREAK, 1)[0]) !== null && _a !== void 0 ? _a : long;
|
|
28176
|
+
return toOneLine(firstParagraph) || undefined;
|
|
28177
|
+
}
|
|
28178
|
+
return undefined;
|
|
28179
|
+
}
|
|
28180
|
+
const NOT_AVAILABLE = /^not available$/i;
|
|
28181
|
+
const PARAGRAPH_BREAK = /\n\s*\n/;
|
|
28182
|
+
function asTrimmed(value) {
|
|
28183
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
28184
|
+
}
|
|
28185
|
+
/** Markers off, whitespace runs to one space, ends trimmed. `''` when nothing is left. */
|
|
28186
|
+
function toOneLine(text) {
|
|
28187
|
+
return stripInlineMarkdown(text).replace(/\s+/gu, ' ').trim();
|
|
28188
|
+
}
|
|
28189
|
+
/**
|
|
28190
|
+
* Inline markdown markers off, in the order bold → italic → code. The bold
|
|
28191
|
+
* passes consume every `**` / `__` pair first, so the italic patterns can match
|
|
28192
|
+
* a lone `*` / `_` without lookarounds.
|
|
28193
|
+
*/
|
|
28194
|
+
function stripInlineMarkdown(input) {
|
|
28195
|
+
return input
|
|
28196
|
+
.replace(/\*\*(.+?)\*\*/g, '$1')
|
|
28197
|
+
.replace(/__(.+?)__/g, '$1')
|
|
28198
|
+
.replace(/\*(.+?)\*/g, '$1')
|
|
28199
|
+
.replace(/_(.+?)_/g, '$1')
|
|
28200
|
+
.replace(/`([^`]+)`/g, '$1');
|
|
28201
|
+
}
|
|
28202
|
+
|
|
27548
28203
|
/**
|
|
27549
28204
|
* High-level facade for browsing Kasu lending strategies (pools).
|
|
27550
28205
|
*
|
|
@@ -27731,6 +28386,7 @@ class StrategiesFacade {
|
|
|
27731
28386
|
subheading: nonEmptyOrUndefined(pool.subheading),
|
|
27732
28387
|
description: pool.description,
|
|
27733
28388
|
liteDescription: nonEmptyOrUndefined(pool.liteDescription),
|
|
28389
|
+
summary: pickShortDescription(pool),
|
|
27734
28390
|
isActive: pool.isActive,
|
|
27735
28391
|
apy: pool.apy,
|
|
27736
28392
|
tvl: pool.totalValueLocked,
|
|
@@ -28177,6 +28833,40 @@ class PortfolioFacade {
|
|
|
28177
28833
|
return { pools, summary };
|
|
28178
28834
|
});
|
|
28179
28835
|
}
|
|
28836
|
+
/**
|
|
28837
|
+
* The lender's settled yield per cycle, per pool and tranche: what each
|
|
28838
|
+
* close credited them, NET of the performance fee (the chain credits it
|
|
28839
|
+
* net), in display units of the deployment's own stable token (AUDD on
|
|
28840
|
+
* XDC AUDD). `epochEnd` is unix seconds. Net only — no gross is offered.
|
|
28841
|
+
*
|
|
28842
|
+
* Validates `options` before any read (`RangeError` on a malformed one).
|
|
28843
|
+
*
|
|
28844
|
+
* ```ts
|
|
28845
|
+
* const entries = await kasu.portfolio.getYieldHistory('0xUser...', { count: 52 });
|
|
28846
|
+
* const allStrategies = aggregateYieldHistory(entries);
|
|
28847
|
+
* const oneStrategy = aggregateYieldHistory(entries, { poolId });
|
|
28848
|
+
* ```
|
|
28849
|
+
*
|
|
28850
|
+
* `getPositions`' `totalYieldEarningsLastEpoch` for a pool equals that
|
|
28851
|
+
* pool's bar for the epoch of its most recently APPLIED interest — which
|
|
28852
|
+
* is older than the last cycle for a pool that has not cleared since (the
|
|
28853
|
+
* bar carries that older `epochId`). A lender who held nothing at that
|
|
28854
|
+
* epoch has no bar there and a figure of 0; their newest bar is from
|
|
28855
|
+
* when they last held shares.
|
|
28856
|
+
*
|
|
28857
|
+
* For a fixed-term lock holder the bars can sum to slightly more than the
|
|
28858
|
+
* lifetime interest (up to about 0.2 %), because each top-up is valued at
|
|
28859
|
+
* today's share price — show "earned to date" from the lifetime figure,
|
|
28860
|
+
* never from the sum of the bars.
|
|
28861
|
+
*
|
|
28862
|
+
* `[]` for a lender with no history, and on a LITE deployment whose
|
|
28863
|
+
* subgraph lacks the entities; on a full deployment that error throws.
|
|
28864
|
+
*/
|
|
28865
|
+
getYieldHistory(userAddress, options) {
|
|
28866
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28867
|
+
return yield this._portfolio.getYieldHistory(userAddress, options);
|
|
28868
|
+
});
|
|
28869
|
+
}
|
|
28180
28870
|
/**
|
|
28181
28871
|
* Fetch a user's full transaction history (deposits, withdrawals, cancellations).
|
|
28182
28872
|
*/
|
|
@@ -29142,6 +29832,7 @@ exports.StrategiesFacade = StrategiesFacade;
|
|
|
29142
29832
|
exports.UPPER_MEZZANINE = UPPER_MEZZANINE;
|
|
29143
29833
|
exports.WithdrawFlow = WithdrawFlow;
|
|
29144
29834
|
exports.accruedInterest = accruedInterest;
|
|
29835
|
+
exports.aggregateYieldHistory = aggregateYieldHistory;
|
|
29145
29836
|
exports.apyToEpochRate = apyToEpochRate;
|
|
29146
29837
|
exports.asContractType = asContractType;
|
|
29147
29838
|
exports.auMinimumRemaining = auMinimumRemaining;
|
|
@@ -29172,6 +29863,7 @@ exports.encodeRequestWithdrawalCall = encodeRequestWithdrawalCall;
|
|
|
29172
29863
|
exports.encodeStableTransfer = encodeStableTransfer;
|
|
29173
29864
|
exports.encodeWithdrawAtExpiryCall = encodeWithdrawAtExpiryCall;
|
|
29174
29865
|
exports.epochRateToApy = epochRateToApy;
|
|
29866
|
+
exports.epochTrancheYield = epochTrancheYield;
|
|
29175
29867
|
exports.explorerAddressUrl = explorerAddressUrl;
|
|
29176
29868
|
exports.explorerTxUrl = explorerTxUrl;
|
|
29177
29869
|
exports.extractRevertData = extractRevertData;
|
|
@@ -29203,6 +29895,7 @@ exports.parseMinorUnits = parseMinorUnits;
|
|
|
29203
29895
|
exports.parseTrancheBound = parseTrancheBound;
|
|
29204
29896
|
exports.pickDefaultTrancheId = pickDefaultTrancheId;
|
|
29205
29897
|
exports.pickHighestYieldTranche = pickHighestYieldTranche;
|
|
29898
|
+
exports.pickShortDescription = pickShortDescription;
|
|
29206
29899
|
exports.poolAllTranchesFull = poolAllTranchesFull;
|
|
29207
29900
|
exports.poolMaxApy = poolMaxApy;
|
|
29208
29901
|
exports.projectedAnnualEarnings = projectedAnnualEarnings;
|