@gearbox-protocol/sdk 14.0.0-next.1 → 14.0.0-next.2
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/dist/cjs/plugins/apy/ApyPlugin.js +266 -0
- package/dist/cjs/plugins/apy/apy-cache.js +120 -0
- package/dist/cjs/plugins/apy/apy-parser.js +169 -0
- package/dist/cjs/plugins/{pools-history/index.js → apy/constants.js} +14 -7
- package/dist/cjs/plugins/apy/index.js +34 -0
- package/dist/cjs/plugins/apy/pool-apy-types.js +16 -0
- package/dist/cjs/plugins/apy/pool-apy-utils.js +141 -0
- package/dist/cjs/rewards/rewards/extra-apy.js +10 -8
- package/dist/esm/plugins/apy/ApyPlugin.js +255 -0
- package/dist/esm/plugins/apy/apy-cache.js +86 -0
- package/dist/esm/plugins/apy/apy-parser.js +143 -0
- package/dist/esm/plugins/apy/constants.js +6 -0
- package/dist/esm/plugins/apy/index.js +7 -0
- package/dist/esm/plugins/apy/pool-apy-utils.js +113 -0
- package/dist/esm/plugins/apy/types.js +0 -0
- package/dist/esm/rewards/rewards/extra-apy.js +10 -8
- package/dist/types/src/plugins/apy/ApyPlugin.d.ts +46 -0
- package/dist/types/src/plugins/apy/apy-cache.d.ts +28 -0
- package/dist/types/src/plugins/apy/apy-parser.d.ts +5 -0
- package/dist/types/src/plugins/apy/constants.d.ts +2 -0
- package/dist/types/src/plugins/apy/index.d.ts +7 -0
- package/dist/types/src/plugins/apy/pool-apy-types.d.ts +41 -0
- package/dist/types/src/plugins/apy/pool-apy-utils.d.ts +73 -0
- package/dist/types/src/plugins/apy/types.d.ts +37 -0
- package/dist/types/src/rewards/rewards/api.d.ts +10 -1
- package/dist/types/src/rewards/rewards/common.d.ts +0 -10
- package/dist/types/src/rewards/rewards/extra-apy.d.ts +4 -6
- package/package.json +1 -1
- package/dist/cjs/plugins/pools-history/Pools7DAgoPlugin.js +0 -108
- package/dist/esm/plugins/pools-history/Pools7DAgoPlugin.js +0 -90
- package/dist/esm/plugins/pools-history/index.js +0 -2
- package/dist/types/src/plugins/pools-history/Pools7DAgoPlugin.d.ts +0 -20
- package/dist/types/src/plugins/pools-history/index.d.ts +0 -2
- package/dist/types/src/plugins/pools-history/types.d.ts +0 -9
- /package/dist/cjs/plugins/{pools-history → apy}/package.json +0 -0
- /package/dist/cjs/plugins/{pools-history → apy}/types.js +0 -0
- /package/dist/esm/plugins/{pools-history → apy}/package.json +0 -0
- /package/dist/esm/plugins/{pools-history/types.js → apy/pool-apy-types.js} +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var pool_apy_utils_exports = {};
|
|
20
|
+
__export(pool_apy_utils_exports, {
|
|
21
|
+
calculatePoolFullAPY: () => calculatePoolFullAPY,
|
|
22
|
+
calculatePoolFullAPY7DAgo: () => calculatePoolFullAPY7DAgo,
|
|
23
|
+
calculatePoolPoints: () => calculatePoolPoints,
|
|
24
|
+
calculateSupplyApy7d: () => calculateSupplyApy7d,
|
|
25
|
+
getPoolExtraAPY: () => getPoolExtraAPY
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(pool_apy_utils_exports);
|
|
28
|
+
var import_constants = require("../../sdk/constants/index.js");
|
|
29
|
+
var import_formatter = require("../../sdk/utils/formatter.js");
|
|
30
|
+
const SCALE = 1000000n;
|
|
31
|
+
const WEEKS_PER_YEAR = 54n;
|
|
32
|
+
function getPoolExtraAPY(lookupAddresses, poolExtraAPYList) {
|
|
33
|
+
if (!poolExtraAPYList) return [];
|
|
34
|
+
const result = [];
|
|
35
|
+
for (const addr of lookupAddresses) {
|
|
36
|
+
const extra = poolExtraAPYList[addr.toLowerCase()];
|
|
37
|
+
if (extra) {
|
|
38
|
+
result.push(...extra);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function calculateSupplyApy7d(currentDieselRate, currentSupplyRate, dieselRate7DAgo) {
|
|
44
|
+
if (dieselRate7DAgo > currentDieselRate) {
|
|
45
|
+
return (0, import_formatter.rayToNumber)(currentSupplyRate * SCALE) / Number(import_constants.PERCENTAGE_FACTOR);
|
|
46
|
+
}
|
|
47
|
+
const apy = (currentDieselRate * import_constants.RAY / dieselRate7DAgo - import_constants.RAY) * WEEKS_PER_YEAR;
|
|
48
|
+
return (0, import_formatter.rayToNumber)(apy * SCALE) / Number(import_constants.PERCENTAGE_FACTOR);
|
|
49
|
+
}
|
|
50
|
+
function calculatePoolFullAPY({
|
|
51
|
+
depositAPY,
|
|
52
|
+
underlyingAPY,
|
|
53
|
+
extraAPY,
|
|
54
|
+
currentExternalList
|
|
55
|
+
}) {
|
|
56
|
+
const baseAPY = [
|
|
57
|
+
{ type: "supplyAPY", apy: depositAPY },
|
|
58
|
+
...underlyingAPY > 0 ? [
|
|
59
|
+
{
|
|
60
|
+
type: "tokenYield",
|
|
61
|
+
apy: Number(underlyingAPY) / Number(import_constants.PERCENTAGE_FACTOR)
|
|
62
|
+
}
|
|
63
|
+
] : []
|
|
64
|
+
];
|
|
65
|
+
const filteredExtra = [...extraAPY ?? []].filter((r) => r.apy > 0);
|
|
66
|
+
const baseAPYTotal = baseAPY.reduce((s, r) => s + (r.apy || 0), 0);
|
|
67
|
+
const extraAPYTotal = filteredExtra.reduce((s, r) => s + (r.apy || 0), 0);
|
|
68
|
+
const total = baseAPYTotal + extraAPYTotal;
|
|
69
|
+
const externalAPY = resolveExternalAPY(currentExternalList, total);
|
|
70
|
+
return {
|
|
71
|
+
totalAPY: total,
|
|
72
|
+
baseAPY,
|
|
73
|
+
extraAPY: filteredExtra,
|
|
74
|
+
extraAPYTotal,
|
|
75
|
+
externalAPY
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function resolveExternalAPY(list, baseTotal) {
|
|
79
|
+
const first = list?.[0];
|
|
80
|
+
if (!first) return void 0;
|
|
81
|
+
return {
|
|
82
|
+
...first,
|
|
83
|
+
totalValue: baseTotal + first.value
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function calculatePoolFullAPY7DAgo({
|
|
87
|
+
supplyAPY7DAgo,
|
|
88
|
+
depositAPY,
|
|
89
|
+
poolAPY
|
|
90
|
+
}) {
|
|
91
|
+
const {
|
|
92
|
+
baseAPY = [],
|
|
93
|
+
extraAPYTotal = 0,
|
|
94
|
+
extraAPY = [],
|
|
95
|
+
externalAPY
|
|
96
|
+
} = poolAPY ?? {};
|
|
97
|
+
const base = [
|
|
98
|
+
{ apy: supplyAPY7DAgo || depositAPY, type: "supplyAPY" },
|
|
99
|
+
...baseAPY.filter((r) => r.type !== "supplyAPY")
|
|
100
|
+
];
|
|
101
|
+
const baseTotal = base.reduce((acc, r) => acc + r.apy, 0);
|
|
102
|
+
const total = baseTotal + extraAPYTotal;
|
|
103
|
+
return {
|
|
104
|
+
totalAPY: total,
|
|
105
|
+
extraAPYTotal,
|
|
106
|
+
baseAPY: base,
|
|
107
|
+
extraAPY,
|
|
108
|
+
externalAPY,
|
|
109
|
+
loading7DAgo: supplyAPY7DAgo === void 0
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function calculatePoolPoints({
|
|
113
|
+
poolTokenSymbol,
|
|
114
|
+
points,
|
|
115
|
+
tokensList
|
|
116
|
+
}) {
|
|
117
|
+
return points?.map(({ info, points: pts }) => {
|
|
118
|
+
const { decimals = 18 } = tokensList.get(info.token) || {};
|
|
119
|
+
const amount = (0, import_formatter.formatBN)(pts, decimals);
|
|
120
|
+
const { name = "Points", duration } = info ?? {};
|
|
121
|
+
return {
|
|
122
|
+
reward: info,
|
|
123
|
+
name,
|
|
124
|
+
amount,
|
|
125
|
+
tokenTitle: poolTokenSymbol,
|
|
126
|
+
fullTip: [
|
|
127
|
+
`${amount} ${name}`,
|
|
128
|
+
...duration ? [duration] : [],
|
|
129
|
+
...poolTokenSymbol ? [poolTokenSymbol] : []
|
|
130
|
+
].join(" per ")
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
135
|
+
0 && (module.exports = {
|
|
136
|
+
calculatePoolFullAPY,
|
|
137
|
+
calculatePoolFullAPY7DAgo,
|
|
138
|
+
calculatePoolPoints,
|
|
139
|
+
calculateSupplyApy7d,
|
|
140
|
+
getPoolExtraAPY
|
|
141
|
+
});
|
|
@@ -82,11 +82,13 @@ class PoolPointsAPI {
|
|
|
82
82
|
tokensList
|
|
83
83
|
}) {
|
|
84
84
|
const r = pools.reduce((acc, p) => {
|
|
85
|
-
const
|
|
85
|
+
const poolAddress = p.pool.pool.address.toLowerCase();
|
|
86
|
+
const pointsInfo = poolRewards[poolAddress] || [];
|
|
86
87
|
const poolPointsList = pointsInfo.reduce(
|
|
87
88
|
(acc2, pointsInfo2) => {
|
|
88
|
-
const {
|
|
89
|
-
const
|
|
89
|
+
const { addr: tokenAddress } = tokensList.get(pointsInfo2.token) || {};
|
|
90
|
+
const tokenAddressLower = (tokenAddress || "").toLowerCase();
|
|
91
|
+
const tokenBalance = totalTokenBalances[tokenAddressLower];
|
|
90
92
|
const points = PoolPointsAPI.getPoolTokenPoints(
|
|
91
93
|
tokenBalance,
|
|
92
94
|
p,
|
|
@@ -104,22 +106,22 @@ class PoolPointsAPI {
|
|
|
104
106
|
},
|
|
105
107
|
[]
|
|
106
108
|
);
|
|
107
|
-
acc[
|
|
109
|
+
acc[poolAddress] = poolPointsList;
|
|
108
110
|
return acc;
|
|
109
111
|
}, {});
|
|
110
112
|
return r;
|
|
111
113
|
}
|
|
112
114
|
static getPoolTokenPoints(tokenBalanceInPool, pool, tokensList, pointsInfo) {
|
|
113
|
-
if (pool.expectedLiquidity <= 0) return 0n;
|
|
115
|
+
if (pool.pool.pool.expectedLiquidity <= 0) return 0n;
|
|
114
116
|
if (pointsInfo.estimation === "relative" && !tokenBalanceInPool)
|
|
115
117
|
return null;
|
|
116
|
-
const { decimals = 18 } = tokensList
|
|
118
|
+
const { decimals = 18 } = tokensList.get(pointsInfo.token) || {};
|
|
117
119
|
const targetFactor = 10n ** BigInt(decimals);
|
|
118
120
|
const defaultPoints = pointsInfo.amount * targetFactor / import_sdk.PERCENTAGE_FACTOR;
|
|
119
121
|
if (pointsInfo.estimation === "absolute") return defaultPoints;
|
|
120
|
-
const { decimals: underlyingDecimals = 18 } = tokensList
|
|
122
|
+
const { decimals: underlyingDecimals = 18 } = tokensList.get(pool.pool.pool.underlying) || {};
|
|
121
123
|
const underlyingFactor = 10n ** BigInt(underlyingDecimals);
|
|
122
|
-
const points = (tokenBalanceInPool?.balance || 0n) * defaultPoints / (pool.expectedLiquidity * targetFactor / underlyingFactor);
|
|
124
|
+
const points = (tokenBalanceInPool?.balance || 0n) * defaultPoints / (pool.pool.pool.expectedLiquidity * targetFactor / underlyingFactor);
|
|
123
125
|
return import_common_utils.BigIntMath.min(points, defaultPoints);
|
|
124
126
|
}
|
|
125
127
|
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { marketCompressorAbi } from "../../abi/compressors/marketCompressor.js";
|
|
2
|
+
import { PoolPointsAPI } from "../../rewards/rewards/extra-apy.js";
|
|
3
|
+
import {
|
|
4
|
+
AddressMap,
|
|
5
|
+
AP_MARKET_COMPRESSOR,
|
|
6
|
+
BasePlugin,
|
|
7
|
+
BLOCKS_PER_WEEK_BY_NETWORK,
|
|
8
|
+
PERCENTAGE_DECIMALS,
|
|
9
|
+
VERSION_RANGE_310
|
|
10
|
+
} from "../../sdk/index.js";
|
|
11
|
+
import { rayToNumber } from "../../sdk/utils/formatter.js";
|
|
12
|
+
import { hexEq } from "../../sdk/utils/hex.js";
|
|
13
|
+
import { ApyOutputCache } from "./apy-cache.js";
|
|
14
|
+
import { parseGearStats, parseNetworkApy } from "./apy-parser.js";
|
|
15
|
+
import { APY_STATE_CACHE_URL, DEFAULT_APY_INTERVAL_MS } from "./constants.js";
|
|
16
|
+
import {
|
|
17
|
+
calculatePoolFullAPY,
|
|
18
|
+
calculatePoolFullAPY7DAgo,
|
|
19
|
+
calculatePoolPoints,
|
|
20
|
+
calculateSupplyApy7d,
|
|
21
|
+
getPoolExtraAPY
|
|
22
|
+
} from "./pool-apy-utils.js";
|
|
23
|
+
const MAP_LABEL = "pools7DAgo";
|
|
24
|
+
class ApyPlugin extends BasePlugin {
|
|
25
|
+
#apyUrl;
|
|
26
|
+
#cacheTtlMs;
|
|
27
|
+
#pools7DAgo;
|
|
28
|
+
#apySnapshot;
|
|
29
|
+
constructor(loadOnAttach = false, options) {
|
|
30
|
+
super(loadOnAttach);
|
|
31
|
+
this.#apyUrl = options?.apyUrl ?? APY_STATE_CACHE_URL;
|
|
32
|
+
this.#cacheTtlMs = options?.cacheTtlMs ?? DEFAULT_APY_INTERVAL_MS;
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Load — single entry point for all data (on-chain + state-cache)
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
async load(force, loadOptions) {
|
|
38
|
+
if (!force && this.loaded) {
|
|
39
|
+
return this.state;
|
|
40
|
+
}
|
|
41
|
+
const targetBlock = this.sdk.currentBlock - BLOCKS_PER_WEEK_BY_NETWORK[this.sdk.networkType];
|
|
42
|
+
const [marketCompressorAddress] = this.sdk.addressProvider.mustGetLatest(
|
|
43
|
+
AP_MARKET_COMPRESSOR,
|
|
44
|
+
VERSION_RANGE_310
|
|
45
|
+
);
|
|
46
|
+
this.#logger?.debug(
|
|
47
|
+
`loading pools 7d ago with market compressor ${marketCompressorAddress}`
|
|
48
|
+
);
|
|
49
|
+
const markets = this.sdk.marketRegister.markets;
|
|
50
|
+
const shouldLoadPools7DAgo = !this.#pools7DAgo || !!loadOptions?.loadPools7DAgo;
|
|
51
|
+
const [multicallResp, apySnapshot] = await Promise.all([
|
|
52
|
+
shouldLoadPools7DAgo ? this.client.multicall({
|
|
53
|
+
allowFailure: true,
|
|
54
|
+
contracts: markets.map(
|
|
55
|
+
(m) => ({
|
|
56
|
+
address: marketCompressorAddress,
|
|
57
|
+
abi: marketCompressorAbi,
|
|
58
|
+
functionName: "getPoolState",
|
|
59
|
+
args: [m.pool.pool.address]
|
|
60
|
+
})
|
|
61
|
+
),
|
|
62
|
+
blockNumber: targetBlock > 0n ? targetBlock : void 0,
|
|
63
|
+
batchSize: 0
|
|
64
|
+
}) : null,
|
|
65
|
+
this.#fetchApy()
|
|
66
|
+
]);
|
|
67
|
+
if (multicallResp !== null) {
|
|
68
|
+
this.#pools7DAgo = new AddressMap(void 0, MAP_LABEL);
|
|
69
|
+
multicallResp.forEach((r, index) => {
|
|
70
|
+
const m = markets[index];
|
|
71
|
+
const cfg = m.configurator.address;
|
|
72
|
+
const pool = m.pool.pool.address;
|
|
73
|
+
if (r.status === "success") {
|
|
74
|
+
this.#pools7DAgo?.upsert(m.pool.pool.address, {
|
|
75
|
+
dieselRate: r.result.dieselRate,
|
|
76
|
+
pool
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
this.#logger?.error(
|
|
80
|
+
`failed to load pools 7d ago for market configurator ${this.labelAddress(cfg)} and pool ${this.labelAddress(pool)}: ${r.error}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (apySnapshot) {
|
|
86
|
+
this.#apySnapshot = apySnapshot;
|
|
87
|
+
}
|
|
88
|
+
return this.state;
|
|
89
|
+
}
|
|
90
|
+
get loaded() {
|
|
91
|
+
return !!this.#pools7DAgo && !!this.#apySnapshot;
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Accessors
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/**
|
|
97
|
+
* @throws if plugin is not loaded
|
|
98
|
+
*/
|
|
99
|
+
get pools7DAgo() {
|
|
100
|
+
if (!this.#pools7DAgo) {
|
|
101
|
+
throw new Error("apy plugin not loaded");
|
|
102
|
+
}
|
|
103
|
+
return this.#pools7DAgo;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* @throws if plugin is not loaded
|
|
107
|
+
*/
|
|
108
|
+
get apySnapshot() {
|
|
109
|
+
if (!this.#apySnapshot) {
|
|
110
|
+
throw new Error("apy plugin not loaded");
|
|
111
|
+
}
|
|
112
|
+
return this.#apySnapshot;
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Aggregated APY / points
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Computes per-pool APY (current + 7d-ago) and points for all markets.
|
|
119
|
+
*
|
|
120
|
+
* @throws if plugin is not loaded
|
|
121
|
+
*/
|
|
122
|
+
getPoolsAPY() {
|
|
123
|
+
if (!this.loaded) {
|
|
124
|
+
throw new Error("apy plugin not loaded");
|
|
125
|
+
}
|
|
126
|
+
const markets = this.sdk.marketRegister.markets;
|
|
127
|
+
const { apy } = this.apySnapshot;
|
|
128
|
+
const totalTokenBalances = {};
|
|
129
|
+
const poolPointsBase = apy.poolRewardsList && Object.keys(apy.poolRewardsList).length > 0 ? PoolPointsAPI.getPointsByPool({
|
|
130
|
+
poolRewards: apy.poolRewardsList,
|
|
131
|
+
totalTokenBalances,
|
|
132
|
+
pools: markets,
|
|
133
|
+
tokensList: this.sdk.tokensMeta
|
|
134
|
+
}) : {};
|
|
135
|
+
const data = {};
|
|
136
|
+
const data7DAgo = {};
|
|
137
|
+
const points = {};
|
|
138
|
+
for (const market of markets) {
|
|
139
|
+
const pool = market.pool.pool;
|
|
140
|
+
const poolAddr = pool.address.toLowerCase();
|
|
141
|
+
const underlyingLc = pool.underlying.toLowerCase();
|
|
142
|
+
const depositAPY = rayToNumber(pool.supplyRate) * Number(PERCENTAGE_DECIMALS);
|
|
143
|
+
const underlyingAPY = apy.apyList?.[underlyingLc] ?? apy.apyList?.[pool.underlying] ?? 0;
|
|
144
|
+
const lookupAddresses = this.#getExtraAPYLookupAddresses(poolAddr);
|
|
145
|
+
const extraAPY = getPoolExtraAPY(lookupAddresses, apy.poolExtraAPYList);
|
|
146
|
+
const currentExternalList = apy.poolExternalAPYList?.[poolAddr];
|
|
147
|
+
const poolAPY = calculatePoolFullAPY({
|
|
148
|
+
depositAPY,
|
|
149
|
+
underlyingAPY,
|
|
150
|
+
extraAPY,
|
|
151
|
+
currentExternalList
|
|
152
|
+
});
|
|
153
|
+
data[poolAddr] = poolAPY;
|
|
154
|
+
const pool7DAgo = this.#pools7DAgo?.get(poolAddr);
|
|
155
|
+
const supplyAPY7DAgo = pool7DAgo ? calculateSupplyApy7d(
|
|
156
|
+
pool.dieselRate,
|
|
157
|
+
pool.supplyRate,
|
|
158
|
+
pool7DAgo.dieselRate
|
|
159
|
+
) : void 0;
|
|
160
|
+
data7DAgo[poolAddr] = calculatePoolFullAPY7DAgo({
|
|
161
|
+
supplyAPY7DAgo,
|
|
162
|
+
depositAPY,
|
|
163
|
+
poolAPY
|
|
164
|
+
});
|
|
165
|
+
const poolTokenMeta = this.sdk.tokensMeta.get(pool.underlying);
|
|
166
|
+
points[poolAddr] = calculatePoolPoints({
|
|
167
|
+
poolTokenSymbol: poolTokenMeta?.symbol,
|
|
168
|
+
points: poolPointsBase[poolAddr],
|
|
169
|
+
tokensList: this.sdk.tokensMeta
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return { data, data7DAgo, pointsBase: poolPointsBase, points };
|
|
173
|
+
}
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Plugin lifecycle
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
async syncState() {
|
|
178
|
+
await this.load();
|
|
179
|
+
}
|
|
180
|
+
stateHuman(_) {
|
|
181
|
+
return this.pools7DAgo.values().flatMap((p) => ({
|
|
182
|
+
address: p.pool,
|
|
183
|
+
version: this.version,
|
|
184
|
+
dieselRate: p.dieselRate
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
get state() {
|
|
188
|
+
return {
|
|
189
|
+
pools7DAgo: this.pools7DAgo.asRecord(),
|
|
190
|
+
apySnapshot: this.apySnapshot
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
hydrate(state) {
|
|
194
|
+
this.#pools7DAgo = new AddressMap(
|
|
195
|
+
Object.entries(state.pools7DAgo),
|
|
196
|
+
MAP_LABEL
|
|
197
|
+
);
|
|
198
|
+
this.#apySnapshot = state.apySnapshot;
|
|
199
|
+
}
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Internal
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
async #fetchApy() {
|
|
204
|
+
try {
|
|
205
|
+
const cache = ApyOutputCache.get(
|
|
206
|
+
this.#apyUrl,
|
|
207
|
+
this.#cacheTtlMs,
|
|
208
|
+
this.#logger
|
|
209
|
+
);
|
|
210
|
+
const output = await cache.fetch();
|
|
211
|
+
if (!output) return void 0;
|
|
212
|
+
const chainData = output.chains[this.sdk.chainId];
|
|
213
|
+
if (!chainData) {
|
|
214
|
+
this.#logger?.debug(
|
|
215
|
+
`apy state-cache: no data for chainId ${this.sdk.chainId}`
|
|
216
|
+
);
|
|
217
|
+
return void 0;
|
|
218
|
+
}
|
|
219
|
+
const apy = parseNetworkApy(chainData.tokens, chainData.pools);
|
|
220
|
+
if (!apy) return void 0;
|
|
221
|
+
return {
|
|
222
|
+
apy,
|
|
223
|
+
gearStats: parseGearStats(output),
|
|
224
|
+
timestamp: output.timestamp
|
|
225
|
+
};
|
|
226
|
+
} catch (e) {
|
|
227
|
+
this.#logger?.error(e, "failed to fetch apy state-cache");
|
|
228
|
+
return void 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Collects addresses to look up in poolExtraAPYList for a given pool.
|
|
233
|
+
* Includes pool address (= diesel token) + any staked diesel token
|
|
234
|
+
* outputs discovered from zappers.
|
|
235
|
+
*/
|
|
236
|
+
#getExtraAPYLookupAddresses(poolAddr) {
|
|
237
|
+
const addresses = [poolAddr];
|
|
238
|
+
try {
|
|
239
|
+
const zappers = this.sdk.marketRegister.poolZappers(poolAddr);
|
|
240
|
+
for (const z of zappers) {
|
|
241
|
+
if (!hexEq(z.tokenOut.addr, poolAddr)) {
|
|
242
|
+
addresses.push(z.tokenOut.addr);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
return addresses;
|
|
248
|
+
}
|
|
249
|
+
get #logger() {
|
|
250
|
+
return this.logger;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
export {
|
|
254
|
+
ApyPlugin
|
|
255
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
class ApyOutputCache {
|
|
3
|
+
static #instances = /* @__PURE__ */ new Map();
|
|
4
|
+
#url;
|
|
5
|
+
#ttlMs;
|
|
6
|
+
#cache;
|
|
7
|
+
#pending;
|
|
8
|
+
#logger;
|
|
9
|
+
constructor(url, ttlMs, logger) {
|
|
10
|
+
this.#url = url;
|
|
11
|
+
this.#ttlMs = ttlMs;
|
|
12
|
+
this.#logger = logger;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns a shared cache instance for the given URL.
|
|
16
|
+
* The same instance is reused across all callers with identical URL.
|
|
17
|
+
*/
|
|
18
|
+
static get(url, ttlMs, logger) {
|
|
19
|
+
let instance = ApyOutputCache.#instances.get(url);
|
|
20
|
+
if (!instance) {
|
|
21
|
+
instance = new ApyOutputCache(url, ttlMs, logger);
|
|
22
|
+
ApyOutputCache.#instances.set(url, instance);
|
|
23
|
+
}
|
|
24
|
+
if (logger) {
|
|
25
|
+
instance.#logger = logger;
|
|
26
|
+
}
|
|
27
|
+
return instance;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns cached Output if fresh, otherwise fetches from the network.
|
|
31
|
+
* Concurrent calls are de-duplicated.
|
|
32
|
+
*/
|
|
33
|
+
async fetch() {
|
|
34
|
+
if (this.#cache && Date.now() - this.#cache.fetchedAt < this.#ttlMs) {
|
|
35
|
+
this.#logger?.debug("apy cache: TTL still valid, returning cached data");
|
|
36
|
+
return this.#cache.data;
|
|
37
|
+
}
|
|
38
|
+
if (this.#pending) {
|
|
39
|
+
this.#logger?.debug("apy cache: request in flight, waiting");
|
|
40
|
+
return this.#pending;
|
|
41
|
+
}
|
|
42
|
+
this.#pending = this.#doFetch();
|
|
43
|
+
try {
|
|
44
|
+
return await this.#pending;
|
|
45
|
+
} finally {
|
|
46
|
+
this.#pending = void 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async #doFetch() {
|
|
50
|
+
try {
|
|
51
|
+
const headers = {};
|
|
52
|
+
if (this.#cache?.etag) {
|
|
53
|
+
headers["If-None-Match"] = this.#cache.etag;
|
|
54
|
+
}
|
|
55
|
+
const response = await axios.get(this.#url, {
|
|
56
|
+
headers,
|
|
57
|
+
validateStatus: (status) => status === 200 || status === 304
|
|
58
|
+
});
|
|
59
|
+
if (response.status === 304 && this.#cache) {
|
|
60
|
+
this.#cache.fetchedAt = Date.now();
|
|
61
|
+
this.#logger?.debug("apy cache: 304 Not Modified, extended TTL");
|
|
62
|
+
return this.#cache.data;
|
|
63
|
+
}
|
|
64
|
+
const etag = response.headers["etag"];
|
|
65
|
+
this.#cache = {
|
|
66
|
+
data: response.data,
|
|
67
|
+
etag,
|
|
68
|
+
fetchedAt: Date.now()
|
|
69
|
+
};
|
|
70
|
+
this.#logger?.debug(
|
|
71
|
+
`apy cache: fetched fresh data (timestamp: ${response.data.timestamp})`
|
|
72
|
+
);
|
|
73
|
+
return response.data;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
this.#logger?.error(e, "apy cache: fetch failed");
|
|
76
|
+
return this.#cache?.data;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Evicts all cached entries. Mainly useful for tests. */
|
|
80
|
+
static clearAll() {
|
|
81
|
+
ApyOutputCache.#instances.clear();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
ApyOutputCache
|
|
86
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { PERCENTAGE_FACTOR } from "../../sdk/index.js";
|
|
2
|
+
function numberToAPY(baseApy) {
|
|
3
|
+
return Math.round(baseApy * Number(PERCENTAGE_FACTOR));
|
|
4
|
+
}
|
|
5
|
+
function parseGearStats(output) {
|
|
6
|
+
const d = output.gearApy?.status === "ok" ? output.gearApy.data : void 0;
|
|
7
|
+
if (!d) return null;
|
|
8
|
+
return {
|
|
9
|
+
base: numberToAPY(d.base ?? 0),
|
|
10
|
+
crv: numberToAPY(d.crv ?? 0),
|
|
11
|
+
gear: numberToAPY(d.gear ?? 0),
|
|
12
|
+
gearPrice: d.gearPrice ?? 0
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function parseNetworkApy(apyResp, poolResp) {
|
|
16
|
+
const baseAPYList = {};
|
|
17
|
+
const extraCollateralAPYList = {};
|
|
18
|
+
const basePointsList = {};
|
|
19
|
+
const extraCollateralPointsList = {};
|
|
20
|
+
const tokenExtraRewardsList = {};
|
|
21
|
+
const apyData = apyResp?.status === "ok" ? apyResp.data : void 0;
|
|
22
|
+
for (const d of apyData ?? []) {
|
|
23
|
+
const tokenAddress = d.address.toLowerCase();
|
|
24
|
+
const tokenSymbol = d.symbol;
|
|
25
|
+
const apy = d.rewards.apy?.[0];
|
|
26
|
+
if (apy) {
|
|
27
|
+
baseAPYList[tokenAddress] = numberToAPY(apy.value ?? 0);
|
|
28
|
+
}
|
|
29
|
+
const points = d.rewards.points?.[0];
|
|
30
|
+
if (points) {
|
|
31
|
+
basePointsList[tokenAddress] = {
|
|
32
|
+
address: tokenAddress,
|
|
33
|
+
symbol: tokenSymbol,
|
|
34
|
+
rewards: points.rewards.map((p) => ({
|
|
35
|
+
...p,
|
|
36
|
+
multiplier: p.multiplier === "soon" ? p.multiplier : BigInt(p.multiplier || 0)
|
|
37
|
+
})),
|
|
38
|
+
debtRewards: points.debtRewards?.map((p) => ({
|
|
39
|
+
...p,
|
|
40
|
+
cm: (p.cm || "").toLowerCase(),
|
|
41
|
+
multiplier: p.multiplier === "soon" ? p.multiplier : BigInt(p.multiplier || 0)
|
|
42
|
+
}))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const extraRewards = d.rewards.extraRewards;
|
|
46
|
+
if (extraRewards && extraRewards.length > 0) {
|
|
47
|
+
tokenExtraRewardsList[tokenAddress] = extraRewards.map((r) => ({
|
|
48
|
+
address: tokenAddress,
|
|
49
|
+
symbol: r.rewardSymbol,
|
|
50
|
+
rewardToken: r.rewardToken.toLowerCase(),
|
|
51
|
+
rewardSymbol: r.rewardSymbol,
|
|
52
|
+
token: r.rewardToken.toLowerCase(),
|
|
53
|
+
finished: BigInt(r.finished || 0),
|
|
54
|
+
duration: BigInt(r.duration || 0),
|
|
55
|
+
reward: BigInt(r.reward || 0),
|
|
56
|
+
balance: BigInt(r.balance || 0)
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
const ecApy = d.rewards.extraCollateralAPY;
|
|
60
|
+
if (ecApy && ecApy.length > 0) {
|
|
61
|
+
for (const ea of ecApy) {
|
|
62
|
+
const pool = ea.pool.toLowerCase();
|
|
63
|
+
if (!extraCollateralAPYList[pool]) extraCollateralAPYList[pool] = {};
|
|
64
|
+
extraCollateralAPYList[pool][tokenAddress] = {
|
|
65
|
+
...ea,
|
|
66
|
+
address: tokenAddress,
|
|
67
|
+
symbol: tokenSymbol,
|
|
68
|
+
pool,
|
|
69
|
+
value: numberToAPY(ea.value ?? 0)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const ecPoints = d.rewards.extraCollateralPoints;
|
|
74
|
+
if (ecPoints && ecPoints.length > 0) {
|
|
75
|
+
for (const ea of ecPoints) {
|
|
76
|
+
const pool = ea.pool.toLowerCase();
|
|
77
|
+
if (!extraCollateralPointsList[pool])
|
|
78
|
+
extraCollateralPointsList[pool] = {};
|
|
79
|
+
extraCollateralPointsList[pool][tokenAddress] = {
|
|
80
|
+
address: tokenAddress,
|
|
81
|
+
symbol: tokenSymbol,
|
|
82
|
+
rewards: ea.rewards.map((p) => ({
|
|
83
|
+
...p,
|
|
84
|
+
multiplier: p.multiplier === "soon" ? p.multiplier : BigInt(p.multiplier || 0)
|
|
85
|
+
})),
|
|
86
|
+
debtRewards: ea.debtRewards?.map((p) => ({
|
|
87
|
+
...p,
|
|
88
|
+
cm: (p.cm || "").toLowerCase(),
|
|
89
|
+
multiplier: p.multiplier === "soon" ? p.multiplier : BigInt(p.multiplier || 0)
|
|
90
|
+
}))
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const poolRewardsList = {};
|
|
96
|
+
const poolExternalAPYList = {};
|
|
97
|
+
const poolExtraAPYList = {};
|
|
98
|
+
const poolData = poolResp?.status === "ok" ? poolResp.data : void 0;
|
|
99
|
+
for (const r of poolData ?? []) {
|
|
100
|
+
const pool = (r.pool || "").toLowerCase();
|
|
101
|
+
const points = r.rewards.points;
|
|
102
|
+
if (points && points.length > 0) {
|
|
103
|
+
poolRewardsList[pool] = points.map((p) => ({
|
|
104
|
+
...p,
|
|
105
|
+
token: p.token.toLowerCase(),
|
|
106
|
+
pool,
|
|
107
|
+
amount: BigInt(p.amount || 0)
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
const externalAPY = r.rewards.externalAPY;
|
|
111
|
+
if (externalAPY && externalAPY.length > 0) {
|
|
112
|
+
poolExternalAPYList[pool] = externalAPY.map((ex) => ({ ...ex, pool }));
|
|
113
|
+
}
|
|
114
|
+
const extraAPY = r.rewards.extraAPY;
|
|
115
|
+
if (extraAPY && extraAPY.length > 0) {
|
|
116
|
+
poolExtraAPYList[extraAPY[0].token.toLowerCase()] = extraAPY.map((ex) => ({
|
|
117
|
+
...ex,
|
|
118
|
+
token: ex.token.toLowerCase(),
|
|
119
|
+
rewardToken: ex.rewardToken.toLowerCase()
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const allResponses = [apyResp, poolResp];
|
|
124
|
+
const allErrors = allResponses.filter((r) => r?.status === "error");
|
|
125
|
+
if (allResponses.length > 0 && allErrors.length === allResponses.length) {
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
apyList: Object.keys(baseAPYList).length > 0 ? baseAPYList : void 0,
|
|
130
|
+
extraCollateralAPYList: Object.keys(extraCollateralAPYList).length > 0 ? extraCollateralAPYList : void 0,
|
|
131
|
+
pointsList: Object.keys(basePointsList).length > 0 ? basePointsList : void 0,
|
|
132
|
+
extraCollateralPointsList: Object.keys(extraCollateralPointsList).length > 0 ? extraCollateralPointsList : void 0,
|
|
133
|
+
poolRewardsList: Object.keys(poolRewardsList).length > 0 ? poolRewardsList : void 0,
|
|
134
|
+
tokenExtraRewardsList: Object.keys(tokenExtraRewardsList).length > 0 ? tokenExtraRewardsList : void 0,
|
|
135
|
+
poolExternalAPYList: Object.keys(poolExternalAPYList).length > 0 ? poolExternalAPYList : void 0,
|
|
136
|
+
poolExtraAPYList: Object.keys(poolExtraAPYList).length > 0 ? poolExtraAPYList : void 0
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
numberToAPY,
|
|
141
|
+
parseGearStats,
|
|
142
|
+
parseNetworkApy
|
|
143
|
+
};
|