@evaafi/sdk 0.9.0-a → 0.9.1
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/api/feeds.d.ts +15 -24
- package/dist/api/feeds.js +22 -49
- package/dist/api/math.d.ts +1 -1
- package/dist/api/math.js +55 -38
- package/dist/api/parser.d.ts +2 -2
- package/dist/api/parser.js +3 -3
- package/dist/api/parsers/PythOracleParser.d.ts +3 -3
- package/dist/api/parsers/PythOracleParser.js +2 -1
- package/dist/api/prices.js +2 -1
- package/dist/constants/general/index.d.ts +25 -11
- package/dist/constants/general/index.js +15 -1
- package/dist/constants/pools/mainnet.js +20 -18
- package/dist/constants/pools/testnet.js +14 -6
- package/dist/contracts/AbstractMaster.d.ts +239 -127
- package/dist/contracts/AbstractMaster.js +101 -16
- package/dist/contracts/ClassicMaster.d.ts +12 -12
- package/dist/contracts/PythMaster.d.ts +40 -24
- package/dist/contracts/PythMaster.js +61 -76
- package/dist/contracts/PythOracle.d.ts +16 -0
- package/dist/contracts/PythOracle.js +19 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{prices → oracles}/Types.d.ts +0 -4
- package/dist/oracles/collectors/AbstractCollector.d.ts +10 -0
- package/dist/oracles/collectors/AbstractCollector.js +6 -0
- package/dist/oracles/collectors/ClassicCollector.d.ts +22 -0
- package/dist/oracles/collectors/ClassicCollector.js +192 -0
- package/dist/oracles/collectors/PythCollector.d.ts +27 -0
- package/dist/oracles/collectors/PythCollector.js +252 -0
- package/dist/oracles/collectors/index.d.ts +3 -0
- package/dist/oracles/collectors/index.js +19 -0
- package/dist/{prices → oracles}/index.d.ts +2 -3
- package/dist/{prices → oracles}/index.js +2 -3
- package/dist/oracles/prices/AbstractPrices.d.ts +33 -0
- package/dist/oracles/prices/AbstractPrices.js +40 -0
- package/dist/oracles/prices/ClassicPrices.d.ts +19 -0
- package/dist/oracles/prices/ClassicPrices.js +48 -0
- package/dist/oracles/prices/PythPrices.d.ts +18 -0
- package/dist/oracles/prices/PythPrices.js +32 -0
- package/dist/oracles/prices/index.d.ts +3 -0
- package/dist/oracles/prices/index.js +19 -0
- package/dist/{prices → oracles}/utils.d.ts +6 -1
- package/dist/{prices → oracles}/utils.js +10 -1
- package/dist/types/Master.d.ts +4 -5
- package/package.json +2 -3
- package/src/api/feeds.ts +24 -60
- package/src/api/math.ts +118 -90
- package/src/api/parser.ts +5 -5
- package/src/api/parsers/PythOracleParser.ts +6 -2
- package/src/api/prices.ts +2 -1
- package/src/constants/general/index.ts +16 -1
- package/src/constants/pools/mainnet.ts +20 -35
- package/src/constants/pools/testnet.ts +17 -8
- package/src/contracts/AbstractMaster.ts +272 -144
- package/src/contracts/ClassicMaster.ts +12 -12
- package/src/contracts/PythMaster.ts +130 -123
- package/src/contracts/PythOracle.ts +20 -0
- package/src/contracts/index.ts +2 -0
- package/src/index.ts +1 -1
- package/src/{prices → oracles}/Types.ts +0 -5
- package/src/oracles/collectors/AbstractCollector.ts +22 -0
- package/src/oracles/collectors/ClassicCollector.ts +263 -0
- package/src/oracles/collectors/PythCollector.ts +358 -0
- package/src/oracles/collectors/index.ts +3 -0
- package/src/{prices → oracles}/index.ts +2 -3
- package/src/oracles/prices/AbstractPrices.ts +59 -0
- package/src/oracles/prices/ClassicPrices.ts +52 -0
- package/src/oracles/prices/PythPrices.ts +40 -0
- package/src/oracles/prices/index.ts +3 -0
- package/src/{prices → oracles}/utils.ts +12 -1
- package/src/types/Master.ts +4 -6
- package/dist/prices/Oracle.interface.d.ts +0 -9
- package/dist/prices/Oracle.interface.js +0 -2
- package/dist/prices/Prices.d.ts +0 -11
- package/dist/prices/Prices.js +0 -53
- package/dist/prices/PricesCollector.d.ts +0 -22
- package/dist/prices/PricesCollector.js +0 -146
- package/dist/prices/PythCollector.d.ts +0 -22
- package/dist/prices/PythCollector.js +0 -217
- package/src/prices/Oracle.interface.ts +0 -18
- package/src/prices/Prices.ts +0 -45
- package/src/prices/PricesCollector.ts +0 -169
- package/src/prices/PythCollector.ts +0 -294
- /package/dist/{prices → oracles}/Types.js +0 -0
- /package/dist/{prices → oracles}/constants.d.ts +0 -0
- /package/dist/{prices → oracles}/constants.js +0 -0
- /package/dist/{prices → oracles}/sources/Backend.d.ts +0 -0
- /package/dist/{prices → oracles}/sources/Backend.js +0 -0
- /package/dist/{prices → oracles}/sources/Icp.d.ts +0 -0
- /package/dist/{prices → oracles}/sources/Icp.js +0 -0
- /package/dist/{prices → oracles}/sources/PriceSource.d.ts +0 -0
- /package/dist/{prices → oracles}/sources/PriceSource.js +0 -0
- /package/dist/{prices → oracles}/sources/index.d.ts +0 -0
- /package/dist/{prices → oracles}/sources/index.js +0 -0
- /package/src/{prices → oracles}/constants.ts +0 -0
- /package/src/{prices → oracles}/sources/Backend.ts +0 -0
- /package/src/{prices → oracles}/sources/Icp.ts +0 -0
- /package/src/{prices → oracles}/sources/PriceSource.ts +0 -0
- /package/src/{prices → oracles}/sources/index.ts +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { HermesClient, HexString, PriceUpdate } from '@pythnetwork/hermes-client';
|
|
2
|
+
import { Dictionary } from '@ton/core';
|
|
3
|
+
import { checkNotInDebtAtAll } from '../../api/math';
|
|
4
|
+
import { OracleConfig } from '../../api/parsers/PythOracleParser';
|
|
5
|
+
import { packPythUpdatesData } from '../../api/prices';
|
|
6
|
+
import { ASSET_PRICE_SCALE } from '../../constants/general';
|
|
7
|
+
import { FeedMapItem, PoolAssetConfig } from '../../types/Master';
|
|
8
|
+
import { FetchConfig, proxyFetchRetries } from '../../utils/utils';
|
|
9
|
+
import { TTL_ORACLE_DATA_SEC } from '../constants';
|
|
10
|
+
import { PythPrices } from '../prices/PythPrices';
|
|
11
|
+
import { PythFeedUpdateType, PythPriceSourcesConfig } from '../Types';
|
|
12
|
+
import { AbstractCollector } from './AbstractCollector';
|
|
13
|
+
|
|
14
|
+
export type PythCollectorConfig = {
|
|
15
|
+
poolAssetsConfig: PoolAssetConfig[];
|
|
16
|
+
pythOracle: OracleConfig;
|
|
17
|
+
pythConfig: PythPriceSourcesConfig;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class PythCollector extends AbstractCollector {
|
|
21
|
+
#parsedFeedsMap = new Map<HexString, FeedMapItem>();
|
|
22
|
+
#pythConfig: PythPriceSourcesConfig;
|
|
23
|
+
#poolAssetsConfig: PoolAssetConfig[];
|
|
24
|
+
#allowedRefTokens: Dictionary<bigint, bigint>;
|
|
25
|
+
|
|
26
|
+
#assetToFeeds = new Map<bigint, HexString[]>();
|
|
27
|
+
|
|
28
|
+
constructor(config: PythCollectorConfig) {
|
|
29
|
+
super();
|
|
30
|
+
this.#pythConfig = config.pythConfig;
|
|
31
|
+
this.#poolAssetsConfig = config.poolAssetsConfig;
|
|
32
|
+
|
|
33
|
+
this.#allowedRefTokens = config.pythOracle.allowedRefTokens;
|
|
34
|
+
|
|
35
|
+
for (const [feedId, feedMap] of config.pythOracle.feedsMap) {
|
|
36
|
+
this.#parsedFeedsMap.set(feedId, feedMap);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const [feedId, connectedFeed] of this.#parsedFeedsMap) {
|
|
40
|
+
this.#assetToFeeds.set(connectedFeed.assetId, [feedId, connectedFeed.feedId]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getPricesForLiquidate(
|
|
45
|
+
realPrincipals: Dictionary<bigint, bigint>,
|
|
46
|
+
fetchConfig?: FetchConfig,
|
|
47
|
+
): Promise<PythPrices> {
|
|
48
|
+
const assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
|
|
49
|
+
if (assets.includes(undefined)) {
|
|
50
|
+
throw new Error('User from another pool');
|
|
51
|
+
}
|
|
52
|
+
return await this.getPrices(
|
|
53
|
+
assets.map((x) => x!),
|
|
54
|
+
fetchConfig,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getPricesForSupplyWithdraw(
|
|
59
|
+
realPrincipals: Dictionary<bigint, bigint>,
|
|
60
|
+
supplyAsset: PoolAssetConfig,
|
|
61
|
+
withdrawAsset: PoolAssetConfig,
|
|
62
|
+
collateralToDebt: boolean,
|
|
63
|
+
fetchConfig?: FetchConfig,
|
|
64
|
+
): Promise<PythPrices> {
|
|
65
|
+
let assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
|
|
66
|
+
if (assets.includes(undefined)) {
|
|
67
|
+
throw new Error('User from another pool');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!assets.find((a) => a?.assetId === supplyAsset.assetId)) {
|
|
71
|
+
assets.push(supplyAsset);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!assets.find((a) => a?.assetId === withdrawAsset.assetId)) {
|
|
75
|
+
assets.push(withdrawAsset);
|
|
76
|
+
}
|
|
77
|
+
if (collateralToDebt && assets.length == 1) {
|
|
78
|
+
throw new Error('Cannot debt only one supplied asset');
|
|
79
|
+
}
|
|
80
|
+
return this.getPrices(
|
|
81
|
+
assets.map((x) => x!),
|
|
82
|
+
fetchConfig,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async getPrices(
|
|
87
|
+
assets: PoolAssetConfig[] = this.#poolAssetsConfig,
|
|
88
|
+
fetchConfig?: FetchConfig,
|
|
89
|
+
): Promise<PythPrices> {
|
|
90
|
+
// Declare variables at the beginning
|
|
91
|
+
let minPublishTime: number | undefined;
|
|
92
|
+
let maxPublishTime: number | undefined;
|
|
93
|
+
|
|
94
|
+
if (assets.length === 0) {
|
|
95
|
+
return PythPrices.createEmptyPrices();
|
|
96
|
+
}
|
|
97
|
+
const requestedFeeds = new Set<HexString>();
|
|
98
|
+
const requestedRefAssets = new Set<PoolAssetConfig>();
|
|
99
|
+
|
|
100
|
+
for (const asset of assets) {
|
|
101
|
+
if (this.#allowedRefTokens.has(asset.assetId)) {
|
|
102
|
+
const refTokenId = this.#allowedRefTokens.get(asset.assetId)!;
|
|
103
|
+
|
|
104
|
+
if (this.#assetToFeeds.has(refTokenId)) {
|
|
105
|
+
const [feedId, refFeedId] = this.#assetToFeeds.get(refTokenId)!;
|
|
106
|
+
requestedFeeds.add(feedId);
|
|
107
|
+
|
|
108
|
+
if (refFeedId !== '0x0') {
|
|
109
|
+
requestedFeeds.add(refFeedId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
requestedRefAssets.add(asset);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (this.#assetToFeeds.has(asset.assetId)) {
|
|
117
|
+
const [feedId, refFeedId] = this.#assetToFeeds.get(asset.assetId)!;
|
|
118
|
+
requestedFeeds.add(feedId);
|
|
119
|
+
|
|
120
|
+
if (refFeedId !== '0x0') {
|
|
121
|
+
requestedFeeds.add(refFeedId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const targetFeeds = Array.from(requestedFeeds);
|
|
127
|
+
const refAssets = Array.from(requestedRefAssets);
|
|
128
|
+
|
|
129
|
+
const pythUpdates = await this.#fetchPythUpdatesWithRetry(targetFeeds, fetchConfig);
|
|
130
|
+
|
|
131
|
+
// Calculate min and max publish times for validation
|
|
132
|
+
if (pythUpdates.parsed && pythUpdates.parsed.length > 0) {
|
|
133
|
+
let tmin = pythUpdates.parsed[0].price.publish_time;
|
|
134
|
+
let tmax = tmin;
|
|
135
|
+
|
|
136
|
+
for (let i = 1; i < pythUpdates.parsed.length; i++) {
|
|
137
|
+
const publishTime = pythUpdates.parsed[i].price.publish_time;
|
|
138
|
+
if (publishTime < tmin) tmin = publishTime;
|
|
139
|
+
if (publishTime > tmax) tmax = publishTime;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (tmax - tmin > TTL_ORACLE_DATA_SEC) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Price feeds don't fit in a single 3-minute window. Time span: ${tmax - tmin} seconds (max allowed: ${TTL_ORACLE_DATA_SEC})`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
minPublishTime = tmin;
|
|
149
|
+
maxPublishTime = tmin + TTL_ORACLE_DATA_SEC;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const pricesDict = Dictionary.empty<bigint, bigint>();
|
|
153
|
+
const pythPriceUpdates = pythUpdates.parsed;
|
|
154
|
+
|
|
155
|
+
if (pythPriceUpdates) {
|
|
156
|
+
// Only set prices for requested assets, not all possible mapped assets
|
|
157
|
+
const requestedAssetIds = new Set(assets.map((a) => a.assetId));
|
|
158
|
+
|
|
159
|
+
for (const u of pythPriceUpdates) {
|
|
160
|
+
const feedId = `0x${u.id}`;
|
|
161
|
+
|
|
162
|
+
const price = (BigInt(u.price.price) * BigInt(10 ** 9)) / BigInt(10 ** (u.price.expo * -1));
|
|
163
|
+
|
|
164
|
+
// Find the feed mapping for this feedId and always set the price for the mapped asset
|
|
165
|
+
const feedMapItem = this.#parsedFeedsMap.get(feedId);
|
|
166
|
+
if (feedMapItem) {
|
|
167
|
+
pricesDict.set(feedMapItem.assetId, price);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Handle reference tokens - check if any asset uses this feed as a reference
|
|
171
|
+
for (const asset of assets) {
|
|
172
|
+
if (this.#allowedRefTokens.has(asset.assetId)) {
|
|
173
|
+
const refTokenId = this.#allowedRefTokens.get(asset.assetId)!;
|
|
174
|
+
const refFeeds = this.#assetToFeeds.get(refTokenId);
|
|
175
|
+
|
|
176
|
+
if (refFeeds && (refFeeds[0] === feedId || refFeeds[1] === feedId)) {
|
|
177
|
+
if (requestedAssetIds.has(asset.assetId)) {
|
|
178
|
+
// For allowedRefTokens, both asset and reference token should have the same price
|
|
179
|
+
// Set the same price for both the asset and its reference token
|
|
180
|
+
pricesDict.set(asset.assetId, price);
|
|
181
|
+
pricesDict.set(refTokenId, price);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Apply dynamic scaling for liquid staking tokens based on feedsMap configuration
|
|
189
|
+
// For each asset that has a feedId (not '0x0'), apply scaling: asset_price = asset_rate * base_price / ASSET_PRICE_SCALE
|
|
190
|
+
for (const [feedId, feedMapItem] of this.#parsedFeedsMap) {
|
|
191
|
+
if (feedMapItem.feedId !== '0x0') {
|
|
192
|
+
const assetPrice = pricesDict.get(feedMapItem.assetId);
|
|
193
|
+
const baseFeedMapItem = this.#parsedFeedsMap.get(feedMapItem.feedId);
|
|
194
|
+
const basePrice = baseFeedMapItem ? pricesDict.get(baseFeedMapItem.assetId) : undefined;
|
|
195
|
+
|
|
196
|
+
if (assetPrice && basePrice) {
|
|
197
|
+
const scaledPrice = (assetPrice * basePrice) / ASSET_PRICE_SCALE;
|
|
198
|
+
pricesDict.set(feedMapItem.assetId, scaledPrice);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Apply scaling for reference tokens (allowedRefTokens)
|
|
204
|
+
// For assets that reference other tokens, they should have the SAME price as their reference token
|
|
205
|
+
// This is different from feedsMap scaling where we multiply rates
|
|
206
|
+
for (const asset of assets) {
|
|
207
|
+
if (this.#allowedRefTokens.has(asset.assetId)) {
|
|
208
|
+
const refTokenId = this.#allowedRefTokens.get(asset.assetId)!;
|
|
209
|
+
const refTokenPrice = pricesDict.get(refTokenId);
|
|
210
|
+
|
|
211
|
+
if (refTokenPrice && requestedAssetIds.has(asset.assetId)) {
|
|
212
|
+
// For allowedRefTokens, the asset price should equal the reference token price
|
|
213
|
+
pricesDict.set(asset.assetId, refTokenPrice);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check that all requested assets have prices
|
|
219
|
+
const missing = assets.map((a) => a.assetId).filter((id) => pricesDict.get(id) === undefined);
|
|
220
|
+
|
|
221
|
+
if (missing.length) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Missing prices for ${missing.length} asset(s): ${missing.map((x) => x.toString()).join(', ')}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const dataCell = packPythUpdatesData(pythUpdates.binary);
|
|
228
|
+
|
|
229
|
+
return new PythPrices({
|
|
230
|
+
dict: pricesDict,
|
|
231
|
+
dataCell,
|
|
232
|
+
minPublishTime,
|
|
233
|
+
maxPublishTime,
|
|
234
|
+
refAssets,
|
|
235
|
+
targetFeeds,
|
|
236
|
+
binaryUpdate: pythUpdates.binary,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return PythPrices.createEmptyPrices();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async getPricesForWithdraw(
|
|
243
|
+
realPrincipals: Dictionary<bigint, bigint>,
|
|
244
|
+
withdrawAsset: PoolAssetConfig,
|
|
245
|
+
collateralToDebt = false,
|
|
246
|
+
fetchConfig?: FetchConfig,
|
|
247
|
+
): Promise<PythPrices> {
|
|
248
|
+
let assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
|
|
249
|
+
if (
|
|
250
|
+
checkNotInDebtAtAll(realPrincipals) &&
|
|
251
|
+
(realPrincipals.get(withdrawAsset.assetId) ?? 0n) > 0n &&
|
|
252
|
+
!collateralToDebt
|
|
253
|
+
) {
|
|
254
|
+
return PythPrices.createEmptyPrices();
|
|
255
|
+
}
|
|
256
|
+
if (assets.includes(undefined)) {
|
|
257
|
+
throw new Error('User from another pool');
|
|
258
|
+
}
|
|
259
|
+
if (!assets.includes(withdrawAsset)) {
|
|
260
|
+
assets.push(withdrawAsset);
|
|
261
|
+
}
|
|
262
|
+
if (collateralToDebt && assets.length == 1) {
|
|
263
|
+
throw new Error('Cannot debt only one supplied asset');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return await this.getPrices(
|
|
267
|
+
assets.map((x) => x!),
|
|
268
|
+
fetchConfig,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Updates feeds data from specified endpoint
|
|
274
|
+
* @param feedIds list of pyth feed ids to fetch
|
|
275
|
+
* @returns binary - buffer of feeds update, parsed - json feeds data
|
|
276
|
+
*/
|
|
277
|
+
async #getPythFeedsUpdates(feedIds: HexString[]): Promise<PythFeedUpdateType> {
|
|
278
|
+
const latestPriceUpdates: PriceUpdate = await Promise.any(
|
|
279
|
+
this.#pythConfig.pythEndpoints.map((x) =>
|
|
280
|
+
new HermesClient(x).getLatestPriceUpdates(feedIds, { encoding: 'hex' }),
|
|
281
|
+
),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const parsed = latestPriceUpdates['parsed'];
|
|
285
|
+
const binary = Buffer.from(latestPriceUpdates.binary.data[0], 'hex');
|
|
286
|
+
|
|
287
|
+
return { binary, parsed };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async #fetchPythUpdatesWithRetry(
|
|
291
|
+
requiredFeeds: HexString[],
|
|
292
|
+
fetchConfig?: FetchConfig,
|
|
293
|
+
): Promise<PythFeedUpdateType> {
|
|
294
|
+
return proxyFetchRetries(this.#getPythFeedsUpdates(requiredFeeds), fetchConfig);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Creates a list of required feed IDs for the given assets
|
|
299
|
+
* @param assets - Array of pool asset configurations
|
|
300
|
+
* @returns Array of unique feed IDs required for the assets
|
|
301
|
+
*/
|
|
302
|
+
createRequiredFeedsList(assets: PoolAssetConfig[]): HexString[] {
|
|
303
|
+
const requestedFeeds = new Set<HexString>();
|
|
304
|
+
|
|
305
|
+
for (const asset of assets) {
|
|
306
|
+
const addFeedsForAsset = (assetId: bigint) => {
|
|
307
|
+
if (this.#assetToFeeds.has(assetId)) {
|
|
308
|
+
const [feedId, refFeedId] = this.#assetToFeeds.get(assetId)!;
|
|
309
|
+
requestedFeeds.add(feedId);
|
|
310
|
+
|
|
311
|
+
if (refFeedId !== '0x0') {
|
|
312
|
+
requestedFeeds.add(refFeedId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
if (this.#allowedRefTokens.has(asset.assetId)) {
|
|
318
|
+
const refTokenId = this.#allowedRefTokens.get(asset.assetId)!;
|
|
319
|
+
addFeedsForAsset(refTokenId);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
addFeedsForAsset(asset.assetId);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return Array.from(requestedFeeds);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// public createRequiredFeedsList(assets: PoolAssetConfig[]): HexString[] {
|
|
329
|
+
// const requiredFeeds = new Set<bigint>();
|
|
330
|
+
|
|
331
|
+
// for (const asset of assets) {
|
|
332
|
+
// let pythId = this.#evaaToPythDirect.get(asset.assetId);
|
|
333
|
+
|
|
334
|
+
// // If assetId no have native feed — try by allowedRefTokens (evAA->baseEvAA->pyth)
|
|
335
|
+
// if (!pythId) {
|
|
336
|
+
// const baseEvaa = this.#allowedRefEvaa.get(asset.assetId);
|
|
337
|
+
// if (baseEvaa) pythId = this.#evaaToPythDirect.get(baseEvaa) ?? null!;
|
|
338
|
+
// }
|
|
339
|
+
|
|
340
|
+
// if (pythId) {
|
|
341
|
+
// requiredFeeds.add(pythId);
|
|
342
|
+
// const feedInfo = this.#parsedFeedsMap.get(pythId);
|
|
343
|
+
// if (feedInfo?.feedId && feedInfo.feedId !== 0n) {
|
|
344
|
+
// requiredFeeds.add(feedInfo.feedId);
|
|
345
|
+
// }
|
|
346
|
+
// }
|
|
347
|
+
// }
|
|
348
|
+
|
|
349
|
+
// return Array.from(requiredFeeds).map((id) => '0x' + id.toString(16));
|
|
350
|
+
// }
|
|
351
|
+
|
|
352
|
+
#filterEmptyPrincipalsAndAssets(principals: Dictionary<bigint, bigint>) {
|
|
353
|
+
return principals
|
|
354
|
+
.keys()
|
|
355
|
+
.filter((x) => principals.get(x)! != 0n)
|
|
356
|
+
.map((x) => this.#poolAssetsConfig.find((asset) => asset.assetId == x));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
export * from './collectors';
|
|
1
2
|
export * from './constants';
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './PricesCollector';
|
|
4
|
-
export * from './PythCollector';
|
|
3
|
+
export * from './prices';
|
|
5
4
|
export * from './sources';
|
|
6
5
|
export * from './Types';
|
|
7
6
|
export * from './utils';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Cell, Dictionary } from '@ton/core';
|
|
2
|
+
import { PoolAssetConfig } from '../../types/Master';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Basic price data structure
|
|
6
|
+
* Simplified version of RawPriceData for internal processing
|
|
7
|
+
*/
|
|
8
|
+
export interface PriceData {
|
|
9
|
+
/** Dictionary mapping asset IDs to their prices */
|
|
10
|
+
readonly dict: Dictionary<bigint, bigint>;
|
|
11
|
+
/** Serialized data cell containing price information uses for smartcontract */
|
|
12
|
+
readonly dataCell: Cell;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Configuration interface for timestamp boundaries
|
|
17
|
+
* Used to filter price data based on publish time
|
|
18
|
+
*/
|
|
19
|
+
export interface TimestampBoundaries {
|
|
20
|
+
/** Maximum valid time for price data in seconds */
|
|
21
|
+
readonly minPublishTime?: number;
|
|
22
|
+
/** Minimum valid time for price data in seconds */
|
|
23
|
+
readonly maxPublishTime?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type PriceParameters = PriceData & TimestampBoundaries;
|
|
27
|
+
|
|
28
|
+
export abstract class AbstractPrices {
|
|
29
|
+
constructor(protected readonly parameters: PriceParameters) {}
|
|
30
|
+
|
|
31
|
+
get dict() {
|
|
32
|
+
const dict = Dictionary.empty<bigint, bigint>();
|
|
33
|
+
for (const [key, value] of this.parameters.dict) {
|
|
34
|
+
dict.set(key, value);
|
|
35
|
+
}
|
|
36
|
+
return dict;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get dataCell() {
|
|
40
|
+
return new Cell(this.parameters.dataCell);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getAssetPrice<T extends bigint | PoolAssetConfig>(asset: T): bigint | undefined {
|
|
44
|
+
const assetId = this.#extractAssetId(asset);
|
|
45
|
+
return this.parameters.dict.get(assetId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#extractAssetId(asset: bigint | PoolAssetConfig): bigint {
|
|
49
|
+
return typeof asset === 'bigint' ? asset : asset.assetId;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get minPublishTime() {
|
|
53
|
+
return this.parameters.minPublishTime;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get maxPublishTime() {
|
|
57
|
+
return this.parameters.maxPublishTime;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Cell, Dictionary } from '@ton/core';
|
|
2
|
+
import { AbstractPrices, PriceParameters } from './AbstractPrices';
|
|
3
|
+
|
|
4
|
+
export enum ClassicPricesMode {
|
|
5
|
+
SPOT = 'spot',
|
|
6
|
+
TWAP = 'twap',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const ClassicPricesOffset = {
|
|
10
|
+
[ClassicPricesMode.SPOT]: 1n,
|
|
11
|
+
[ClassicPricesMode.TWAP]: 0n,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface ClassicPricesParams extends PriceParameters {
|
|
15
|
+
readonly mode?: ClassicPricesMode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ClassicPrices extends AbstractPrices {
|
|
19
|
+
constructor(private readonly params: ClassicPricesParams) {
|
|
20
|
+
super(params);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static createEmptyTwapPrices(): ClassicPrices {
|
|
24
|
+
return new ClassicPrices({
|
|
25
|
+
mode: ClassicPricesMode.TWAP,
|
|
26
|
+
dict: Dictionary.empty<bigint, bigint>(),
|
|
27
|
+
dataCell: Cell.EMPTY,
|
|
28
|
+
minPublishTime: undefined,
|
|
29
|
+
maxPublishTime: undefined,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static createEmptySpotPrices(): ClassicPrices {
|
|
34
|
+
return new ClassicPrices({
|
|
35
|
+
mode: ClassicPricesMode.SPOT,
|
|
36
|
+
dict: Dictionary.empty<bigint, bigint>(),
|
|
37
|
+
dataCell: Cell.EMPTY,
|
|
38
|
+
minPublishTime: undefined,
|
|
39
|
+
maxPublishTime: undefined,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static createEmptyPrices(): ClassicPrices {
|
|
44
|
+
return new ClassicPrices({
|
|
45
|
+
mode: undefined,
|
|
46
|
+
dict: Dictionary.empty<bigint, bigint>(),
|
|
47
|
+
dataCell: Cell.EMPTY,
|
|
48
|
+
minPublishTime: undefined,
|
|
49
|
+
maxPublishTime: undefined,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HexString } from '@pythnetwork/hermes-client';
|
|
2
|
+
import { Cell, Dictionary } from '@ton/core';
|
|
3
|
+
import { PoolAssetConfig } from '../../types/Master';
|
|
4
|
+
import { AbstractPrices, PriceParameters } from './AbstractPrices';
|
|
5
|
+
|
|
6
|
+
export interface PythPricesParams extends PriceParameters {
|
|
7
|
+
targetFeeds: HexString[];
|
|
8
|
+
refAssets: PoolAssetConfig[];
|
|
9
|
+
binaryUpdate: Buffer;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class PythPrices extends AbstractPrices {
|
|
13
|
+
constructor(private readonly params: PythPricesParams) {
|
|
14
|
+
super(params);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static createEmptyPrices(): PythPrices {
|
|
18
|
+
return new PythPrices({
|
|
19
|
+
dict: Dictionary.empty<bigint, bigint>(),
|
|
20
|
+
dataCell: Cell.EMPTY,
|
|
21
|
+
minPublishTime: undefined,
|
|
22
|
+
maxPublishTime: undefined,
|
|
23
|
+
targetFeeds: [],
|
|
24
|
+
refAssets: [],
|
|
25
|
+
binaryUpdate: Buffer.alloc(0),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
refAssets(): PoolAssetConfig[] {
|
|
30
|
+
return this.params.refAssets;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
targetFeeds(): HexString[] {
|
|
34
|
+
return this.params.targetFeeds;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
binaryUpdate(): Buffer {
|
|
38
|
+
return this.params.binaryUpdate;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -4,10 +4,21 @@ import { EvaaOracle, ExtendedEvaaOracle } from '../types/Master';
|
|
|
4
4
|
import { convertToMerkleProof, generateMerkleProofDirect } from '../utils/merkleProof';
|
|
5
5
|
import { FetchConfig } from '../utils/utils';
|
|
6
6
|
import { TTL_ORACLE_DATA_SEC } from './constants';
|
|
7
|
+
import { PriceData } from './prices';
|
|
7
8
|
import { BackendPriceSource } from './sources/Backend';
|
|
8
9
|
import { IcpPriceSource } from './sources/Icp';
|
|
9
10
|
import { PriceSource } from './sources/PriceSource';
|
|
10
|
-
import { OraclePricesData,
|
|
11
|
+
import { OraclePricesData, PriceSourcesConfig, RawPriceData } from './Types';
|
|
12
|
+
|
|
13
|
+
export const UPDATE_PRICE_FEEDS_BASE_GAS = 300000n;
|
|
14
|
+
export const UPDATE_PRICE_FEEDS_PER_UPDATE_GAS = 90000n;
|
|
15
|
+
// Current settings in basechain are as follows: 1 unit of gas costs 400 nanotons
|
|
16
|
+
export const GAS_PRICE_FACTOR = 400n;
|
|
17
|
+
|
|
18
|
+
// TODO: use PythContract for dynamic fee calc
|
|
19
|
+
export function calcPythUpdateFee(numUpdates: number) {
|
|
20
|
+
return (UPDATE_PRICE_FEEDS_BASE_GAS + UPDATE_PRICE_FEEDS_PER_UPDATE_GAS * BigInt(numUpdates)) * GAS_PRICE_FACTOR;
|
|
21
|
+
}
|
|
11
22
|
|
|
12
23
|
export function verifyPricesTimestamp() {
|
|
13
24
|
return function (priceData: RawPriceData): boolean {
|
package/src/types/Master.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Address, Cell, Dictionary } from '@ton/core';
|
|
2
|
-
import {
|
|
2
|
+
import { AbstractCollector } from '../oracles';
|
|
3
3
|
export { FeedMapItem, parseFeedsMapDict } from '../api/feeds';
|
|
4
4
|
|
|
5
5
|
export type MasterConstants = {
|
|
@@ -16,8 +16,6 @@ export type MasterConstants = {
|
|
|
16
16
|
COLLATERAL_WORTH_THRESHOLD: bigint;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export type PoolAssetsConfig = PoolAssetConfig[];
|
|
20
|
-
|
|
21
19
|
export type PoolAssetConfig = {
|
|
22
20
|
name: string;
|
|
23
21
|
assetId: bigint;
|
|
@@ -29,9 +27,9 @@ export type PoolConfig = {
|
|
|
29
27
|
masterAddress: Address;
|
|
30
28
|
masterVersion: number;
|
|
31
29
|
masterConstants: MasterConstants;
|
|
32
|
-
poolAssetsConfig:
|
|
30
|
+
poolAssetsConfig: PoolAssetConfig[];
|
|
33
31
|
lendingCode: Cell;
|
|
34
|
-
|
|
32
|
+
collector: AbstractCollector;
|
|
35
33
|
};
|
|
36
34
|
|
|
37
35
|
export type UpgradeConfig = {
|
|
@@ -77,7 +75,7 @@ export type AssetData = {
|
|
|
77
75
|
bRate: bigint;
|
|
78
76
|
totalSupply: bigint;
|
|
79
77
|
totalBorrow: bigint;
|
|
80
|
-
|
|
78
|
+
lastAccrual: bigint;
|
|
81
79
|
balance: bigint;
|
|
82
80
|
trackingSupplyIndex: bigint;
|
|
83
81
|
trackingBorrowIndex: bigint;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Dictionary } from '@ton/core';
|
|
2
|
-
import { Prices } from '.';
|
|
3
|
-
import { PoolAssetConfig, PoolAssetsConfig } from '../types/Master';
|
|
4
|
-
import { FetchConfig } from '../utils/utils';
|
|
5
|
-
export interface Oracle {
|
|
6
|
-
getPricesForLiquidate(realPrincipals: Dictionary<bigint, bigint>, fetchConfig?: FetchConfig): Promise<Prices>;
|
|
7
|
-
getPricesForSupplyWithdraw(realPrincipals: Dictionary<bigint, bigint>, supplyAsset: PoolAssetConfig | undefined, withdrawAsset: PoolAssetConfig | undefined, collateralToDebt: boolean, fetchConfig?: FetchConfig): Promise<Prices>;
|
|
8
|
-
getPrices(assets: PoolAssetsConfig, fetchConfig?: FetchConfig): Promise<Prices>;
|
|
9
|
-
}
|
package/dist/prices/Prices.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Cell, Dictionary } from '@ton/core';
|
|
2
|
-
import { PoolAssetConfig } from '../types/Master';
|
|
3
|
-
export declare class Prices {
|
|
4
|
-
#private;
|
|
5
|
-
constructor(dict: Dictionary<bigint, bigint>, dataCell: Cell, minPublishTime?: bigint, maxPublishTime?: bigint);
|
|
6
|
-
get dict(): Dictionary<bigint, bigint>;
|
|
7
|
-
get dataCell(): Cell;
|
|
8
|
-
get minPublishTime(): bigint | undefined;
|
|
9
|
-
get maxPublishTime(): bigint | undefined;
|
|
10
|
-
getAssetPrice<T extends bigint | PoolAssetConfig>(asset: T): bigint | undefined;
|
|
11
|
-
}
|
package/dist/prices/Prices.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
-
};
|
|
8
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
-
};
|
|
13
|
-
var _Prices_instances, _Prices_dict, _Prices_dataCell, _Prices_minPublishTime, _Prices_maxPublishTime, _Prices_extractAssetId;
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.Prices = void 0;
|
|
16
|
-
const core_1 = require("@ton/core");
|
|
17
|
-
class Prices {
|
|
18
|
-
constructor(dict, dataCell, minPublishTime, maxPublishTime) {
|
|
19
|
-
_Prices_instances.add(this);
|
|
20
|
-
_Prices_dict.set(this, void 0);
|
|
21
|
-
_Prices_dataCell.set(this, void 0);
|
|
22
|
-
_Prices_minPublishTime.set(this, void 0);
|
|
23
|
-
_Prices_maxPublishTime.set(this, void 0);
|
|
24
|
-
__classPrivateFieldSet(this, _Prices_dict, dict, "f");
|
|
25
|
-
__classPrivateFieldSet(this, _Prices_dataCell, dataCell, "f");
|
|
26
|
-
__classPrivateFieldSet(this, _Prices_minPublishTime, minPublishTime, "f");
|
|
27
|
-
__classPrivateFieldSet(this, _Prices_maxPublishTime, maxPublishTime, "f");
|
|
28
|
-
}
|
|
29
|
-
get dict() {
|
|
30
|
-
const dict = core_1.Dictionary.empty();
|
|
31
|
-
for (const [key, value] of __classPrivateFieldGet(this, _Prices_dict, "f")) {
|
|
32
|
-
dict.set(key, value);
|
|
33
|
-
}
|
|
34
|
-
return dict;
|
|
35
|
-
}
|
|
36
|
-
get dataCell() {
|
|
37
|
-
return new core_1.Cell(__classPrivateFieldGet(this, _Prices_dataCell, "f"));
|
|
38
|
-
}
|
|
39
|
-
get minPublishTime() {
|
|
40
|
-
return __classPrivateFieldGet(this, _Prices_minPublishTime, "f");
|
|
41
|
-
}
|
|
42
|
-
get maxPublishTime() {
|
|
43
|
-
return __classPrivateFieldGet(this, _Prices_maxPublishTime, "f");
|
|
44
|
-
}
|
|
45
|
-
getAssetPrice(asset) {
|
|
46
|
-
const assetId = __classPrivateFieldGet(this, _Prices_instances, "m", _Prices_extractAssetId).call(this, asset);
|
|
47
|
-
return __classPrivateFieldGet(this, _Prices_dict, "f").get(assetId);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
exports.Prices = Prices;
|
|
51
|
-
_Prices_dict = new WeakMap(), _Prices_dataCell = new WeakMap(), _Prices_minPublishTime = new WeakMap(), _Prices_maxPublishTime = new WeakMap(), _Prices_instances = new WeakSet(), _Prices_extractAssetId = function _Prices_extractAssetId(asset) {
|
|
52
|
-
return typeof asset === 'bigint' ? asset : asset.assetId;
|
|
53
|
-
};
|