@symmetry-hq/temp-v3-sdk 0.0.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.
Files changed (148) hide show
  1. package/dist/idl/idl.d.ts +8 -0
  2. package/dist/idl/idl.js +4958 -0
  3. package/dist/idl/types.d.ts +4955 -0
  4. package/dist/idl/types.js +2 -0
  5. package/dist/index.d.ts +24 -0
  6. package/dist/index.js +60 -0
  7. package/dist/layouts/basket.d.ts +42 -0
  8. package/dist/layouts/basket.js +43 -0
  9. package/dist/layouts/config.d.ts +97 -0
  10. package/dist/layouts/config.js +102 -0
  11. package/dist/layouts/fraction.d.ts +6 -0
  12. package/dist/layouts/fraction.js +9 -0
  13. package/dist/layouts/oracle.d.ts +36 -0
  14. package/dist/layouts/oracle.js +37 -0
  15. package/dist/src/constants.d.ts +15 -0
  16. package/dist/src/constants.js +19 -0
  17. package/dist/src/idl/idl.d.ts +8 -0
  18. package/dist/src/idl/idl.js +4958 -0
  19. package/dist/src/idl/types.d.ts +4955 -0
  20. package/dist/src/idl/types.js +2 -0
  21. package/dist/src/index.d.ts +53 -0
  22. package/dist/src/index.js +199 -0
  23. package/dist/src/instructions/accounts.d.ts +27 -0
  24. package/dist/src/instructions/accounts.js +110 -0
  25. package/dist/src/instructions/auction.d.ts +7 -0
  26. package/dist/src/instructions/auction.js +43 -0
  27. package/dist/src/instructions/automation/auction.d.ts +6 -0
  28. package/dist/src/instructions/automation/auction.js +40 -0
  29. package/dist/src/instructions/automation/claimBounty.d.ts +12 -0
  30. package/dist/src/instructions/automation/claimBounty.js +44 -0
  31. package/dist/src/instructions/automation/priceUpdate.d.ts +15 -0
  32. package/dist/src/instructions/automation/priceUpdate.js +47 -0
  33. package/dist/src/instructions/automation/rebalanceSwap.d.ts +10 -0
  34. package/dist/src/instructions/automation/rebalanceSwap.js +42 -0
  35. package/dist/src/instructions/basket.d.ts +120 -0
  36. package/dist/src/instructions/basket.js +622 -0
  37. package/dist/src/instructions/bounty.d.ts +18 -0
  38. package/dist/src/instructions/bounty.js +81 -0
  39. package/dist/src/instructions/fee.d.ts +8 -0
  40. package/dist/src/instructions/fee.js +46 -0
  41. package/dist/src/instructions/globalConfig.d.ts +11 -0
  42. package/dist/src/instructions/globalConfig.js +43 -0
  43. package/dist/src/instructions/luts.d.ts +33 -0
  44. package/dist/src/instructions/luts.js +91 -0
  45. package/dist/src/instructions/management/addBounty.d.ts +7 -0
  46. package/dist/src/instructions/management/addBounty.js +39 -0
  47. package/dist/src/instructions/management/admin.d.ts +9 -0
  48. package/dist/src/instructions/management/admin.js +43 -0
  49. package/dist/src/instructions/management/claimFees.d.ts +7 -0
  50. package/dist/src/instructions/management/claimFees.js +47 -0
  51. package/dist/src/instructions/management/createBasket.d.ts +22 -0
  52. package/dist/src/instructions/management/createBasket.js +101 -0
  53. package/dist/src/instructions/management/edit.d.ts +34 -0
  54. package/dist/src/instructions/management/edit.js +192 -0
  55. package/dist/src/instructions/management/luts.d.ts +29 -0
  56. package/dist/src/instructions/management/luts.js +88 -0
  57. package/dist/src/instructions/pda.d.ts +26 -0
  58. package/dist/src/instructions/pda.js +110 -0
  59. package/dist/src/instructions/price.d.ts +17 -0
  60. package/dist/src/instructions/price.js +47 -0
  61. package/dist/src/instructions/user/deposit.d.ts +32 -0
  62. package/dist/src/instructions/user/deposit.js +168 -0
  63. package/dist/src/instructions/user/withdraw.d.ts +16 -0
  64. package/dist/src/instructions/user/withdraw.js +82 -0
  65. package/dist/src/layouts/basket.d.ts +41 -0
  66. package/dist/src/layouts/basket.js +43 -0
  67. package/dist/src/layouts/config.d.ts +133 -0
  68. package/dist/src/layouts/config.js +137 -0
  69. package/dist/src/layouts/fraction.d.ts +6 -0
  70. package/dist/src/layouts/fraction.js +9 -0
  71. package/dist/src/layouts/intents/bounty.d.ts +18 -0
  72. package/dist/src/layouts/intents/bounty.js +19 -0
  73. package/dist/src/layouts/intents/intent.d.ts +101 -0
  74. package/dist/src/layouts/intents/intent.js +113 -0
  75. package/dist/src/layouts/intents/rebalanceIntent.d.ts +56 -0
  76. package/dist/src/layouts/intents/rebalanceIntent.js +63 -0
  77. package/dist/src/layouts/lookupTable.d.ts +7 -0
  78. package/dist/src/layouts/lookupTable.js +10 -0
  79. package/dist/src/layouts/oracle.d.ts +42 -0
  80. package/dist/src/layouts/oracle.js +43 -0
  81. package/dist/src/states/basket.d.ts +8 -0
  82. package/dist/src/states/basket.js +54 -0
  83. package/dist/src/states/intents/intent.d.ts +14 -0
  84. package/dist/src/states/intents/intent.js +90 -0
  85. package/dist/src/states/intents/rebalanceIntent.d.ts +8 -0
  86. package/dist/src/states/intents/rebalanceIntent.js +54 -0
  87. package/dist/src/states/oracles/clmm_oracle.d.ts +178 -0
  88. package/dist/src/states/oracles/clmm_oracle.js +546 -0
  89. package/dist/src/states/oracles/constants.d.ts +8 -0
  90. package/dist/src/states/oracles/constants.js +12 -0
  91. package/dist/src/states/oracles/oracle.d.ts +60 -0
  92. package/dist/src/states/oracles/oracle.js +237 -0
  93. package/dist/src/states/oracles/pythOracle.d.ts +39 -0
  94. package/dist/src/states/oracles/pythOracle.js +202 -0
  95. package/dist/src/states/oracles/pyth_oracle.d.ts +39 -0
  96. package/dist/src/states/oracles/pyth_oracle.js +202 -0
  97. package/dist/src/states/oracles/raydiumClmmOracle.d.ts +178 -0
  98. package/dist/src/states/oracles/raydiumClmmOracle.js +546 -0
  99. package/dist/src/states/oracles/raydiumCpmmOracle.d.ts +139 -0
  100. package/dist/src/states/oracles/raydiumCpmmOracle.js +420 -0
  101. package/dist/src/states/oracles/raydium_cpmm_oracle.d.ts +139 -0
  102. package/dist/src/states/oracles/raydium_cpmm_oracle.js +420 -0
  103. package/dist/src/states/oracles/switchboardOracle.d.ts +0 -0
  104. package/dist/src/states/oracles/switchboardOracle.js +1 -0
  105. package/dist/src/states/oracles/switchboard_oracle.d.ts +0 -0
  106. package/dist/src/states/oracles/switchboard_oracle.js +1 -0
  107. package/dist/src/txUtils.d.ts +26 -0
  108. package/dist/src/txUtils.js +183 -0
  109. package/dist/states/basket.d.ts +8 -0
  110. package/dist/states/basket.js +57 -0
  111. package/dist/test.d.ts +1 -0
  112. package/dist/test.js +39 -0
  113. package/package.json +30 -0
  114. package/src/constants.ts +24 -0
  115. package/src/index.ts +260 -0
  116. package/src/instructions/automation/auction.ts +55 -0
  117. package/src/instructions/automation/claimBounty.ts +69 -0
  118. package/src/instructions/automation/priceUpdate.ts +73 -0
  119. package/src/instructions/automation/rebalanceSwap.ts +60 -0
  120. package/src/instructions/management/addBounty.ts +56 -0
  121. package/src/instructions/management/admin.ts +65 -0
  122. package/src/instructions/management/claimFees.ts +59 -0
  123. package/src/instructions/management/createBasket.ts +148 -0
  124. package/src/instructions/management/edit.ts +255 -0
  125. package/src/instructions/management/luts.ts +134 -0
  126. package/src/instructions/pda.ts +160 -0
  127. package/src/instructions/user/deposit.ts +237 -0
  128. package/src/instructions/user/withdraw.ts +130 -0
  129. package/src/layouts/basket.ts +82 -0
  130. package/src/layouts/config.ts +301 -0
  131. package/src/layouts/fraction.ts +12 -0
  132. package/src/layouts/intents/bounty.ts +35 -0
  133. package/src/layouts/intents/intent.ts +204 -0
  134. package/src/layouts/intents/rebalanceIntent.ts +111 -0
  135. package/src/layouts/lookupTable.ts +14 -0
  136. package/src/layouts/oracle.ts +96 -0
  137. package/src/states/basket.ts +56 -0
  138. package/src/states/intents/intent.ts +107 -0
  139. package/src/states/intents/rebalanceIntent.ts +57 -0
  140. package/src/states/oracles/constants.ts +13 -0
  141. package/src/states/oracles/oracle.ts +260 -0
  142. package/src/states/oracles/pythOracle.ts +270 -0
  143. package/src/states/oracles/raydiumClmmOracle.ts +812 -0
  144. package/src/states/oracles/raydiumCpmmOracle.ts +614 -0
  145. package/src/states/oracles/switchboardOracle.ts +0 -0
  146. package/src/txUtils.ts +250 -0
  147. package/test.ts +30 -0
  148. package/tsconfig.json +101 -0
@@ -0,0 +1,260 @@
1
+ import Decimal from 'decimal.js';
2
+
3
+ import { BN } from '@coral-xyz/anchor';
4
+
5
+ import { U32_MAX, U64_MAX } from './constants';
6
+
7
+ export enum ErrorCode {
8
+ NONE = 0,
9
+
10
+ // Basic Errors –––––––––––––––––––––
11
+ INVALID_PRICE = 1 << 0,
12
+ TOO_VOLATILE = 1 << 1,
13
+ TOO_UNCERTAIN = 1 << 2,
14
+ STALE = 1 << 3,
15
+ NOT_ENOUGH_LIQUIDITY = 1 << 4,
16
+ NOT_ENABLED = 1 << 5,
17
+
18
+ // Aggregation errors –––––––––––––––
19
+ NOT_ENOUGH_ORACLES = 1 << 6,
20
+ MISSING_REQUIRED_ORACLES = 1 << 7,
21
+ }
22
+
23
+ export class OracleErrors extends Error{
24
+ code: ErrorCode;
25
+
26
+ constructor(code: ErrorCode, message?: string ) {
27
+ super(message ?? ErrorCode[code]);
28
+ this.name = "OracleErrors";
29
+ this.code = code;
30
+ };
31
+ static NONE = new OracleErrors(ErrorCode.NONE);
32
+ static INVALID_PRICE = new OracleErrors(ErrorCode.INVALID_PRICE);
33
+ static TOO_VOLATILE = new OracleErrors(ErrorCode.TOO_VOLATILE);
34
+ static TOO_UNCERTAIN = new OracleErrors(ErrorCode.TOO_UNCERTAIN);
35
+ static STALE = new OracleErrors(ErrorCode.STALE);
36
+ static NOT_ENOUGH_LIQUIDITY = new OracleErrors(ErrorCode.NOT_ENOUGH_LIQUIDITY);
37
+ static NOT_ENABLED = new OracleErrors(ErrorCode.NOT_ENABLED);
38
+ static NOT_ENOUGH_ORACLES = new OracleErrors(ErrorCode.NOT_ENOUGH_ORACLES);
39
+ static MISSING_REQUIRED_ORACLES = new OracleErrors(ErrorCode.NOT_ENABLED);
40
+ }
41
+
42
+ export class OraclePrice {
43
+ price: Decimal;
44
+ conf: Decimal;
45
+ updateTime: BN;
46
+ mid: Decimal;
47
+ low: Decimal;
48
+ high: Decimal;
49
+
50
+ constructor(price: Decimal, conf: Decimal, updateTime: BN){
51
+ this.price = new Decimal(price);
52
+ this.conf = new Decimal(conf);
53
+ this.updateTime = updateTime;
54
+ this.mid = this.price;
55
+ this.low = this.price.sub(this.conf);
56
+ this.high = this.price.add(this.conf);
57
+
58
+ let zero_Decimal = new Decimal(0);
59
+ if(this.price.lte(zero_Decimal)) throw new Error("price should be more than 0");
60
+ if(this.conf.lt(zero_Decimal)) throw new Error("confidence can't be negative");
61
+ if(this.updateTime.lte(0)) throw new Error("update time should be more than 0");
62
+ }
63
+ }
64
+
65
+ export class OracleResult {
66
+ price: OraclePrice | null;
67
+ error: OracleErrors = OracleErrors.NONE;
68
+ constructor(price: OraclePrice | null, error?: OracleErrors){
69
+ this.price = price;
70
+ this.error = error ?? OracleErrors.NONE;
71
+ }
72
+ ok(): boolean {
73
+ return this.error === OracleErrors.NONE;
74
+ }
75
+ }
76
+
77
+ export abstract class Oracle {
78
+ weight: number = 1;
79
+ confThreshBps: number = 1500;
80
+ stalenessThresh: number = 60;
81
+ volatilityThresh: Decimal = new Decimal(5);
82
+ minLiquidity: Decimal = new Decimal(0);
83
+ stalenessConfRateBps: number = 0;
84
+
85
+ constructor(weight: number, confThreshBps?: number, stalenessThresh?: number,
86
+ volatilityThresh?: number | Decimal, minLiquidity?: number | Decimal,
87
+ stalenessConfRateBps?: number){
88
+ this.weight = weight;
89
+ this.confThreshBps = confThreshBps ?? 1500;
90
+ this.stalenessThresh = stalenessThresh ?? 60;
91
+ this.volatilityThresh = new Decimal(volatilityThresh ?? 5);
92
+ this.minLiquidity = new Decimal(minLiquidity ?? 0);
93
+ this.stalenessConfRateBps = stalenessConfRateBps ?? 0;
94
+
95
+ if(!(0 <= this.weight && new BN(this.weight).lte(U32_MAX)))
96
+ throw Error(`Weight must be between 0 and ${U32_MAX.toString()}`);
97
+ if(!(0 <= this.confThreshBps && this.confThreshBps < 10_000))
98
+ throw Error(`Confidence threshold must be between 0 and 10000`);
99
+ if(!(0 <= this.stalenessConfRateBps && new BN(this.stalenessConfRateBps).lt(U32_MAX)))
100
+ throw Error(`Staleness confidence rate must be between 0 and ${U32_MAX.toString()}`);
101
+ if(!(new Decimal(1).lte(this.volatilityThresh) && this.volatilityThresh.lte(new Decimal(U32_MAX.toString()))))
102
+ throw Error(`Volatility threshold must be between 1 and ${U32_MAX.toString()}`);
103
+ if(!(new Decimal(0).lte(this.minLiquidity) && this.minLiquidity.lte(new Decimal(U64_MAX.toString()))))
104
+ throw Error(`Minimum liquidity must be between 0 and ${U64_MAX.toString()}`);
105
+ if(!(0 <= this.stalenessThresh && this.stalenessThresh <= 10_000))
106
+ throw Error(`Staleness threshold must be between 0 and 10000`);
107
+
108
+ }
109
+ abstract fetch(): Promise<OracleResult>;
110
+
111
+ }
112
+
113
+ export class OracleAggregator{
114
+ oracles: Oracle[];
115
+ requiredOracles: Oracle[];
116
+ minConfBps: number = 0;
117
+ confThreshBps: number = 0;
118
+ minOraclesThresh: number = 0;
119
+
120
+ constructor(oracles: Oracle[], requiredOracles: Oracle[], minConfBps: number = 0,
121
+ confThreshBps: number = 0, minOraclesThresh: number = 0){
122
+ this.oracles = oracles;
123
+ this.requiredOracles = requiredOracles;
124
+ this.minConfBps = minConfBps;
125
+ this.confThreshBps = confThreshBps;
126
+ this.minOraclesThresh = minOraclesThresh;
127
+
128
+ if(!(this.oracles.length > 0))
129
+ throw new Error(`At least one oracle is required`);
130
+ if(!(0 <= this.minConfBps && this.minConfBps < 10_000))
131
+ throw new Error(`Minimum confidence must be between 0 and 10000`);
132
+ if(!(0 <= this.confThreshBps && this.confThreshBps < 10_000))
133
+ throw new Error(`Confidence threshold must be between 0 and 10000`);
134
+ if(!(0 <= this.minOraclesThresh && new BN(this.minOraclesThresh).lte(U32_MAX)))
135
+ throw new Error(`Minimum number of oracles must be between 0 and ${U32_MAX}`);
136
+ }
137
+ async fetch(): Promise<OracleResult> {
138
+ console.log("Fetching price from oracle aggregator");
139
+ let prices: (OraclePrice | null)[] = Array(0);
140
+ let weights: Decimal[] = Array(0);
141
+
142
+ for(let i = 0; i< this.oracles.length; i++){
143
+ try {
144
+ let result = await this.oracles[i].fetch();
145
+ if(result.ok()){
146
+ prices.push(result.price);
147
+ weights.push(new Decimal(this.oracles[i].weight));
148
+ }
149
+ else {
150
+ if(this.requiredOracles.includes(this.oracles[i])){
151
+ return new OracleResult(null, OracleErrors.MISSING_REQUIRED_ORACLES);
152
+ }
153
+ }
154
+ }
155
+ catch {
156
+ throw new Error("Unexpected error occured while fetching prices");
157
+ }
158
+ }
159
+ if(prices.length <= this.minOraclesThresh)
160
+ return new OracleResult(null, OracleErrors.NOT_ENOUGH_ORACLES);
161
+
162
+ let allPrices: (Decimal | null)[] = Array(0);
163
+ let allWeights: Decimal[] = Array(0);
164
+ let frac3: Decimal = new Decimal(3);
165
+ prices.map((price, i) => {
166
+ allPrices.push(price?.mid ?? null , price?.low ?? null , price?.high ?? null);
167
+ let w = weights[i].div(frac3);
168
+ allWeights.push(w,w,w);
169
+ });
170
+
171
+ let medianPrice = weightedMedian(allPrices, allWeights);
172
+ let p25Price = weightedPercentile(allPrices, allWeights, 25);
173
+ let p75Price = weightedPercentile(allPrices, allWeights, 75);
174
+
175
+ console.log("Median price: ", medianPrice.toString());
176
+ console.log("P25 price: ", p25Price.toString());
177
+ console.log("P75 price: ", p75Price.toString());
178
+
179
+ let first: Decimal = medianPrice.sub(p25Price);
180
+ let second: Decimal = p75Price.sub(medianPrice);
181
+ let conf = first.gte(second) ? first : second;
182
+ conf = conf.lte(medianPrice) ? conf : medianPrice;
183
+ if(conf.div(medianPrice).lt(this.minConfBps)) {
184
+ conf = medianPrice.mul(this.minConfBps)
185
+ };
186
+ if (conf.div(medianPrice).gt(this.confThreshBps)){
187
+ return new OracleResult(null, OracleErrors.TOO_UNCERTAIN);
188
+ }
189
+ const times = prices.filter((p): p is OraclePrice => p !== null) // drop nulls
190
+ .map(p => p.updateTime);
191
+
192
+ let oldestPriceTime: number = Math.min(...times);
193
+ return new OracleResult(new OraclePrice(medianPrice, conf, oldestPriceTime))
194
+
195
+ }
196
+ }
197
+
198
+ function weightedMedian(values: (Decimal | null)[], weights: Decimal[]): Decimal{
199
+ return weightedPercentile(values, weights=weights, 50);
200
+ }
201
+
202
+ function weightedPercentile(values: (Decimal | null)[], weights: Decimal[], percentile: number): Decimal {
203
+ if (percentile < 0 || percentile > 100) {
204
+ throw new Error("Percentile must be between 0 and 100");
205
+ }
206
+
207
+ if (!values || values.length === 0) {
208
+ throw new Error("Prices list is empty");
209
+ }
210
+
211
+ if (weights.length !== values.length) {
212
+ throw new Error("Weights and prices must have the same length");
213
+ }
214
+
215
+ if (weights.some((w) => w.lt(0))) {
216
+ throw new Error("Weights must be non-negative");
217
+ }
218
+
219
+ if (weights.every((w) => w.eq(0))) {
220
+ throw new Error("Weights must be non-zero");
221
+ }
222
+
223
+ // Filter out null prices along with their weights
224
+ const arr: { price: Decimal; weight: Decimal }[] = [];
225
+ for (let i = 0; i < values.length; i++) {
226
+ const p = values[i];
227
+ if (p !== null) {
228
+ arr.push({ price: p, weight: weights[i] });
229
+ }
230
+ }
231
+
232
+ if (arr.length === 0) {
233
+ throw new Error("All prices are null");
234
+ }
235
+
236
+ // Sort by price ascending
237
+ arr.sort((a, b) => a.price.sub(b.price).toNumber());
238
+
239
+ // Edge cases
240
+ if (percentile === 0) return arr[0].price;
241
+ if (percentile === 100) return arr[arr.length - 1].price;
242
+
243
+ // Compute total weight
244
+ const totalWeight = arr.reduce((sum, x) => sum.add(x.weight), new Decimal(0));
245
+
246
+ // Threshold = percentile% of total weight
247
+ const thresh = totalWeight.mul(new Decimal(percentile));
248
+
249
+ // Cumulative sum
250
+ let cum = new Decimal(0);
251
+ for (const { price, weight } of arr) {
252
+ cum = cum.add(weight);
253
+ if (cum.gte(thresh)) {
254
+ return price; // nearest-rank percentile
255
+ }
256
+ }
257
+
258
+ // fallback (should not happen, but safe)
259
+ return arr[arr.length - 1].price;
260
+ }
@@ -0,0 +1,270 @@
1
+ import { Decimal } from 'decimal.js';
2
+
3
+ import { AnchorProvider, BN, Provider, Wallet } from '@coral-xyz/anchor';
4
+ import { HermesClient } from '@pythnetwork/hermes-client';
5
+ import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver';
6
+ import {
7
+ Connection, PublicKey, Signer, TransactionSignature, VersionedTransaction
8
+ } from '@solana/web3.js';
9
+
10
+ import { HERMES_PUBLIC_ENDPOINT, SOLANA_DEVNET_ENDPOINT } from './constants';
11
+ import { ErrorCode, Oracle, OracleErrors, OraclePrice, OracleResult } from './oracle';
12
+
13
+ interface PythState {
14
+ price: BN, // i64,
15
+ conf: BN, // u64,
16
+ expo: number, // i32,
17
+ publishTime: BN, // i64
18
+ };
19
+
20
+
21
+ export class PythOracle extends Oracle {
22
+ priceFeedAccount: PublicKey;
23
+ state: PythState;
24
+ connection: Connection;
25
+ priceClient: HermesClient;
26
+ priceFeedId: string = "";
27
+
28
+ private constructor(
29
+ priceFeedAccount: PublicKey,
30
+ connection: Connection,
31
+ priceClient: HermesClient,
32
+ state: PythState,
33
+ weight: number,
34
+ confThreshBps?: number,
35
+ stalenessThresh?: number,
36
+ volatilityThresh?: Decimal,
37
+ minLiquidity?: Decimal,
38
+ stalenessConfRateBps?: number
39
+ ){
40
+ super(weight, confThreshBps, stalenessThresh, volatilityThresh, minLiquidity, stalenessConfRateBps);
41
+ this.priceFeedAccount = priceFeedAccount;
42
+ this.connection = connection;
43
+ this.priceClient = priceClient;
44
+ this.state = state;
45
+ };
46
+
47
+ static async create(
48
+ connection: Connection = new Connection(SOLANA_DEVNET_ENDPOINT),
49
+ priceFeedAccount: PublicKey,
50
+ weight: number,
51
+ priceClient?: HermesClient,
52
+ confThreshBps?: number,
53
+ stalenessThresh?: number,
54
+ volatilityThresh?: Decimal,
55
+ minLiquidity?: Decimal,
56
+ stalenessConfRateBps?: number
57
+ ): Promise<PythOracle> {
58
+
59
+ priceClient = priceClient ?? new HermesClient(HERMES_PUBLIC_ENDPOINT, {});
60
+ const priceAccountInfo = await connection.getAccountInfo(priceFeedAccount);
61
+ if(!priceAccountInfo) {
62
+ throw new Error(`Failed to fetch price account info for ${priceFeedAccount.toBase58()}`);
63
+ }
64
+ else {
65
+ let buffer: Buffer = priceAccountInfo!.data;
66
+ let offset = 8;
67
+ let writeAuthority = new PublicKey(buffer.slice(offset, offset+32));
68
+ offset += 32;
69
+ let {level, size} = parseVerificationLevel(buffer, offset);
70
+ offset += size;
71
+ let priceMessage = new PublicKey(buffer.slice(offset, offset+32));
72
+ offset += 32;
73
+ let price = new BN(buffer.slice(offset, offset+8), 'le').toString();
74
+ offset += 8;
75
+ let conf = new BN(buffer.slice(offset, offset+8), 'le').toString();
76
+ offset += 8;
77
+ let exp = new BN(buffer.slice(offset, offset + 4), 'le').fromTwos(32).toString();
78
+ offset += 4;
79
+ let publishTime = new BN(buffer.slice(offset, offset+8), 'le').fromTwos(64).toString();
80
+ offset += 8;
81
+ let state: PythState = {
82
+ price: new BN(price),
83
+ conf: new BN(conf),
84
+ expo: parseInt(exp),
85
+ publishTime: new BN(publishTime)
86
+ };
87
+ return new PythOracle(
88
+ // priceFeedId,
89
+ priceFeedAccount,
90
+ connection,
91
+ priceClient,
92
+ state,
93
+ weight,
94
+ confThreshBps,
95
+ stalenessThresh,
96
+ volatilityThresh,
97
+ minLiquidity,
98
+ stalenessConfRateBps
99
+ );
100
+ }
101
+ };
102
+
103
+ static async createWithId(
104
+ connection: Connection = new Connection(SOLANA_DEVNET_ENDPOINT),
105
+ priceFeedId: string,
106
+ wallet: Wallet,
107
+ weight: number,
108
+ priceClient?: HermesClient,
109
+ confThreshBps?: number,
110
+ stalenessThresh?: number,
111
+ volatilityThresh?: Decimal,
112
+ minLiquidity?: Decimal,
113
+ stalenessConfRateBps?: number
114
+ ): Promise<PythOracle> {
115
+
116
+ const pythSolanaReceiver = new PythSolanaReceiver({
117
+ connection,
118
+ wallet,
119
+ });
120
+ priceClient = priceClient ?? new HermesClient(HERMES_PUBLIC_ENDPOINT, {});
121
+ const priceAccount = pythSolanaReceiver.getPriceFeedAccountAddress(1, priceFeedId);
122
+ const priceAccountInfo = await connection.getAccountInfo(priceAccount);
123
+ if(!priceAccountInfo) {
124
+ throw new Error(`Failed to fetch price account info for ${priceAccount.toBase58()}`);
125
+ }
126
+ else {
127
+ let buffer: Buffer = priceAccountInfo!.data;
128
+ let offset = 8;
129
+ let writeAuthority = new PublicKey(buffer.slice(offset, offset+32));
130
+ offset += 32;
131
+ let {level, size} = parseVerificationLevel(buffer, offset);
132
+ offset += size;
133
+ let priceMessage = new PublicKey(buffer.slice(offset, offset+32));
134
+ offset += 32;
135
+ let price = new BN(buffer.slice(offset, offset+8), 'le').toString();
136
+ offset += 8;
137
+ let conf = new BN(buffer.slice(offset, offset+8), 'le').toString();
138
+ offset += 8;
139
+ let exp = new BN(buffer.slice(offset, offset + 4), 'le').fromTwos(32).toString();
140
+ offset += 4;
141
+ let publishTime = new BN(buffer.slice(offset, offset+8), 'le').fromTwos(64).toString();
142
+ offset += 8;
143
+ let state: PythState = {
144
+ price: new BN(price),
145
+ conf: new BN(conf),
146
+ expo: parseInt(exp),
147
+ publishTime: new BN(publishTime)
148
+ };
149
+
150
+
151
+
152
+ let pythOracle = new PythOracle(
153
+ priceAccount,
154
+ connection,
155
+ priceClient,
156
+ state,
157
+ weight,
158
+ confThreshBps,
159
+ stalenessThresh,
160
+ volatilityThresh,
161
+ minLiquidity,
162
+ stalenessConfRateBps
163
+ );
164
+
165
+ pythOracle.setPriceFeedId(priceFeedId);
166
+
167
+ return pythOracle;
168
+ }
169
+ };
170
+
171
+ getConfidence(): Decimal {
172
+ return new Decimal(this.state.conf.toString()).mul(10**this.state.expo);
173
+ }
174
+
175
+ setPriceFeedId(priceFeedId: string) {
176
+ this.priceFeedId = priceFeedId;
177
+ }
178
+
179
+ async fetch(): Promise<OracleResult> {
180
+ const currentTime = new BN(Math.floor(Date.now() / 1000));
181
+ if(currentTime.sub(this.state.publishTime).gt(new BN(this.stalenessThresh))) {
182
+ this.state = await this.fetchPriceFromHermes(this.priceFeedId);
183
+ }
184
+
185
+ let errors = 0;
186
+ console.log(this.confThreshBps)
187
+ console.log(this.state.conf.toString());
188
+ let pr = new Decimal(this.state.price.toString()).mul(10**this.state.expo);
189
+ let cf = new Decimal(this.state.conf.toString()).mul(10**this.state.expo);
190
+ console.log(cf.div(pr).mul(10000));
191
+ if(this.getConfidence().div(this.state.price.toString()).mul(10000).gt(this.confThreshBps)) {
192
+ errors |= ErrorCode.TOO_UNCERTAIN;
193
+ }
194
+
195
+ let err = new OracleErrors(errors);
196
+ console.log(this.state.publishTime);
197
+ let oraclePrice = new OraclePrice(
198
+ new Decimal(this.state.price.toString()).mul(10**this.state.expo),
199
+ new Decimal(this.state.conf.toString()).mul(10**this.state.expo),
200
+ this.state.publishTime
201
+ )
202
+ return new OracleResult(oraclePrice, err);
203
+
204
+ }
205
+
206
+ async fetchPriceFromHermes(priceFeedId: string): Promise<PythState> {
207
+ const priceUpdate = await this.priceClient.getLatestPriceUpdates([priceFeedId], {encoding: "base64"});
208
+ if(priceUpdate && priceUpdate.parsed && priceUpdate.parsed.length > 0) {
209
+ const priceData = priceUpdate.parsed[0].price;
210
+ console.log(priceData.publishTime);
211
+ return {
212
+ price: new BN(priceData.price),
213
+ conf: new BN(priceData.conf),
214
+ expo: priceData.expo,
215
+ publishTime: new BN(priceData.publishTime)
216
+ };
217
+ } else {
218
+ throw new Error(`Failed to fetch price update from Hermes for ${priceFeedId}`);
219
+ }
220
+ }
221
+ /**
222
+ * Get parsed baskets by manager. Uses 10 paralel rpc calls so might get heavy. Should set feedDd before running this
223
+ * @param {number} shardId - between 0-2^64, default is 0
224
+ * @param {number} [computeUnitPrice] - compute unit price in microlamports, default is 100_000
225
+ */
226
+ async updateFeedTx(wallet: Wallet, shardId: number = 0, computeUnitPrice: number = 100000) {
227
+ if(!shardId) shardId = 0;
228
+ if(!computeUnitPrice) computeUnitPrice = 100000;
229
+ let priceUpdate = await this.priceClient.getLatestPriceUpdates([this.priceFeedId], {encoding: "base64"});
230
+ const pythSolanaReceiver = new PythSolanaReceiver({
231
+ connection: this.connection,
232
+ wallet: wallet,
233
+ });
234
+ const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
235
+ // closeUpdateAccounts: false
236
+ });
237
+ await transactionBuilder.addUpdatePriceFeed(priceUpdate.binary.data, shardId);
238
+ let build = await transactionBuilder.buildVersionedTransactions({
239
+ computeUnitPriceMicroLamports: computeUnitPrice,
240
+ });
241
+
242
+ return build;
243
+ };
244
+
245
+ async sendUpdateTx(
246
+ provider: AnchorProvider,
247
+ updateFeedTx: {
248
+ tx: VersionedTransaction;
249
+ signers: Signer[];
250
+ }[]
251
+ ): Promise<TransactionSignature[]>{
252
+
253
+ let txs = await provider.sendAll(updateFeedTx, { skipPreflight: true, commitment: "confirmed"});
254
+ return txs;
255
+ }
256
+
257
+
258
+ }
259
+
260
+ function parseVerificationLevel(buf: Buffer, offset: number): { level: string, size: number } {
261
+ const discr = buf.readUInt8(offset);
262
+ if (discr === 0) { // Partial
263
+ const numSignatures = buf.readUInt8(offset + 1);
264
+ return { level: `Partial(${numSignatures})`, size: 2 };
265
+ } else if (discr === 1) { // Full
266
+ return { level: "Full", size: 1 };
267
+ } else {
268
+ throw new Error(`Unknown verification level: ${discr}`);
269
+ }
270
+ }