@evaafi/sdk 0.9.0 → 0.9.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 (129) hide show
  1. package/dist/api/feeds.d.ts +15 -24
  2. package/dist/api/feeds.js +22 -49
  3. package/dist/api/math.d.ts +1 -1
  4. package/dist/api/math.js +55 -38
  5. package/dist/api/parser.d.ts +2 -2
  6. package/dist/api/parser.js +3 -3
  7. package/dist/api/parsers/PythOracleParser.d.ts +3 -2
  8. package/dist/api/parsers/PythOracleParser.js +2 -1
  9. package/dist/api/prices.js +2 -1
  10. package/dist/constants/assets/assetId.d.ts +3 -1
  11. package/dist/constants/assets/assetId.js +5 -1
  12. package/dist/constants/assets/mainnet.d.ts +2 -0
  13. package/dist/constants/assets/mainnet.js +13 -1
  14. package/dist/constants/general/index.d.ts +25 -11
  15. package/dist/constants/general/index.js +15 -1
  16. package/dist/constants/general/mainnet.d.ts +2 -1
  17. package/dist/constants/general/mainnet.js +3 -2
  18. package/dist/constants/pools/mainnet.d.ts +4 -0
  19. package/dist/constants/pools/mainnet.js +45 -28
  20. package/dist/constants/pools/testnet.js +14 -6
  21. package/dist/contracts/AbstractMaster.d.ts +239 -127
  22. package/dist/contracts/AbstractMaster.js +101 -16
  23. package/dist/contracts/ClassicMaster.d.ts +12 -12
  24. package/dist/contracts/PythMaster.d.ts +40 -24
  25. package/dist/contracts/PythMaster.js +61 -76
  26. package/dist/contracts/PythOracle.d.ts +15 -0
  27. package/dist/contracts/PythOracle.js +19 -0
  28. package/dist/contracts/index.d.ts +1 -0
  29. package/dist/contracts/index.js +1 -0
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/{prices → oracles}/Types.d.ts +0 -4
  33. package/dist/oracles/collectors/AbstractCollector.d.ts +10 -0
  34. package/dist/oracles/collectors/AbstractCollector.js +6 -0
  35. package/dist/oracles/collectors/ClassicCollector.d.ts +22 -0
  36. package/dist/oracles/collectors/ClassicCollector.js +192 -0
  37. package/dist/oracles/collectors/PythCollector.d.ts +27 -0
  38. package/dist/oracles/collectors/PythCollector.js +266 -0
  39. package/dist/oracles/collectors/index.d.ts +3 -0
  40. package/dist/oracles/collectors/index.js +19 -0
  41. package/dist/{prices → oracles}/index.d.ts +2 -3
  42. package/dist/{prices → oracles}/index.js +2 -3
  43. package/dist/oracles/prices/AbstractPrices.d.ts +33 -0
  44. package/dist/oracles/prices/AbstractPrices.js +40 -0
  45. package/dist/oracles/prices/ClassicPrices.d.ts +19 -0
  46. package/dist/oracles/prices/ClassicPrices.js +48 -0
  47. package/dist/oracles/prices/PythPrices.d.ts +17 -0
  48. package/dist/oracles/prices/PythPrices.js +32 -0
  49. package/dist/oracles/prices/index.d.ts +3 -0
  50. package/dist/oracles/prices/index.js +19 -0
  51. package/dist/{prices → oracles}/sources/Backend.js +11 -8
  52. package/dist/{prices → oracles}/sources/Icp.js +10 -6
  53. package/dist/{prices → oracles}/utils.d.ts +6 -1
  54. package/dist/{prices → oracles}/utils.js +10 -1
  55. package/dist/types/Master.d.ts +4 -5
  56. package/dist/utils/fivaUtils.d.ts +2 -0
  57. package/dist/utils/fivaUtils.js +19 -0
  58. package/dist/utils/userJettonWallet.js +15 -1
  59. package/dist/utils/utils.d.ts +1 -1
  60. package/dist/utils/utils.js +4 -2
  61. package/package.json +1 -1
  62. package/src/api/feeds.ts +24 -60
  63. package/src/api/math.ts +118 -90
  64. package/src/api/parser.ts +5 -5
  65. package/src/api/parsers/PythOracleParser.ts +6 -2
  66. package/src/api/prices.ts +2 -1
  67. package/src/constants/assets/assetId.ts +7 -1
  68. package/src/constants/assets/mainnet.ts +24 -0
  69. package/src/constants/general/index.ts +16 -1
  70. package/src/constants/general/mainnet.ts +2 -1
  71. package/src/constants/pools/mainnet.ts +42 -38
  72. package/src/constants/pools/testnet.ts +17 -8
  73. package/src/contracts/AbstractMaster.ts +272 -144
  74. package/src/contracts/ClassicMaster.ts +12 -12
  75. package/src/contracts/PythMaster.ts +130 -123
  76. package/src/contracts/PythOracle.ts +20 -0
  77. package/src/contracts/index.ts +2 -0
  78. package/src/index.ts +1 -1
  79. package/src/{prices → oracles}/Types.ts +0 -5
  80. package/src/oracles/collectors/AbstractCollector.ts +22 -0
  81. package/src/oracles/collectors/ClassicCollector.ts +263 -0
  82. package/src/oracles/collectors/PythCollector.ts +372 -0
  83. package/src/oracles/collectors/index.ts +3 -0
  84. package/src/{prices → oracles}/index.ts +2 -3
  85. package/src/oracles/prices/AbstractPrices.ts +59 -0
  86. package/src/oracles/prices/ClassicPrices.ts +52 -0
  87. package/src/oracles/prices/PythPrices.ts +40 -0
  88. package/src/oracles/prices/index.ts +3 -0
  89. package/src/{prices → oracles}/sources/Backend.ts +12 -11
  90. package/src/{prices → oracles}/sources/Icp.ts +10 -9
  91. package/src/{prices → oracles}/utils.ts +12 -1
  92. package/src/types/Master.ts +4 -6
  93. package/src/utils/fivaUtils.ts +19 -0
  94. package/src/utils/userJettonWallet.ts +18 -1
  95. package/src/utils/utils.ts +4 -2
  96. package/dist/api/pyth.d.ts +0 -16
  97. package/dist/api/pyth.js +0 -35
  98. package/dist/constants/assets.d.ts +0 -48
  99. package/dist/constants/assets.js +0 -176
  100. package/dist/constants/general.d.ts +0 -67
  101. package/dist/constants/general.js +0 -75
  102. package/dist/constants/pools.d.ts +0 -13
  103. package/dist/constants/pools.js +0 -120
  104. package/dist/contracts/MasterContract.d.ts +0 -156
  105. package/dist/contracts/MasterContract.js +0 -260
  106. package/dist/prices/Oracle.interface.d.ts +0 -9
  107. package/dist/prices/Oracle.interface.js +0 -2
  108. package/dist/prices/Prices.d.ts +0 -11
  109. package/dist/prices/Prices.js +0 -53
  110. package/dist/prices/PricesCollector.d.ts +0 -22
  111. package/dist/prices/PricesCollector.js +0 -146
  112. package/dist/prices/PythCollector.d.ts +0 -22
  113. package/dist/prices/PythCollector.js +0 -217
  114. package/src/prices/Oracle.interface.ts +0 -18
  115. package/src/prices/Prices.ts +0 -45
  116. package/src/prices/PricesCollector.ts +0 -169
  117. package/src/prices/PythCollector.ts +0 -294
  118. /package/dist/{prices → oracles}/Types.js +0 -0
  119. /package/dist/{prices → oracles}/constants.d.ts +0 -0
  120. /package/dist/{prices → oracles}/constants.js +0 -0
  121. /package/dist/{prices → oracles}/sources/Backend.d.ts +0 -0
  122. /package/dist/{prices → oracles}/sources/Icp.d.ts +0 -0
  123. /package/dist/{prices → oracles}/sources/PriceSource.d.ts +0 -0
  124. /package/dist/{prices → oracles}/sources/PriceSource.js +0 -0
  125. /package/dist/{prices → oracles}/sources/index.d.ts +0 -0
  126. /package/dist/{prices → oracles}/sources/index.js +0 -0
  127. /package/src/{prices → oracles}/constants.ts +0 -0
  128. /package/src/{prices → oracles}/sources/PriceSource.ts +0 -0
  129. /package/src/{prices → oracles}/sources/index.ts +0 -0
@@ -4,12 +4,14 @@ import { PythOracleInfo, PythOracleParser } from '../api/parsers/PythOracleParse
4
4
  import { composeFeedsCell, packPythUpdatesData } from '../api/prices';
5
5
  import { TON_MAINNET } from '../constants';
6
6
  import { FEES, OPCODES } from '../constants/general';
7
+ import { PoolAssetConfig } from '../types/Master';
7
8
  import { isTonAsset } from '../utils/utils';
8
9
  import {
9
10
  AbstractEvaaMaster,
10
11
  BaseMasterConfig,
11
12
  BaseMasterData,
12
13
  EvaaParameters,
14
+ JettonParams,
13
15
  LiquidationInnerParameters,
14
16
  LiquidationOperationBuilderParameters,
15
17
  LiquidationParameters,
@@ -20,26 +22,26 @@ import {
20
22
  /**
21
23
  * pyth specific parameters
22
24
  */
23
- export type PythBaseData = {
24
- priceData: Buffer | Cell;
25
- targetFeeds: HexString[];
26
- requestedRefTokens: bigint[];
27
- };
25
+ export interface PythBaseData {
26
+ readonly priceData: Buffer | Cell;
27
+ readonly targetFeeds: HexString[];
28
+ readonly refAssets: PoolAssetConfig[];
29
+ }
28
30
 
29
- export type ProxySpecificPythParams = {
30
- pythAddress: Address;
31
- minPublishTime: number | bigint;
32
- maxPublishTime: number | bigint;
33
- };
31
+ export interface ProxySpecificPythParams {
32
+ readonly pythAddress: Address;
33
+ readonly minPublishTime: number | bigint;
34
+ readonly maxPublishTime: number | bigint;
35
+ }
34
36
 
35
- export type OnchainSpecificPythParams = {
36
- publishGap: number | bigint;
37
- maxStaleness: number | bigint;
38
- };
37
+ export interface OnchainSpecificPythParams {
38
+ readonly publishGap: number | bigint;
39
+ readonly maxStaleness: number | bigint;
40
+ }
39
41
 
40
- export type JettonPythParams = PythBaseData & OnchainSpecificPythParams;
42
+ export interface JettonPythParams extends PythBaseData, OnchainSpecificPythParams {}
41
43
 
42
- export type TonPythParams = PythBaseData & ProxySpecificPythParams;
44
+ export interface TonPythParams extends PythBaseData, ProxySpecificPythParams {}
43
45
 
44
46
  export type PythProxyParams = {
45
47
  pyth: PythBaseData & (ProxySpecificPythParams | OnchainSpecificPythParams) & { pythAddress: Address };
@@ -48,7 +50,7 @@ export type PythProxyParams = {
48
50
  export type PythSupplyWithdrawParameters = SupplyWithdrawParameters & Partial<PythProxyParams>;
49
51
 
50
52
  export type PythWithdrawParameters = WithdrawParameters & {
51
- pyth: TonPythParams;
53
+ pyth?: TonPythParams;
52
54
  };
53
55
 
54
56
  export type PythLiquidationOperationParameters = LiquidationOperationBuilderParameters &
@@ -58,14 +60,14 @@ export type PythLiquidationOperationParameters = LiquidationOperationBuilderPara
58
60
  export type PythLiquidationParameters = LiquidationParameters & PythLiquidationOperationParameters;
59
61
 
60
62
  // Specific master configurations
61
- export type PythMasterConfig = BaseMasterConfig & {
62
- oraclesInfo: PythOracleInfo;
63
- };
63
+ export interface PythMasterConfig extends BaseMasterConfig {
64
+ readonly oraclesInfo: PythOracleInfo;
65
+ }
64
66
 
65
67
  // Specific master data types
66
- export type PythMasterData = BaseMasterData & {
67
- masterConfig: PythMasterConfig;
68
- };
68
+ export interface PythMasterData extends BaseMasterData {
69
+ readonly masterConfig: PythMasterConfig;
70
+ }
69
71
 
70
72
  export class EvaaMasterPyth extends AbstractEvaaMaster<PythMasterData> {
71
73
  constructor(parameters: EvaaParameters) {
@@ -117,94 +119,120 @@ export class EvaaMasterPyth extends AbstractEvaaMaster<PythMasterData> {
117
119
  .endCell();
118
120
  }
119
121
 
120
- // TODO: support without OPCODES.SUPPLY_WITHDRAW_MASTER_WITHOUT_PRICES
122
+ /**
123
+ * Creates a wrapped operation payload for TON assets
124
+ */
125
+ private createWrappedOperationPayload(opCode: number, queryID: number | bigint, operationPayload: Cell): Cell {
126
+ return beginCell().storeUint(opCode, 32).storeUint(queryID, 64).storeRef(operationPayload).endCell();
127
+ }
128
+
129
+ /**
130
+ * Creates a jetton transfer message with pyth data
131
+ */
132
+ private createJettonPythMessage(
133
+ parameters: JettonParams & { queryID: number | bigint },
134
+ operationPayload: Cell,
135
+ pythParams: JettonPythParams,
136
+ opCode: number,
137
+ ): Cell {
138
+ const { priceData, targetFeeds, publishGap, maxStaleness } = pythParams;
139
+ const masterMessage = this.buildPythMasterMessage(
140
+ {
141
+ queryId: parameters.queryID,
142
+ opCode,
143
+ updateDataCell: packPythUpdatesData(priceData),
144
+ targetFeedsCell: composeFeedsCell(targetFeeds),
145
+ publishGap,
146
+ maxStaleness,
147
+ },
148
+ operationPayload,
149
+ );
150
+
151
+ return this.createJettonTransferMessage(parameters, FEES.SUPPLY_WITHDRAW, masterMessage);
152
+ }
153
+
154
+ /**
155
+ * Creates a TON pyth proxy message
156
+ */
157
+ private createTonPythMessage(
158
+ parameters: { queryID: number | bigint },
159
+ operationPayload: Cell,
160
+ pythParams: TonPythParams,
161
+ opCode: number,
162
+ ): Cell {
163
+ const { priceData, targetFeeds, minPublishTime, maxPublishTime } = pythParams;
164
+ const wrappedOperationPayload = this.createWrappedOperationPayload(
165
+ opCode,
166
+ parameters.queryID,
167
+ operationPayload,
168
+ );
169
+
170
+ return this.buildPythProxyMessage(
171
+ this.address,
172
+ packPythUpdatesData(priceData),
173
+ composeFeedsCell(targetFeeds),
174
+ minPublishTime,
175
+ maxPublishTime,
176
+ wrappedOperationPayload,
177
+ );
178
+ }
179
+
180
+ protected createSupplyWithdrawMessageNoPrices(parameters: SupplyWithdrawParameters, operationPayload: Cell): Cell {
181
+ const messageBody = beginCell()
182
+ .storeUint(OPCODES.SUPPLY_WITHDRAW_MASTER_WITHOUT_PRICES, 32)
183
+ .storeSlice(operationPayload.beginParse())
184
+ .endCell();
185
+
186
+ if (!isTonAsset(parameters.supplyAsset)) {
187
+ return this.createJettonTransferMessage(parameters, FEES.SUPPLY_WITHDRAW + FEES.JETTON_FWD, messageBody);
188
+ } else {
189
+ return beginCell()
190
+ .storeUint(OPCODES.SUPPLY_WITHDRAW_MASTER_WITHOUT_PRICES, 32)
191
+ .storeUint(parameters.queryID, 64)
192
+ .storeSlice(operationPayload.beginParse())
193
+ .endCell();
194
+ }
195
+ }
196
+
121
197
  createSupplyWithdrawMessage(parameters: PythSupplyWithdrawParameters): Cell {
122
198
  const operationPayload = this.buildSupplyWithdrawOperationPayload(parameters);
123
199
 
200
+ // Handle case without pyth data
201
+ if (!parameters.pyth) {
202
+ return this.createSupplyWithdrawMessageNoPrices(parameters, operationPayload);
203
+ }
204
+
124
205
  if (!isTonAsset(parameters.supplyAsset)) {
125
- const { priceData, targetFeeds, publishGap, maxStaleness } = parameters.pyth as JettonPythParams;
126
- const masterMessage = this.buildPythMasterMessage(
127
- {
128
- queryId: parameters.queryID,
129
- opCode: OPCODES.SUPPLY_WITHDRAW_MASTER_JETTON,
130
- updateDataCell: packPythUpdatesData(priceData),
131
- targetFeedsCell: composeFeedsCell(targetFeeds),
132
- publishGap,
133
- maxStaleness,
134
- },
206
+ return this.createJettonPythMessage(
207
+ parameters, // as JettonParams & { queryID: number | bigint },
135
208
  operationPayload,
209
+ parameters.pyth as JettonPythParams,
210
+ OPCODES.SUPPLY_WITHDRAW_MASTER_JETTON,
136
211
  );
137
-
138
- return this.createJettonTransferMessage(parameters, FEES.SUPPLY_WITHDRAW, masterMessage);
139
212
  } else {
140
- const { priceData, targetFeeds, minPublishTime, maxPublishTime } = parameters.pyth as TonPythParams;
141
- const wrappedOperationPayload = beginCell()
142
- .storeUint(OPCODES.SUPPLY_WITHDRAW_MASTER, 32)
143
- .storeUint(parameters.queryID, 64)
144
- .storeRef(operationPayload)
145
- .endCell();
146
- return this.buildPythProxyMessage(
147
- this.address,
148
- packPythUpdatesData(priceData),
149
- composeFeedsCell(targetFeeds),
150
- minPublishTime,
151
- maxPublishTime,
152
- wrappedOperationPayload,
213
+ return this.createTonPythMessage(
214
+ parameters,
215
+ operationPayload,
216
+ parameters.pyth as TonPythParams,
217
+ OPCODES.SUPPLY_WITHDRAW_MASTER,
153
218
  );
154
219
  }
155
220
  }
156
221
 
157
- // private createPythWithdrawMessage(parameters: PythWithdrawParameters): Cell {
158
- // const extraTail =
159
- // (parameters.subaccountId ?? 0) == 0
160
- // ? beginCell().endCell()
161
- // : beginCell()
162
- // .storeInt(parameters.subaccountId ?? 0, 16)
163
- // .storeUint(0, 2)
164
- // .endCell();
165
-
166
- // const wrappedOperationPayload = beginCell()
167
- // .storeUint(OPCODES.SUPPLY_WITHDRAW_MASTER, 32) // op_code: 0x4
168
- // .storeUint(parameters.queryID, 64)
169
- // .storeRef(
170
- // beginCell()
171
- // .storeUint(parameters.asset.assetId, 256)
172
- // .storeUint(parameters.amount, 64)
173
- // .storeAddress(parameters.userAddress)
174
- // .storeInt(parameters.includeUserCode ? -1 : 0, 2)
175
- // .storeUint(parameters.amountToTransfer, 64)
176
- // .storeRef(parameters.payload)
177
- // .storeSlice(extraTail.beginParse())
178
- // .endCell(),
179
- // )
180
- // .endCell();
181
-
182
- // const { priceData, targetFeeds, minPublishTime, maxPublishTime } = parameters.pyth as TonPythParams;
183
-
184
- // return makePythProxyMessage(
185
- // this.address,
186
- // packPythUpdatesData(priceData),
187
- // composeFeedsCell(targetFeeds),
188
- // minPublishTime,
189
- // maxPublishTime,
190
- // wrappedOperationPayload,
191
- // );
192
- // }
193
-
194
- buildRefTokensDict(requestedRefTokens: bigint[]): Dictionary<bigint, Buffer> {
222
+ buildRefTokensDict(refAssets: PoolAssetConfig[]): Dictionary<bigint, Buffer> {
195
223
  const refsDict: Dictionary<bigint, Buffer> = Dictionary.empty(
196
224
  Dictionary.Keys.BigUint(256),
197
225
  Dictionary.Values.Buffer(0),
198
226
  );
199
- for (const refToken of requestedRefTokens) {
200
- refsDict.set(refToken, Buffer.alloc(0));
227
+ for (const refAsset of refAssets) {
228
+ refsDict.set(refAsset.assetId, Buffer.alloc(0));
201
229
  }
202
230
 
203
231
  return refsDict;
204
232
  }
205
233
 
206
234
  buildGeneralDataPayload(parameters: PythSupplyWithdrawParameters): Cell {
207
- const refTokensDict = this.buildRefTokensDict(parameters.pyth?.requestedRefTokens ?? []);
235
+ const refTokensDict = this.buildRefTokensDict(parameters.pyth?.refAssets ?? []);
208
236
  return beginCell()
209
237
  .storeInt(parameters.includeUserCode ? -1 : 0, 2)
210
238
  .storeDict(refTokensDict, Dictionary.Keys.BigUint(256), Dictionary.Values.Buffer(0))
@@ -240,7 +268,7 @@ export class EvaaMasterPyth extends AbstractEvaaMaster<PythMasterData> {
240
268
  });
241
269
  await via.send({
242
270
  value,
243
- to: parameters.pyth.pythAddress,
271
+ to: parameters.pyth?.pythAddress ?? this.address,
244
272
  sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS,
245
273
  body: message,
246
274
  });
@@ -250,48 +278,27 @@ export class EvaaMasterPyth extends AbstractEvaaMaster<PythMasterData> {
250
278
  const operationPayloadBuilder = this.buildLiquidationOperationPayloadBuilder(parameters);
251
279
  const innerBuilder = this.buildLiquidationInnerBuilder(parameters);
252
280
 
253
- const refTokensDict = this.buildRefTokensDict(parameters.pyth.requestedRefTokens);
281
+ const refTokensDict = this.buildRefTokensDict(parameters.pyth.refAssets);
254
282
 
255
283
  return operationPayloadBuilder.storeDict(refTokensDict).storeRef(innerBuilder).endCell();
256
284
  }
257
285
 
258
286
  createLiquidationMessage(parameters: PythLiquidationParameters): Cell {
259
- const isTon = isTonAsset(parameters.asset);
260
-
261
287
  const operationPayload = this.buildLiquidationOperationPayload(parameters);
262
288
 
263
- if (!isTon) {
264
- const { priceData, targetFeeds, publishGap, maxStaleness } =
265
- parameters.pyth as OnchainSpecificPythParams & { priceData: Buffer | Cell; targetFeeds: HexString[] };
266
- const masterMessage = this.buildPythMasterMessage(
267
- {
268
- queryId: parameters.queryID,
269
- opCode: OPCODES.LIQUIDATE_MASTER,
270
- updateDataCell: packPythUpdatesData(priceData as Buffer | Cell),
271
- targetFeedsCell: composeFeedsCell(targetFeeds),
272
- publishGap,
273
- maxStaleness,
274
- },
289
+ if (!isTonAsset(parameters.asset)) {
290
+ return this.createJettonPythMessage(
291
+ parameters,
275
292
  operationPayload,
293
+ parameters.pyth as JettonPythParams,
294
+ OPCODES.LIQUIDATE_MASTER,
276
295
  );
277
- return this.createJettonTransferMessage(parameters, FEES.LIQUIDATION_JETTON_FWD, masterMessage);
278
296
  } else {
279
- const { priceData, targetFeeds, minPublishTime, maxPublishTime } = parameters.pyth as TonPythParams & {
280
- priceData: Buffer | Cell;
281
- targetFeeds: HexString[];
282
- };
283
- const wrappedOperationPayload = beginCell()
284
- .storeUint(OPCODES.LIQUIDATE_MASTER, 32)
285
- .storeUint(parameters.queryID, 64)
286
- .storeRef(operationPayload)
287
- .endCell();
288
- return this.buildPythProxyMessage(
289
- this.address,
290
- packPythUpdatesData(priceData as Buffer | Cell),
291
- composeFeedsCell(targetFeeds),
292
- minPublishTime,
293
- maxPublishTime,
294
- wrappedOperationPayload,
297
+ return this.createTonPythMessage(
298
+ parameters,
299
+ operationPayload,
300
+ parameters.pyth as TonPythParams,
301
+ OPCODES.LIQUIDATE_MASTER,
295
302
  );
296
303
  }
297
304
  }
@@ -0,0 +1,20 @@
1
+ import { calculateUpdatePriceFeedsFee, createCellChain } from '@pythnetwork/pyth-ton-js';
2
+ import { Address, Cell, Contract, ContractProvider } from '@ton/core';
3
+
4
+ export class PythOracle implements Contract {
5
+ constructor(
6
+ readonly address: Address,
7
+ readonly init?: { code: Cell; data: Cell },
8
+ ) {}
9
+
10
+ static createFromAddress(address: Address) {
11
+ return new this(address);
12
+ }
13
+
14
+ async getUpdateFee(provider: ContractProvider, vm: Buffer) {
15
+ const result = await provider.get('get_update_fee', [{ type: 'slice', cell: createCellChain(vm) }]);
16
+ const numUpdates = result.stack.readNumber();
17
+
18
+ return calculateUpdatePriceFeedsFee(BigInt(numUpdates)) + BigInt(numUpdates);
19
+ }
20
+ }
@@ -5,3 +5,5 @@ export * from './PythMaster';
5
5
  export * from './UserContract';
6
6
 
7
7
  export * from './JettonWallet';
8
+
9
+ export * from './PythOracle';
package/src/index.ts CHANGED
@@ -37,7 +37,7 @@ export * from './constants/general';
37
37
  export * from './constants/pools';
38
38
 
39
39
  export * from './constants/assets';
40
- export * from './prices';
40
+ export * from './oracles';
41
41
  export * from './utils/utils';
42
42
 
43
43
  // Utils
@@ -51,11 +51,6 @@ export type PythFeedUpdateType = {
51
51
  binary: Buffer;
52
52
  };
53
53
 
54
- export type PriceData = {
55
- dict: Dictionary<bigint, bigint>;
56
- dataCell: Cell;
57
- };
58
-
59
54
  export type OraclePricesData = {
60
55
  timestamp: number;
61
56
  prices: Dictionary<bigint, bigint>;
@@ -0,0 +1,22 @@
1
+ import { Dictionary } from '@ton/core';
2
+ import { PoolAssetConfig } from '../../types/Master';
3
+ import { FetchConfig } from '../../utils/utils';
4
+ import { ClassicPrices } from '../prices/ClassicPrices';
5
+ import { PythPrices } from '../prices/PythPrices';
6
+
7
+ export abstract class AbstractCollector {
8
+ abstract getPricesForLiquidate(
9
+ realPrincipals: Dictionary<bigint, bigint>,
10
+ fetchConfig?: FetchConfig,
11
+ ): Promise<ClassicPrices | PythPrices>;
12
+
13
+ abstract getPricesForSupplyWithdraw(
14
+ realPrincipals: Dictionary<bigint, bigint>,
15
+ supplyAsset: PoolAssetConfig | undefined,
16
+ withdrawAsset: PoolAssetConfig | undefined,
17
+ collateralToDebt: boolean,
18
+ fetchConfig?: FetchConfig,
19
+ ): Promise<ClassicPrices | PythPrices>;
20
+
21
+ abstract getPrices(assets: PoolAssetConfig[], fetchConfig?: FetchConfig): Promise<ClassicPrices | PythPrices>;
22
+ }
@@ -0,0 +1,263 @@
1
+ import { Dictionary } from '@ton/core';
2
+ import { checkNotInDebtAtAll } from '../../api/math';
3
+ import { ExtendedEvaaOracle, PoolAssetConfig } from '../../types/Master';
4
+ import { FetchConfig, proxyFetchRetries } from '../../utils/utils';
5
+ import { ClassicPrices, ClassicPricesMode, ClassicPricesOffset } from '../prices/ClassicPrices';
6
+ import { PriceSource } from '../sources';
7
+ import { DefaultPriceSourcesConfig, PriceSourcesConfig, RawPriceData } from '../Types';
8
+ import {
9
+ collectAndFilterPrices,
10
+ generatePriceSources,
11
+ getMedianPrice,
12
+ packAssetsData,
13
+ packOraclesData,
14
+ packPrices,
15
+ verifyPricesTimestamp,
16
+ } from '../utils';
17
+ import { AbstractCollector } from './AbstractCollector';
18
+
19
+ export type ClassicCollectorConfig = {
20
+ poolAssetsConfig: PoolAssetConfig[];
21
+ minimalOracles: number;
22
+ evaaOracles: ExtendedEvaaOracle[];
23
+ sourcesConfig?: PriceSourcesConfig;
24
+ additionalPriceSources?: PriceSource[];
25
+ };
26
+
27
+ export class ClassicCollector extends AbstractCollector {
28
+ #prices: RawPriceData[];
29
+ #poolAssetsConfig: PoolAssetConfig[];
30
+ #sourcesConfig: PriceSourcesConfig;
31
+ #priceSources: PriceSource[];
32
+ #minimalOracles: number;
33
+
34
+ constructor(config: ClassicCollectorConfig) {
35
+ super();
36
+
37
+ this.#poolAssetsConfig = config.poolAssetsConfig;
38
+ this.#sourcesConfig = config.sourcesConfig ?? DefaultPriceSourcesConfig;
39
+ this.#priceSources = generatePriceSources(this.#sourcesConfig, config.evaaOracles);
40
+ this.#minimalOracles = config.minimalOracles;
41
+ if (config.additionalPriceSources) {
42
+ this.#priceSources.push(...config.additionalPriceSources);
43
+ }
44
+ this.#prices = [];
45
+ }
46
+
47
+ async getPricesForLiquidate(
48
+ realPrincipals: Dictionary<bigint, bigint>,
49
+ fetchConfig?: FetchConfig,
50
+ ): Promise<ClassicPrices> {
51
+ const assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
52
+ if (assets.includes(undefined)) {
53
+ throw new Error('User from another pool');
54
+ }
55
+
56
+ const validAssets = assets.map((x) => x!);
57
+ const spotAssets = this.#convertToSpotAssets(validAssets);
58
+
59
+ return await this.#getPricesWithMode(spotAssets, ClassicPricesMode.SPOT, fetchConfig);
60
+ }
61
+
62
+ async getPricesForWithdraw(
63
+ realPrincipals: Dictionary<bigint, bigint>,
64
+ withdrawAsset: PoolAssetConfig,
65
+ collateralToDebt = false,
66
+ fetchConfig?: FetchConfig,
67
+ ): Promise<ClassicPrices> {
68
+ let assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
69
+ if (
70
+ checkNotInDebtAtAll(realPrincipals) &&
71
+ (realPrincipals.get(withdrawAsset.assetId) ?? 0n) > 0n &&
72
+ !collateralToDebt
73
+ ) {
74
+ return ClassicPrices.createEmptyTwapPrices();
75
+ }
76
+ if (assets.includes(undefined)) {
77
+ throw new Error('User from another pool');
78
+ }
79
+ if (!assets.includes(withdrawAsset)) {
80
+ assets.push(withdrawAsset);
81
+ }
82
+ if (collateralToDebt && assets.length == 1) {
83
+ throw new Error('Cannot debt only one supplied asset');
84
+ }
85
+ return await this.getPrices(
86
+ assets.map((x) => x!),
87
+ fetchConfig,
88
+ );
89
+ }
90
+
91
+ async getPricesForSupplyWithdraw(
92
+ realPrincipals: Dictionary<bigint, bigint>,
93
+ supplyAsset: PoolAssetConfig | undefined,
94
+ withdrawAsset: PoolAssetConfig | undefined,
95
+ collateralToDebt: boolean,
96
+ fetchConfig?: FetchConfig,
97
+ ): Promise<ClassicPrices> {
98
+ let assets = this.#filterEmptyPrincipalsAndAssets(realPrincipals);
99
+ if (
100
+ checkNotInDebtAtAll(realPrincipals) &&
101
+ withdrawAsset &&
102
+ (realPrincipals.get(withdrawAsset.assetId) ?? 0n) > 0n &&
103
+ !collateralToDebt
104
+ ) {
105
+ return ClassicPrices.createEmptyTwapPrices();
106
+ }
107
+ if (assets.includes(undefined)) {
108
+ throw new Error('User from another pool');
109
+ }
110
+ if (withdrawAsset && !assets.includes(withdrawAsset)) {
111
+ assets.push(withdrawAsset);
112
+ }
113
+ if (collateralToDebt && assets.length == 1) {
114
+ throw new Error('Cannot debt only one supplied asset');
115
+ }
116
+
117
+ const validAssets = assets.map((x) => x!);
118
+ const twapAssets = this.#convertToTwapAssets(validAssets);
119
+
120
+ return await this.#getPricesWithMode(twapAssets, ClassicPricesMode.TWAP, fetchConfig);
121
+ }
122
+
123
+ async getPrices(
124
+ assets: PoolAssetConfig[] = this.#poolAssetsConfig,
125
+ fetchConfig?: FetchConfig,
126
+ ): Promise<ClassicPrices> {
127
+ if (assets.length == 0) {
128
+ return ClassicPrices.createEmptyPrices();
129
+ }
130
+
131
+ await this.#collectPricesWithValidation(fetchConfig);
132
+
133
+ if (this.#prices.length < this.#minimalOracles) {
134
+ throw new Error(`Error per updating prices, valid ${this.#prices.length} of ${this.#minimalOracles}`);
135
+ }
136
+ const prices = this.#getPricesByAssetList(assets);
137
+ return new ClassicPrices({
138
+ dict: prices.dict,
139
+ dataCell: prices.dataCell,
140
+ minPublishTime: undefined,
141
+ maxPublishTime: undefined,
142
+ });
143
+ }
144
+
145
+ #getPricesByAssetList(assets: PoolAssetConfig[]) {
146
+ let pricesFiltered = this.#prices;
147
+ if (pricesFiltered.length < this.#minimalOracles) {
148
+ throw new Error('Not enough price data');
149
+ }
150
+ if (pricesFiltered.length > this.#minimalOracles) {
151
+ const sortedByTimestamp = pricesFiltered.slice().sort((a, b) => b.timestamp - a.timestamp);
152
+ const newerPrices = sortedByTimestamp.slice(0, this.#minimalOracles);
153
+ pricesFiltered = newerPrices.sort((a, b) => a.oracleId - b.oracleId);
154
+ }
155
+ const medianData = assets.map((asset) => ({
156
+ assetId: asset.assetId,
157
+ medianPrice: getMedianPrice(pricesFiltered, asset.assetId),
158
+ }));
159
+ const nonEmptymedianData = medianData.filter((x) => x.medianPrice != null) as {
160
+ assetId: bigint;
161
+ medianPrice: bigint;
162
+ }[];
163
+ const packedMedianData = packAssetsData(nonEmptymedianData);
164
+ const oraclesData = pricesFiltered.map((x) => ({
165
+ oracle: { id: x.oracleId, pubkey: x.pubkey },
166
+ data: { timestamp: x.timestamp, prices: x.dict },
167
+ signature: x.signature,
168
+ }));
169
+ const packedOracleData = packOraclesData(
170
+ oraclesData,
171
+ nonEmptymedianData.map((x) => x.assetId),
172
+ );
173
+ const dict = Dictionary.empty<bigint, bigint>();
174
+ for (const medianDataAsset of nonEmptymedianData) {
175
+ dict.set(medianDataAsset.assetId, medianDataAsset.medianPrice);
176
+ }
177
+ return {
178
+ dict: dict,
179
+ dataCell: packPrices(packedMedianData, packedOracleData),
180
+ };
181
+ }
182
+
183
+ async #collectPrices(fetchConfig?: FetchConfig): Promise<boolean> {
184
+ for (const priceSource of this.#priceSources) {
185
+ try {
186
+ this.#prices = await proxyFetchRetries(
187
+ () => collectAndFilterPrices(priceSource, this.#minimalOracles, fetchConfig),
188
+ fetchConfig,
189
+ );
190
+ return true;
191
+ } catch (error) {
192
+ // Try next source
193
+ continue;
194
+ }
195
+ }
196
+
197
+ return false;
198
+ }
199
+
200
+ async #collectPricesWithValidation(fetchConfig?: FetchConfig): Promise<void> {
201
+ if (!this.#prices || this.#filterPrices() < this.#minimalOracles) {
202
+ const success = await this.#collectPrices(fetchConfig);
203
+ if (!success || this.#prices.length < this.#minimalOracles) {
204
+ throw new Error(
205
+ `Failed to collect sufficient prices: ${this.#prices?.length || 0} of ${this.#minimalOracles}`,
206
+ );
207
+ }
208
+ }
209
+ }
210
+
211
+ #filterPrices(): number {
212
+ this.#prices = this.#prices.filter(verifyPricesTimestamp());
213
+ return this.#prices.length;
214
+ }
215
+
216
+ #filterEmptyPrincipalsAndAssets(principals: Dictionary<bigint, bigint>) {
217
+ return principals
218
+ .keys()
219
+ .filter((x) => principals.get(x)! != 0n)
220
+ .map((x) => this.#poolAssetsConfig.find((asset) => asset.assetId == x));
221
+ }
222
+
223
+ #convertToTwapAssets(assets: PoolAssetConfig[]): PoolAssetConfig[] {
224
+ return assets.map((asset) => ({
225
+ ...asset,
226
+ assetId: asset.assetId - ClassicPricesOffset[ClassicPricesMode.TWAP],
227
+ }));
228
+ }
229
+
230
+ #convertToSpotAssets(assets: PoolAssetConfig[]): PoolAssetConfig[] {
231
+ return assets.map((asset) => ({
232
+ ...asset,
233
+ assetId: asset.assetId - ClassicPricesOffset[ClassicPricesMode.SPOT],
234
+ }));
235
+ }
236
+
237
+ async #getPricesWithMode(
238
+ assets: PoolAssetConfig[],
239
+ mode: ClassicPricesMode,
240
+ fetchConfig?: FetchConfig,
241
+ ): Promise<ClassicPrices> {
242
+ if (assets.length == 0) {
243
+ return ClassicPricesMode.TWAP
244
+ ? ClassicPrices.createEmptyTwapPrices()
245
+ : ClassicPrices.createEmptySpotPrices();
246
+ }
247
+
248
+ await this.#collectPricesWithValidation(fetchConfig);
249
+
250
+ if (this.#prices.length < this.#minimalOracles) {
251
+ throw new Error(`Error per updating prices, valid ${this.#prices.length} of ${this.#minimalOracles}`);
252
+ }
253
+
254
+ const prices = this.#getPricesByAssetList(assets);
255
+ return new ClassicPrices({
256
+ mode,
257
+ dict: prices.dict,
258
+ dataCell: prices.dataCell,
259
+ minPublishTime: undefined,
260
+ maxPublishTime: undefined,
261
+ });
262
+ }
263
+ }