@kasufinance/kasu-sdk 2.11.0 → 2.13.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 +37 -2
- package/dist/bundle.cjs.js +1516 -71
- package/dist/bundle.esm.js +1511 -73
- package/dist/domain/index.d.ts +6 -2
- package/dist/domain/index.js +2 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/pool-tvl.d.ts +62 -0
- package/dist/domain/pool-tvl.js +22 -0
- package/dist/domain/pool-tvl.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/chain-configs.js +24 -0
- package/dist/facade/chain-configs.js.map +1 -1
- package/dist/facade/kasu.d.ts +2 -0
- package/dist/facade/kasu.js +13 -5
- package/dist/facade/kasu.js.map +1 -1
- package/dist/facade/strategies.d.ts +20 -0
- package/dist/facade/strategies.js +22 -0
- package/dist/facade/strategies.js.map +1 -1
- package/dist/facade/types.d.ts +20 -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 +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kasu-sdk.d.ts +9 -1
- package/dist/kasu-sdk.js +2 -2
- package/dist/kasu-sdk.js.map +1 -1
- package/dist/sdk-config.d.ts +20 -0
- package/dist/sdk-config.js +3 -1
- package/dist/sdk-config.js.map +1 -1
- package/dist/services/DataService/data-service.d.ts +81 -1
- package/dist/services/DataService/data-service.js +211 -6
- package/dist/services/DataService/data-service.js.map +1 -1
- package/dist/services/DataService/tvl-history.d.ts +296 -0
- package/dist/services/DataService/tvl-history.js +558 -0
- package/dist/services/DataService/tvl-history.js.map +1 -0
- 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 +162 -0
- package/dist/services/Portfolio/yield-history.js +378 -0
- package/dist/services/Portfolio/yield-history.js.map +1 -0
- package/dist/services/shared.d.ts +16 -0
- package/dist/services/shared.js +30 -0
- package/dist/services/shared.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/index.ts +15 -10
- package/src/domain/pool-tvl.ts +66 -0
- package/src/domain/yield-history.ts +194 -0
- package/src/facade/chain-configs.ts +26 -0
- package/src/facade/kasu.ts +20 -2
- package/src/facade/strategies.ts +26 -0
- package/src/facade/types.ts +20 -0
- package/src/facade/user-portfolio.ts +40 -3
- package/src/index.ts +11 -0
- package/src/kasu-sdk.ts +19 -2
- package/src/sdk-config.ts +22 -0
- package/src/services/DataService/data-service.ts +296 -6
- package/src/services/DataService/tvl-history.ts +853 -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 +541 -0
- package/src/services/shared.ts +38 -1
package/dist/bundle.cjs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var utils$2 = require('ethers/lib/utils');
|
|
4
3
|
var ethers = require('ethers');
|
|
4
|
+
var utils$2 = require('ethers/lib/utils');
|
|
5
5
|
|
|
6
6
|
const defaultJsonSerializer = JSON;
|
|
7
7
|
|
|
@@ -15481,6 +15481,28 @@ class KasuPoolExternalTVLAbi__factory {
|
|
|
15481
15481
|
}
|
|
15482
15482
|
KasuPoolExternalTVLAbi__factory.abi = _abi;
|
|
15483
15483
|
|
|
15484
|
+
/**
|
|
15485
|
+
* A pool's TVL — the total lent into it by everyone — and the shape of its
|
|
15486
|
+
* history. Pure: the reads live in `services/DataService/tvl-history.ts`.
|
|
15487
|
+
*/
|
|
15488
|
+
/**
|
|
15489
|
+
* The ONE definition of a pool's TVL: what lenders have lent on chain
|
|
15490
|
+
* (`lendingPool.balance` in the subgraph) plus the off-chain amount the
|
|
15491
|
+
* pool's `ExternalTVL` contract records (`externalTVLOfPool`).
|
|
15492
|
+
*
|
|
15493
|
+
* Both inputs, and the result, are in DISPLAY units of the deployment's own
|
|
15494
|
+
* stable token (USDC on Base and XDC USDC, AUDD on XDC AUDD) — `'1.5'` is
|
|
15495
|
+
* one and a half tokens, not 1.5 minor units. Convert before formatting as
|
|
15496
|
+
* USD.
|
|
15497
|
+
*
|
|
15498
|
+
* `getPoolOverview` (its `totalValueLocked.total`) and `getPoolTvlHistory`
|
|
15499
|
+
* (every point) both call this, so the chart and the figure above it cannot
|
|
15500
|
+
* be computed two ways.
|
|
15501
|
+
*/
|
|
15502
|
+
function poolTvl(onChainBalance, offChainTvl) {
|
|
15503
|
+
return parseFloat(onChainBalance) + parseFloat(offChainTvl);
|
|
15504
|
+
}
|
|
15505
|
+
|
|
15484
15506
|
/**
|
|
15485
15507
|
* Epochs in a year — the single source of truth for every rate conversion in
|
|
15486
15508
|
* the SDK and in every consumer that undoes one.
|
|
@@ -15720,6 +15742,36 @@ function filterArray(array, id_in) {
|
|
|
15720
15742
|
});
|
|
15721
15743
|
}
|
|
15722
15744
|
}
|
|
15745
|
+
/**
|
|
15746
|
+
* A GraphQL VALIDATION error naming an entity, field or argument this
|
|
15747
|
+
* deployment's subgraph does not have. A network error, a timeout or an
|
|
15748
|
+
* indexing error is not one.
|
|
15749
|
+
*/
|
|
15750
|
+
function isMissingEntityError(error) {
|
|
15751
|
+
if (typeof error !== 'object' || error === null)
|
|
15752
|
+
return false;
|
|
15753
|
+
const response = error.response;
|
|
15754
|
+
const errors = response === null || response === void 0 ? void 0 : response.errors;
|
|
15755
|
+
if (!Array.isArray(errors) || errors.length === 0)
|
|
15756
|
+
return false;
|
|
15757
|
+
return errors.every((e) => {
|
|
15758
|
+
const message = e === null || e === void 0 ? void 0 : e.message;
|
|
15759
|
+
return (typeof message === 'string' &&
|
|
15760
|
+
/has no field|cannot query field|unknown (type|field|argument)|no such (type|field)/i.test(message));
|
|
15761
|
+
});
|
|
15762
|
+
}
|
|
15763
|
+
/**
|
|
15764
|
+
* The only failure a history read (`getYieldHistory`, `getPoolTvlHistory`)
|
|
15765
|
+
* turns into `[]`: a missing-schema error
|
|
15766
|
+
* on a LITE deployment, whose subgraph may predate the entities. On a full
|
|
15767
|
+
* deployment the entities are part of the schema, so the same error means a
|
|
15768
|
+
* broken query or subgraph and THROWS — an empty chart there would hide it.
|
|
15769
|
+
* Everything else (network, timeout, indexing) throws on every deployment, so
|
|
15770
|
+
* a consumer can tell "no history here" from "history unavailable".
|
|
15771
|
+
*/
|
|
15772
|
+
function isEmptyHistoryError(error, liteDeployment) {
|
|
15773
|
+
return liteDeployment && isMissingEntityError(error);
|
|
15774
|
+
}
|
|
15723
15775
|
|
|
15724
15776
|
/**
|
|
15725
15777
|
* The error a CMS-only call raises on a deployment configured without
|
|
@@ -15909,11 +15961,569 @@ const getLendingPoolWithdrawalAndDepositsQuery = gql `
|
|
|
15909
15961
|
}
|
|
15910
15962
|
`;
|
|
15911
15963
|
|
|
15964
|
+
/** Clearings per subgraph page (the subgraph's own row cap). */
|
|
15965
|
+
const TVL_HISTORY_PAGE_SIZE = 1000;
|
|
15966
|
+
/**
|
|
15967
|
+
* A runaway guard on the clearings pagination, not a working limit: a pool
|
|
15968
|
+
* clears once per epoch, so 20 pages would be twenty thousand cycles.
|
|
15969
|
+
*/
|
|
15970
|
+
const TVL_HISTORY_MAX_PAGES = 20;
|
|
15971
|
+
/**
|
|
15972
|
+
* How many past blocks one time-travel request reads, as GraphQL aliases. A
|
|
15973
|
+
* pool with ~120 clearings costs two such requests.
|
|
15974
|
+
*/
|
|
15975
|
+
const TVL_HISTORY_BLOCKS_PER_REQUEST = 100;
|
|
15976
|
+
/** At most this many past-state RPC reads are in flight at once. */
|
|
15977
|
+
const TVL_HISTORY_RPC_CONCURRENCY = 8;
|
|
15978
|
+
/** Attempts per past-state RPC read (each over every history RPC in turn). */
|
|
15979
|
+
const TVL_HISTORY_RPC_ATTEMPTS = 3;
|
|
15980
|
+
/** Back-off before the second attempt; doubled for each one after. */
|
|
15981
|
+
const TVL_HISTORY_RPC_RETRY_DELAY_MS = 500;
|
|
15982
|
+
/** Per-call timeout of the history providers the SDK builds itself. */
|
|
15983
|
+
const TVL_HISTORY_RPC_TIMEOUT_MS = 10000;
|
|
15984
|
+
/** Default `timeoutMs` of a whole `getPoolTvlHistory` call. */
|
|
15985
|
+
const TVL_HISTORY_DEFAULT_TIMEOUT_MS = 30000;
|
|
15986
|
+
/**
|
|
15987
|
+
* Blocks at least this far below the subgraph's head are treated as final
|
|
15988
|
+
* and their reads are cached; nearer ones are read again every time.
|
|
15989
|
+
*/
|
|
15990
|
+
const TVL_HISTORY_CACHE_DEPTH = 64;
|
|
15991
|
+
/** Entries per SDK instance before the cache starts again from empty. */
|
|
15992
|
+
const TVL_HISTORY_CACHE_LIMIT = 20000;
|
|
15993
|
+
/** `getPoolTvlHistory` took longer than its `timeoutMs`. */
|
|
15994
|
+
class PoolTvlHistoryTimeoutError extends Error {
|
|
15995
|
+
constructor(timeoutMs) {
|
|
15996
|
+
super(`getPoolTvlHistory: timed out after ${String(timeoutMs)} ms`);
|
|
15997
|
+
this.timeoutMs = timeoutMs;
|
|
15998
|
+
this.name = 'PoolTvlHistoryTimeoutError';
|
|
15999
|
+
}
|
|
16000
|
+
}
|
|
16001
|
+
/** `getPoolTvlHistory` was abandoned through its `signal`. */
|
|
16002
|
+
class PoolTvlHistoryAbortedError extends Error {
|
|
16003
|
+
constructor() {
|
|
16004
|
+
super('getPoolTvlHistory: aborted');
|
|
16005
|
+
this.name = 'PoolTvlHistoryAbortedError';
|
|
16006
|
+
}
|
|
16007
|
+
}
|
|
16008
|
+
/**
|
|
16009
|
+
* A history provider answered a past block as if nothing were deployed — no
|
|
16010
|
+
* code even for the pool, which cleared in that very block. A pruned node
|
|
16011
|
+
* does that; trusting it would draw the off-chain TVL as 0.
|
|
16012
|
+
*/
|
|
16013
|
+
class PastStateUnavailableError extends Error {
|
|
16014
|
+
constructor(block) {
|
|
16015
|
+
super(`getPoolTvlHistory: history provider does not serve past state (no code for the pool at block ${String(block)}, where it cleared)`);
|
|
16016
|
+
this.block = block;
|
|
16017
|
+
this.name = 'PastStateUnavailableError';
|
|
16018
|
+
}
|
|
16019
|
+
}
|
|
16020
|
+
/**
|
|
16021
|
+
* Throws `RangeError` / `TypeError` on a malformed option.
|
|
16022
|
+
* `getPoolTvlHistory` calls it ONCE, BEFORE any network read;
|
|
16023
|
+
* `resolvePoolTvlHistoryWindow` assumes it has run.
|
|
16024
|
+
*/
|
|
16025
|
+
function validatePoolTvlHistoryOptions(options) {
|
|
16026
|
+
const { fromEpoch, count, timeoutMs, signal } = options;
|
|
16027
|
+
if (fromEpoch !== undefined &&
|
|
16028
|
+
(!Number.isSafeInteger(fromEpoch) || fromEpoch < 0)) {
|
|
16029
|
+
throw new RangeError(`getPoolTvlHistory: fromEpoch must be a non-negative integer, got ${String(fromEpoch)}`);
|
|
16030
|
+
}
|
|
16031
|
+
if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) {
|
|
16032
|
+
throw new RangeError(`getPoolTvlHistory: count must be a positive integer, got ${String(count)}`);
|
|
16033
|
+
}
|
|
16034
|
+
if (timeoutMs !== undefined &&
|
|
16035
|
+
(typeof timeoutMs !== 'number' ||
|
|
16036
|
+
!Number.isFinite(timeoutMs) ||
|
|
16037
|
+
timeoutMs <= 0)) {
|
|
16038
|
+
throw new RangeError(`getPoolTvlHistory: timeoutMs must be a positive number, got ${String(timeoutMs)}`);
|
|
16039
|
+
}
|
|
16040
|
+
if (signal !== undefined &&
|
|
16041
|
+
(typeof signal !== 'object' ||
|
|
16042
|
+
typeof signal.aborted !== 'boolean')) {
|
|
16043
|
+
throw new TypeError('getPoolTvlHistory: signal must be an AbortSignal');
|
|
16044
|
+
}
|
|
16045
|
+
}
|
|
16046
|
+
/** The epoch window. Options must already have passed validation. */
|
|
16047
|
+
function resolvePoolTvlHistoryWindow(options, currentEpoch) {
|
|
16048
|
+
const { fromEpoch, count } = options;
|
|
16049
|
+
let from;
|
|
16050
|
+
let to = currentEpoch;
|
|
16051
|
+
if (fromEpoch !== undefined) {
|
|
16052
|
+
from = fromEpoch;
|
|
16053
|
+
if (count !== undefined) {
|
|
16054
|
+
to = Math.min(currentEpoch, fromEpoch + count - 1);
|
|
16055
|
+
}
|
|
16056
|
+
}
|
|
16057
|
+
else {
|
|
16058
|
+
from = count !== undefined ? Math.max(0, currentEpoch - count + 1) : 0;
|
|
16059
|
+
}
|
|
16060
|
+
return from > to ? null : { fromEpoch: from, toEpoch: to };
|
|
16061
|
+
}
|
|
16062
|
+
/**
|
|
16063
|
+
* Runs `work` under a deadline and an optional caller signal. Whichever comes
|
|
16064
|
+
* first rejects the returned promise with its typed error at once, aborts
|
|
16065
|
+
* `cancellation.signal` (cancelling in-flight subgraph requests) and makes
|
|
16066
|
+
* every later `throwIfCancelled()` throw, so no new read starts. An RPC call
|
|
16067
|
+
* already sent cannot be recalled; its answer is dropped.
|
|
16068
|
+
*/
|
|
16069
|
+
function runCancellable(timeoutMs, external, work) {
|
|
16070
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16071
|
+
if (external === null || external === void 0 ? void 0 : external.aborted)
|
|
16072
|
+
throw new PoolTvlHistoryAbortedError();
|
|
16073
|
+
const controller = new AbortController();
|
|
16074
|
+
let reason;
|
|
16075
|
+
let rejectCancelled = () => undefined;
|
|
16076
|
+
const cancelled = new Promise((_, reject) => {
|
|
16077
|
+
rejectCancelled = reject;
|
|
16078
|
+
});
|
|
16079
|
+
const cancel = (error) => {
|
|
16080
|
+
if (reason)
|
|
16081
|
+
return;
|
|
16082
|
+
reason = error;
|
|
16083
|
+
controller.abort();
|
|
16084
|
+
rejectCancelled(error);
|
|
16085
|
+
};
|
|
16086
|
+
const timer = setTimeout(() => {
|
|
16087
|
+
cancel(new PoolTvlHistoryTimeoutError(timeoutMs));
|
|
16088
|
+
}, timeoutMs);
|
|
16089
|
+
const onAbort = () => {
|
|
16090
|
+
cancel(new PoolTvlHistoryAbortedError());
|
|
16091
|
+
};
|
|
16092
|
+
external === null || external === void 0 ? void 0 : external.addEventListener('abort', onAbort);
|
|
16093
|
+
const cancellation = {
|
|
16094
|
+
signal: controller.signal,
|
|
16095
|
+
throwIfCancelled: () => {
|
|
16096
|
+
if (reason)
|
|
16097
|
+
throw reason;
|
|
16098
|
+
},
|
|
16099
|
+
};
|
|
16100
|
+
try {
|
|
16101
|
+
const running = work(cancellation);
|
|
16102
|
+
// The losing side must never surface as an unhandled rejection.
|
|
16103
|
+
running.catch(() => undefined);
|
|
16104
|
+
return yield Promise.race([running, cancelled]);
|
|
16105
|
+
}
|
|
16106
|
+
finally {
|
|
16107
|
+
clearTimeout(timer);
|
|
16108
|
+
external === null || external === void 0 ? void 0 : external.removeEventListener('abort', onAbort);
|
|
16109
|
+
}
|
|
16110
|
+
});
|
|
16111
|
+
}
|
|
16112
|
+
// ---------------------------------------------------------------------------
|
|
16113
|
+
// Subgraph reads
|
|
16114
|
+
// ---------------------------------------------------------------------------
|
|
16115
|
+
/** Above any epoch number: "no upper bound" for the clearings query. */
|
|
16116
|
+
const NO_EPOCH_BOUND = '1000000000000';
|
|
16117
|
+
/**
|
|
16118
|
+
* The pool's FINISHED clearings, plus — on the first page only — its balance
|
|
16119
|
+
* now and the subgraph's latest block.
|
|
16120
|
+
*
|
|
16121
|
+
* Two modes over one document: ascending from `$epochGte` (a `fromEpoch`
|
|
16122
|
+
* window, or everything), or descending below `$epochLt` (the newest N, for
|
|
16123
|
+
* a `count`-only window, so it never pages through the whole history).
|
|
16124
|
+
*/
|
|
16125
|
+
const poolTvlClearingsQuery = gql `
|
|
16126
|
+
query PoolTvlClearings(
|
|
16127
|
+
$poolId: ID!
|
|
16128
|
+
$pool: String!
|
|
16129
|
+
$epochGte: BigInt!
|
|
16130
|
+
$epochLt: BigInt!
|
|
16131
|
+
$direction: OrderDirection!
|
|
16132
|
+
$first: Int!
|
|
16133
|
+
$withSnapshot: Boolean!
|
|
16134
|
+
) {
|
|
16135
|
+
clearings: lendingPoolClearings(
|
|
16136
|
+
first: $first
|
|
16137
|
+
orderBy: epochId
|
|
16138
|
+
orderDirection: $direction
|
|
16139
|
+
where: {
|
|
16140
|
+
lendingPool: $pool
|
|
16141
|
+
isFinished: true
|
|
16142
|
+
epochId_gte: $epochGte
|
|
16143
|
+
epochId_lt: $epochLt
|
|
16144
|
+
}
|
|
16145
|
+
) {
|
|
16146
|
+
epochId
|
|
16147
|
+
endBlockNumber
|
|
16148
|
+
endTimestamp
|
|
16149
|
+
}
|
|
16150
|
+
pool: lendingPool(id: $poolId) @include(if: $withSnapshot) {
|
|
16151
|
+
balance
|
|
16152
|
+
}
|
|
16153
|
+
meta: _meta @include(if: $withSnapshot) {
|
|
16154
|
+
block {
|
|
16155
|
+
number
|
|
16156
|
+
timestamp
|
|
16157
|
+
}
|
|
16158
|
+
}
|
|
16159
|
+
}
|
|
16160
|
+
`;
|
|
16161
|
+
/** The time-travel alias for one block: `b<block>`. */
|
|
16162
|
+
function blockAlias(block) {
|
|
16163
|
+
return `b${String(block)}`;
|
|
16164
|
+
}
|
|
16165
|
+
/**
|
|
16166
|
+
* One time-travel request: the pool's balance at the END of each block, one
|
|
16167
|
+
* alias per block. Block numbers are written into the document, so each must
|
|
16168
|
+
* be a positive safe integer — anything else throws rather than reaching the
|
|
16169
|
+
* query text. The pool id travels as a variable.
|
|
16170
|
+
*/
|
|
16171
|
+
function poolBalancesAtBlocksQuery(blocks) {
|
|
16172
|
+
const fields = blocks.map((block) => {
|
|
16173
|
+
if (!Number.isSafeInteger(block) || block <= 0) {
|
|
16174
|
+
throw new Error(`getPoolTvlHistory: refusing block number ${String(block)}`);
|
|
16175
|
+
}
|
|
16176
|
+
return `${blockAlias(block)}: lendingPool(id: $poolId, block: { number: ${String(block)} }) { balance }`;
|
|
16177
|
+
});
|
|
16178
|
+
return `query PoolTvlAtBlocks($poolId: ID!) { ${fields.join(' ')} }`;
|
|
16179
|
+
}
|
|
16180
|
+
function parseClearing(row) {
|
|
16181
|
+
const clearing = {
|
|
16182
|
+
epochId: Number(row.epochId),
|
|
16183
|
+
block: Number(row.endBlockNumber),
|
|
16184
|
+
timestamp: Number(row.endTimestamp),
|
|
16185
|
+
};
|
|
16186
|
+
if (!Number.isSafeInteger(clearing.epochId) ||
|
|
16187
|
+
clearing.epochId < 0 ||
|
|
16188
|
+
!Number.isSafeInteger(clearing.block) ||
|
|
16189
|
+
clearing.block <= 0 ||
|
|
16190
|
+
!Number.isSafeInteger(clearing.timestamp) ||
|
|
16191
|
+
clearing.timestamp <= 0) {
|
|
16192
|
+
throw new Error(`getPoolTvlHistory: malformed clearing row ${JSON.stringify(row)}`);
|
|
16193
|
+
}
|
|
16194
|
+
return clearing;
|
|
16195
|
+
}
|
|
16196
|
+
/**
|
|
16197
|
+
* The scope's finished clearings, ascending by epoch, and the snapshot from
|
|
16198
|
+
* the first page. Normally ONE request.
|
|
16199
|
+
*/
|
|
16200
|
+
function fetchPoolClearings(request, poolId, scope, cancellation) {
|
|
16201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16202
|
+
const clearings = [];
|
|
16203
|
+
let snapshot = {};
|
|
16204
|
+
let gte = scope.kind === 'from' ? scope.fromEpoch : 0;
|
|
16205
|
+
let lt;
|
|
16206
|
+
let requests = 0;
|
|
16207
|
+
for (;;) {
|
|
16208
|
+
cancellation.throwIfCancelled();
|
|
16209
|
+
if (requests >= TVL_HISTORY_MAX_PAGES) {
|
|
16210
|
+
throw new Error(`getPoolTvlHistory: more than ${String(TVL_HISTORY_MAX_PAGES)} pages of clearings for one pool`);
|
|
16211
|
+
}
|
|
16212
|
+
const first = requests === 0;
|
|
16213
|
+
const remaining = scope.kind === 'newest'
|
|
16214
|
+
? scope.count - clearings.length
|
|
16215
|
+
: TVL_HISTORY_PAGE_SIZE;
|
|
16216
|
+
const pageSize = Math.min(TVL_HISTORY_PAGE_SIZE, remaining);
|
|
16217
|
+
const page = yield request(poolTvlClearingsQuery, {
|
|
16218
|
+
poolId,
|
|
16219
|
+
pool: poolId,
|
|
16220
|
+
epochGte: String(gte),
|
|
16221
|
+
epochLt: lt === undefined ? NO_EPOCH_BOUND : String(lt),
|
|
16222
|
+
direction: scope.kind === 'newest' ? 'desc' : 'asc',
|
|
16223
|
+
first: pageSize,
|
|
16224
|
+
withSnapshot: first,
|
|
16225
|
+
}, cancellation.signal);
|
|
16226
|
+
requests += 1;
|
|
16227
|
+
if (first)
|
|
16228
|
+
snapshot = { pool: page.pool, meta: page.meta };
|
|
16229
|
+
const rows = page.clearings.map(parseClearing);
|
|
16230
|
+
clearings.push(...rows);
|
|
16231
|
+
const last = rows.at(-1);
|
|
16232
|
+
if (rows.length < pageSize || last === undefined)
|
|
16233
|
+
break;
|
|
16234
|
+
if (scope.kind === 'newest') {
|
|
16235
|
+
if (clearings.length >= scope.count)
|
|
16236
|
+
break;
|
|
16237
|
+
if (lt !== undefined && last.epochId >= lt) {
|
|
16238
|
+
throw new Error('getPoolTvlHistory: the clearings cursor did not advance');
|
|
16239
|
+
}
|
|
16240
|
+
lt = last.epochId;
|
|
16241
|
+
}
|
|
16242
|
+
else {
|
|
16243
|
+
if (last.epochId < gte) {
|
|
16244
|
+
throw new Error('getPoolTvlHistory: the clearings cursor did not advance');
|
|
16245
|
+
}
|
|
16246
|
+
gte = last.epochId + 1;
|
|
16247
|
+
}
|
|
16248
|
+
}
|
|
16249
|
+
clearings.sort((a, b) => a.epochId - b.epochId);
|
|
16250
|
+
return { clearings, snapshot, requests };
|
|
16251
|
+
});
|
|
16252
|
+
}
|
|
16253
|
+
/**
|
|
16254
|
+
* The pool's balance (display units, the subgraph's decimal string) at the end
|
|
16255
|
+
* of each block, `TVL_HISTORY_BLOCKS_PER_REQUEST` blocks per request. A block
|
|
16256
|
+
* at which the subgraph has no such pool THROWS: a finished clearing of the
|
|
16257
|
+
* pool happened in it, so a missing pool means a broken read, not a zero.
|
|
16258
|
+
*/
|
|
16259
|
+
function fetchPoolBalancesAtBlocks(request, poolId, blocks, cancellation) {
|
|
16260
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16261
|
+
const chunks = [];
|
|
16262
|
+
for (let i = 0; i < blocks.length; i += TVL_HISTORY_BLOCKS_PER_REQUEST) {
|
|
16263
|
+
chunks.push(blocks.slice(i, i + TVL_HISTORY_BLOCKS_PER_REQUEST));
|
|
16264
|
+
}
|
|
16265
|
+
cancellation.throwIfCancelled();
|
|
16266
|
+
const pages = yield Promise.all(chunks.map((chunk) => request(poolBalancesAtBlocksQuery(chunk), { poolId }, cancellation.signal).then((page) => ({ chunk, page }))));
|
|
16267
|
+
const balances = new Map();
|
|
16268
|
+
for (const { chunk, page } of pages) {
|
|
16269
|
+
for (const block of chunk) {
|
|
16270
|
+
const state = page[blockAlias(block)];
|
|
16271
|
+
if (!state) {
|
|
16272
|
+
throw new Error(`getPoolTvlHistory: the subgraph has no state for the pool at block ${String(block)}`);
|
|
16273
|
+
}
|
|
16274
|
+
balances.set(block, state.balance);
|
|
16275
|
+
}
|
|
16276
|
+
}
|
|
16277
|
+
return { balances, requests: chunks.length };
|
|
16278
|
+
});
|
|
16279
|
+
}
|
|
16280
|
+
const EXTERNAL_TVL_INTERFACE = new utils$2.Interface(KasuPoolExternalTVLAbi__factory.abi);
|
|
16281
|
+
function hasCode(code) {
|
|
16282
|
+
if (typeof code !== 'string' || !/^0x[0-9a-fA-F]*$/.test(code)) {
|
|
16283
|
+
throw new Error(`getPoolTvlHistory: getCode returned ${String(code)}, not hex`);
|
|
16284
|
+
}
|
|
16285
|
+
return code !== '0x';
|
|
16286
|
+
}
|
|
16287
|
+
/**
|
|
16288
|
+
* The real reader: each read goes to the history RPCs IN ORDER until one
|
|
16289
|
+
* answers, and the whole chain is retried `attempts` times with back-off.
|
|
16290
|
+
* After the last attempt the last error propagates — never a value. Each
|
|
16291
|
+
* attempt first checks the cancellation, so a timed-out call stops here.
|
|
16292
|
+
*/
|
|
16293
|
+
function createOffChainTvlReader({ externalTvl, pool, rpcs, cancellation, attempts = TVL_HISTORY_RPC_ATTEMPTS, delayMs = TVL_HISTORY_RPC_RETRY_DELAY_MS, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), }) {
|
|
16294
|
+
if (rpcs.length === 0) {
|
|
16295
|
+
throw new Error('getPoolTvlHistory: no history RPC to read from');
|
|
16296
|
+
}
|
|
16297
|
+
const data = EXTERNAL_TVL_INTERFACE.encodeFunctionData('externalTVLOfPool', [pool]);
|
|
16298
|
+
const read = (op) => __awaiter(this, void 0, void 0, function* () {
|
|
16299
|
+
let lastError;
|
|
16300
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
16301
|
+
for (const rpc of rpcs) {
|
|
16302
|
+
cancellation.throwIfCancelled();
|
|
16303
|
+
try {
|
|
16304
|
+
return yield op(rpc);
|
|
16305
|
+
}
|
|
16306
|
+
catch (error) {
|
|
16307
|
+
lastError = error;
|
|
16308
|
+
}
|
|
16309
|
+
}
|
|
16310
|
+
if (attempt < attempts) {
|
|
16311
|
+
yield sleep(delayMs * Math.pow(2, (attempt - 1)));
|
|
16312
|
+
}
|
|
16313
|
+
}
|
|
16314
|
+
cancellation.throwIfCancelled();
|
|
16315
|
+
throw lastError;
|
|
16316
|
+
});
|
|
16317
|
+
return {
|
|
16318
|
+
readAt: (block) => read((rpc) => __awaiter(this, void 0, void 0, function* () {
|
|
16319
|
+
const result = yield rpc.call({ to: externalTvl, data }, block);
|
|
16320
|
+
const [value] = EXTERNAL_TVL_INTERFACE.decodeFunctionResult('externalTVLOfPool', result);
|
|
16321
|
+
return value;
|
|
16322
|
+
})),
|
|
16323
|
+
hasCodeAt: (block) => read((rpc) => __awaiter(this, void 0, void 0, function* () { return hasCode(yield rpc.getCode(externalTvl, block)); })),
|
|
16324
|
+
poolHasCodeAt: (block) => read((rpc) => __awaiter(this, void 0, void 0, function* () { return hasCode(yield rpc.getCode(pool, block)); })),
|
|
16325
|
+
};
|
|
16326
|
+
}
|
|
16327
|
+
/**
|
|
16328
|
+
* Index of the first block (of `blocks`, ascending) at which the contract has
|
|
16329
|
+
* code; `blocks.length` when it has code at none. A deployed contract keeps
|
|
16330
|
+
* its code (it is a proxy, never self-destructed), so the answer is a single
|
|
16331
|
+
* boundary and a binary search finds it: ONE `getCode` when the contract
|
|
16332
|
+
* already existed at the oldest clearing, about log2(n) otherwise.
|
|
16333
|
+
*/
|
|
16334
|
+
function firstBlockWithCode(blocks, hasCodeAt) {
|
|
16335
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16336
|
+
if (blocks.length === 0)
|
|
16337
|
+
return 0;
|
|
16338
|
+
if (yield hasCodeAt(blocks[0]))
|
|
16339
|
+
return 0;
|
|
16340
|
+
const last = blocks.length - 1;
|
|
16341
|
+
if (!(yield hasCodeAt(blocks[last])))
|
|
16342
|
+
return blocks.length;
|
|
16343
|
+
let lo = 0; // no code
|
|
16344
|
+
let hi = last; // code
|
|
16345
|
+
while (hi - lo > 1) {
|
|
16346
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
16347
|
+
if (yield hasCodeAt(blocks[mid]))
|
|
16348
|
+
hi = mid;
|
|
16349
|
+
else
|
|
16350
|
+
lo = mid;
|
|
16351
|
+
}
|
|
16352
|
+
return hi;
|
|
16353
|
+
});
|
|
16354
|
+
}
|
|
16355
|
+
/**
|
|
16356
|
+
* `fn` over `items`, at most `limit` in flight; rejects on the first failure
|
|
16357
|
+
* and starts nothing after it (or after `beforeEach` throws).
|
|
16358
|
+
*/
|
|
16359
|
+
function mapWithConcurrency(items_1, limit_1, fn_1) {
|
|
16360
|
+
return __awaiter(this, arguments, void 0, function* (items, limit, fn, beforeEach = () => undefined) {
|
|
16361
|
+
const results = new Array(items.length);
|
|
16362
|
+
let next = 0;
|
|
16363
|
+
let failed = false;
|
|
16364
|
+
const worker = () => __awaiter(this, void 0, void 0, function* () {
|
|
16365
|
+
while (!failed && next < items.length) {
|
|
16366
|
+
const index = next++;
|
|
16367
|
+
try {
|
|
16368
|
+
beforeEach();
|
|
16369
|
+
results[index] = yield fn(items[index]);
|
|
16370
|
+
}
|
|
16371
|
+
catch (error) {
|
|
16372
|
+
failed = true;
|
|
16373
|
+
throw error;
|
|
16374
|
+
}
|
|
16375
|
+
}
|
|
16376
|
+
});
|
|
16377
|
+
yield Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
16378
|
+
return results;
|
|
16379
|
+
});
|
|
16380
|
+
}
|
|
16381
|
+
/**
|
|
16382
|
+
* Immutable past-block reads, per SDK instance. Keys carry the contract (or
|
|
16383
|
+
* subgraph), the pool and the block, so two deployments never collide.
|
|
16384
|
+
*/
|
|
16385
|
+
class PastBlockCache {
|
|
16386
|
+
constructor() {
|
|
16387
|
+
this._entries = new Map();
|
|
16388
|
+
}
|
|
16389
|
+
get(key) {
|
|
16390
|
+
return this._entries.get(key);
|
|
16391
|
+
}
|
|
16392
|
+
set(key, value) {
|
|
16393
|
+
if (this._entries.size >= TVL_HISTORY_CACHE_LIMIT) {
|
|
16394
|
+
this._entries.clear();
|
|
16395
|
+
}
|
|
16396
|
+
this._entries.set(key, value);
|
|
16397
|
+
}
|
|
16398
|
+
get size() {
|
|
16399
|
+
return this._entries.size;
|
|
16400
|
+
}
|
|
16401
|
+
}
|
|
16402
|
+
/**
|
|
16403
|
+
* The off-chain TVL (BASE units) at each block, and — with `withLatest` — at
|
|
16404
|
+
* the latest block, all inside ONE bounded pool of reads.
|
|
16405
|
+
*
|
|
16406
|
+
* Before the contract existed there was nothing to record, so those blocks
|
|
16407
|
+
* are 0 — decided by `getCode`, never by catching a revert: an RPC that
|
|
16408
|
+
* refuses past state can surface that refusal as a "call exception". And a
|
|
16409
|
+
* "no code" answer is believed only if the same provider DOES show code for
|
|
16410
|
+
* the pool at the oldest and newest such block (it cleared there); otherwise
|
|
16411
|
+
* `PastStateUnavailableError`. Every other failed read throws.
|
|
16412
|
+
*
|
|
16413
|
+
* `cached` blocks (final, below the head) are answered from `cache` and
|
|
16414
|
+
* stored there after a successful read.
|
|
16415
|
+
*/
|
|
16416
|
+
function fetchOffChainTvlAtBlocks(reader_1, blocks_1, _a) {
|
|
16417
|
+
return __awaiter(this, arguments, void 0, function* (reader, blocks, { withLatest = false, cancellation, cache, cacheKey, cacheable = () => false, }) {
|
|
16418
|
+
let rpcReads = 0;
|
|
16419
|
+
const cached = (kind, block, load) => {
|
|
16420
|
+
const key = cache && cacheKey && cacheable(block)
|
|
16421
|
+
? cacheKey(kind, block)
|
|
16422
|
+
: undefined;
|
|
16423
|
+
const hit = key === undefined ? undefined : cache === null || cache === void 0 ? void 0 : cache.get(key);
|
|
16424
|
+
if (hit !== undefined)
|
|
16425
|
+
return Promise.resolve(hit);
|
|
16426
|
+
cancellation.throwIfCancelled();
|
|
16427
|
+
rpcReads += 1;
|
|
16428
|
+
return load().then((value) => {
|
|
16429
|
+
if (key !== undefined)
|
|
16430
|
+
cache === null || cache === void 0 ? void 0 : cache.set(key, value);
|
|
16431
|
+
return value;
|
|
16432
|
+
});
|
|
16433
|
+
};
|
|
16434
|
+
const deployedFrom = yield firstBlockWithCode(blocks, (block) => cached('code', block, () => reader.hasCodeAt(block)));
|
|
16435
|
+
if (deployedFrom > 0) {
|
|
16436
|
+
for (const block of new Set([blocks[0], blocks[deployedFrom - 1]])) {
|
|
16437
|
+
const poolDeployed = yield cached('pool', block, () => reader.poolHasCodeAt(block));
|
|
16438
|
+
if (!poolDeployed)
|
|
16439
|
+
throw new PastStateUnavailableError(block);
|
|
16440
|
+
}
|
|
16441
|
+
}
|
|
16442
|
+
const offChain = new Map();
|
|
16443
|
+
for (const block of blocks.slice(0, deployedFrom)) {
|
|
16444
|
+
offChain.set(block, ethers.BigNumber.from(0));
|
|
16445
|
+
}
|
|
16446
|
+
const targets = [...blocks.slice(deployedFrom)];
|
|
16447
|
+
if (withLatest)
|
|
16448
|
+
targets.push('latest');
|
|
16449
|
+
const values = yield mapWithConcurrency(targets, TVL_HISTORY_RPC_CONCURRENCY, (block) => {
|
|
16450
|
+
if (block !== 'latest') {
|
|
16451
|
+
return cached('tvl', block, () => reader.readAt(block));
|
|
16452
|
+
}
|
|
16453
|
+
// Never cached: "latest" moves.
|
|
16454
|
+
rpcReads += 1;
|
|
16455
|
+
return reader.readAt('latest');
|
|
16456
|
+
}, () => {
|
|
16457
|
+
cancellation.throwIfCancelled();
|
|
16458
|
+
});
|
|
16459
|
+
let latest = null;
|
|
16460
|
+
targets.forEach((block, i) => {
|
|
16461
|
+
if (block === 'latest')
|
|
16462
|
+
latest = values[i];
|
|
16463
|
+
else
|
|
16464
|
+
offChain.set(block, values[i]);
|
|
16465
|
+
});
|
|
16466
|
+
return { offChain, latest, rpcReads };
|
|
16467
|
+
});
|
|
16468
|
+
}
|
|
16469
|
+
/**
|
|
16470
|
+
* Pure: the reads → the history. One `clearing` point per clearing, ordered by
|
|
16471
|
+
* the block it finished in (then by epoch, for clearings that shared a block),
|
|
16472
|
+
* so the series is ascending in TIME even where epoch numbers jump; then the
|
|
16473
|
+
* `current` point. Each point is `poolTvl(onChain, offChain)` — the same
|
|
16474
|
+
* function `getPoolOverview` uses for `totalValueLocked.total`.
|
|
16475
|
+
*/
|
|
16476
|
+
function buildPoolTvlHistory(read) {
|
|
16477
|
+
const offChainDisplay = (value) => utils$2.formatUnits(value, read.decimals);
|
|
16478
|
+
const entries = [...read.clearings]
|
|
16479
|
+
.sort((a, b) => a.block - b.block || a.epochId - b.epochId)
|
|
16480
|
+
.map((clearing) => {
|
|
16481
|
+
const balance = read.balances.get(clearing.block);
|
|
16482
|
+
const offChain = read.offChain.get(clearing.block);
|
|
16483
|
+
if (balance === undefined || offChain === undefined) {
|
|
16484
|
+
throw new Error(`getPoolTvlHistory: no reading at block ${String(clearing.block)}`);
|
|
16485
|
+
}
|
|
16486
|
+
return {
|
|
16487
|
+
epochId: clearing.epochId,
|
|
16488
|
+
timestamp: clearing.timestamp,
|
|
16489
|
+
tvl: poolTvl(balance, offChainDisplay(offChain)),
|
|
16490
|
+
kind: 'clearing',
|
|
16491
|
+
};
|
|
16492
|
+
});
|
|
16493
|
+
if (read.current) {
|
|
16494
|
+
entries.push({
|
|
16495
|
+
epochId: read.current.epochId,
|
|
16496
|
+
timestamp: read.current.timestamp,
|
|
16497
|
+
tvl: poolTvl(read.current.balance, offChainDisplay(read.current.offChain)),
|
|
16498
|
+
kind: 'current',
|
|
16499
|
+
});
|
|
16500
|
+
}
|
|
16501
|
+
return entries;
|
|
16502
|
+
}
|
|
16503
|
+
/** Ascending, de-duplicated clearing blocks: each past block is read ONCE. */
|
|
16504
|
+
function distinctBlocks(clearings) {
|
|
16505
|
+
return [...new Set(clearings.map((c) => c.block))].sort((a, b) => a - b);
|
|
16506
|
+
}
|
|
16507
|
+
|
|
16508
|
+
/** Thrown by `getPoolTvlHistory` when no provider that serves past state is configured. */
|
|
16509
|
+
const NO_HISTORY_PROVIDER_MESSAGE = 'getPoolTvlHistory: no history provider; pass historyProvider or set historyRpcUrls to RPCs that serve past state';
|
|
15912
16510
|
class DataService {
|
|
15913
|
-
|
|
16511
|
+
/**
|
|
16512
|
+
* @param historyProvider - a provider that SERVES PAST STATE (an archive
|
|
16513
|
+
* RPC), used only by `getPoolTvlHistory` for the off-chain TVL at old
|
|
16514
|
+
* blocks. Without one, providers are built on `SdkConfig.historyRpcUrls`
|
|
16515
|
+
* (tried in order); with neither, that one call throws
|
|
16516
|
+
* `NO_HISTORY_PROVIDER_MESSAGE`. Never the default provider: a pruned
|
|
16517
|
+
* node refuses old blocks.
|
|
16518
|
+
*/
|
|
16519
|
+
constructor(_kasuConfig, signerOrProvider, historyProvider) {
|
|
15914
16520
|
this._kasuConfig = _kasuConfig;
|
|
16521
|
+
/** Immutable past-block reads, kept for the life of this instance. */
|
|
16522
|
+
this._tvlHistoryCache = new PastBlockCache();
|
|
15915
16523
|
this._externalTvlAbi = KasuPoolExternalTVLAbi__factory.connect(_kasuConfig.contracts.ExternalTVL, signerOrProvider);
|
|
16524
|
+
this._systemVariablesAbi = ISystemVariablesAbi__factory.connect(_kasuConfig.contracts.SystemVariables, signerOrProvider);
|
|
15916
16525
|
this._graph = new GraphQLClient(_kasuConfig.subgraphUrl);
|
|
16526
|
+
this._explicitHistoryProvider = historyProvider;
|
|
15917
16527
|
this._directus = createDirectusClient(_kasuConfig.directusUrl);
|
|
15918
16528
|
}
|
|
15919
16529
|
getUrlFromFile(fileName) {
|
|
@@ -15993,6 +16603,194 @@ class DataService {
|
|
|
15993
16603
|
return tvlMap;
|
|
15994
16604
|
});
|
|
15995
16605
|
}
|
|
16606
|
+
/**
|
|
16607
|
+
* The pool's TVL — the total lent into it by everyone — at every cycle
|
|
16608
|
+
* boundary, then as of now: the data behind a TVL chart.
|
|
16609
|
+
*
|
|
16610
|
+
* One `clearing` point per FINISHED clearing, read at the block it
|
|
16611
|
+
* finished in (`LendingPoolClearing.endBlockNumber`), then one `current`
|
|
16612
|
+
* point. Every point is `poolTvl(onChain, offChain)`, the definition
|
|
16613
|
+
* `getPoolOverview` uses for `totalValueLocked.total`:
|
|
16614
|
+
*
|
|
16615
|
+
* - on-chain: `lendingPool.balance` from the subgraph, read with
|
|
16616
|
+
* time-travel queries (`block: { number }`);
|
|
16617
|
+
* - off-chain: `ExternalTVL.externalTVLOfPool(pool)` at the same block,
|
|
16618
|
+
* through the HISTORY providers (see the constructor). 0 before the
|
|
16619
|
+
* contract was deployed (decided by `getCode`, and only if the same
|
|
16620
|
+
* provider shows the pool's own code there); any failed read throws.
|
|
16621
|
+
*
|
|
16622
|
+
* The `current` point mixes two sources: the subgraph's latest balance
|
|
16623
|
+
* (and its latest block time) and `externalTVLOfPool` at `latest` on the
|
|
16624
|
+
* history provider. `getPoolOverview` reads the same two, the off-chain
|
|
16625
|
+
* one on the default provider, so the last point equals
|
|
16626
|
+
* `totalValueLocked.total` when both reads succeed. They can differ when
|
|
16627
|
+
* `getPoolOverview`'s off-chain read FAILS — it then falls back to 0 —
|
|
16628
|
+
* or when the figure moves between the two calls.
|
|
16629
|
+
*
|
|
16630
|
+
* Units: `tvl` in display units of the deployment's own stable token
|
|
16631
|
+
* (USDC on Base and XDC USDC, AUDD on XDC AUDD); `timestamp` in unix
|
|
16632
|
+
* seconds. Ascending in time. See `PoolTvlHistoryEntry`.
|
|
16633
|
+
*
|
|
16634
|
+
* Cost: one subgraph request for the clearings (per thousand), one per
|
|
16635
|
+
* hundred distinct uncached clearing blocks, a `currentEpochNumber` read;
|
|
16636
|
+
* then, on the history providers, one `externalTVLOfPool` per distinct
|
|
16637
|
+
* uncached block plus the latest, and one to about log2(n) `getCode`s —
|
|
16638
|
+
* at most `TVL_HISTORY_RPC_CONCURRENCY` in flight. Final blocks are
|
|
16639
|
+
* cached on this instance, so a second call reads only what is new.
|
|
16640
|
+
* `timeoutMs` (default 30 s) bounds the whole call.
|
|
16641
|
+
*
|
|
16642
|
+
* `[]` for a pool the subgraph does not know, for a window with no epoch
|
|
16643
|
+
* in it, and on a LITE deployment whose subgraph lacks the entities. On a
|
|
16644
|
+
* full deployment that schema error throws, like every network, indexing
|
|
16645
|
+
* or past-state failure.
|
|
16646
|
+
*
|
|
16647
|
+
* @throws RangeError / TypeError on a malformed option or a pool id that
|
|
16648
|
+
* is not an address, and `NO_HISTORY_PROVIDER_MESSAGE` when the
|
|
16649
|
+
* deployment has an `ExternalTVL` contract but no history provider — all
|
|
16650
|
+
* before any network read. `PoolTvlHistoryTimeoutError` /
|
|
16651
|
+
* `PoolTvlHistoryAbortedError` on the deadline or the signal,
|
|
16652
|
+
* `PastStateUnavailableError` from a pruned history provider.
|
|
16653
|
+
*/
|
|
16654
|
+
getPoolTvlHistory(poolId_1) {
|
|
16655
|
+
return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
|
|
16656
|
+
var _a;
|
|
16657
|
+
validatePoolTvlHistoryOptions(options);
|
|
16658
|
+
if (!utils$2.isAddress(poolId)) {
|
|
16659
|
+
throw new TypeError(`getPoolTvlHistory: poolId must be an address, got ${String(poolId)}`);
|
|
16660
|
+
}
|
|
16661
|
+
const pool = poolId.toLowerCase();
|
|
16662
|
+
const externalTvl = this._externalTvlAddress();
|
|
16663
|
+
const rpcs = externalTvl ? this._historyRpcs() : [];
|
|
16664
|
+
if (externalTvl && rpcs.length === 0) {
|
|
16665
|
+
throw new Error(NO_HISTORY_PROVIDER_MESSAGE);
|
|
16666
|
+
}
|
|
16667
|
+
return yield runCancellable((_a = options.timeoutMs) !== null && _a !== void 0 ? _a : TVL_HISTORY_DEFAULT_TIMEOUT_MS, options.signal, (cancellation) => this._readPoolTvlHistory(pool, options, externalTvl
|
|
16668
|
+
? this._createOffChainTvlReader({
|
|
16669
|
+
externalTvl,
|
|
16670
|
+
pool,
|
|
16671
|
+
rpcs,
|
|
16672
|
+
cancellation,
|
|
16673
|
+
})
|
|
16674
|
+
: null, externalTvl !== null && externalTvl !== void 0 ? externalTvl : '', cancellation));
|
|
16675
|
+
});
|
|
16676
|
+
}
|
|
16677
|
+
_readPoolTvlHistory(pool, options, reader, externalTvl, cancellation) {
|
|
16678
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16679
|
+
var _a, _b, _c;
|
|
16680
|
+
const graph = (document, variables, signal) => this._graph.request({ document, variables, signal });
|
|
16681
|
+
const scope = options.fromEpoch === undefined && options.count !== undefined
|
|
16682
|
+
? { kind: 'newest', count: options.count }
|
|
16683
|
+
: { kind: 'from', fromEpoch: (_a = options.fromEpoch) !== null && _a !== void 0 ? _a : 0 };
|
|
16684
|
+
try {
|
|
16685
|
+
const [currentEpochNumber, { clearings, snapshot }] = yield Promise.all([
|
|
16686
|
+
this._systemVariablesAbi.currentEpochNumber(),
|
|
16687
|
+
fetchPoolClearings(graph, pool, scope, cancellation),
|
|
16688
|
+
]);
|
|
16689
|
+
const currentEpoch = currentEpochNumber.toNumber();
|
|
16690
|
+
const window = resolvePoolTvlHistoryWindow(options, currentEpoch);
|
|
16691
|
+
const poolNow = snapshot.pool;
|
|
16692
|
+
if (!window || !poolNow)
|
|
16693
|
+
return [];
|
|
16694
|
+
const head = (_b = snapshot.meta) === null || _b === void 0 ? void 0 : _b.block;
|
|
16695
|
+
if (!head || typeof head.timestamp !== 'number') {
|
|
16696
|
+
throw new Error('getPoolTvlHistory: the subgraph returned no latest block time');
|
|
16697
|
+
}
|
|
16698
|
+
const inWindow = clearings.filter((c) => c.epochId >= window.fromEpoch &&
|
|
16699
|
+
c.epochId <= window.toEpoch);
|
|
16700
|
+
const blocks = distinctBlocks(inWindow);
|
|
16701
|
+
const withCurrent = window.toEpoch >= currentEpoch;
|
|
16702
|
+
const cacheable = (block) => block <= head.number - TVL_HISTORY_CACHE_DEPTH;
|
|
16703
|
+
const [balances, offChainRead] = yield Promise.all([
|
|
16704
|
+
this._balancesAtBlocks(graph, pool, blocks, cacheable, cancellation),
|
|
16705
|
+
reader
|
|
16706
|
+
? fetchOffChainTvlAtBlocks(reader, blocks, {
|
|
16707
|
+
withLatest: withCurrent,
|
|
16708
|
+
cancellation,
|
|
16709
|
+
cache: this._tvlHistoryCache,
|
|
16710
|
+
cacheKey: (kind, block) => `${kind}|${externalTvl}|${pool}|${String(block)}`,
|
|
16711
|
+
cacheable,
|
|
16712
|
+
})
|
|
16713
|
+
: {
|
|
16714
|
+
offChain: new Map(blocks.map((b) => [b, ethers.BigNumber.from(0)])),
|
|
16715
|
+
latest: null,
|
|
16716
|
+
},
|
|
16717
|
+
]);
|
|
16718
|
+
return buildPoolTvlHistory({
|
|
16719
|
+
clearings: inWindow,
|
|
16720
|
+
balances,
|
|
16721
|
+
offChain: offChainRead.offChain,
|
|
16722
|
+
current: withCurrent
|
|
16723
|
+
? {
|
|
16724
|
+
epochId: currentEpoch,
|
|
16725
|
+
timestamp: head.timestamp,
|
|
16726
|
+
balance: poolNow.balance,
|
|
16727
|
+
offChain: (_c = offChainRead.latest) !== null && _c !== void 0 ? _c : ethers.BigNumber.from(0),
|
|
16728
|
+
}
|
|
16729
|
+
: null,
|
|
16730
|
+
decimals: this._kasuConfig.stableAssetDecimals,
|
|
16731
|
+
});
|
|
16732
|
+
}
|
|
16733
|
+
catch (error) {
|
|
16734
|
+
if (isEmptyHistoryError(error, this._kasuConfig.isLiteDeployment)) {
|
|
16735
|
+
return [];
|
|
16736
|
+
}
|
|
16737
|
+
throw error;
|
|
16738
|
+
}
|
|
16739
|
+
});
|
|
16740
|
+
}
|
|
16741
|
+
/** The pool's balance at each block: cached final blocks, then one batch. */
|
|
16742
|
+
_balancesAtBlocks(graph, pool, blocks, cacheable, cancellation) {
|
|
16743
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16744
|
+
const key = (block) => `balance|${this._kasuConfig.subgraphUrl}|${pool}|${String(block)}`;
|
|
16745
|
+
const balances = new Map();
|
|
16746
|
+
const missing = [];
|
|
16747
|
+
for (const block of blocks) {
|
|
16748
|
+
const hit = cacheable(block)
|
|
16749
|
+
? this._tvlHistoryCache.get(key(block))
|
|
16750
|
+
: undefined;
|
|
16751
|
+
if (hit === undefined)
|
|
16752
|
+
missing.push(block);
|
|
16753
|
+
else
|
|
16754
|
+
balances.set(block, hit);
|
|
16755
|
+
}
|
|
16756
|
+
if (missing.length > 0) {
|
|
16757
|
+
const read = yield fetchPoolBalancesAtBlocks(graph, pool, missing, cancellation);
|
|
16758
|
+
for (const [block, balance] of read.balances) {
|
|
16759
|
+
balances.set(block, balance);
|
|
16760
|
+
if (cacheable(block))
|
|
16761
|
+
this._tvlHistoryCache.set(key(block), balance);
|
|
16762
|
+
}
|
|
16763
|
+
}
|
|
16764
|
+
return balances;
|
|
16765
|
+
});
|
|
16766
|
+
}
|
|
16767
|
+
/** `null` when the deployment has no `ExternalTVL` contract. */
|
|
16768
|
+
_externalTvlAddress() {
|
|
16769
|
+
const address = this._kasuConfig.contracts.ExternalTVL;
|
|
16770
|
+
if (!address || /^0x0{40}$/i.test(address))
|
|
16771
|
+
return null;
|
|
16772
|
+
return address.toLowerCase();
|
|
16773
|
+
}
|
|
16774
|
+
/** Seam for tests: the real reader over the history RPCs. */
|
|
16775
|
+
_createOffChainTvlReader(args) {
|
|
16776
|
+
return createOffChainTvlReader(args);
|
|
16777
|
+
}
|
|
16778
|
+
/**
|
|
16779
|
+
* The history RPCs, in fallback order: the explicit provider, else ONE
|
|
16780
|
+
* provider per `historyRpcUrls` entry — built here and nowhere else,
|
|
16781
|
+
* pinned to `SdkConfig.chainId` when known, each with a per-call timeout.
|
|
16782
|
+
* Built once per instance.
|
|
16783
|
+
*/
|
|
16784
|
+
_historyRpcs() {
|
|
16785
|
+
if (this._explicitHistoryProvider)
|
|
16786
|
+
return [this._explicitHistoryProvider];
|
|
16787
|
+
if (!this._builtHistoryProviders) {
|
|
16788
|
+
// Through the `ethers` namespace, so the consumer's own build
|
|
16789
|
+
// (Node or browser) is used — see `defaultProvider` in the facade.
|
|
16790
|
+
this._builtHistoryProviders = this._kasuConfig.historyRpcUrls.map((url) => new ethers.providers.StaticJsonRpcProvider({ url, timeout: TVL_HISTORY_RPC_TIMEOUT_MS }, this._kasuConfig.chainId));
|
|
16791
|
+
}
|
|
16792
|
+
return this._builtHistoryProviders;
|
|
16793
|
+
}
|
|
15996
16794
|
getPoolOverview(epochId, id_in) {
|
|
15997
16795
|
return __awaiter(this, void 0, void 0, function* () {
|
|
15998
16796
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
@@ -16133,8 +16931,8 @@ class DataService {
|
|
|
16133
16931
|
subheading: lendingPoolDirectus === null || lendingPoolDirectus === void 0 ? void 0 : lendingPoolDirectus.subheading,
|
|
16134
16932
|
operatingSince: lendingPoolDirectus === null || lendingPoolDirectus === void 0 ? void 0 : lendingPoolDirectus.operatingSince,
|
|
16135
16933
|
totalValueLocked: {
|
|
16136
|
-
|
|
16137
|
-
|
|
16934
|
+
// The one TVL definition, shared with getPoolTvlHistory.
|
|
16935
|
+
total: poolTvl(lendingPoolSubgraph.balance, offChainTvl).toString(),
|
|
16138
16936
|
offchain: offChainTvl,
|
|
16139
16937
|
},
|
|
16140
16938
|
loansUnderManagement: (_q = lendingPoolDirectus === null || lendingPoolDirectus === void 0 ? void 0 : lendingPoolDirectus.loansUnderManagement) !== null && _q !== void 0 ? _q : '0',
|
|
@@ -16943,6 +17741,101 @@ class KSULocking {
|
|
|
16943
17741
|
}
|
|
16944
17742
|
}
|
|
16945
17743
|
|
|
17744
|
+
/**
|
|
17745
|
+
* Settled interest per cycle (epoch): what a lender was credited at each close,
|
|
17746
|
+
* per pool and tranche, and the one formula that turns a tranche's credited
|
|
17747
|
+
* interest into one lender's share of it.
|
|
17748
|
+
*
|
|
17749
|
+
* **The figures are NET, and only net.** `LendingPool._applyTrancheInterest`
|
|
17750
|
+
* takes the performance fee off first and emits `InterestApplied` with the
|
|
17751
|
+
* lender side only (`interest × (1 − performanceFee)`); a fixed-term top-up
|
|
17752
|
+
* (`_applyFixedRateInterests`) is net of the fee the same way. So the figure
|
|
17753
|
+
* this module computes from those records IS what the lender was credited —
|
|
17754
|
+
* never take the fee off it again. No gross is offered: the subgraph keeps
|
|
17755
|
+
* only TODAY's fee, so a historical gross would be a guess.
|
|
17756
|
+
*
|
|
17757
|
+
* Units: amounts are display units of the DEPLOYMENT'S OWN stable token (USDC
|
|
17758
|
+
* on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
|
|
17759
|
+
* shares are display units of tranche shares, as the subgraph's BigDecimals.
|
|
17760
|
+
*/
|
|
17761
|
+
/**
|
|
17762
|
+
* The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
|
|
17763
|
+
* the last settled epoch and `getYieldHistory` for every epoch of a window, so
|
|
17764
|
+
* the two figures cannot drift apart.
|
|
17765
|
+
*
|
|
17766
|
+
* ```
|
|
17767
|
+
* base = epochInterest × userShares / trancheShares
|
|
17768
|
+
* lockBase = base × lock.sharesBeforeEpoch / userShares (active locks)
|
|
17769
|
+
* topUp = (lock.sharesAtEpoch − lock.sharesBeforeEpoch) × trancheBalanceNow / trancheSharesNow
|
|
17770
|
+
* variable = base − Σ lockBase
|
|
17771
|
+
* total = variable + Σ (lockBase + topUp)
|
|
17772
|
+
* ```
|
|
17773
|
+
*
|
|
17774
|
+
* A zero denominator contributes 0 (a tranche or a lender with no shares
|
|
17775
|
+
* earned nothing at that close), never `NaN`.
|
|
17776
|
+
*/
|
|
17777
|
+
function epochTrancheYield(input) {
|
|
17778
|
+
const base = input.trancheShares === 0
|
|
17779
|
+
? 0
|
|
17780
|
+
: (input.epochInterest * input.userShares) / input.trancheShares;
|
|
17781
|
+
let variable = base;
|
|
17782
|
+
const locks = input.locks.map((lock) => {
|
|
17783
|
+
if (!lock.active)
|
|
17784
|
+
return 0;
|
|
17785
|
+
const topUp = input.trancheSharesNow === 0
|
|
17786
|
+
? 0
|
|
17787
|
+
: ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
|
|
17788
|
+
input.trancheBalanceNow) /
|
|
17789
|
+
input.trancheSharesNow;
|
|
17790
|
+
const lockBase = input.userShares === 0
|
|
17791
|
+
? 0
|
|
17792
|
+
: (base * lock.sharesBeforeEpoch) / input.userShares;
|
|
17793
|
+
variable -= lockBase;
|
|
17794
|
+
return lockBase + topUp;
|
|
17795
|
+
});
|
|
17796
|
+
const total = locks.reduce((sum, lockYield) => sum + lockYield, variable);
|
|
17797
|
+
return { total, variable, locks };
|
|
17798
|
+
}
|
|
17799
|
+
/**
|
|
17800
|
+
* Pool/tranche entries → one point per epoch, ascending by `epochId`. With no
|
|
17801
|
+
* filter it sums every strategy (the Lend tab's bars); with `poolId` it is one
|
|
17802
|
+
* strategy (a strategy page's bars).
|
|
17803
|
+
*
|
|
17804
|
+
* An epoch appears only if a kept entry has it — there is no zero padding, so
|
|
17805
|
+
* an empty input (a new lender) is an empty output.
|
|
17806
|
+
*
|
|
17807
|
+
* For a fixed-term lock holder the bars can sum to slightly MORE than the
|
|
17808
|
+
* lender's lifetime interest (up to about 0.2 %): each top-up is valued at the
|
|
17809
|
+
* tranche's CURRENT share price, which has grown since. Show "earned to date"
|
|
17810
|
+
* from the lifetime figure, never from this sum.
|
|
17811
|
+
*/
|
|
17812
|
+
function aggregateYieldHistory(entries, filter = {}) {
|
|
17813
|
+
var _a, _b;
|
|
17814
|
+
const poolId = (_a = filter.poolId) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
17815
|
+
const trancheId = (_b = filter.trancheId) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
17816
|
+
const byEpoch = new Map();
|
|
17817
|
+
for (const entry of entries) {
|
|
17818
|
+
if (poolId !== undefined && entry.poolId.toLowerCase() !== poolId) {
|
|
17819
|
+
continue;
|
|
17820
|
+
}
|
|
17821
|
+
if (trancheId !== undefined &&
|
|
17822
|
+
entry.trancheId.toLowerCase() !== trancheId) {
|
|
17823
|
+
continue;
|
|
17824
|
+
}
|
|
17825
|
+
const point = byEpoch.get(entry.epochId);
|
|
17826
|
+
if (point) {
|
|
17827
|
+
point.netYield += entry.netYield;
|
|
17828
|
+
continue;
|
|
17829
|
+
}
|
|
17830
|
+
byEpoch.set(entry.epochId, {
|
|
17831
|
+
epochId: entry.epochId,
|
|
17832
|
+
epochEnd: entry.epochEnd,
|
|
17833
|
+
netYield: entry.netYield,
|
|
17834
|
+
});
|
|
17835
|
+
}
|
|
17836
|
+
return [...byEpoch.values()].sort((a, b) => a.epochId - b.epochId);
|
|
17837
|
+
}
|
|
17838
|
+
|
|
16946
17839
|
/**
|
|
16947
17840
|
* Check if the SDK is configured for a Lite deployment.
|
|
16948
17841
|
* Lite deployments don't have KSU token, locking, or loyalty features.
|
|
@@ -17726,9 +18619,11 @@ const lendingPortfolioQuery = gql `
|
|
|
17726
18619
|
$epochId: BigInt!
|
|
17727
18620
|
$lastEpochId: BigInt!
|
|
17728
18621
|
$unusedPools: [String!]!
|
|
18622
|
+
$first: Int!
|
|
17729
18623
|
) {
|
|
17730
18624
|
user(id: $userAddress) {
|
|
17731
18625
|
lendingPoolUserDetails(
|
|
18626
|
+
first: $first
|
|
17732
18627
|
where: { lendingPool_not_in: $unusedPools }
|
|
17733
18628
|
) {
|
|
17734
18629
|
id
|
|
@@ -17736,11 +18631,11 @@ const lendingPortfolioQuery = gql `
|
|
|
17736
18631
|
id
|
|
17737
18632
|
name
|
|
17738
18633
|
balance
|
|
17739
|
-
tranches {
|
|
18634
|
+
tranches(first: $first) {
|
|
17740
18635
|
shares
|
|
17741
18636
|
}
|
|
17742
18637
|
}
|
|
17743
|
-
lendingPoolTrancheUserDetails {
|
|
18638
|
+
lendingPoolTrancheUserDetails(first: $first) {
|
|
17744
18639
|
shares
|
|
17745
18640
|
lendingPoolTrancheUserEpochSharesUpdates(
|
|
17746
18641
|
orderDirection: desc
|
|
@@ -17750,7 +18645,7 @@ const lendingPortfolioQuery = gql `
|
|
|
17750
18645
|
) {
|
|
17751
18646
|
shares
|
|
17752
18647
|
}
|
|
17753
|
-
userLendingPoolTrancheFixedTermDepositLocks {
|
|
18648
|
+
userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
|
|
17754
18649
|
isLocked
|
|
17755
18650
|
unlockAmount
|
|
17756
18651
|
initialAmount
|
|
@@ -17774,6 +18669,14 @@ const lendingPortfolioQuery = gql `
|
|
|
17774
18669
|
) {
|
|
17775
18670
|
shares
|
|
17776
18671
|
}
|
|
18672
|
+
lockSharesAtLastEpoch: userLendingPoolTrancheFixedTermDepositLockShareUpdate(
|
|
18673
|
+
orderBy: shareUpdatesIndex
|
|
18674
|
+
orderDirection: desc
|
|
18675
|
+
first: 1
|
|
18676
|
+
where: { epochId_lt: $epochId }
|
|
18677
|
+
) {
|
|
18678
|
+
shares
|
|
18679
|
+
}
|
|
17777
18680
|
}
|
|
17778
18681
|
tranche {
|
|
17779
18682
|
id
|
|
@@ -17800,6 +18703,7 @@ const lendingPortfolioQuery = gql `
|
|
|
17800
18703
|
}
|
|
17801
18704
|
}
|
|
17802
18705
|
userRequests(
|
|
18706
|
+
first: $first
|
|
17803
18707
|
where: {
|
|
17804
18708
|
user: $userAddress
|
|
17805
18709
|
userRequestEvents_: {
|
|
@@ -17815,6 +18719,7 @@ const lendingPortfolioQuery = gql `
|
|
|
17815
18719
|
}
|
|
17816
18720
|
fixedTermConfigId
|
|
17817
18721
|
userRequestEvents(
|
|
18722
|
+
first: $first
|
|
17818
18723
|
where: { type_in: [DepositInitiated, DepositIncreased] }
|
|
17819
18724
|
) {
|
|
17820
18725
|
id
|
|
@@ -17824,6 +18729,371 @@ const lendingPortfolioQuery = gql `
|
|
|
17824
18729
|
}
|
|
17825
18730
|
`;
|
|
17826
18731
|
|
|
18732
|
+
/** The subgraph returns at most this many rows per collection per request. */
|
|
18733
|
+
const YIELD_HISTORY_PAGE_SIZE = 1000;
|
|
18734
|
+
/**
|
|
18735
|
+
* A runaway guard, not a working limit: a collection this many pages long
|
|
18736
|
+
* (`MAX × PAGE_SIZE` rows for one lender) is a broken response, not a history.
|
|
18737
|
+
*/
|
|
18738
|
+
const YIELD_HISTORY_MAX_PAGES = 100;
|
|
18739
|
+
/**
|
|
18740
|
+
* Throws `RangeError` on a malformed `fromEpoch` / `count`. `getYieldHistory`
|
|
18741
|
+
* calls it BEFORE any network read.
|
|
18742
|
+
*/
|
|
18743
|
+
function validateYieldHistoryOptions(options) {
|
|
18744
|
+
const { fromEpoch, count } = options;
|
|
18745
|
+
if (fromEpoch !== undefined &&
|
|
18746
|
+
(!Number.isSafeInteger(fromEpoch) || fromEpoch < 0)) {
|
|
18747
|
+
throw new RangeError(`getYieldHistory: fromEpoch must be a non-negative integer, got ${String(fromEpoch)}`);
|
|
18748
|
+
}
|
|
18749
|
+
if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) {
|
|
18750
|
+
throw new RangeError(`getYieldHistory: count must be a positive integer, got ${String(count)}`);
|
|
18751
|
+
}
|
|
18752
|
+
}
|
|
18753
|
+
function resolveYieldHistoryWindow(options, currentEpoch) {
|
|
18754
|
+
validateYieldHistoryOptions(options);
|
|
18755
|
+
const { fromEpoch, count } = options;
|
|
18756
|
+
const newest = currentEpoch - 1;
|
|
18757
|
+
if (newest < 0)
|
|
18758
|
+
return null;
|
|
18759
|
+
let from;
|
|
18760
|
+
let to = newest;
|
|
18761
|
+
if (fromEpoch !== undefined) {
|
|
18762
|
+
from = fromEpoch;
|
|
18763
|
+
if (count !== undefined)
|
|
18764
|
+
to = Math.min(newest, fromEpoch + count - 1);
|
|
18765
|
+
}
|
|
18766
|
+
else {
|
|
18767
|
+
from = count !== undefined ? Math.max(0, newest - count + 1) : 0;
|
|
18768
|
+
}
|
|
18769
|
+
return from > to ? null : { fromEpoch: from, toEpoch: to };
|
|
18770
|
+
}
|
|
18771
|
+
// ---------------------------------------------------------------------------
|
|
18772
|
+
// Subgraph reads
|
|
18773
|
+
// ---------------------------------------------------------------------------
|
|
18774
|
+
/**
|
|
18775
|
+
* Round trip 1: the lender's tranche positions and fixed-term locks. Same
|
|
18776
|
+
* pool exclusion and the same nested-list size (`$first`) as
|
|
18777
|
+
* `lendingPortfolioQuery`, so both read the same lock set.
|
|
18778
|
+
*/
|
|
18779
|
+
const yieldHistoryPositionsQuery = gql `
|
|
18780
|
+
query YieldHistoryPositions(
|
|
18781
|
+
$userAddress: ID!
|
|
18782
|
+
$unusedPools: [String!]!
|
|
18783
|
+
$first: Int!
|
|
18784
|
+
) {
|
|
18785
|
+
user(id: $userAddress) {
|
|
18786
|
+
lendingPoolUserDetails(
|
|
18787
|
+
first: $first
|
|
18788
|
+
where: { lendingPool_not_in: $unusedPools }
|
|
18789
|
+
) {
|
|
18790
|
+
lendingPool {
|
|
18791
|
+
id
|
|
18792
|
+
}
|
|
18793
|
+
lendingPoolTrancheUserDetails(first: $first) {
|
|
18794
|
+
id
|
|
18795
|
+
tranche {
|
|
18796
|
+
id
|
|
18797
|
+
balance
|
|
18798
|
+
shares
|
|
18799
|
+
}
|
|
18800
|
+
userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
|
|
18801
|
+
id
|
|
18802
|
+
epochLockStart
|
|
18803
|
+
epochLockEnd
|
|
18804
|
+
initialTrancheShares
|
|
18805
|
+
}
|
|
18806
|
+
}
|
|
18807
|
+
}
|
|
18808
|
+
}
|
|
18809
|
+
}
|
|
18810
|
+
`;
|
|
18811
|
+
/**
|
|
18812
|
+
* Round trip 2 onwards: the four per-epoch series, one page of each per
|
|
18813
|
+
* request, cursor-paginated by `id`. A series that is exhausted is skipped with
|
|
18814
|
+
* `@include`, so a history costs as many requests as its LONGEST series has
|
|
18815
|
+
* pages — normally one — never one per epoch.
|
|
18816
|
+
*
|
|
18817
|
+
* Share snapshots are read up to the window's end with no lower bound: the
|
|
18818
|
+
* value AS OF the first epoch may come from an update long before it.
|
|
18819
|
+
*/
|
|
18820
|
+
const yieldHistoryPageQuery = gql `
|
|
18821
|
+
query YieldHistoryPage(
|
|
18822
|
+
$tranches: [String!]!
|
|
18823
|
+
$userTranches: [String!]!
|
|
18824
|
+
$locks: [String!]!
|
|
18825
|
+
$fromEpoch: BigInt!
|
|
18826
|
+
$toEpoch: BigInt!
|
|
18827
|
+
$interestCursor: ID!
|
|
18828
|
+
$trancheSharesCursor: ID!
|
|
18829
|
+
$userSharesCursor: ID!
|
|
18830
|
+
$lockSharesCursor: ID!
|
|
18831
|
+
$withInterest: Boolean!
|
|
18832
|
+
$withTrancheShares: Boolean!
|
|
18833
|
+
$withUserShares: Boolean!
|
|
18834
|
+
$withLockShares: Boolean!
|
|
18835
|
+
$first: Int!
|
|
18836
|
+
) {
|
|
18837
|
+
interest: lendingPoolTrancheEpochInterests(
|
|
18838
|
+
first: $first
|
|
18839
|
+
orderBy: id
|
|
18840
|
+
orderDirection: asc
|
|
18841
|
+
where: {
|
|
18842
|
+
id_gt: $interestCursor
|
|
18843
|
+
lendingPoolTranche_in: $tranches
|
|
18844
|
+
epochId_gte: $fromEpoch
|
|
18845
|
+
epochId_lte: $toEpoch
|
|
18846
|
+
}
|
|
18847
|
+
) @include(if: $withInterest) {
|
|
18848
|
+
id
|
|
18849
|
+
epochId
|
|
18850
|
+
epochInterestAmount
|
|
18851
|
+
lendingPoolTranche {
|
|
18852
|
+
id
|
|
18853
|
+
}
|
|
18854
|
+
}
|
|
18855
|
+
trancheShares: lendingPoolTrancheShareUpdates(
|
|
18856
|
+
first: $first
|
|
18857
|
+
orderBy: id
|
|
18858
|
+
orderDirection: asc
|
|
18859
|
+
where: {
|
|
18860
|
+
id_gt: $trancheSharesCursor
|
|
18861
|
+
lendingPoolTranche_in: $tranches
|
|
18862
|
+
epochId_lte: $toEpoch
|
|
18863
|
+
}
|
|
18864
|
+
) @include(if: $withTrancheShares) {
|
|
18865
|
+
id
|
|
18866
|
+
epochId
|
|
18867
|
+
shareUpdatesIndex
|
|
18868
|
+
shares
|
|
18869
|
+
lendingPoolTranche {
|
|
18870
|
+
id
|
|
18871
|
+
}
|
|
18872
|
+
}
|
|
18873
|
+
userShares: lendingPoolTrancheUserEpochSharesUpdates(
|
|
18874
|
+
first: $first
|
|
18875
|
+
orderBy: id
|
|
18876
|
+
orderDirection: asc
|
|
18877
|
+
where: {
|
|
18878
|
+
id_gt: $userSharesCursor
|
|
18879
|
+
lendingPoolTrancheUserDetails_in: $userTranches
|
|
18880
|
+
epochId_lte: $toEpoch
|
|
18881
|
+
}
|
|
18882
|
+
) @include(if: $withUserShares) {
|
|
18883
|
+
id
|
|
18884
|
+
epochId
|
|
18885
|
+
shareUpdatesIndex
|
|
18886
|
+
shares
|
|
18887
|
+
lendingPoolTrancheUserDetails {
|
|
18888
|
+
id
|
|
18889
|
+
}
|
|
18890
|
+
}
|
|
18891
|
+
lockShares: userLendingPoolTrancheFtdLockShareUpdates(
|
|
18892
|
+
first: $first
|
|
18893
|
+
orderBy: id
|
|
18894
|
+
orderDirection: asc
|
|
18895
|
+
where: {
|
|
18896
|
+
id_gt: $lockSharesCursor
|
|
18897
|
+
userLendingPoolTrancheFixedTermDepositLock_in: $locks
|
|
18898
|
+
epochId_lte: $toEpoch
|
|
18899
|
+
}
|
|
18900
|
+
) @include(if: $withLockShares) {
|
|
18901
|
+
id
|
|
18902
|
+
epochId
|
|
18903
|
+
shareUpdatesIndex
|
|
18904
|
+
shares
|
|
18905
|
+
userLendingPoolTrancheFixedTermDepositLock {
|
|
18906
|
+
id
|
|
18907
|
+
}
|
|
18908
|
+
}
|
|
18909
|
+
}
|
|
18910
|
+
`;
|
|
18911
|
+
/**
|
|
18912
|
+
* Fetch the four series for the window, paginating each by `id` until a short
|
|
18913
|
+
* page. Returns the rows and how many requests it took.
|
|
18914
|
+
*/
|
|
18915
|
+
function fetchYieldHistorySeries(request, ids, window) {
|
|
18916
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
18917
|
+
var _a;
|
|
18918
|
+
const rows = {
|
|
18919
|
+
interest: [],
|
|
18920
|
+
trancheShares: [],
|
|
18921
|
+
userShares: [],
|
|
18922
|
+
lockShares: [],
|
|
18923
|
+
};
|
|
18924
|
+
const series = [
|
|
18925
|
+
{ key: 'interest', flag: 'withInterest', cursor: 'interestCursor' },
|
|
18926
|
+
{
|
|
18927
|
+
key: 'trancheShares',
|
|
18928
|
+
flag: 'withTrancheShares',
|
|
18929
|
+
cursor: 'trancheSharesCursor',
|
|
18930
|
+
},
|
|
18931
|
+
{
|
|
18932
|
+
key: 'userShares',
|
|
18933
|
+
flag: 'withUserShares',
|
|
18934
|
+
cursor: 'userSharesCursor',
|
|
18935
|
+
},
|
|
18936
|
+
{
|
|
18937
|
+
key: 'lockShares',
|
|
18938
|
+
flag: 'withLockShares',
|
|
18939
|
+
cursor: 'lockSharesCursor',
|
|
18940
|
+
},
|
|
18941
|
+
];
|
|
18942
|
+
const cursors = {
|
|
18943
|
+
interest: '',
|
|
18944
|
+
trancheShares: '',
|
|
18945
|
+
userShares: '',
|
|
18946
|
+
lockShares: '',
|
|
18947
|
+
};
|
|
18948
|
+
const open = {
|
|
18949
|
+
interest: ids.tranches.length > 0,
|
|
18950
|
+
trancheShares: ids.tranches.length > 0,
|
|
18951
|
+
userShares: ids.userTranches.length > 0,
|
|
18952
|
+
lockShares: ids.locks.length > 0,
|
|
18953
|
+
};
|
|
18954
|
+
let requests = 0;
|
|
18955
|
+
while (series.some(({ key }) => open[key])) {
|
|
18956
|
+
if (requests >= YIELD_HISTORY_MAX_PAGES) {
|
|
18957
|
+
throw new Error(`getYieldHistory: more than ${String(YIELD_HISTORY_MAX_PAGES)} pages for one lender`);
|
|
18958
|
+
}
|
|
18959
|
+
const variables = {
|
|
18960
|
+
tranches: ids.tranches,
|
|
18961
|
+
userTranches: ids.userTranches,
|
|
18962
|
+
locks: ids.locks,
|
|
18963
|
+
fromEpoch: String(window.fromEpoch),
|
|
18964
|
+
toEpoch: String(window.toEpoch),
|
|
18965
|
+
first: YIELD_HISTORY_PAGE_SIZE,
|
|
18966
|
+
};
|
|
18967
|
+
for (const { key, flag, cursor } of series) {
|
|
18968
|
+
variables[flag] = open[key];
|
|
18969
|
+
variables[cursor] = cursors[key];
|
|
18970
|
+
}
|
|
18971
|
+
const page = yield request(variables);
|
|
18972
|
+
requests += 1;
|
|
18973
|
+
for (const { key } of series) {
|
|
18974
|
+
if (!open[key])
|
|
18975
|
+
continue;
|
|
18976
|
+
const got = (_a = page[key]) !== null && _a !== void 0 ? _a : [];
|
|
18977
|
+
rows[key].push(...got);
|
|
18978
|
+
const last = got.at(-1);
|
|
18979
|
+
if (got.length < YIELD_HISTORY_PAGE_SIZE || last === undefined) {
|
|
18980
|
+
open[key] = false;
|
|
18981
|
+
continue;
|
|
18982
|
+
}
|
|
18983
|
+
if (last.id <= cursors[key]) {
|
|
18984
|
+
throw new Error(`getYieldHistory: the ${key} cursor did not advance`);
|
|
18985
|
+
}
|
|
18986
|
+
cursors[key] = last.id;
|
|
18987
|
+
}
|
|
18988
|
+
}
|
|
18989
|
+
return { rows, requests };
|
|
18990
|
+
});
|
|
18991
|
+
}
|
|
18992
|
+
/**
|
|
18993
|
+
* The snapshot in force at `epoch`: among updates with `epochId ≤ epoch`, the
|
|
18994
|
+
* one with the highest `shareUpdatesIndex` — exactly what
|
|
18995
|
+
* `lendingPortfolioQuery` selects with `orderBy: shareUpdatesIndex, desc,
|
|
18996
|
+
* first: 1, where: { epochId_lt: epoch + 1 }`. `undefined` when none.
|
|
18997
|
+
*/
|
|
18998
|
+
function sharesAsOf(updates, epoch) {
|
|
18999
|
+
let found;
|
|
19000
|
+
for (const update of updates) {
|
|
19001
|
+
if (Number(update.epochId) > epoch)
|
|
19002
|
+
continue;
|
|
19003
|
+
if (!found || update.shareUpdatesIndex > found.shareUpdatesIndex) {
|
|
19004
|
+
found = update;
|
|
19005
|
+
}
|
|
19006
|
+
}
|
|
19007
|
+
return found;
|
|
19008
|
+
}
|
|
19009
|
+
function groupBy(rows, key) {
|
|
19010
|
+
const map = new Map();
|
|
19011
|
+
for (const row of rows) {
|
|
19012
|
+
const k = key(row);
|
|
19013
|
+
const list = map.get(k);
|
|
19014
|
+
if (list)
|
|
19015
|
+
list.push(row);
|
|
19016
|
+
else
|
|
19017
|
+
map.set(k, [row]);
|
|
19018
|
+
}
|
|
19019
|
+
return map;
|
|
19020
|
+
}
|
|
19021
|
+
/**
|
|
19022
|
+
* Pure: the fetched series → one entry per pool, tranche and settled epoch in
|
|
19023
|
+
* which the lender held shares (or a lock earned a top-up). Each entry runs the
|
|
19024
|
+
* same `epochTrancheYield` as the portfolio's last-epoch figure, with the same
|
|
19025
|
+
* share selection, so the newest entry equals that figure.
|
|
19026
|
+
*
|
|
19027
|
+
* Sorted by `epochId`, then `poolId`, then `trancheId`.
|
|
19028
|
+
*/
|
|
19029
|
+
function buildYieldHistory(read, window, clock) {
|
|
19030
|
+
var _a, _b, _c, _d, _e, _f;
|
|
19031
|
+
const user = read.positions.user;
|
|
19032
|
+
if (!user)
|
|
19033
|
+
return [];
|
|
19034
|
+
const interestByTranche = groupBy(read.interest, (row) => row.lendingPoolTranche.id);
|
|
19035
|
+
const trancheSharesByTranche = groupBy(read.trancheShares, (row) => row.lendingPoolTranche.id);
|
|
19036
|
+
const userSharesByPosition = groupBy(read.userShares, (row) => row.lendingPoolTrancheUserDetails.id);
|
|
19037
|
+
const lockSharesByLock = groupBy(read.lockShares, (row) => row.userLendingPoolTrancheFixedTermDepositLock.id);
|
|
19038
|
+
const entries = [];
|
|
19039
|
+
for (const pool of user.lendingPoolUserDetails) {
|
|
19040
|
+
for (const position of pool.lendingPoolTrancheUserDetails) {
|
|
19041
|
+
const trancheId = position.tranche.id;
|
|
19042
|
+
const trancheUpdates = (_a = trancheSharesByTranche.get(trancheId)) !== null && _a !== void 0 ? _a : [];
|
|
19043
|
+
const userUpdates = (_b = userSharesByPosition.get(position.id)) !== null && _b !== void 0 ? _b : [];
|
|
19044
|
+
const trancheBalanceNow = parseFloat(position.tranche.balance);
|
|
19045
|
+
const trancheSharesNow = parseFloat(position.tranche.shares);
|
|
19046
|
+
for (const record of (_c = interestByTranche.get(trancheId)) !== null && _c !== void 0 ? _c : []) {
|
|
19047
|
+
const epoch = Number(record.epochId);
|
|
19048
|
+
if (epoch < window.fromEpoch || epoch > window.toEpoch) {
|
|
19049
|
+
continue;
|
|
19050
|
+
}
|
|
19051
|
+
const userSnapshot = sharesAsOf(userUpdates, epoch);
|
|
19052
|
+
const userShares = parseFloat((_d = userSnapshot === null || userSnapshot === void 0 ? void 0 : userSnapshot.shares) !== null && _d !== void 0 ? _d : '0');
|
|
19053
|
+
const locks = position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => {
|
|
19054
|
+
var _a, _b, _c, _d, _e;
|
|
19055
|
+
const lockUpdates = (_a = lockSharesByLock.get(lock.id)) !== null && _a !== void 0 ? _a : [];
|
|
19056
|
+
return {
|
|
19057
|
+
active: parseFloat(lock.epochLockStart) < epoch &&
|
|
19058
|
+
parseFloat(lock.epochLockEnd) >= epoch &&
|
|
19059
|
+
userSnapshot !== undefined,
|
|
19060
|
+
// No snapshot yet = the lock's opening
|
|
19061
|
+
// shares: a lock made after clearing E
|
|
19062
|
+
// starts at E but its first update is
|
|
19063
|
+
// tagged E + 1, and reading that gap as 0
|
|
19064
|
+
// would book its principal as a top-up.
|
|
19065
|
+
sharesAtEpoch: parseFloat((_c = (_b = sharesAsOf(lockUpdates, epoch)) === null || _b === void 0 ? void 0 : _b.shares) !== null && _c !== void 0 ? _c : lock.initialTrancheShares),
|
|
19066
|
+
sharesBeforeEpoch: parseFloat((_e = (_d = sharesAsOf(lockUpdates, epoch - 1)) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : lock.initialTrancheShares),
|
|
19067
|
+
};
|
|
19068
|
+
});
|
|
19069
|
+
const { total } = epochTrancheYield({
|
|
19070
|
+
epochInterest: parseFloat(record.epochInterestAmount),
|
|
19071
|
+
userShares,
|
|
19072
|
+
trancheShares: parseFloat((_f = (_e = sharesAsOf(trancheUpdates, epoch)) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : '0'),
|
|
19073
|
+
trancheBalanceNow,
|
|
19074
|
+
trancheSharesNow,
|
|
19075
|
+
locks,
|
|
19076
|
+
});
|
|
19077
|
+
// Nothing held and nothing topped up: not part of this
|
|
19078
|
+
// lender's history (a new lender's history is empty).
|
|
19079
|
+
if (userShares <= 0 && total === 0)
|
|
19080
|
+
continue;
|
|
19081
|
+
entries.push({
|
|
19082
|
+
epochId: epoch,
|
|
19083
|
+
epochEnd: clock.epochZeroStart +
|
|
19084
|
+
(epoch + 1) * clock.epochDuration,
|
|
19085
|
+
poolId: pool.lendingPool.id,
|
|
19086
|
+
trancheId,
|
|
19087
|
+
netYield: total,
|
|
19088
|
+
});
|
|
19089
|
+
}
|
|
19090
|
+
}
|
|
19091
|
+
}
|
|
19092
|
+
return entries.sort((a, b) => a.epochId - b.epochId ||
|
|
19093
|
+
a.poolId.localeCompare(b.poolId) ||
|
|
19094
|
+
a.trancheId.localeCompare(b.trancheId));
|
|
19095
|
+
}
|
|
19096
|
+
|
|
17827
19097
|
class Portfolio {
|
|
17828
19098
|
constructor(_kasuConfig, signerOrProvider) {
|
|
17829
19099
|
this._kasuConfig = _kasuConfig;
|
|
@@ -17924,7 +19194,7 @@ class Portfolio {
|
|
|
17924
19194
|
performanceFee *
|
|
17925
19195
|
ecosystemFeeRate;
|
|
17926
19196
|
}
|
|
17927
|
-
return (
|
|
19197
|
+
return (totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount;
|
|
17928
19198
|
}
|
|
17929
19199
|
/**
|
|
17930
19200
|
* Calculate the user's weekly KASU bonus rewards (KASU) based on current deposits,
|
|
@@ -17932,7 +19202,7 @@ class Portfolio {
|
|
|
17932
19202
|
*/
|
|
17933
19203
|
calculateWeeklyKsuBonusRewards(params) {
|
|
17934
19204
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17935
|
-
const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice } = params;
|
|
19205
|
+
const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice, } = params;
|
|
17936
19206
|
const totalUserDeposits = userDepositAmounts[0].add(userDepositAmounts[1]);
|
|
17937
19207
|
if (totalUserDeposits.isZero())
|
|
17938
19208
|
return 0;
|
|
@@ -18041,7 +19311,7 @@ class Portfolio {
|
|
|
18041
19311
|
}
|
|
18042
19312
|
getPortfolioLendingData(userAddress, poolOverviews, currentEpoch, provider) {
|
|
18043
19313
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18044
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
19314
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
18045
19315
|
const SECONDS_PER_EPOCH = 604800; // seconds in a week
|
|
18046
19316
|
const portfolioLendingPoolsPromise = [];
|
|
18047
19317
|
const previousEpoch = ethers.BigNumber.from(currentEpoch).sub(ethers.BigNumber.from(1));
|
|
@@ -18051,6 +19321,7 @@ class Portfolio {
|
|
|
18051
19321
|
epochId: parseFloat(currentEpoch),
|
|
18052
19322
|
lastEpochId: previousEpoch.toNumber(),
|
|
18053
19323
|
unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
|
|
19324
|
+
first: YIELD_HISTORY_PAGE_SIZE,
|
|
18054
19325
|
}),
|
|
18055
19326
|
this._userLendingService.getPortfolioUserTrancheBalances(userAddress),
|
|
18056
19327
|
this._userLendingService.getUserPoolBalance(userAddress, poolOverviews.map(({ id }) => id), provider),
|
|
@@ -18088,7 +19359,6 @@ class Portfolio {
|
|
|
18088
19359
|
}
|
|
18089
19360
|
}
|
|
18090
19361
|
let totalInvestments = ethers.BigNumber.from(0);
|
|
18091
|
-
let totalYieldEarnedLastEpoch = 0;
|
|
18092
19362
|
for (const poolOverview of poolOverviews) {
|
|
18093
19363
|
const userPoolBalance = userPoolBalances.find(({ poolId }) => poolId === poolOverview.id);
|
|
18094
19364
|
if (!userPoolBalance) {
|
|
@@ -18096,6 +19366,7 @@ class Portfolio {
|
|
|
18096
19366
|
}
|
|
18097
19367
|
totalInvestments = userPoolBalance.balance.add(totalInvestments);
|
|
18098
19368
|
const tranches = [];
|
|
19369
|
+
let poolYieldEarnedLastEpoch = 0;
|
|
18099
19370
|
const lendingPoolUserDetails = lastEpochData.user.lendingPoolUserDetails.find(({ lendingPool }) => lendingPool.id === poolOverview.id);
|
|
18100
19371
|
if (!lendingPoolUserDetails)
|
|
18101
19372
|
continue;
|
|
@@ -18114,18 +19385,42 @@ class Portfolio {
|
|
|
18114
19385
|
}, depositDetails, fixedLoans: [] }));
|
|
18115
19386
|
continue;
|
|
18116
19387
|
}
|
|
18117
|
-
const
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
const
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
: (
|
|
18128
|
-
|
|
19388
|
+
const lastEpoch = previousEpoch.toNumber();
|
|
19389
|
+
const userHasLastEpochShares = Boolean((_c = lastEpochUserTrancheDetails
|
|
19390
|
+
.lendingPoolTrancheUserEpochSharesUpdates) === null || _c === void 0 ? void 0 : _c.length);
|
|
19391
|
+
const fixedTermDeposits = lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks;
|
|
19392
|
+
// The one per-epoch formula `getYieldHistory` runs too, with
|
|
19393
|
+
// the same share selection — so the last bar of a history
|
|
19394
|
+
// equals these figures.
|
|
19395
|
+
const lastEpochYield = epochTrancheYield({
|
|
19396
|
+
epochInterest: parseFloat((_f = (_e = (_d = lastEpochUserTrancheDetails.tranche
|
|
19397
|
+
.lendingPoolTrancheEpochInterest) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.epochInterestAmount) !== null && _f !== void 0 ? _f : '0'),
|
|
19398
|
+
userShares: parseFloat((_j = (_h = (_g = lastEpochUserTrancheDetails
|
|
19399
|
+
.lendingPoolTrancheUserEpochSharesUpdates) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.shares) !== null && _j !== void 0 ? _j : '0'),
|
|
19400
|
+
trancheShares: parseFloat((_m = (_l = (_k = lastEpochUserTrancheDetails.tranche
|
|
19401
|
+
.lendingPoolTrancheShareUpdates) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.shares) !== null && _m !== void 0 ? _m : '0'),
|
|
19402
|
+
trancheBalanceNow: parseFloat(lastEpochUserTrancheDetails.tranche.balance),
|
|
19403
|
+
trancheSharesNow: parseFloat(lastEpochUserTrancheDetails.tranche.shares),
|
|
19404
|
+
locks: fixedTermDeposits.map((fixedTermDeposit) => {
|
|
19405
|
+
var _a, _b, _c, _d, _e, _f;
|
|
19406
|
+
return ({
|
|
19407
|
+
active: parseFloat(fixedTermDeposit.epochLockStart) <
|
|
19408
|
+
lastEpoch &&
|
|
19409
|
+
parseFloat(fixedTermDeposit.epochLockEnd) >=
|
|
19410
|
+
lastEpoch &&
|
|
19411
|
+
userHasLastEpochShares,
|
|
19412
|
+
// No snapshot = the lock's opening shares, exactly
|
|
19413
|
+
// as `getYieldHistory` reads it: a lock made after
|
|
19414
|
+
// clearing E starts at E with its first update
|
|
19415
|
+
// tagged E + 1. Never the CURRENT shares, which
|
|
19416
|
+
// would carry a top-up from a clearing that ran
|
|
19417
|
+
// after the last settled epoch.
|
|
19418
|
+
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),
|
|
19419
|
+
sharesBeforeEpoch: parseFloat((_f = (_e = (_d = fixedTermDeposit
|
|
19420
|
+
.userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : fixedTermDeposit.initialTrancheShares),
|
|
19421
|
+
});
|
|
19422
|
+
}),
|
|
19423
|
+
});
|
|
18129
19424
|
const userTrancheBalance = {
|
|
18130
19425
|
balance: '0',
|
|
18131
19426
|
yieldEarned: 0,
|
|
@@ -18140,47 +19435,20 @@ class Portfolio {
|
|
|
18140
19435
|
}
|
|
18141
19436
|
}
|
|
18142
19437
|
let totalLifetimeFtdYield = 0;
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
var _a, _b, _c, _d, _e;
|
|
19438
|
+
const fixedLoans = fixedTermDeposits.map((fixedTermDeposit, lockIndex) => {
|
|
19439
|
+
var _a, _b;
|
|
18146
19440
|
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);
|
|
19441
|
+
.lendingPoolTrancheFixedTermConfig.configId]) !== null && _a !== void 0 ? _a : [];
|
|
19442
|
+
const lastEpochYieldEarned = (_b = lastEpochYield.locks[lockIndex]) !== null && _b !== void 0 ? _b : 0;
|
|
19443
|
+
const lifetimeYieldEarned = this.calculateFixedTermDepositEarnings(fixedTermDeposit, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares);
|
|
18174
19444
|
totalLifetimeFtdYield += lifetimeYieldEarned;
|
|
18175
|
-
totalYieldEarnedLastEpoch += lastEpochYieldEarned;
|
|
18176
19445
|
const lockDuration = parseFloat(fixedTermDeposit
|
|
18177
19446
|
.lendingPoolTrancheFixedTermConfig
|
|
18178
19447
|
.epochLockDuration) * SECONDS_PER_EPOCH;
|
|
18179
19448
|
return {
|
|
18180
19449
|
lockId: fixedTermDeposit.lockId,
|
|
18181
19450
|
configId: fixedTermDeposit
|
|
18182
|
-
.lendingPoolTrancheFixedTermConfig
|
|
18183
|
-
.configId,
|
|
19451
|
+
.lendingPoolTrancheFixedTermConfig.configId,
|
|
18184
19452
|
epochLockDuration: fixedTermDeposit
|
|
18185
19453
|
.lendingPoolTrancheFixedTermConfig
|
|
18186
19454
|
.epochLockDuration,
|
|
@@ -18190,9 +19458,7 @@ class Portfolio {
|
|
|
18190
19458
|
.epochInterestRate,
|
|
18191
19459
|
epochLockStart: fixedTermDeposit.epochLockStart,
|
|
18192
19460
|
amount: this._userLendingService
|
|
18193
|
-
.convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche
|
|
18194
|
-
.balance, lastEpochUserTrancheDetails.tranche
|
|
18195
|
-
.shares)
|
|
19461
|
+
.convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares)
|
|
18196
19462
|
.toString(),
|
|
18197
19463
|
endTime: parseInt(fixedTermDeposit.createdOn) +
|
|
18198
19464
|
lockDuration,
|
|
@@ -18200,20 +19466,22 @@ class Portfolio {
|
|
|
18200
19466
|
isWithdrawalRequested: fixedTermDeposit.isWithdrawalRequested,
|
|
18201
19467
|
startTime: parseInt(fixedTermDeposit.createdOn),
|
|
18202
19468
|
yieldEarnings: {
|
|
18203
|
-
lastEpoch:
|
|
19469
|
+
lastEpoch: this.precisionToString(lastEpochYieldEarned),
|
|
18204
19470
|
lifetime: this.precisionToString(lifetimeYieldEarned),
|
|
18205
19471
|
},
|
|
18206
19472
|
depositDetails: fixedLoanDepositDetails,
|
|
18207
19473
|
};
|
|
18208
19474
|
});
|
|
18209
|
-
|
|
19475
|
+
// This POOL's last-epoch total: the tranche totals summed, the
|
|
19476
|
+
// locks counted once (inside `total`).
|
|
19477
|
+
poolYieldEarnedLastEpoch += lastEpochYield.total;
|
|
18210
19478
|
tranches.push(Object.assign(Object.assign({}, tranche), { investedAmount: userTrancheBalance, yieldEarnings: {
|
|
18211
|
-
lastEpoch:
|
|
19479
|
+
lastEpoch: this.precisionToString(lastEpochYield.variable),
|
|
18212
19480
|
lifetime: this.precisionToString(totalLifetimeFtdYield), // passing in total FTD yield to be subtracted by total trache lifetime later down
|
|
18213
19481
|
}, fixedLoans,
|
|
18214
19482
|
depositDetails }));
|
|
18215
19483
|
}
|
|
18216
|
-
portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch:
|
|
19484
|
+
portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: this.precisionToString(poolYieldEarnedLastEpoch), totalInvestedAmount: userPoolBalance.balance.toString(), totalYieldEarningsLifetime: userPoolBalance.yieldEarned.toString(), tranches }));
|
|
18217
19485
|
}
|
|
18218
19486
|
const portfolioLendingPools = portfolioLendingPoolsPromise.map((pool) => (Object.assign(Object.assign({}, pool), { tranches: pool.tranches.map((tranche) => {
|
|
18219
19487
|
const trancheBalance = tranche.investedAmount;
|
|
@@ -18231,7 +19499,87 @@ class Portfolio {
|
|
|
18231
19499
|
!utils$2.parseUnits(pool.totalYieldEarningsLifetime).isZero());
|
|
18232
19500
|
});
|
|
18233
19501
|
}
|
|
19502
|
+
/**
|
|
19503
|
+
* The lender's settled yield per cycle (epoch), per pool and tranche —
|
|
19504
|
+
* what the chain credited at each close, NET of the performance fee
|
|
19505
|
+
* (`domain/yield-history.ts`), in the deployment's own stable token. Each
|
|
19506
|
+
* entry runs the same `epochTrancheYield` as `getPortfolioLendingData`'s
|
|
19507
|
+
* last-epoch figures: a tranche's `yieldEarnings.lastEpoch` plus its
|
|
19508
|
+
* locks' equals its entry for the epoch of the tranche's most recently
|
|
19509
|
+
* APPLIED interest (older than the last cycle for a pool that has not
|
|
19510
|
+
* cleared since; no entry, and a figure of 0, when the lender held
|
|
19511
|
+
* nothing at that epoch).
|
|
19512
|
+
*
|
|
19513
|
+
* Sum entries with `aggregateYieldHistory` (all strategies, or one with
|
|
19514
|
+
* `{ poolId }`). Only epochs in which the lender held shares appear — a
|
|
19515
|
+
* new lender gets `[]`. Pools in `UNUSED_LENDING_POOL_IDS` are left out,
|
|
19516
|
+
* as in `lendingPortfolioQuery`; Directus enablement is NOT applied.
|
|
19517
|
+
*
|
|
19518
|
+
* Cost: three parallel `SystemVariables` reads and the positions query in
|
|
19519
|
+
* one round, then one subgraph request per page of the longest series
|
|
19520
|
+
* (1000 rows a page) — normally two subgraph round trips in all, never one
|
|
19521
|
+
* per epoch.
|
|
19522
|
+
*
|
|
19523
|
+
* On a LITE deployment, a subgraph that lacks the entities (a validation
|
|
19524
|
+
* error naming them) yields `[]`; on a full deployment that error throws,
|
|
19525
|
+
* like every other failure (`isEmptyHistoryError`).
|
|
19526
|
+
*
|
|
19527
|
+
* @throws RangeError on a malformed `fromEpoch` / `count`, before any
|
|
19528
|
+
* network read.
|
|
19529
|
+
*/
|
|
19530
|
+
getYieldHistory(userAddress_1) {
|
|
19531
|
+
return __awaiter(this, arguments, void 0, function* (userAddress, options = {}) {
|
|
19532
|
+
validateYieldHistoryOptions(options);
|
|
19533
|
+
try {
|
|
19534
|
+
const [currentEpoch, epochZeroStart, epochDuration, positions] = yield Promise.all([
|
|
19535
|
+
this._systemVariablesAbi.currentEpochNumber(),
|
|
19536
|
+
this._systemVariablesAbi.epochStartTimestamp(0),
|
|
19537
|
+
this._systemVariablesAbi.epochDuration(),
|
|
19538
|
+
this._graph.request(yieldHistoryPositionsQuery, {
|
|
19539
|
+
userAddress: userAddress.toLowerCase(),
|
|
19540
|
+
unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
|
|
19541
|
+
first: YIELD_HISTORY_PAGE_SIZE,
|
|
19542
|
+
}),
|
|
19543
|
+
]);
|
|
19544
|
+
const window = resolveYieldHistoryWindow(options, currentEpoch.toNumber());
|
|
19545
|
+
if (!window || !positions.user)
|
|
19546
|
+
return [];
|
|
19547
|
+
const userTranches = [];
|
|
19548
|
+
const tranches = new Set();
|
|
19549
|
+
const locks = [];
|
|
19550
|
+
for (const pool of positions.user.lendingPoolUserDetails) {
|
|
19551
|
+
for (const position of pool.lendingPoolTrancheUserDetails) {
|
|
19552
|
+
userTranches.push(position.id);
|
|
19553
|
+
tranches.add(position.tranche.id);
|
|
19554
|
+
for (const lock of position.userLendingPoolTrancheFixedTermDepositLocks) {
|
|
19555
|
+
locks.push(lock.id);
|
|
19556
|
+
}
|
|
19557
|
+
}
|
|
19558
|
+
}
|
|
19559
|
+
if (userTranches.length === 0)
|
|
19560
|
+
return [];
|
|
19561
|
+
const { rows } = yield fetchYieldHistorySeries((variables) => this._graph.request(yieldHistoryPageQuery, variables), { tranches: [...tranches], userTranches, locks }, window);
|
|
19562
|
+
return buildYieldHistory(Object.assign({ positions }, rows), window, {
|
|
19563
|
+
epochZeroStart: epochZeroStart.toNumber(),
|
|
19564
|
+
epochDuration: epochDuration.toNumber(),
|
|
19565
|
+
});
|
|
19566
|
+
}
|
|
19567
|
+
catch (error) {
|
|
19568
|
+
if (isEmptyHistoryError(error, this._isLiteDeployment))
|
|
19569
|
+
return [];
|
|
19570
|
+
throw error;
|
|
19571
|
+
}
|
|
19572
|
+
});
|
|
19573
|
+
}
|
|
19574
|
+
/**
|
|
19575
|
+
* A money figure as a plain decimal string. Below a millionth of a token
|
|
19576
|
+
* (the stable asset's own precision) is float noise, never money, so it
|
|
19577
|
+
* is `'0'` — not `'1e-7'`, which `parseUnits` rejects, nor `'-3e-12'`. A
|
|
19578
|
+
* small negative remainder (above −0.0001) is `'0'` as well.
|
|
19579
|
+
*/
|
|
18234
19580
|
precisionToString(val) {
|
|
19581
|
+
if (Math.abs(val) < 1e-6)
|
|
19582
|
+
return '0';
|
|
18235
19583
|
if (val < 0 && val > -1e-4) {
|
|
18236
19584
|
return '0';
|
|
18237
19585
|
}
|
|
@@ -24188,11 +25536,11 @@ class Swapper {
|
|
|
24188
25536
|
}
|
|
24189
25537
|
|
|
24190
25538
|
class KasuSdk {
|
|
24191
|
-
constructor(config, signerOrProvider) {
|
|
25539
|
+
constructor(config, signerOrProvider, options = {}) {
|
|
24192
25540
|
this._graphClient = new GraphQLClient(config.subgraphUrl);
|
|
24193
25541
|
this.Locking = new KSULocking(config, signerOrProvider);
|
|
24194
25542
|
this.UserLending = new UserLending(config, signerOrProvider);
|
|
24195
|
-
this.DataService = new DataService(config, signerOrProvider);
|
|
25543
|
+
this.DataService = new DataService(config, signerOrProvider, options.historyProvider);
|
|
24196
25544
|
this.Portfolio = new Portfolio(config, signerOrProvider);
|
|
24197
25545
|
this.Swapper = new Swapper();
|
|
24198
25546
|
}
|
|
@@ -24200,7 +25548,7 @@ class KasuSdk {
|
|
|
24200
25548
|
|
|
24201
25549
|
class SdkConfig {
|
|
24202
25550
|
constructor(options) {
|
|
24203
|
-
var _a, _b, _c, _d;
|
|
25551
|
+
var _a, _b, _c, _d, _e;
|
|
24204
25552
|
this.subgraphUrl = options.subgraphUrl;
|
|
24205
25553
|
this.contracts = options.contracts;
|
|
24206
25554
|
this.directusUrl = (_a = options.directusUrl) !== null && _a !== void 0 ? _a : '';
|
|
@@ -24215,6 +25563,8 @@ class SdkConfig {
|
|
|
24215
25563
|
this.isLiteDeployment = (_b = options.isLiteDeployment) !== null && _b !== void 0 ? _b : false;
|
|
24216
25564
|
this.poolMetadataMapping = (_c = options.poolMetadataMapping) !== null && _c !== void 0 ? _c : {};
|
|
24217
25565
|
this.stableAssetDecimals = (_d = options.stableAssetDecimals) !== null && _d !== void 0 ? _d : 6;
|
|
25566
|
+
this.historyRpcUrls = [...((_e = options.historyRpcUrls) !== null && _e !== void 0 ? _e : [])];
|
|
25567
|
+
this.chainId = options.chainId;
|
|
24218
25568
|
}
|
|
24219
25569
|
}
|
|
24220
25570
|
|
|
@@ -24521,6 +25871,27 @@ const XDC_RPC_URLS = [
|
|
|
24521
25871
|
'https://rpc.xinfin.network',
|
|
24522
25872
|
'https://rpc.primenumbers.xyz/',
|
|
24523
25873
|
];
|
|
25874
|
+
/**
|
|
25875
|
+
* XDC RPCs that serve PAST state, for `getPoolTvlHistory` (each verified
|
|
25876
|
+
* with `externalTVLOfPool` at old blocks, CORS `*`), in fallback order. Not
|
|
25877
|
+
* `rpc.xdcrpc.com`, which refuses older blocks, and never `rpc.xdc.org` /
|
|
25878
|
+
* `erpc.xdc.org`. Public and rate-limited.
|
|
25879
|
+
*/
|
|
25880
|
+
const XDC_HISTORY_RPC_URLS = [
|
|
25881
|
+
'https://rpc.primenumbers.xyz/',
|
|
25882
|
+
'https://rpc.ankr.com/xdc',
|
|
25883
|
+
];
|
|
25884
|
+
/**
|
|
25885
|
+
* Base RPCs that serve PAST state, in fallback order (each verified with
|
|
25886
|
+
* `externalTVLOfPool` at old blocks; all answer CORS). Not
|
|
25887
|
+
* `base-rpc.publicnode.com`, the default read RPC, which refuses old state.
|
|
25888
|
+
* Public and rate-limited.
|
|
25889
|
+
*/
|
|
25890
|
+
const BASE_HISTORY_RPC_URLS = [
|
|
25891
|
+
'https://mainnet.base.org',
|
|
25892
|
+
'https://base.drpc.org',
|
|
25893
|
+
'https://1rpc.io/base',
|
|
25894
|
+
];
|
|
24524
25895
|
/**
|
|
24525
25896
|
* Built-in chain configurations for Kasu-supported networks.
|
|
24526
25897
|
*
|
|
@@ -24564,6 +25935,7 @@ const CHAIN_CONFIGS = {
|
|
|
24564
25935
|
'https://base-rpc.publicnode.com',
|
|
24565
25936
|
'https://mainnet.base.org',
|
|
24566
25937
|
],
|
|
25938
|
+
historyRpcUrls: BASE_HISTORY_RPC_URLS,
|
|
24567
25939
|
blockExplorerUrl: 'https://basescan.org',
|
|
24568
25940
|
},
|
|
24569
25941
|
xdc: {
|
|
@@ -24600,6 +25972,7 @@ const CHAIN_CONFIGS = {
|
|
|
24600
25972
|
currencyCode: 'AUD',
|
|
24601
25973
|
},
|
|
24602
25974
|
rpcUrls: XDC_RPC_URLS,
|
|
25975
|
+
historyRpcUrls: XDC_HISTORY_RPC_URLS,
|
|
24603
25976
|
blockExplorerUrl: 'https://xdcscan.com',
|
|
24604
25977
|
},
|
|
24605
25978
|
'xdc-usdc': {
|
|
@@ -24632,6 +26005,7 @@ const CHAIN_CONFIGS = {
|
|
|
24632
26005
|
currencyCode: 'USD',
|
|
24633
26006
|
},
|
|
24634
26007
|
rpcUrls: XDC_RPC_URLS,
|
|
26008
|
+
historyRpcUrls: XDC_HISTORY_RPC_URLS,
|
|
24635
26009
|
blockExplorerUrl: 'https://xdcscan.com',
|
|
24636
26010
|
},
|
|
24637
26011
|
plume: {
|
|
@@ -27743,6 +29117,28 @@ class StrategiesFacade {
|
|
|
27743
29117
|
return yield this._dataService.getPerformanceFee();
|
|
27744
29118
|
});
|
|
27745
29119
|
}
|
|
29120
|
+
/**
|
|
29121
|
+
* The strategy's TVL (total lent by everyone) at every cycle boundary,
|
|
29122
|
+
* then as of now — the data behind a TVL chart. Ascending in time; the
|
|
29123
|
+
* last point (`kind: 'current'`) equals `strategy.tvl.total` when both
|
|
29124
|
+
* calls' reads succeed (see `PoolTvlHistoryEntry`).
|
|
29125
|
+
*
|
|
29126
|
+
* `tvl` is in display units of the deployment's OWN stable token (USDC on
|
|
29127
|
+
* Base and XDC USDC, AUDD on XDC AUDD); `timestamp` is unix SECONDS.
|
|
29128
|
+
* `{ count }` = the newest `count` epochs, `{ fromEpoch }` = from that
|
|
29129
|
+
* epoch on; `timeoutMs` (default 30 s) and `signal` bound the call. A
|
|
29130
|
+
* malformed option throws before any network read. See
|
|
29131
|
+
* `DataService.getPoolTvlHistory` for the reads, the cost and the errors.
|
|
29132
|
+
*
|
|
29133
|
+
* ```ts
|
|
29134
|
+
* const points = await kasu.strategies.getPoolTvlHistory(poolId, { count: 52 });
|
|
29135
|
+
* ```
|
|
29136
|
+
*/
|
|
29137
|
+
getPoolTvlHistory(poolId_1) {
|
|
29138
|
+
return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
|
|
29139
|
+
return yield this._dataService.getPoolTvlHistory(poolId, options);
|
|
29140
|
+
});
|
|
29141
|
+
}
|
|
27746
29142
|
/**
|
|
27747
29143
|
* Fetch a single strategy by pool ID. Returns `null` if not found.
|
|
27748
29144
|
*/
|
|
@@ -28254,6 +29650,40 @@ class PortfolioFacade {
|
|
|
28254
29650
|
return { pools, summary };
|
|
28255
29651
|
});
|
|
28256
29652
|
}
|
|
29653
|
+
/**
|
|
29654
|
+
* The lender's settled yield per cycle, per pool and tranche: what each
|
|
29655
|
+
* close credited them, NET of the performance fee (the chain credits it
|
|
29656
|
+
* net), in display units of the deployment's own stable token (AUDD on
|
|
29657
|
+
* XDC AUDD). `epochEnd` is unix seconds. Net only — no gross is offered.
|
|
29658
|
+
*
|
|
29659
|
+
* Validates `options` before any read (`RangeError` on a malformed one).
|
|
29660
|
+
*
|
|
29661
|
+
* ```ts
|
|
29662
|
+
* const entries = await kasu.portfolio.getYieldHistory('0xUser...', { count: 52 });
|
|
29663
|
+
* const allStrategies = aggregateYieldHistory(entries);
|
|
29664
|
+
* const oneStrategy = aggregateYieldHistory(entries, { poolId });
|
|
29665
|
+
* ```
|
|
29666
|
+
*
|
|
29667
|
+
* `getPositions`' `totalYieldEarningsLastEpoch` for a pool equals that
|
|
29668
|
+
* pool's bar for the epoch of its most recently APPLIED interest — which
|
|
29669
|
+
* is older than the last cycle for a pool that has not cleared since (the
|
|
29670
|
+
* bar carries that older `epochId`). A lender who held nothing at that
|
|
29671
|
+
* epoch has no bar there and a figure of 0; their newest bar is from
|
|
29672
|
+
* when they last held shares.
|
|
29673
|
+
*
|
|
29674
|
+
* For a fixed-term lock holder the bars can sum to slightly more than the
|
|
29675
|
+
* lifetime interest (up to about 0.2 %), because each top-up is valued at
|
|
29676
|
+
* today's share price — show "earned to date" from the lifetime figure,
|
|
29677
|
+
* never from the sum of the bars.
|
|
29678
|
+
*
|
|
29679
|
+
* `[]` for a lender with no history, and on a LITE deployment whose
|
|
29680
|
+
* subgraph lacks the entities; on a full deployment that error throws.
|
|
29681
|
+
*/
|
|
29682
|
+
getYieldHistory(userAddress, options) {
|
|
29683
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29684
|
+
return yield this._portfolio.getYieldHistory(userAddress, options);
|
|
29685
|
+
});
|
|
29686
|
+
}
|
|
28257
29687
|
/**
|
|
28258
29688
|
* Fetch a user's full transaction history (deposits, withdrawals, cancellations).
|
|
28259
29689
|
*/
|
|
@@ -28310,12 +29740,13 @@ class PortfolioFacade {
|
|
|
28310
29740
|
* ```
|
|
28311
29741
|
*/
|
|
28312
29742
|
class Kasu {
|
|
28313
|
-
constructor(sdk, chainConfig, signerOrProvider, configOverrides) {
|
|
29743
|
+
constructor(sdk, chainConfig, signerOrProvider, configOverrides, historyProvider) {
|
|
28314
29744
|
var _a, _b;
|
|
28315
29745
|
this._sdk = sdk;
|
|
28316
29746
|
this._chainConfig = chainConfig;
|
|
28317
29747
|
this._signerOrProvider = signerOrProvider;
|
|
28318
29748
|
this._configOverrides = configOverrides;
|
|
29749
|
+
this._historyProvider = historyProvider;
|
|
28319
29750
|
this.strategies = new StrategiesFacade(sdk.DataService, sdk.UserLending, normaliseMapping((_a = configOverrides.poolMetadataMapping) !== null && _a !== void 0 ? _a : chainConfig.poolMetadataMapping));
|
|
28320
29751
|
// Derived ONCE and handed to every facade that needs it. Each of them
|
|
28321
29752
|
// re-deriving `Signer.isSigner` is how two facades come to disagree
|
|
@@ -28353,7 +29784,7 @@ class Kasu {
|
|
|
28353
29784
|
* ```
|
|
28354
29785
|
*/
|
|
28355
29786
|
static create(options) {
|
|
28356
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
29787
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
28357
29788
|
const chainConfig = resolveChainConfig(options.chain);
|
|
28358
29789
|
const overrides = (_a = options.configOverrides) !== null && _a !== void 0 ? _a : {};
|
|
28359
29790
|
const sdkConfig = new SdkConfig({
|
|
@@ -28368,12 +29799,18 @@ class Kasu {
|
|
|
28368
29799
|
// of 6. Every live deployment happens to be 6dp today, so the
|
|
28369
29800
|
// default was right by luck; the next one need not be.
|
|
28370
29801
|
stableAssetDecimals: (_h = overrides.stableAssetDecimals) !== null && _h !== void 0 ? _h : (_j = stableAssetOf(chainConfig)) === null || _j === void 0 ? void 0 : _j.decimals,
|
|
29802
|
+
historyRpcUrls: (_k = overrides.historyRpcUrls) !== null && _k !== void 0 ? _k : chainConfig.historyRpcUrls,
|
|
29803
|
+
chainId: (_l = overrides.chainId) !== null && _l !== void 0 ? _l : chainConfig.chainId,
|
|
28371
29804
|
});
|
|
28372
|
-
const signerOrProvider = (
|
|
29805
|
+
const signerOrProvider = (_m = options.signerOrProvider) !== null && _m !== void 0 ? _m : defaultProvider(chainConfig, typeof options.chain === 'string'
|
|
28373
29806
|
? options.chain
|
|
28374
29807
|
: chainConfig.name);
|
|
28375
|
-
|
|
28376
|
-
|
|
29808
|
+
// An explicit history provider wins; otherwise DataService builds
|
|
29809
|
+
// them from `historyRpcUrls`, pinned to `chainId` (the one place).
|
|
29810
|
+
const sdk = new KasuSdk(sdkConfig, signerOrProvider, {
|
|
29811
|
+
historyProvider: options.historyProvider,
|
|
29812
|
+
});
|
|
29813
|
+
return new Kasu(sdk, chainConfig, signerOrProvider, overrides, options.historyProvider);
|
|
28377
29814
|
}
|
|
28378
29815
|
/**
|
|
28379
29816
|
* A NEW instance bound to `signer`, with the same chain config and the same
|
|
@@ -28393,6 +29830,7 @@ class Kasu {
|
|
|
28393
29830
|
chain: this._chainConfig,
|
|
28394
29831
|
signerOrProvider: signer,
|
|
28395
29832
|
configOverrides: this._configOverrides,
|
|
29833
|
+
historyProvider: this._historyProvider,
|
|
28396
29834
|
});
|
|
28397
29835
|
}
|
|
28398
29836
|
/**
|
|
@@ -29204,9 +30642,13 @@ exports.MIN_LENDING_AMOUNT_FALLBACK = MIN_LENDING_AMOUNT_FALLBACK;
|
|
|
29204
30642
|
exports.MIN_TRANCHE_CAPACITY = MIN_TRANCHE_CAPACITY;
|
|
29205
30643
|
exports.NOT_A_WITHDRAWAL_CALL_MESSAGE = NOT_A_WITHDRAWAL_CALL_MESSAGE;
|
|
29206
30644
|
exports.NO_DIRECTUS_URL_MESSAGE = NO_DIRECTUS_URL_MESSAGE;
|
|
30645
|
+
exports.NO_HISTORY_PROVIDER_MESSAGE = NO_HISTORY_PROVIDER_MESSAGE;
|
|
29207
30646
|
exports.NO_KYC_PARAMS_MESSAGE = NO_KYC_PARAMS_MESSAGE;
|
|
29208
30647
|
exports.NO_SPENDER_MESSAGE = NO_SPENDER_MESSAGE;
|
|
29209
30648
|
exports.NO_WITHDRAWAL_ENCODER_MESSAGE = NO_WITHDRAWAL_ENCODER_MESSAGE;
|
|
30649
|
+
exports.PastStateUnavailableError = PastStateUnavailableError;
|
|
30650
|
+
exports.PoolTvlHistoryAbortedError = PoolTvlHistoryAbortedError;
|
|
30651
|
+
exports.PoolTvlHistoryTimeoutError = PoolTvlHistoryTimeoutError;
|
|
29210
30652
|
exports.PortfolioFacade = PortfolioFacade;
|
|
29211
30653
|
exports.READ_ONLY_MESSAGE = READ_ONLY_MESSAGE;
|
|
29212
30654
|
exports.RIXON_CAPITAL = RIXON_CAPITAL;
|
|
@@ -29219,6 +30661,7 @@ exports.StrategiesFacade = StrategiesFacade;
|
|
|
29219
30661
|
exports.UPPER_MEZZANINE = UPPER_MEZZANINE;
|
|
29220
30662
|
exports.WithdrawFlow = WithdrawFlow;
|
|
29221
30663
|
exports.accruedInterest = accruedInterest;
|
|
30664
|
+
exports.aggregateYieldHistory = aggregateYieldHistory;
|
|
29222
30665
|
exports.apyToEpochRate = apyToEpochRate;
|
|
29223
30666
|
exports.asContractType = asContractType;
|
|
29224
30667
|
exports.auMinimumRemaining = auMinimumRemaining;
|
|
@@ -29249,6 +30692,7 @@ exports.encodeRequestWithdrawalCall = encodeRequestWithdrawalCall;
|
|
|
29249
30692
|
exports.encodeStableTransfer = encodeStableTransfer;
|
|
29250
30693
|
exports.encodeWithdrawAtExpiryCall = encodeWithdrawAtExpiryCall;
|
|
29251
30694
|
exports.epochRateToApy = epochRateToApy;
|
|
30695
|
+
exports.epochTrancheYield = epochTrancheYield;
|
|
29252
30696
|
exports.explorerAddressUrl = explorerAddressUrl;
|
|
29253
30697
|
exports.explorerTxUrl = explorerTxUrl;
|
|
29254
30698
|
exports.extractRevertData = extractRevertData;
|
|
@@ -29283,6 +30727,7 @@ exports.pickHighestYieldTranche = pickHighestYieldTranche;
|
|
|
29283
30727
|
exports.pickShortDescription = pickShortDescription;
|
|
29284
30728
|
exports.poolAllTranchesFull = poolAllTranchesFull;
|
|
29285
30729
|
exports.poolMaxApy = poolMaxApy;
|
|
30730
|
+
exports.poolTvl = poolTvl;
|
|
29286
30731
|
exports.projectedAnnualEarnings = projectedAnnualEarnings;
|
|
29287
30732
|
exports.proveDepositInReceipt = proveDepositInReceipt;
|
|
29288
30733
|
exports.proveUserOperationInReceipt = proveUserOperationInReceipt;
|