@evaafi/sdk 0.6.1 → 0.6.2-a

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.
Files changed (71) hide show
  1. package/LICENSE.md +7 -0
  2. package/dist/api/liquidation.js +1 -3
  3. package/dist/api/math.js +0 -2
  4. package/dist/api/parser.js +11 -11
  5. package/dist/api/prices.d.ts +5 -2
  6. package/dist/api/prices.js +35 -13
  7. package/dist/constants/assets.d.ts +8 -0
  8. package/dist/constants/assets.js +31 -1
  9. package/dist/constants/general.d.ts +5 -2
  10. package/dist/constants/general.js +12 -22
  11. package/dist/constants/pools.d.ts +1 -0
  12. package/dist/constants/pools.js +19 -3
  13. package/dist/contracts/MasterContract.d.ts +5 -0
  14. package/dist/contracts/MasterContract.js +6 -0
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +7 -1
  17. package/dist/prices/Prices.d.ts +9 -0
  18. package/dist/prices/Prices.js +43 -0
  19. package/dist/prices/PricesCollector.d.ts +12 -0
  20. package/dist/prices/PricesCollector.js +123 -0
  21. package/dist/prices/Types.d.ts +33 -0
  22. package/dist/prices/Types.js +11 -0
  23. package/dist/prices/constants.d.ts +1 -0
  24. package/dist/prices/constants.js +4 -0
  25. package/dist/prices/index.d.ts +6 -0
  26. package/dist/prices/index.js +22 -0
  27. package/dist/prices/sources/Backend.d.ts +13 -0
  28. package/dist/prices/sources/Backend.js +52 -0
  29. package/dist/prices/sources/Icp.d.ts +10 -0
  30. package/dist/prices/sources/Icp.js +23 -0
  31. package/dist/prices/sources/Iota.d.ts +39 -0
  32. package/dist/prices/sources/Iota.js +49 -0
  33. package/dist/prices/sources/PriceSource.d.ts +14 -0
  34. package/dist/prices/sources/PriceSource.js +26 -0
  35. package/dist/prices/sources/index.d.ts +4 -0
  36. package/dist/prices/sources/index.js +20 -0
  37. package/dist/prices/utils.d.ts +23 -0
  38. package/dist/prices/utils.js +148 -0
  39. package/dist/types/Master.d.ts +2 -1
  40. package/dist/types/User.d.ts +1 -0
  41. package/dist/utils/userJettonWallet.js +42 -43
  42. package/dist/utils/utils.d.ts +1 -0
  43. package/dist/utils/utils.js +5 -1
  44. package/package.json +4 -2
  45. package/src/api/liquidation.ts +0 -1
  46. package/src/api/math.ts +1 -2
  47. package/src/api/parser.ts +10 -13
  48. package/src/api/prices.ts +20 -7
  49. package/src/constants/assets.ts +57 -0
  50. package/src/constants/general.ts +11 -23
  51. package/src/constants/pools.ts +21 -5
  52. package/src/contracts/MasterContract.ts +8 -1
  53. package/src/index.ts +7 -2
  54. package/src/prices/Prices.ts +32 -0
  55. package/src/prices/PricesCollector.ts +139 -0
  56. package/src/prices/Types.ts +44 -0
  57. package/src/prices/constants.ts +1 -0
  58. package/src/prices/index.ts +6 -0
  59. package/src/prices/sources/Backend.ts +62 -0
  60. package/src/prices/sources/Icp.ts +27 -0
  61. package/src/prices/sources/Iota.ts +90 -0
  62. package/src/prices/sources/PriceSource.ts +35 -0
  63. package/src/prices/sources/index.ts +4 -0
  64. package/src/prices/utils.ts +170 -0
  65. package/src/types/Master.ts +3 -2
  66. package/src/types/User.ts +2 -2
  67. package/src/utils/userJettonWallet.ts +43 -53
  68. package/src/utils/utils.ts +5 -1
  69. package/src/config.ts +0 -1
  70. package/src/types/Common.ts +0 -16
  71. package/src/utils/priceUtils.ts +0 -177
@@ -0,0 +1,139 @@
1
+ import { Cell, Dictionary } from "@ton/core";
2
+ import { MAINNET_POOL_CONFIG } from "../constants/pools";
3
+ import { PoolAssetConfig, PoolAssetsConfig, PoolConfig } from "../types/Master";
4
+ import { PriceSource } from "./sources";
5
+ import { DefaultPriceSourcesConfig, PriceSourcesConfig, RawPriceData } from "./Types";
6
+ import { collectAndFilterPrices, generatePriceSources, getMedianPrice, packAssetsData, packOraclesData, packPrices, verifyPricesSign, verifyPricesTimestamp } from "./utils";
7
+ import { delay } from "../utils/utils";
8
+ import { Prices } from "./Prices";
9
+ import { checkNotInDebtAtAll } from "../api/math";
10
+
11
+
12
+ export class PricesCollector {
13
+ #prices: RawPriceData[];
14
+ #poolConfig: PoolConfig;
15
+ #sourcesConfig: PriceSourcesConfig;
16
+ #priceSources: PriceSource[];
17
+
18
+ constructor(poolConfig: PoolConfig = MAINNET_POOL_CONFIG, sourcesConfig: PriceSourcesConfig = DefaultPriceSourcesConfig, additionalPriceSources?: PriceSource[]) {
19
+ this.#poolConfig = poolConfig;
20
+ this.#sourcesConfig = sourcesConfig;
21
+ this.#priceSources = generatePriceSources(this.#sourcesConfig, this.#poolConfig.oracles);
22
+
23
+ if (additionalPriceSources) {
24
+ this.#priceSources.push(...additionalPriceSources);
25
+ }
26
+
27
+ this.#prices = [];
28
+ }
29
+
30
+ // TODO Make UserData class and incapsulate raw bigintegers
31
+
32
+ async getPricesForLiquidate(realPrincipals: Dictionary<bigint, bigint>, retries: number = 1, timeout: number = 3000): Promise<Prices> {
33
+ const assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
34
+ if (assets.includes(undefined)) {
35
+ throw new Error("User from another pool");
36
+ }
37
+ return await this.getPrices(assets.map(x => x!), retries, timeout);
38
+ }
39
+
40
+
41
+ async getPricesForWithdraw(realPrincipals: Dictionary<bigint, bigint>, withdrawAsset: PoolAssetConfig, collateralToDebt = false, retries: number = 1, timeout: number = 3000): Promise<Prices> {
42
+ let assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
43
+ if (checkNotInDebtAtAll(realPrincipals) && (realPrincipals.get(withdrawAsset.assetId) ?? 0n) > 0n && !collateralToDebt) {
44
+ return new Prices(Dictionary.empty<bigint, bigint>(), Cell.EMPTY);
45
+ }
46
+
47
+ if (assets.includes(undefined)) {
48
+ throw new Error("User from another pool");
49
+ }
50
+
51
+ if (!assets.includes(withdrawAsset)) {
52
+ assets.push(withdrawAsset);
53
+ }
54
+
55
+ if (collateralToDebt && assets.length == 1) {
56
+ throw new Error("Cannot debt only one supplied asset");
57
+ }
58
+
59
+ return await this.getPrices(assets.map(x => x!), retries, timeout);
60
+ }
61
+
62
+ async getPrices(assets: PoolAssetsConfig = this.#poolConfig.poolAssetsConfig, retries: number = 1, timeout: number = 3000): Promise<Prices> {
63
+ console.debug('[getPrices] Assets length', assets.length);
64
+
65
+ if (assets.length == 0) {
66
+ return new Prices(Dictionary.empty<bigint, bigint>(), Cell.EMPTY);
67
+ }
68
+
69
+ for (let i = 0; i <= retries; i++) { // attemts = retries + 1
70
+ if (!this.#prices || this.#filterPrices() < this.#poolConfig.minimalOracles) {
71
+ //console.debug('[getPrices] Load prices attemp', i + 1)
72
+ if (i > 0) {
73
+ await delay(timeout);
74
+ }
75
+ await this.#collectPrices();
76
+ } else {
77
+ break;
78
+ }
79
+ }
80
+
81
+ if (this.#prices.length < this.#poolConfig.minimalOracles) {
82
+ throw new Error(`Error per updating prices, valid ${this.#prices.length} of ${this.#poolConfig.minimalOracles}`); // if still not enough data after retries
83
+ }
84
+ const prices = this.#getPricesByAssetList(assets);
85
+ return new Prices(prices.dict, prices.dataCell);
86
+ }
87
+
88
+ #getPricesByAssetList(assets: PoolAssetsConfig) {
89
+ //console.debug('[getPricesByAssetList] start')
90
+ let pricesFiltered = this.#prices; // for strict check this.#prices.filter(x => assets.every(asset => x.dict.has(asset.assetId)));
91
+
92
+ if (pricesFiltered.length < this.#poolConfig.minimalOracles) {
93
+ throw new Error("Not enough price data");
94
+ }
95
+
96
+ if (pricesFiltered.length > this.#poolConfig.minimalOracles) {
97
+ const sortedByTimestamp = pricesFiltered.slice().sort((a, b) => b.timestamp - a.timestamp);
98
+ const newerPrices = sortedByTimestamp.slice(0, this.#poolConfig.minimalOracles);
99
+ pricesFiltered = newerPrices.sort((a, b) => a.oracleId - b.oracleId);
100
+ }
101
+
102
+ const medianData = assets.map(asset => ({ assetId: asset.assetId, medianPrice: getMedianPrice(this.#prices, asset.assetId)}));
103
+
104
+ const nonEmptymedianData = medianData.filter(x => x.medianPrice != null) as { assetId: bigint, medianPrice: bigint }[];
105
+
106
+ const packedMedianData = packAssetsData(nonEmptymedianData);
107
+
108
+ const oraclesData = this.#prices.map(x => ({oracle: {id: x.oracleId, pubkey: x.pubkey}, data: {timestamp: x.timestamp, prices: x.dict}, signature: x.signature}));
109
+ const packedOracleData = packOraclesData(oraclesData, nonEmptymedianData.map(x => x.assetId));
110
+
111
+ const dict = Dictionary.empty<bigint, bigint>();
112
+ for (const medianDataAsset of nonEmptymedianData) {
113
+ dict.set(medianDataAsset.assetId, medianDataAsset.medianPrice);
114
+ }
115
+
116
+ return {
117
+ dict: dict,
118
+ dataCell: packPrices(packedMedianData, packedOracleData)
119
+ }
120
+ }
121
+
122
+ async #collectPrices(): Promise<boolean> {
123
+ try {
124
+ this.#prices = await Promise.any(this.#priceSources.map(x => collectAndFilterPrices(x, this.#poolConfig)));
125
+ return true;
126
+ }
127
+ catch { }
128
+ return false;
129
+ }
130
+
131
+ #filterPrices(): number { // filter again for expire check
132
+ this.#prices = this.#prices.filter(verifyPricesTimestamp());
133
+ return this.#prices.length;
134
+ }
135
+
136
+ #filterEmptyPrincipalsAndAssets(principals: Dictionary<bigint, bigint>) {
137
+ return principals.keys().filter(x => principals.get(x)! > 0n).map(x => this.#poolConfig.poolAssetsConfig.find(asset => asset.assetId == x));
138
+ }
139
+ }
@@ -0,0 +1,44 @@
1
+ import { Cell, Dictionary } from '@ton/core';
2
+
3
+ /**
4
+ * Configuration for price source endpoints.
5
+ */
6
+ export type PriceSourcesConfig = {
7
+ /** Endpoints for backend price data */
8
+ backendEndpoints: string[];
9
+
10
+ /** Endpoints for IOTA price data */
11
+ iotaEndpoints: string[];
12
+
13
+ /** Endpoints for ICP price data */
14
+ icpEndpoints: string[];
15
+ };
16
+
17
+ /**
18
+ * Default configuration for price source endpoints.
19
+ */
20
+ export const DefaultPriceSourcesConfig: PriceSourcesConfig = {
21
+ backendEndpoints: ['evaa.space'],
22
+ iotaEndpoints: ['api.stardust-mainnet.iotaledger.net'],
23
+ icpEndpoints: ['6khmc-aiaaa-aaaap-ansfq-cai.raw.icp0.io'],
24
+ }
25
+
26
+ export type RawPriceData = {
27
+ dict: Dictionary<bigint, bigint>;
28
+ dataCell: Cell;
29
+ oracleId: number;
30
+ signature: Buffer;
31
+ pubkey: Buffer;
32
+ timestamp: number;
33
+ };
34
+
35
+
36
+ export type PriceData = {
37
+ dict: Dictionary<bigint, bigint>;
38
+ dataCell: Cell;
39
+ };
40
+
41
+ export type OraclePricesData = {
42
+ timestamp: number,
43
+ prices: Dictionary<bigint, bigint>
44
+ }
@@ -0,0 +1 @@
1
+ export const TTL_ORACLE_DATA_SEC = 120;
@@ -0,0 +1,6 @@
1
+ export * from './sources';
2
+ export * from './Types';
3
+ export * from './constants';
4
+ export * from './utils';
5
+ export * from './Prices';
6
+ export * from './PricesCollector';
@@ -0,0 +1,62 @@
1
+ import { beginCell, Cell, Dictionary } from "@ton/core";
2
+ import { RawPriceData } from "..";
3
+ import { PriceSource } from "./PriceSource";
4
+
5
+ export class BackendPriceSource extends PriceSource {
6
+ protected priceSourceName: string = 'BackendPriceSource';
7
+
8
+ async getPrices(): Promise<RawPriceData[]> {
9
+ const data = await this.loadOracleData();
10
+ return data.map(outputData => this.parsePrices(outputData));
11
+ }
12
+
13
+ async loadOracleData(): Promise<OutputData[]> {
14
+ let response = await fetch(`https://${this._endpoint}/api/prices`, {
15
+ headers: { accept: 'application/json' },
16
+ signal: AbortSignal.timeout(5000)
17
+ });
18
+
19
+ const resp = (await response.json());
20
+ const data = resp as Record<string, string>;
21
+ let outputData: OutputData[] = [];
22
+
23
+ for (const nft of this._nfts) {
24
+ outputData.push({ oracleId: nft.id, data: data[nft.address] })
25
+ }
26
+
27
+ return outputData;
28
+ }
29
+
30
+
31
+ parsePrices(outputData: OutputData): RawPriceData {
32
+ try {
33
+ //console.debug('outputData', outputData);
34
+ //console.debug(outputData.data.replace('0x', '').replace(/[0-9a-f]{2}/g, '%$&'))
35
+ const data = JSON.parse(
36
+ decodeURIComponent(outputData.data.replace('0x', '').replace(/[0-9a-f]{2}/g, '%$&')),
37
+ );
38
+ const pricesCell = Cell.fromBoc(Buffer.from(data['packedPrices'], 'hex'))[0];
39
+ const signature = Buffer.from(data['signature'], 'hex');
40
+ const publicKey = Buffer.from(data['publicKey'], 'hex');
41
+ const timestamp = Number(data['timestamp']);
42
+
43
+ return {
44
+ dict: pricesCell.beginParse().loadRef().beginParse().loadDictDirect(Dictionary.Keys.BigUint(256), Dictionary.Values.BigVarUint(4)),
45
+ dataCell: beginCell().storeRef(pricesCell).storeBuffer(signature).endCell(),
46
+ oracleId: outputData.oracleId,
47
+ signature: signature,
48
+ pubkey: publicKey,
49
+ timestamp: timestamp,
50
+ };
51
+ }
52
+ catch (error) {
53
+ //console.debug(`Price source error ${this.priceSourceName} ${outputData.oracleId} ${outputData.data} ${error}`);
54
+ throw error;
55
+ }
56
+ }
57
+ }
58
+
59
+ type OutputData = {
60
+ oracleId: number,
61
+ data: string
62
+ };
@@ -0,0 +1,27 @@
1
+ import { BackendPriceSource } from ".";
2
+
3
+ export class IcpPriceSource extends BackendPriceSource {
4
+ protected priceSourceName: string = 'IcpPriceSource';
5
+
6
+ async loadOracleData(): Promise<OutputData[]> {
7
+ let response = await fetch(`https://${this._endpoint}/prices`, {
8
+ headers: { accept: 'application/json' },
9
+ signal: AbortSignal.timeout(5000)
10
+ });
11
+
12
+ const data = (await response.json()) as Record<string, string>;
13
+
14
+ let outputData: OutputData[] = [];
15
+
16
+ for (const nft of this._nfts) {
17
+ outputData.push({oracleId: nft.id, data: data[nft.address] })
18
+ }
19
+
20
+ return outputData;
21
+ }
22
+ }
23
+
24
+ type OutputData = {
25
+ oracleId: number,
26
+ data: string
27
+ };
@@ -0,0 +1,90 @@
1
+ import { beginCell, Cell, Dictionary } from "@ton/core";
2
+ import { PriceSource } from "./PriceSource";
3
+ import { RawPriceData } from "..";
4
+
5
+ export class IotaPriceSource extends PriceSource {
6
+ protected priceSourceName: string = 'IotaPriceSource';
7
+
8
+ async getPrices(): Promise<RawPriceData[]> {
9
+ return await Promise.all(this.nfts.map(nft => this.loadOracleData(nft.address)
10
+ .then(x => this.parsePrices(x, nft.id))));
11
+ }
12
+
13
+ async loadOracleData(oracleNftId: String): Promise<OutputData> {
14
+ let result = await fetch(`https://${this._endpoint}/api/indexer/v1/outputs/nft/${oracleNftId}`, {
15
+ headers: { accept: 'application/json' },
16
+ signal: AbortSignal.timeout(5000)
17
+ });
18
+ let outputId = (await result.json()) as NftData;
19
+
20
+ result = await fetch(`https://${this._endpoint}/api/core/v2/outputs/${outputId.items[0]}`, {
21
+ headers: { accept: 'application/json' },
22
+ signal: AbortSignal.timeout(5000)
23
+ });
24
+ return (await result.json() as OutputData);
25
+ }
26
+
27
+
28
+ parsePrices(outputData: OutputData, oracleId: number): RawPriceData {
29
+ const data = JSON.parse(
30
+ decodeURIComponent(outputData.output.features[0].data.replace('0x', '').replace(/[0-9a-f]{2}/g, '%$&')),
31
+ );
32
+
33
+ try {
34
+ const pricesCell = Cell.fromBoc(Buffer.from(data['packedPrices'], 'hex'))[0];
35
+ const signature = Buffer.from(data['signature'], 'hex');
36
+ const publicKey = Buffer.from(data['publicKey'], 'hex');
37
+ const timestamp = Number(data['timestamp']);
38
+
39
+ return {
40
+ dict: pricesCell.beginParse().loadRef().beginParse().loadDictDirect(Dictionary.Keys.BigUint(256), Dictionary.Values.BigVarUint(4)),
41
+ dataCell: beginCell().storeRef(pricesCell).storeBuffer(signature).endCell(),
42
+ oracleId: oracleId,
43
+ signature: signature,
44
+ pubkey: publicKey,
45
+ timestamp: timestamp,
46
+ };
47
+ }
48
+ catch (error) {
49
+ //console.debug(`Price source error ${this.priceSourceName} ${outputData} ${error}`);
50
+ throw error;
51
+ }
52
+ }
53
+ }
54
+
55
+ type NftData = {
56
+ ledgerIndex: number;
57
+ pageSize: number;
58
+ items: string[];
59
+ };
60
+
61
+ type OutputData = {
62
+ metadata: {
63
+ blockId: string;
64
+ transactionId: string;
65
+ outputIndex: number;
66
+ isSpent: boolean;
67
+ milestoneIndexSpent: number;
68
+ milestoneTimestampSpent: number;
69
+ transactionIdSpent: string;
70
+ milestoneIndexBooked: number;
71
+ milestoneTimestampBooked: number;
72
+ ledgerIndex: number;
73
+ };
74
+ output: {
75
+ type: number;
76
+ amount: string;
77
+ nftId: string;
78
+ unlockConditions: {
79
+ type: number;
80
+ address: {
81
+ type: number;
82
+ pubKeyHash: string;
83
+ };
84
+ }[];
85
+ features: {
86
+ type: number;
87
+ data: string;
88
+ }[];
89
+ };
90
+ };
@@ -0,0 +1,35 @@
1
+ import { RawPriceData } from "..";
2
+ import { OracleNFT } from "../../types/Master";
3
+
4
+ export abstract class PriceSource {
5
+ protected priceSourceName: string = 'BackendPriceSource';
6
+ protected _endpoint: string;
7
+ protected _nfts: OracleNFT[];
8
+
9
+ constructor(endpoint: string, nfts: OracleNFT[]) {
10
+ this._endpoint = endpoint;
11
+ this._nfts = nfts;
12
+ }
13
+
14
+ get sourceName() {
15
+ return this.priceSourceName;
16
+ }
17
+
18
+ get endpoint() {
19
+ return this._endpoint;
20
+ }
21
+
22
+ get nfts() {
23
+ return this._nfts;
24
+ }
25
+
26
+ set endpoint(endpoint: string) {
27
+ this._endpoint = endpoint;
28
+ }
29
+
30
+ set nfts(nfts: OracleNFT[]) {
31
+ this._nfts = nfts;
32
+ }
33
+
34
+ abstract getPrices(): Promise<RawPriceData[]>;
35
+ }
@@ -0,0 +1,4 @@
1
+ export * from './Backend';
2
+ export * from './Iota';
3
+ export * from './Icp';
4
+ export * from './PriceSource';
@@ -0,0 +1,170 @@
1
+ import { beginCell, Cell, Dictionary, Slice } from "@ton/core";
2
+ import { BackendPriceSource, DefaultPriceSourcesConfig, IcpPriceSource, IotaPriceSource, MAINNET_POOL_CONFIG, OraclePricesData, PriceData, PriceSource, PriceSourcesConfig, RawPriceData, TTL_ORACLE_DATA_SEC } from "..";
3
+ import { Oracle, OracleNFT, PoolConfig } from "../types/Master";
4
+ import { convertToMerkleProof, generateMerkleProofDirect } from "../utils/merkleProof";
5
+ import { signVerify } from "@ton/crypto";
6
+
7
+ export function verifyPricesTimestamp() {
8
+ return function(priceData: RawPriceData): boolean {
9
+ const timestamp = Date.now() / 1000;
10
+ const pricesTime = priceData.timestamp;
11
+
12
+ //console.debug('timestamp - pricesTime, pricesTime', timestamp - pricesTime, pricesTime);
13
+ return timestamp - pricesTime < TTL_ORACLE_DATA_SEC;
14
+ }
15
+ }
16
+
17
+ export function verifyPricesSign(nfts: OracleNFT[]) {
18
+ return function(priceData: RawPriceData): boolean {
19
+ if (nfts.findIndex(x => x.pubkey.equals(priceData.pubkey)) == -1) {
20
+ //console.debug('[verifyPricesSign] nft not found');
21
+ return false;
22
+ }
23
+
24
+ return verifyRawPriceDataSign(priceData)
25
+ }
26
+ }
27
+
28
+ /* export function verifyPricesAssets(assets: PoolAssetsConfig) {
29
+ return function(priceData: RawPriceData): boolean {
30
+ for (const asset of assets) {
31
+ if(!priceData.dict.has(asset.assetId)) {
32
+ return false;
33
+ }
34
+ }
35
+ return true;
36
+ }
37
+ } */
38
+
39
+ export function getMedianPrice(pricesData: PriceData[], asset: bigint): bigint | null {
40
+ try {
41
+ const usingPrices = pricesData.filter(x => x.dict.has(asset));
42
+ const sorted = usingPrices.map(x => x.dict.get(asset)!).sort((a, b) => Number(a) - Number(b));
43
+
44
+ if (sorted.length == 0) {
45
+ return null;
46
+ }
47
+
48
+ const mid = Math.floor(sorted.length / 2);
49
+ if (sorted.length % 2 === 0) {
50
+ return (sorted[mid - 1] + sorted[mid]) / 2n;
51
+ } else {
52
+ return sorted[mid];
53
+ }
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ export function packAssetsData(assetsData: {assetId: bigint, medianPrice: bigint}[]): Cell {
61
+ if (assetsData.length == 0) {
62
+ throw new Error("No assets data to pack");
63
+ }
64
+ return assetsData.reduceRight(
65
+ (acc: Cell | null, {assetId, medianPrice}) => beginCell()
66
+ .storeUint(assetId, 256)
67
+ .storeCoins(medianPrice)
68
+ .storeMaybeRef(acc)
69
+ .endCell(),
70
+ null
71
+ )!;
72
+ }
73
+
74
+ export function packPrices(assetsDataCell: Cell, oraclesDataCell: Cell): Cell {
75
+ let pricesCell = beginCell()
76
+ .storeRef(assetsDataCell)
77
+ .storeRef(oraclesDataCell)
78
+ .endCell();
79
+ return pricesCell;
80
+ }
81
+
82
+ export function createOracleDataProof(oracle: Oracle,
83
+ data: OraclePricesData,
84
+ signature: Buffer,
85
+ assets: Array<bigint>): Slice {
86
+ let prunedDict = generateMerkleProofDirect(data.prices, assets, Dictionary.Keys.BigUint(256));
87
+ let prunedData = beginCell().storeUint(data.timestamp, 32).storeMaybeRef(prunedDict).endCell();
88
+ let merkleProof = convertToMerkleProof(prunedData);
89
+ let oracleDataProof = beginCell().storeUint(oracle.id, 32).storeRef(merkleProof).storeBuffer(signature).asSlice();
90
+ return oracleDataProof;
91
+ }
92
+
93
+ export function packOraclesData(oraclesData: {oracle: Oracle, data: OraclePricesData, signature: Buffer}[],
94
+ assets: Array<bigint>): Cell {
95
+ if (oraclesData.length == 0) {
96
+ throw new Error("no oracles data to pack");
97
+ }
98
+ let proofs = oraclesData.sort((d1, d2) => d1.oracle.id - d2.oracle.id).map(
99
+ ({oracle, data, signature}) => createOracleDataProof(oracle, data, signature, assets)
100
+ );
101
+ return proofs.reduceRight((acc: Cell | null, val) => beginCell().storeSlice(val).storeMaybeRef(acc).endCell(), null)!;
102
+ }
103
+
104
+ export function sumDicts(result: Dictionary<bigint, bigint>, addendum: Dictionary<bigint, bigint>) {
105
+ for (const key of addendum.keys()) {
106
+ const current = result.get(key)!;
107
+ const value = addendum.get(key)!;
108
+
109
+ if (current === undefined) {
110
+ result.set(key, value);
111
+ continue;
112
+ }
113
+
114
+ result.set(key, current + value);
115
+ }
116
+ }
117
+
118
+ export function generatePriceSources(config: PriceSourcesConfig = DefaultPriceSourcesConfig, nfts: OracleNFT[] = MAINNET_POOL_CONFIG.oracles) {
119
+ let result: PriceSource[] = config.backendEndpoints.map(x => new BackendPriceSource(x, nfts));
120
+
121
+ result.push(...config.icpEndpoints.map(x => new IcpPriceSource(x, nfts)));
122
+ result.push(...config.iotaEndpoints.map(x => new IotaPriceSource(x, nfts)));
123
+
124
+ return result;
125
+ }
126
+
127
+ export async function collectAndFilterPrices(priceSource: PriceSource, poolConfig: PoolConfig): Promise<RawPriceData[]> {
128
+ const prices = await priceSource.getPrices();
129
+
130
+ //console.debug('[FILTERING] before filtering prices len ', priceSource.sourceName, prices.length);
131
+ return await (async () => {
132
+ const acceptedPrices: RawPriceData[] = prices.filter(
133
+ price => verifyPricesTimestamp()(price) && verifyPricesSign(poolConfig.oracles)(price)
134
+ );
135
+
136
+ //console.debug('[FILTERING] after filtering prices len ', priceSource.sourceName, acceptedPrices.length);
137
+
138
+ if (acceptedPrices.length < poolConfig.minimalOracles) {
139
+ throw new Error("Prices are outdated");
140
+ }
141
+
142
+ return acceptedPrices;
143
+ })();
144
+ }
145
+
146
+ export function unpackMedianPrices(pricesCell: Cell): Dictionary<bigint, bigint> | undefined {
147
+ if (!pricesCell) return undefined;
148
+ const slice = pricesCell.beginParse();
149
+ let assetCell: Cell | null = slice.loadRef();
150
+ const res = Dictionary.empty<bigint, bigint>();
151
+ while (assetCell != Cell.EMPTY && assetCell !== null) {
152
+ const slice = assetCell.beginParse();
153
+ const assetId = slice.loadUintBig(256);
154
+ const medianPrice = slice.loadCoins();
155
+ res.set(assetId, medianPrice);
156
+ assetCell = slice.loadMaybeRef();
157
+ }
158
+ return res;
159
+ }
160
+
161
+ export function verifyRawPriceDataSign(priceData: RawPriceData): boolean {
162
+ const message = priceData.dataCell.refs[0].hash()
163
+ const signature = priceData.signature;
164
+ const publicKey = priceData.pubkey;
165
+
166
+ const valid: boolean = signVerify(message, signature, publicKey);
167
+ //console.debug('[verifyRawPriceDataSign] sign is valid:', valid);
168
+
169
+ return valid;
170
+ }
@@ -89,7 +89,7 @@ export type AssetData = {
89
89
  balance: bigint;
90
90
  trackingSupplyIndex: bigint;
91
91
  trackingBorrowIndex: bigint;
92
- awaitedSupply?: bigint;
92
+ awaitedSupply: bigint;
93
93
  };
94
94
 
95
95
  export type AssetInterest = {
@@ -126,7 +126,8 @@ export type AgregatedBalances = {
126
126
 
127
127
  export type OracleNFT = {
128
128
  id: number,
129
- address: string
129
+ address: string,
130
+ pubkey: Buffer
130
131
  }
131
132
 
132
133
  export type Oracle = {
package/src/types/User.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Address, Cell, Dictionary } from '@ton/core';
1
+ import { Address, BitBuilder, Cell, Dictionary } from '@ton/core';
2
2
  import { AssetConfig, ExtendedAssetData, ExtendedAssetsConfig, ExtendedAssetsData, MasterConfig, MasterConstants, PoolAssetConfig, PoolConfig } from './Master';
3
3
 
4
4
  export enum BalanceType {
@@ -38,6 +38,7 @@ export type UserLiteData = {
38
38
  masterAddress: Address;
39
39
  ownerAddress: Address;
40
40
  principals: Dictionary<bigint, bigint>;
41
+ realPrincipals: Dictionary<bigint, bigint>; // principals before applying dusts
41
42
  state: number;
42
43
  balances: Dictionary<bigint, UserBalance>;
43
44
  trackingSupplyIndex: bigint;
@@ -59,7 +60,6 @@ export type UserDataActive = UserLiteData & {
59
60
  limitUsedPercent: number;
60
61
  limitUsed: bigint;
61
62
  healthFactor: number;
62
-
63
63
  liquidationData: LiquidationData;
64
64
  };
65
65