@symmetry-hq/sdk 1.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 (121) hide show
  1. package/dist/src/constants.d.ts +23 -0
  2. package/dist/src/constants.js +38 -0
  3. package/dist/src/index.d.ts +804 -0
  4. package/dist/src/index.js +2097 -0
  5. package/dist/src/instructions/automation/auction.d.ts +6 -0
  6. package/dist/src/instructions/automation/auction.js +40 -0
  7. package/dist/src/instructions/automation/claimBounty.d.ts +12 -0
  8. package/dist/src/instructions/automation/claimBounty.js +44 -0
  9. package/dist/src/instructions/automation/flashSwap.d.ts +21 -0
  10. package/dist/src/instructions/automation/flashSwap.js +74 -0
  11. package/dist/src/instructions/automation/priceUpdate.d.ts +19 -0
  12. package/dist/src/instructions/automation/priceUpdate.js +89 -0
  13. package/dist/src/instructions/automation/rebalanceIntent.d.ts +32 -0
  14. package/dist/src/instructions/automation/rebalanceIntent.js +117 -0
  15. package/dist/src/instructions/automation/rebalanceSwap.d.ts +11 -0
  16. package/dist/src/instructions/automation/rebalanceSwap.js +42 -0
  17. package/dist/src/instructions/management/addBounty.d.ts +7 -0
  18. package/dist/src/instructions/management/addBounty.js +41 -0
  19. package/dist/src/instructions/management/admin.d.ts +9 -0
  20. package/dist/src/instructions/management/admin.js +53 -0
  21. package/dist/src/instructions/management/claimFees.d.ts +15 -0
  22. package/dist/src/instructions/management/claimFees.js +95 -0
  23. package/dist/src/instructions/management/createBasket.d.ts +21 -0
  24. package/dist/src/instructions/management/createBasket.js +98 -0
  25. package/dist/src/instructions/management/edit.d.ts +51 -0
  26. package/dist/src/instructions/management/edit.js +477 -0
  27. package/dist/src/instructions/management/luts.d.ts +30 -0
  28. package/dist/src/instructions/management/luts.js +99 -0
  29. package/dist/src/instructions/pda.d.ts +25 -0
  30. package/dist/src/instructions/pda.js +128 -0
  31. package/dist/src/instructions/user/deposit.d.ts +20 -0
  32. package/dist/src/instructions/user/deposit.js +100 -0
  33. package/dist/src/instructions/user/withdraw.d.ts +8 -0
  34. package/dist/src/instructions/user/withdraw.js +36 -0
  35. package/dist/src/jup.d.ts +49 -0
  36. package/dist/src/jup.js +80 -0
  37. package/dist/src/keeperMonitor.d.ts +52 -0
  38. package/dist/src/keeperMonitor.js +624 -0
  39. package/dist/src/layouts/basket.d.ts +191 -0
  40. package/dist/src/layouts/basket.js +51 -0
  41. package/dist/src/layouts/config.d.ts +281 -0
  42. package/dist/src/layouts/config.js +237 -0
  43. package/dist/src/layouts/fraction.d.ts +20 -0
  44. package/dist/src/layouts/fraction.js +164 -0
  45. package/dist/src/layouts/intents/bounty.d.ts +18 -0
  46. package/dist/src/layouts/intents/bounty.js +19 -0
  47. package/dist/src/layouts/intents/intent.d.ts +209 -0
  48. package/dist/src/layouts/intents/intent.js +97 -0
  49. package/dist/src/layouts/intents/rebalanceIntent.d.ts +212 -0
  50. package/dist/src/layouts/intents/rebalanceIntent.js +94 -0
  51. package/dist/src/layouts/lookupTable.d.ts +7 -0
  52. package/dist/src/layouts/lookupTable.js +10 -0
  53. package/dist/src/layouts/oracle.d.ts +63 -0
  54. package/dist/src/layouts/oracle.js +96 -0
  55. package/dist/src/states/basket.d.ts +14 -0
  56. package/dist/src/states/basket.js +479 -0
  57. package/dist/src/states/config.d.ts +3 -0
  58. package/dist/src/states/config.js +71 -0
  59. package/dist/src/states/intents/intent.d.ts +10 -0
  60. package/dist/src/states/intents/intent.js +316 -0
  61. package/dist/src/states/intents/rebalanceIntent.d.ts +42 -0
  62. package/dist/src/states/intents/rebalanceIntent.js +680 -0
  63. package/dist/src/states/oracles/constants.d.ts +9 -0
  64. package/dist/src/states/oracles/constants.js +15 -0
  65. package/dist/src/states/oracles/oracle.d.ts +24 -0
  66. package/dist/src/states/oracles/oracle.js +168 -0
  67. package/dist/src/states/oracles/pythOracle.d.ts +132 -0
  68. package/dist/src/states/oracles/pythOracle.js +609 -0
  69. package/dist/src/states/oracles/raydiumClmmOracle.d.ts +184 -0
  70. package/dist/src/states/oracles/raydiumClmmOracle.js +843 -0
  71. package/dist/src/states/oracles/raydiumCpmmOracle.d.ts +120 -0
  72. package/dist/src/states/oracles/raydiumCpmmOracle.js +540 -0
  73. package/dist/src/states/oracles/switchboardOracle.d.ts +0 -0
  74. package/dist/src/states/oracles/switchboardOracle.js +1 -0
  75. package/dist/src/states/withdrawBasketFees.d.ts +10 -0
  76. package/dist/src/states/withdrawBasketFees.js +154 -0
  77. package/dist/src/txUtils.d.ts +65 -0
  78. package/dist/src/txUtils.js +306 -0
  79. package/dist/test.d.ts +1 -0
  80. package/dist/test.js +561 -0
  81. package/package.json +31 -0
  82. package/src/constants.ts +40 -0
  83. package/src/index.ts +2431 -0
  84. package/src/instructions/automation/auction.ts +55 -0
  85. package/src/instructions/automation/claimBounty.ts +69 -0
  86. package/src/instructions/automation/flashSwap.ts +104 -0
  87. package/src/instructions/automation/priceUpdate.ts +117 -0
  88. package/src/instructions/automation/rebalanceIntent.ts +181 -0
  89. package/src/instructions/management/addBounty.ts +55 -0
  90. package/src/instructions/management/admin.ts +72 -0
  91. package/src/instructions/management/claimFees.ts +129 -0
  92. package/src/instructions/management/createBasket.ts +138 -0
  93. package/src/instructions/management/edit.ts +602 -0
  94. package/src/instructions/management/luts.ts +157 -0
  95. package/src/instructions/pda.ts +151 -0
  96. package/src/instructions/user/deposit.ts +143 -0
  97. package/src/instructions/user/withdraw.ts +53 -0
  98. package/src/jup.ts +113 -0
  99. package/src/keeperMonitor.ts +585 -0
  100. package/src/layouts/basket.ts +233 -0
  101. package/src/layouts/config.ts +576 -0
  102. package/src/layouts/fraction.ts +164 -0
  103. package/src/layouts/intents/bounty.ts +35 -0
  104. package/src/layouts/intents/intent.ts +324 -0
  105. package/src/layouts/intents/rebalanceIntent.ts +306 -0
  106. package/src/layouts/lookupTable.ts +14 -0
  107. package/src/layouts/oracle.ts +157 -0
  108. package/src/states/basket.ts +527 -0
  109. package/src/states/config.ts +62 -0
  110. package/src/states/intents/intent.ts +311 -0
  111. package/src/states/intents/rebalanceIntent.ts +751 -0
  112. package/src/states/oracles/constants.ts +13 -0
  113. package/src/states/oracles/oracle.ts +212 -0
  114. package/src/states/oracles/pythOracle.ts +874 -0
  115. package/src/states/oracles/raydiumClmmOracle.ts +1193 -0
  116. package/src/states/oracles/raydiumCpmmOracle.ts +784 -0
  117. package/src/states/oracles/switchboardOracle.ts +0 -0
  118. package/src/states/withdrawBasketFees.ts +160 -0
  119. package/src/txUtils.ts +424 -0
  120. package/test.ts +609 -0
  121. package/tsconfig.json +101 -0
@@ -0,0 +1,784 @@
1
+ import Decimal from 'decimal.js';
2
+
3
+ import BN from 'bn.js';
4
+ import { AccountInfo, PublicKey } from '@solana/web3.js';
5
+
6
+ import { HUNDRED_PERCENT_BPS } from '../../constants';
7
+ import { OracleSettings, Quote, Side } from '../../layouts/oracle';
8
+ import { OraclePrice } from './oracle';
9
+
10
+ const CPMM_PROGRAM_ID = new PublicKey("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C");
11
+ const DEV_CPMM_PROGRAM_ID = new PublicKey("DRaycpLY18LhpbydsBWbVJtxpNv9oXPgjRSfpF2bWpYb");
12
+
13
+
14
+ class Observation {
15
+ timestamp: BN; // u64
16
+ cumT0Price: BN; // u128
17
+ cumT1Price: BN; // u128
18
+ constructor(params: {timestamp: BN, cumT0Price: BN, cumT1Price: BN}){
19
+ this.timestamp = params.timestamp;
20
+ this.cumT0Price = params.cumT0Price;
21
+ this.cumT1Price = params.cumT1Price;
22
+ }
23
+ static decode(data: Buffer, offset = 0): [Observation, number] {
24
+
25
+ const timestamp = new BN(
26
+ data.subarray(offset, offset+8),
27
+ "le"
28
+ );
29
+
30
+ const cumT0Price = new BN(
31
+ data.subarray(offset + 8, offset + 24),
32
+ 'le'
33
+ );
34
+
35
+ const cumT1Price = new BN(
36
+ data.subarray(offset + 24, offset + 40),
37
+ 'le'
38
+ );
39
+
40
+
41
+
42
+ return [
43
+ new Observation({
44
+ timestamp,
45
+ cumT0Price,
46
+ cumT1Price }),
47
+ offset + 40
48
+ ];
49
+ }
50
+
51
+
52
+
53
+ sub(other: Observation): Observation {
54
+ if (!(other instanceof Observation)) {
55
+ throw new TypeError("Subtraction is only supported between Observation instances");
56
+ }
57
+
58
+ return new Observation({
59
+ timestamp: this.timestamp.sub(other.timestamp),
60
+ cumT0Price: this.cumT0Price.sub(other.cumT0Price),
61
+ cumT1Price: this.cumT1Price.sub(other.cumT1Price),
62
+ });
63
+ }
64
+
65
+ add(other: Observation): Observation {
66
+ if (!(other instanceof Observation)) {
67
+ throw new TypeError("Addition is only supported between Observation instances");
68
+ }
69
+ return new Observation({
70
+ timestamp: this.timestamp.add(other.timestamp),
71
+ cumT0Price: this.cumT0Price.add(other.cumT0Price),
72
+ cumT1Price: this.cumT1Price.add(other.cumT1Price),
73
+ });
74
+ }
75
+
76
+ getWeightedObservation(time: BN): Observation {
77
+ const cumT0Price = this.cumT0Price.mul(time).div(this.timestamp);
78
+ const cumT1Price = this.cumT1Price.mul(time).div(this.timestamp);
79
+ return new Observation({
80
+ timestamp: time,
81
+ cumT0Price,
82
+ cumT1Price
83
+ });
84
+ }
85
+
86
+ adjustToTimestamp(targetTimestamp: BN, observationPrev: Observation): Observation {
87
+ // delta = self.sub(prevPbservation)
88
+ const delta = this.sub(observationPrev);
89
+
90
+ // weightedDelta = delta.getWeightedObservation(targetTimestamp - self.blockTimestamp)
91
+ const timeForWeighted = targetTimestamp.sub(this.timestamp);
92
+ const weightedDelta = delta.getWeightedObservation(timeForWeighted);
93
+
94
+ // return self.add(weightedDelta)
95
+ return this.add(weightedDelta);
96
+ }
97
+
98
+ public getTwap(side: Side): BN {
99
+ let cumPrice = new BN(0);
100
+ if(side === Side.Base)
101
+ cumPrice = this.cumT0Price;
102
+ else cumPrice = this.cumT1Price;
103
+ return cumPrice.div(this.timestamp);
104
+ }
105
+
106
+ }
107
+
108
+ class VaultState {
109
+
110
+ mint: PublicKey;
111
+ owner: PublicKey;
112
+ amount: string;
113
+
114
+ constructor(params: {mint: PublicKey, owner: PublicKey, amount: string}) {
115
+ this.mint = params.mint;
116
+ this.owner = params.owner;
117
+ this.amount = params.amount;
118
+ }
119
+ static decode(data: Buffer, offset: number = 0): [VaultState, number] {
120
+ // mint (32)
121
+ const mint = new PublicKey(data.slice(offset, offset + 32));
122
+ offset += 32;
123
+
124
+ // owner (32)
125
+ const owner = new PublicKey(data.slice(offset, offset + 32));
126
+ offset += 32;
127
+
128
+ // amount u64 (8)
129
+ const amount = (new BN(data.subarray(offset, offset + 8), 'le')).toString();
130
+ offset += 8;
131
+
132
+ return [
133
+ new VaultState({ mint, owner, amount }),
134
+ offset
135
+ ];
136
+ }
137
+ }
138
+
139
+ class ObservationState {
140
+ initialized : boolean;
141
+ observationIndex: number; //u16
142
+ poolId: PublicKey;
143
+ observations: Observation[];
144
+ padding: BN[]; // [u64; 3]
145
+
146
+ constructor(params: {
147
+ initialized: boolean,
148
+ observationIndex: number,
149
+ poolId: PublicKey,
150
+ observations: Observation[],
151
+ padding: BN[]
152
+ }){
153
+ this.initialized = params.initialized;
154
+ this.observationIndex = params.observationIndex;
155
+ this.poolId = params.poolId;
156
+ this.observations = params.observations;
157
+ this.padding = params.padding;
158
+ }
159
+
160
+ static decode(data: Buffer, offset = 8): [ObservationState, number] {
161
+ let cursor = offset;
162
+
163
+ const initialized = data.readUInt8(cursor) !== 0;
164
+ cursor += 1;
165
+
166
+ const observationIndex = data.readUInt16LE(cursor);
167
+ cursor += 2;
168
+
169
+ const poolId = new PublicKey(data.subarray(cursor, cursor + 32));
170
+ cursor += 32;
171
+
172
+ const observations: Observation[] = [];
173
+ for (let i = 0; i < 100; i++) {
174
+ let obs: Observation;
175
+ [obs, cursor] = Observation.decode(data, cursor);
176
+ observations.push(obs);
177
+ }
178
+
179
+
180
+ const padding: BN[] = [];
181
+ for (let i = 0; i < 4; i++) {
182
+ padding.push(new BN(data.subarray(cursor, cursor + 8), "le"));
183
+ cursor += 8;
184
+ }
185
+
186
+ return [
187
+ new ObservationState({
188
+ initialized,
189
+ observationIndex,
190
+ poolId,
191
+ observations,
192
+ padding
193
+ }),
194
+ cursor
195
+ ]
196
+ }
197
+ }
198
+
199
+ class PoolState {
200
+ ammConfig: PublicKey;
201
+ poolCreator: PublicKey;
202
+ token0Vault: PublicKey;
203
+ token1Vault: PublicKey;
204
+ lpMint: PublicKey;
205
+ token0Mint: PublicKey;
206
+ token1Mint: PublicKey;
207
+ token0Program: PublicKey;
208
+ token1Program: PublicKey;
209
+ observationKey: PublicKey;
210
+ authBump: number; // u8
211
+ status: number; // u8
212
+ lpMintDecimals: number; // u8
213
+ mint0Decimals: number; // u8
214
+ mint1Decimals: number; // u8
215
+ lpSupply: BN; // u64
216
+ protocolFeesToken0: BN; // u64
217
+ protocolFeesToken1: BN; // u64
218
+ fundFeesToken0: BN; // u64
219
+ fundFeesToken1: BN; // u64
220
+ openTime: BN; // u64
221
+ recentEpoch: BN; // u64
222
+ creatorFeeOn: number; //u8
223
+ enableCreatorFee: boolean;
224
+ padding1: number[]; // [u8; 6]
225
+ creatorFeesToken0: BN; // u64
226
+ creatorFeesToken1: BN; // u64
227
+ padding: BN[]; // [u64; 28]
228
+
229
+ constructor(
230
+ params: {
231
+ ammConfig: PublicKey;
232
+ poolCreator: PublicKey;
233
+ token0Vault: PublicKey;
234
+ token1Vault: PublicKey;
235
+ lpMint: PublicKey;
236
+ token0Mint: PublicKey;
237
+ token1Mint: PublicKey;
238
+ token0Program: PublicKey;
239
+ token1Program: PublicKey;
240
+ observationKey: PublicKey;
241
+ authBump: number; // u8
242
+ status: number; // u8
243
+ lpMintDecimals: number; // u8
244
+ mint0Decimals: number; // u8
245
+ mint1Decimals: number; // u8
246
+ lpSupply: BN; // u64
247
+ protocolFeesToken0: BN; // u64
248
+ protocolFeesToken1: BN; // u64
249
+ fundFeesToken0: BN; // u64
250
+ fundFeesToken1: BN; // u64
251
+ openTime: BN; // u64
252
+ recentEpoch: BN; // u64
253
+ creatorFeeOn: number; //u8
254
+ enableCreatorFee: boolean;
255
+ padding1: number[]; // [u8; 6]
256
+ creatorFeesToken0: BN; // u64
257
+ creatorFeesToken1: BN; // u64
258
+ padding: BN[]; // [u64; 28]
259
+ }){
260
+ this.ammConfig = params.ammConfig;
261
+ this.poolCreator = params.poolCreator;
262
+ this.token0Vault = params.token0Vault;
263
+ this.token1Vault = params.token1Vault;
264
+ this.lpMint = params.lpMint;
265
+ this.token0Mint = params.token0Mint;
266
+ this.token1Mint = params.token1Mint;
267
+ this.token0Program = params.token0Program;
268
+ this.token1Program = params.token1Program;
269
+ this.observationKey = params.observationKey;
270
+ this.authBump = params.authBump; // u8
271
+ this.status = params.status; // u8
272
+ this.lpMintDecimals = params.lpMintDecimals; // u8
273
+ this.mint0Decimals = params.mint0Decimals; // u8
274
+ this.mint1Decimals = params.mint1Decimals; // u8
275
+ this.lpSupply = params.lpSupply; // u64
276
+ this.protocolFeesToken0 = params.protocolFeesToken0; // u64
277
+ this.protocolFeesToken1 = params.protocolFeesToken1; // u64
278
+ this.fundFeesToken0 = params.fundFeesToken0; // u64
279
+ this.fundFeesToken1 = params.fundFeesToken1; // u64
280
+ this.openTime = params.openTime; // u64
281
+ this.recentEpoch = params.recentEpoch; // u64
282
+ this.creatorFeeOn = params.creatorFeeOn; //u8
283
+ this.enableCreatorFee = params.enableCreatorFee;
284
+ this.padding1 = params.padding1; // [u8; 6]
285
+ this.creatorFeesToken0 = params.creatorFeesToken0; // u64
286
+ this.creatorFeesToken1 = params.creatorFeesToken1; // u64
287
+ this.padding = params.padding // [u64; 28]
288
+ }
289
+
290
+ static decode(data: Buffer, offset: number = 8): [PoolState, number] {
291
+ let cursor = offset;
292
+
293
+ const ammConfig = new PublicKey(data.subarray(cursor, cursor + 32));
294
+ cursor += 32;
295
+
296
+ const poolCreator = new PublicKey(data.subarray(cursor, cursor + 32));
297
+ cursor += 32;
298
+
299
+ const token0Vault = new PublicKey(data.subarray(cursor, cursor + 32));
300
+ cursor += 32;
301
+
302
+ const token1Vault = new PublicKey(data.subarray(cursor, cursor + 32));
303
+ cursor += 32;
304
+
305
+ const lpMint = new PublicKey(data.subarray(cursor, cursor + 32));
306
+ cursor += 32;
307
+
308
+ const token0Mint = new PublicKey(data.subarray(cursor, cursor + 32));
309
+ cursor += 32;
310
+
311
+ const token1Mint = new PublicKey(data.subarray(cursor, cursor + 32));
312
+ cursor += 32;
313
+
314
+ const token0Program = new PublicKey(data.subarray(cursor, cursor + 32));
315
+ cursor += 32;
316
+
317
+ const token1Program = new PublicKey(data.subarray(cursor, cursor + 32));
318
+ cursor += 32;
319
+
320
+ const observationKey = new PublicKey(data.subarray(cursor, cursor + 32));
321
+ cursor += 32;
322
+
323
+ const authBump = data.readUInt8(cursor);
324
+ cursor += 1;
325
+
326
+ const status = data.readUInt8(cursor);
327
+ cursor += 1;
328
+
329
+ const lpMintDecimals = data.readUInt8(cursor);
330
+ cursor += 1;
331
+
332
+ const mint0Decimals = data.readUInt8(cursor);
333
+ cursor += 1;
334
+
335
+ const mint1Decimals = data.readUInt8(cursor);
336
+ cursor += 1;
337
+
338
+ const lpSupply = new BN(data.subarray(cursor, cursor + 8), "le");
339
+ cursor += 8;
340
+
341
+ const protocolFeesToken0 = new BN(data.subarray(cursor, cursor + 8), "le");
342
+ cursor += 8;
343
+
344
+ const protocolFeesToken1 = new BN(data.subarray(cursor, cursor + 8), "le");
345
+ cursor += 8;
346
+
347
+ const fundFeesToken0 = new BN(data.subarray(cursor, cursor + 8), "le");
348
+ cursor += 8;
349
+
350
+ const fundFeesToken1 = new BN(data.subarray(cursor, cursor + 8), "le");
351
+ cursor += 8;
352
+
353
+ const openTime = new BN(data.subarray(cursor, cursor + 8), "le");
354
+ cursor += 8;
355
+
356
+ const recentEpoch = new BN(data.subarray(cursor, cursor + 8), "le");
357
+ cursor += 8;
358
+
359
+ const creatorFeeOn = data.readUInt8(cursor);
360
+ cursor += 1;
361
+
362
+ const enableCreatorFee = !!data.readUInt8(cursor);
363
+ cursor += 1;
364
+
365
+ const padding1: number[] = [];
366
+ for (let i = 0; i < 6; i++) {
367
+ padding1.push(data.readUInt8(cursor));
368
+ cursor += 1;
369
+ }
370
+
371
+ const creatorFeesToken0 = new BN(data.subarray(cursor, cursor + 8), "le");
372
+ cursor += 8;
373
+
374
+ const creatorFeesToken1 = new BN(data.subarray(cursor, cursor + 8), "le");
375
+ cursor += 8;
376
+
377
+ const padding: BN[] = [];
378
+ for (let i = 0; i < 28; i++) {
379
+ padding.push(new BN(data.subarray(cursor, cursor + 8), "le"));
380
+ cursor += 8;
381
+ }
382
+
383
+ return [
384
+ new PoolState({
385
+ ammConfig,
386
+ poolCreator,
387
+ token0Vault,
388
+ token1Vault,
389
+ lpMint,
390
+ token0Mint,
391
+ token1Mint,
392
+ token0Program,
393
+ token1Program,
394
+ observationKey,
395
+ authBump,
396
+ status,
397
+ lpMintDecimals,
398
+ mint0Decimals,
399
+ mint1Decimals,
400
+ lpSupply,
401
+ protocolFeesToken0,
402
+ protocolFeesToken1,
403
+ fundFeesToken0,
404
+ fundFeesToken1,
405
+ openTime,
406
+ recentEpoch,
407
+ creatorFeeOn,
408
+ enableCreatorFee,
409
+ padding1,
410
+ creatorFeesToken0,
411
+ creatorFeesToken1,
412
+ padding,
413
+ }),
414
+ cursor,
415
+ ];
416
+ }
417
+ }
418
+
419
+ export class RaydiumCPMMOracle {
420
+ // poolId: PublicKey;
421
+ // poolState: PoolState;
422
+ // observationState: ObservationState;
423
+ // vault0: VaultState;
424
+ // vault1: VaultState;
425
+
426
+ // constructor(
427
+ // oracleParams: OracleSettings,
428
+ // cpmmParams: {
429
+ // poolId: PublicKey;
430
+ // poolState: PoolState;
431
+ // observationState: ObservationState,
432
+ // vault0: VaultState,
433
+ // vault1: VaultState,
434
+ // }){
435
+ // super(
436
+ // oracleParams
437
+ // );
438
+
439
+ // this.poolId = cpmmParams.poolId;
440
+ // this.poolState = cpmmParams.poolState;
441
+ // this.observationState = cpmmParams.observationState;
442
+ // this.vault0 = cpmmParams.vault0;
443
+ // this.vault1 = cpmmParams.vault1;
444
+
445
+ // }
446
+
447
+ static deriveObservationKey(poolId: PublicKey): [PublicKey, number] {
448
+ const seeds = [
449
+ Buffer.from("observation"),
450
+ poolId.toBuffer(),
451
+ ];
452
+ return PublicKey.findProgramAddressSync(seeds, CPMM_PROGRAM_ID);
453
+ };
454
+
455
+ static getObservationAtIndex(observations: Observation[], index: number) {
456
+ return observations[index];
457
+ }
458
+
459
+ static getObservationAtTimestamp(timestamp: BN, observations: Observation[], startIndex: number): Observation {
460
+ let observationCurrent = this.getObservationAtIndex(observations, startIndex);
461
+ let index = startIndex;
462
+ // Loop backwards until we find the observation <= timestamp
463
+ while (observationCurrent.timestamp.gt(timestamp)) {
464
+ index = (index - 1 + 100) % 100;
465
+ observationCurrent = this.getObservationAtIndex(observations, index);
466
+
467
+ if (index === startIndex) {
468
+ throw Error("Observations do not go back far enough in time");
469
+ }
470
+ }
471
+
472
+ // Previous index for interpolation
473
+ const prevIndex = (index - 1 + 100) % 100;
474
+ const observationPrev = this.getObservationAtIndex(observations, prevIndex);
475
+
476
+ // Adjust current observation to the target timestamp
477
+ const result = observationCurrent.adjustToTimestamp(timestamp, observationPrev);
478
+ return result;
479
+ }
480
+
481
+ static getDeltaObservations(currentTime: BN, observations: Observation[], observationIndex: number, primarySeconds: BN, secondarySeconds: BN): Observation[] {
482
+
483
+ const obsIndex = observationIndex;
484
+ const observationCurrent = this.getObservationAtTimestamp(currentTime, observations, obsIndex);
485
+
486
+ const observationPrimary = this.getObservationAtTimestamp(currentTime.sub(primarySeconds), observations, obsIndex);
487
+ const deltaPrimary = observationCurrent.sub(observationPrimary);
488
+
489
+ const observationSecondary = this.getObservationAtTimestamp(currentTime.sub(secondarySeconds), observations, obsIndex);
490
+ const deltaSecondary = observationCurrent.sub(observationSecondary);
491
+
492
+ return [deltaPrimary, deltaSecondary];
493
+ };
494
+
495
+
496
+ static getDecimals(side : Side, mint0Decimals: number, mint1Decimals: number): number {
497
+
498
+ let dec;
499
+ if(side === Side.Base)
500
+ dec = mint0Decimals - mint1Decimals;
501
+ else dec = mint1Decimals - mint0Decimals;
502
+ return dec;
503
+ }
504
+
505
+ // static getSpotPrice(
506
+ // baseAmount: BN, quoteAmount: BN, protocolFeesToken0: BN, protocolFeesToken1: BN,
507
+ // fundFeesToken0: BN, fundFeesToken1: BN, mint0Decimals: number, mint1Decimals: number, side: Side
508
+ // ): Decimal {
509
+ // let decimals = this.getDecimals(side, mint0Decimals, mint1Decimals);
510
+ // let fees0 = protocolFeesToken0.add(fundFeesToken0);
511
+ // let fees1 = protocolFeesToken1.add(fundFeesToken1);
512
+
513
+ // if(side === Side.Base) {
514
+ // baseAmount = baseAmount.sub(fees0);
515
+ // quoteAmount = quoteAmount.sub(fees1);
516
+ // }
517
+ // else {
518
+ // baseAmount = baseAmount.sub(fees1);
519
+ // quoteAmount = quoteAmount.sub(fees0);
520
+ // }
521
+ // let price_scaled = new Decimal(baseAmount.toString()).div(quoteAmount.toString());
522
+ // return price_scaled.div(10 ** decimals);
523
+ // }
524
+
525
+ /**
526
+ * Calculate spot price from pool reserves after subtracting all fees.
527
+ * Returns: { netBase, netQuote, spotPrice } where spotPrice is in USD per base token.
528
+ */
529
+ static getSpotPriceWithReserves(
530
+ poolState: PoolState,
531
+ vault0State: VaultState,
532
+ vault1State: VaultState,
533
+ side: Side,
534
+ quotePrice: OraclePrice, // wsol or usdc oracle price
535
+ ): { netBase: Decimal; netQuote: Decimal; spotPrice: Decimal } {
536
+ // Total fees per token (protocol + fund + creator)
537
+ let fees0 = new Decimal(poolState.protocolFeesToken0.toString())
538
+ .add(new Decimal(poolState.fundFeesToken0.toString()))
539
+ .add(new Decimal(poolState.creatorFeesToken0.toString()));
540
+ let fees1 = new Decimal(poolState.protocolFeesToken1.toString())
541
+ .add(new Decimal(poolState.fundFeesToken1.toString()))
542
+ .add(new Decimal(poolState.creatorFeesToken1.toString()));
543
+
544
+ // Swap fees if side is Quote
545
+ if (side === Side.Quote) {
546
+ [fees0, fees1] = [fees1, fees0];
547
+ }
548
+
549
+ // Get vault balances
550
+ const baseBalance = new Decimal(vault0State.amount);
551
+ const quoteBalance = new Decimal(vault1State.amount);
552
+
553
+ // Net reserves after subtracting fees
554
+ const netBase = baseBalance.sub(fees0);
555
+ const netQuote = quoteBalance.sub(fees1);
556
+
557
+ // Guard against zero or negative reserves
558
+ if (netBase.lte(0) || netQuote.lte(0)) {
559
+ return { netBase: new Decimal(0), netQuote: new Decimal(0), spotPrice: new Decimal(0) };
560
+ }
561
+ // Relative price = netQuote / netBase (quote tokens per 1 base token)
562
+ // Example: 10,000 USDC / 100 SOL = 100 USDC per SOL
563
+ const relativePrice = netQuote.div(netBase);
564
+
565
+ // Convert to USD: spotPrice = relativePrice * quotePrice.price
566
+ // If quote is USDC and quotePrice.price ≈ 1, spotPrice ≈ relativePrice
567
+ // If quote is WSOL and quotePrice.price = $150, spotPrice = relativePrice * 150
568
+ const spotPrice = relativePrice.mul(quotePrice.price);
569
+
570
+ return { netBase, netQuote, spotPrice };
571
+ }
572
+
573
+ static getTwapPrimary(
574
+ currentTime: BN, observations: Observation[], observationIndex: number,
575
+ side: Side, primarySeconds: BN, secondarySeconds: BN,
576
+ mint0Decimals: number, mint1Decimals: number
577
+ ): Decimal {
578
+
579
+ let observation = this.getDeltaObservations(currentTime, observations, observationIndex, primarySeconds, secondarySeconds)[0];
580
+ // TODO: use constant for 2**32 number is not safe
581
+ let twap_scaled = new Decimal(observation.getTwap(side).toString()).div(2**32);
582
+ let twap = new Decimal(twap_scaled.toString()).div(10**this.getDecimals(side, mint0Decimals, mint1Decimals));
583
+ return twap;
584
+ }
585
+
586
+ static getTwapSecondary(
587
+ currentTime: BN, observations: Observation[], observationIndex: number,
588
+ side: Side, primarySeconds: BN, secondarySeconds: BN,
589
+ mint0Decimals: number, mint1Decimals: number
590
+ ): Decimal {
591
+ let observation = this.getDeltaObservations(currentTime, observations, observationIndex, primarySeconds, secondarySeconds)[1];
592
+ // TODO: use constant for 2**32 number is not safe
593
+ let twap_scaled = new Decimal(observation.getTwap(side).toString()).div(2**32);
594
+ let twap = new Decimal(twap_scaled.toString()).div(10**this.getDecimals(side, mint0Decimals, mint1Decimals));
595
+ return twap;
596
+ }
597
+
598
+ /**
599
+ * Calculate max price impact for minLiquidity trade in both directions (buy and sell).
600
+ * Uses constant-product AMM formulas:
601
+ * - BUY: delta_base_out = netBase * delta_q / (netQuote + delta_q)
602
+ * - SELL: delta_quote_out = netQuote * delta_base / (netBase + delta_base)
603
+ */
604
+ static calculateMaxPriceImpactForMinLiquidity(
605
+ oracleParams: OracleSettings,
606
+ netBase: Decimal,
607
+ netQuote: Decimal,
608
+ spotPriceUsd: Decimal,
609
+ quotePrice: OraclePrice,
610
+ ): Decimal {
611
+ const minLiquidityUsd = new Decimal(oracleParams.minLiquidity.toString());
612
+ if (spotPriceUsd.lte(0)) {
613
+ return new Decimal(10000); // 100% impact - invalid
614
+ }
615
+ // Convert minLiquidity USD to quote tokens
616
+ // delta_q_tokens = minLiquidityUsd / quotePrice.price
617
+ const deltaQTokens = minLiquidityUsd.div(quotePrice.price);
618
+
619
+ // Require pool has at least that many quote tokens
620
+ if (netQuote.lt(deltaQTokens)) {
621
+ // Not enough liquidity - return max impact (will fail validation)
622
+ return new Decimal(10000); // 100% impact
623
+ }
624
+
625
+ // === BUY simulation (user spends delta_q quote to receive base) ===
626
+ // delta_base_buy = netBase * delta_q / (netQuote + delta_q)
627
+ const denomBuy = netQuote.add(deltaQTokens);
628
+ const deltaBaseBuy = netBase.mul(deltaQTokens).div(denomBuy);
629
+
630
+ let impactBuy = new Decimal(0);
631
+ if (deltaBaseBuy.gt(0)) {
632
+ // quote spent in USD = delta_q * quotePrice
633
+ const quoteUsd = deltaQTokens.mul(quotePrice.price);
634
+ // execution price in USD per base = quoteUsd / deltaBaseBuy
635
+ const execPriceBuy = quoteUsd.div(deltaBaseBuy);
636
+ // impact = |execPrice - spotPrice| / spotPrice
637
+ const diffBuy = execPriceBuy.sub(spotPriceUsd).abs();
638
+ impactBuy = diffBuy.div(spotPriceUsd);
639
+ }
640
+
641
+ // === SELL simulation (user sells base roughly equivalent to minLiquidity USD) ===
642
+ // Approximate base amount to sell: delta_base_sell = delta_q * netBase / netQuote
643
+ let deltaBaseSell = new Decimal(0);
644
+ if (netQuote.gt(0)) {
645
+ deltaBaseSell = deltaQTokens.mul(netBase).div(netQuote);
646
+ }
647
+
648
+ let impactSell = new Decimal(0);
649
+ if (deltaBaseSell.gt(0)) {
650
+ // delta_quote_out = netQuote * delta_base_sell / (netBase + delta_base_sell)
651
+ const denomSell = netBase.add(deltaBaseSell);
652
+ const deltaQuoteOut = netQuote.mul(deltaBaseSell).div(denomSell);
653
+ // quote out in USD
654
+ const quoteOutUsd = deltaQuoteOut.mul(quotePrice.price);
655
+ // execution price in USD per base = quoteOutUsd / deltaBaseSell
656
+ const execPriceSell = quoteOutUsd.div(deltaBaseSell);
657
+ // impact = |execPrice - spotPrice| / spotPrice
658
+ const diffSell = execPriceSell.sub(spotPriceUsd).abs();
659
+ impactSell = diffSell.div(spotPriceUsd);
660
+ }
661
+
662
+ // Take max impact of both sides and convert to bps
663
+ const maxImpact = Decimal.max(impactBuy, impactSell);
664
+ return maxImpact.mul(new Decimal(10000)); // Convert to bps
665
+ }
666
+
667
+ static fetch(
668
+ oracleParams: OracleSettings,
669
+ accountInfos: AccountInfo<Buffer>[],
670
+ solPrice: OraclePrice,
671
+ usdPrice: OraclePrice,
672
+ ): OraclePrice {
673
+ const poolStateInfo = accountInfos[0];
674
+ const vault0Info = accountInfos[1];
675
+ const vault1Info = accountInfos[2];
676
+ const observationStateInfo = accountInfos[3];
677
+
678
+ const [decodedPoolState, _] = PoolState.decode(poolStateInfo.data, 8);
679
+ const [decodedVault0State, __] = VaultState.decode(vault0Info.data, 0);
680
+ const [decodedVault1State, ___] = VaultState.decode(vault1Info.data, 0);
681
+ const [decodedObservationState, ____] = ObservationState.decode(observationStateInfo.data, 8);
682
+
683
+ const currentTime = new BN(Math.floor(Date.now() / 1000));
684
+
685
+ // Determine quote oracle
686
+ const quotePrice = oracleParams.quote === Quote.Usdc ? usdPrice : solPrice;
687
+
688
+ // Get spot price with reserves
689
+ const { netBase, netQuote, spotPrice } = this.getSpotPriceWithReserves(
690
+ decodedPoolState,
691
+ decodedVault0State,
692
+ decodedVault1State,
693
+ oracleParams.side,
694
+ quotePrice,
695
+ );
696
+
697
+ // Get TWAP prices
698
+ let primaryPrice = RaydiumCPMMOracle.getTwapPrimary(
699
+ currentTime, decodedObservationState.observations, decodedObservationState.observationIndex,
700
+ oracleParams.side, oracleParams.twapSecondsAgo, oracleParams.twapSecondarySecondsAgo,
701
+ decodedPoolState.mint0Decimals, decodedPoolState.mint1Decimals
702
+ );
703
+
704
+ let secondaryPrice = RaydiumCPMMOracle.getTwapSecondary(
705
+ currentTime, decodedObservationState.observations, decodedObservationState.observationIndex,
706
+ oracleParams.side, oracleParams.twapSecondsAgo, oracleParams.twapSecondarySecondsAgo,
707
+ decodedPoolState.mint0Decimals, decodedPoolState.mint1Decimals
708
+ );
709
+
710
+ // Convert TWAP prices to USD
711
+ const primaryPriceUsd = primaryPrice.mul(quotePrice.price);
712
+ const secondaryPriceUsd = secondaryPrice.mul(quotePrice.price);
713
+
714
+ // Validate primary TWAP is not zero
715
+ if (primaryPriceUsd.eq(0)) {
716
+ return new OraclePrice(new Decimal(0), new Decimal(0), 0);
717
+ }
718
+
719
+ // === Min liquidity and price impact validation ===
720
+ if (oracleParams.minLiquidity.gt(new BN(0))) {
721
+ const impactBps = this.calculateMaxPriceImpactForMinLiquidity(
722
+ oracleParams,
723
+ netBase,
724
+ netQuote,
725
+ spotPrice,
726
+ quotePrice,
727
+ );
728
+
729
+ if (oracleParams.maxSlippageBps > 0) {
730
+ if (impactBps.gt(new Decimal(oracleParams.maxSlippageBps))) {
731
+ return new OraclePrice(new Decimal(0), new Decimal(0), 0);
732
+ }
733
+ }
734
+ }
735
+
736
+ // === Confidence calculation ===
737
+ const maxPrice = Decimal.max(primaryPriceUsd, secondaryPriceUsd, spotPrice);
738
+ const minPrice = Decimal.min(primaryPriceUsd, secondaryPriceUsd, spotPrice);
739
+
740
+ let confidence = maxPrice.gt(minPrice) ? maxPrice.sub(minPrice) : new Decimal(0);
741
+
742
+ // Get last update timestamp from latest observation
743
+ const lastUpdateTimestamp = decodedObservationState.observations[decodedObservationState.observationIndex].timestamp;
744
+
745
+ // === Inflate confidence by staleness ===
746
+ // confidence = confidence * (1 + delta_t * stalenessConfRateBps / 10_000)
747
+ const deltaSecondsBN = currentTime.sub(lastUpdateTimestamp);
748
+ const deltaSeconds = new Decimal(deltaSecondsBN.toString());
749
+ if (oracleParams.stalenessConfRateBps > 0 && deltaSeconds.gt(0)) {
750
+ const stalenessRate = new Decimal(oracleParams.stalenessConfRateBps).div(new Decimal(10000));
751
+ const inflateFactor = new Decimal(1).add(deltaSeconds.mul(stalenessRate));
752
+ confidence = confidence.mul(inflateFactor);
753
+ }
754
+
755
+ // === Validate confidence threshold ===
756
+ // confidence / primaryPrice * 10_000 < confThreshBps
757
+ const confRatioBps = confidence.div(primaryPriceUsd).mul(new Decimal(10000));
758
+ if (confRatioBps.gt(new Decimal(oracleParams.confThreshBps))) {
759
+ return new OraclePrice(new Decimal(0), new Decimal(0), 0);
760
+ }
761
+
762
+ // === Validate staleness threshold ===
763
+ if (oracleParams.stalenessThresh.gt(new BN(0))) {
764
+ if (currentTime.sub(lastUpdateTimestamp).gt(oracleParams.stalenessThresh)) {
765
+ return new OraclePrice(new Decimal(0), new Decimal(0), 0);
766
+ }
767
+ }
768
+
769
+ // === Validate volatility threshold ===
770
+ // (maxPrice - minPrice) / minPrice * 10_000 <= volatilityThreshBps
771
+ if (!minPrice.eq(0)) {
772
+ const volRatioBps = maxPrice.sub(minPrice).div(minPrice).mul(new Decimal(10000));
773
+ if (volRatioBps.gt(new Decimal(oracleParams.volatilityThreshBps))) {
774
+ return new OraclePrice(new Decimal(0), new Decimal(0), 0);
775
+ }
776
+ } else {
777
+ // minPrice == 0 is invalid
778
+ return new OraclePrice(new Decimal(0), new Decimal(0), 0);
779
+ }
780
+
781
+ return new OraclePrice(primaryPriceUsd, confidence, currentTime.toNumber());
782
+ }
783
+
784
+ }