@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.
- package/dist/src/constants.d.ts +23 -0
- package/dist/src/constants.js +38 -0
- package/dist/src/index.d.ts +804 -0
- package/dist/src/index.js +2097 -0
- package/dist/src/instructions/automation/auction.d.ts +6 -0
- package/dist/src/instructions/automation/auction.js +40 -0
- package/dist/src/instructions/automation/claimBounty.d.ts +12 -0
- package/dist/src/instructions/automation/claimBounty.js +44 -0
- package/dist/src/instructions/automation/flashSwap.d.ts +21 -0
- package/dist/src/instructions/automation/flashSwap.js +74 -0
- package/dist/src/instructions/automation/priceUpdate.d.ts +19 -0
- package/dist/src/instructions/automation/priceUpdate.js +89 -0
- package/dist/src/instructions/automation/rebalanceIntent.d.ts +32 -0
- package/dist/src/instructions/automation/rebalanceIntent.js +117 -0
- package/dist/src/instructions/automation/rebalanceSwap.d.ts +11 -0
- package/dist/src/instructions/automation/rebalanceSwap.js +42 -0
- package/dist/src/instructions/management/addBounty.d.ts +7 -0
- package/dist/src/instructions/management/addBounty.js +41 -0
- package/dist/src/instructions/management/admin.d.ts +9 -0
- package/dist/src/instructions/management/admin.js +53 -0
- package/dist/src/instructions/management/claimFees.d.ts +15 -0
- package/dist/src/instructions/management/claimFees.js +95 -0
- package/dist/src/instructions/management/createBasket.d.ts +21 -0
- package/dist/src/instructions/management/createBasket.js +98 -0
- package/dist/src/instructions/management/edit.d.ts +51 -0
- package/dist/src/instructions/management/edit.js +477 -0
- package/dist/src/instructions/management/luts.d.ts +30 -0
- package/dist/src/instructions/management/luts.js +99 -0
- package/dist/src/instructions/pda.d.ts +25 -0
- package/dist/src/instructions/pda.js +128 -0
- package/dist/src/instructions/user/deposit.d.ts +20 -0
- package/dist/src/instructions/user/deposit.js +100 -0
- package/dist/src/instructions/user/withdraw.d.ts +8 -0
- package/dist/src/instructions/user/withdraw.js +36 -0
- package/dist/src/jup.d.ts +49 -0
- package/dist/src/jup.js +80 -0
- package/dist/src/keeperMonitor.d.ts +52 -0
- package/dist/src/keeperMonitor.js +624 -0
- package/dist/src/layouts/basket.d.ts +191 -0
- package/dist/src/layouts/basket.js +51 -0
- package/dist/src/layouts/config.d.ts +281 -0
- package/dist/src/layouts/config.js +237 -0
- package/dist/src/layouts/fraction.d.ts +20 -0
- package/dist/src/layouts/fraction.js +164 -0
- package/dist/src/layouts/intents/bounty.d.ts +18 -0
- package/dist/src/layouts/intents/bounty.js +19 -0
- package/dist/src/layouts/intents/intent.d.ts +209 -0
- package/dist/src/layouts/intents/intent.js +97 -0
- package/dist/src/layouts/intents/rebalanceIntent.d.ts +212 -0
- package/dist/src/layouts/intents/rebalanceIntent.js +94 -0
- package/dist/src/layouts/lookupTable.d.ts +7 -0
- package/dist/src/layouts/lookupTable.js +10 -0
- package/dist/src/layouts/oracle.d.ts +63 -0
- package/dist/src/layouts/oracle.js +96 -0
- package/dist/src/states/basket.d.ts +14 -0
- package/dist/src/states/basket.js +479 -0
- package/dist/src/states/config.d.ts +3 -0
- package/dist/src/states/config.js +71 -0
- package/dist/src/states/intents/intent.d.ts +10 -0
- package/dist/src/states/intents/intent.js +316 -0
- package/dist/src/states/intents/rebalanceIntent.d.ts +42 -0
- package/dist/src/states/intents/rebalanceIntent.js +680 -0
- package/dist/src/states/oracles/constants.d.ts +9 -0
- package/dist/src/states/oracles/constants.js +15 -0
- package/dist/src/states/oracles/oracle.d.ts +24 -0
- package/dist/src/states/oracles/oracle.js +168 -0
- package/dist/src/states/oracles/pythOracle.d.ts +132 -0
- package/dist/src/states/oracles/pythOracle.js +609 -0
- package/dist/src/states/oracles/raydiumClmmOracle.d.ts +184 -0
- package/dist/src/states/oracles/raydiumClmmOracle.js +843 -0
- package/dist/src/states/oracles/raydiumCpmmOracle.d.ts +120 -0
- package/dist/src/states/oracles/raydiumCpmmOracle.js +540 -0
- package/dist/src/states/oracles/switchboardOracle.d.ts +0 -0
- package/dist/src/states/oracles/switchboardOracle.js +1 -0
- package/dist/src/states/withdrawBasketFees.d.ts +10 -0
- package/dist/src/states/withdrawBasketFees.js +154 -0
- package/dist/src/txUtils.d.ts +65 -0
- package/dist/src/txUtils.js +306 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +561 -0
- package/package.json +31 -0
- package/src/constants.ts +40 -0
- package/src/index.ts +2431 -0
- package/src/instructions/automation/auction.ts +55 -0
- package/src/instructions/automation/claimBounty.ts +69 -0
- package/src/instructions/automation/flashSwap.ts +104 -0
- package/src/instructions/automation/priceUpdate.ts +117 -0
- package/src/instructions/automation/rebalanceIntent.ts +181 -0
- package/src/instructions/management/addBounty.ts +55 -0
- package/src/instructions/management/admin.ts +72 -0
- package/src/instructions/management/claimFees.ts +129 -0
- package/src/instructions/management/createBasket.ts +138 -0
- package/src/instructions/management/edit.ts +602 -0
- package/src/instructions/management/luts.ts +157 -0
- package/src/instructions/pda.ts +151 -0
- package/src/instructions/user/deposit.ts +143 -0
- package/src/instructions/user/withdraw.ts +53 -0
- package/src/jup.ts +113 -0
- package/src/keeperMonitor.ts +585 -0
- package/src/layouts/basket.ts +233 -0
- package/src/layouts/config.ts +576 -0
- package/src/layouts/fraction.ts +164 -0
- package/src/layouts/intents/bounty.ts +35 -0
- package/src/layouts/intents/intent.ts +324 -0
- package/src/layouts/intents/rebalanceIntent.ts +306 -0
- package/src/layouts/lookupTable.ts +14 -0
- package/src/layouts/oracle.ts +157 -0
- package/src/states/basket.ts +527 -0
- package/src/states/config.ts +62 -0
- package/src/states/intents/intent.ts +311 -0
- package/src/states/intents/rebalanceIntent.ts +751 -0
- package/src/states/oracles/constants.ts +13 -0
- package/src/states/oracles/oracle.ts +212 -0
- package/src/states/oracles/pythOracle.ts +874 -0
- package/src/states/oracles/raydiumClmmOracle.ts +1193 -0
- package/src/states/oracles/raydiumCpmmOracle.ts +784 -0
- package/src/states/oracles/switchboardOracle.ts +0 -0
- package/src/states/withdrawBasketFees.ts +160 -0
- package/src/txUtils.ts +424 -0
- package/test.ts +609 -0
- package/tsconfig.json +101 -0
|
@@ -0,0 +1,1193 @@
|
|
|
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 { U128_MAX } from './constants';
|
|
9
|
+
import { OraclePrice } from './oracle';
|
|
10
|
+
|
|
11
|
+
const CLMM_PROGRAM_ID = new PublicKey("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK");
|
|
12
|
+
const TICK_ARRAY_SEED = Buffer.from("tick_array");
|
|
13
|
+
const MIN_TICK = -443636;
|
|
14
|
+
const MAX_TICK = 443636;
|
|
15
|
+
|
|
16
|
+
export function i32ToBytes(num: number): Uint8Array {
|
|
17
|
+
const arr = new ArrayBuffer(4);
|
|
18
|
+
const view = new DataView(arr);
|
|
19
|
+
view.setInt32(0, num, false);
|
|
20
|
+
return new Uint8Array(arr);
|
|
21
|
+
}
|
|
22
|
+
export function getTickArrayStartIndexByTick(tickIndex: number, tickSpacing: number): number {
|
|
23
|
+
const perArray = TICK_ARRAY_SIZE_USIZE * tickSpacing;
|
|
24
|
+
return Math.floor(tickIndex / perArray) * perArray;
|
|
25
|
+
}
|
|
26
|
+
export function getNextTickArrayStartIndex(lastStart: number, tickSpacing: number, zeroForOne: boolean): number {
|
|
27
|
+
return zeroForOne ? lastStart - tickSpacing * TICK_ARRAY_SIZE_USIZE : lastStart + tickSpacing * TICK_ARRAY_SIZE_USIZE;
|
|
28
|
+
}
|
|
29
|
+
export function getPdaTickArrayAddress(poolId: PublicKey, startIndex: number): PublicKey {
|
|
30
|
+
return PublicKey.findProgramAddressSync(
|
|
31
|
+
[TICK_ARRAY_SEED, poolId.toBuffer(), i32ToBytes(startIndex)],
|
|
32
|
+
CLMM_PROGRAM_ID
|
|
33
|
+
)[0];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Observation {
|
|
38
|
+
blockTimestamp: BN;
|
|
39
|
+
tickCumulative: BN;
|
|
40
|
+
padding: BN[];
|
|
41
|
+
constructor(params: {blockTimestamp: BN, tickCumulative: BN, padding: BN[]}){
|
|
42
|
+
this.blockTimestamp = params.blockTimestamp;
|
|
43
|
+
this.tickCumulative = params.tickCumulative;
|
|
44
|
+
this.padding = params.padding;
|
|
45
|
+
}
|
|
46
|
+
static decode(data: Buffer, offset = 0): [Observation, number] {
|
|
47
|
+
|
|
48
|
+
const blockTimestamp = new BN(data.readUInt32LE(offset));
|
|
49
|
+
|
|
50
|
+
const tickCumulative = new BN(
|
|
51
|
+
data.subarray(offset + 4, offset + 12),
|
|
52
|
+
"le"
|
|
53
|
+
).fromTwos(64);
|
|
54
|
+
|
|
55
|
+
const padding: BN[] = [];
|
|
56
|
+
let cursor = offset + 12;
|
|
57
|
+
for (let i = 0; i < 4; i++) {
|
|
58
|
+
padding.push(new BN(data.subarray(cursor, cursor + 8), "le"));
|
|
59
|
+
cursor += 8;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return [
|
|
63
|
+
new Observation({
|
|
64
|
+
blockTimestamp,
|
|
65
|
+
tickCumulative,
|
|
66
|
+
padding }),
|
|
67
|
+
cursor
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
sub(other: Observation): Observation {
|
|
74
|
+
if (!(other instanceof Observation)) {
|
|
75
|
+
throw new TypeError("Subtraction is only supported between Observation instances");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return new Observation({
|
|
79
|
+
blockTimestamp: this.blockTimestamp.sub(other.blockTimestamp),
|
|
80
|
+
tickCumulative: this.tickCumulative.sub(other.tickCumulative),
|
|
81
|
+
padding: this.padding
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
add(other: Observation): Observation {
|
|
86
|
+
if (!(other instanceof Observation)) {
|
|
87
|
+
throw new TypeError("Addition is only supported between Observation instances");
|
|
88
|
+
}
|
|
89
|
+
return new Observation({
|
|
90
|
+
blockTimestamp: this.blockTimestamp.add(other.blockTimestamp),
|
|
91
|
+
tickCumulative: this.tickCumulative.add(other.tickCumulative),
|
|
92
|
+
padding: this.padding
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getWeightedObservation(time: BN): Observation {
|
|
97
|
+
// cumT = (self.tickCumulative * time) / self.blockTimestamp
|
|
98
|
+
const cum = this.tickCumulative.mul(time).div(this.blockTimestamp);
|
|
99
|
+
return new Observation({
|
|
100
|
+
blockTimestamp: time,
|
|
101
|
+
tickCumulative: cum,
|
|
102
|
+
padding: this.padding
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
adjustToTimestamp(targetTimestamp: BN, observationPrev: Observation): Observation {
|
|
107
|
+
// delta = self.sub(prevObservation)
|
|
108
|
+
const delta = this.sub(observationPrev);
|
|
109
|
+
|
|
110
|
+
// weightedDelta = delta.getWeightedObservation(targetTimestamp - self.blockTimestamp)
|
|
111
|
+
const timeForWeighted = targetTimestamp.sub(this.blockTimestamp);
|
|
112
|
+
const weightedDelta = delta.getWeightedObservation(timeForWeighted);
|
|
113
|
+
|
|
114
|
+
// return self.add(weightedDelta)
|
|
115
|
+
return this.add(weightedDelta);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class ObservationState {
|
|
121
|
+
initialized: boolean;
|
|
122
|
+
recentEpoch: BN;
|
|
123
|
+
observationIndex: number;
|
|
124
|
+
poolId: PublicKey;
|
|
125
|
+
observations: Observation[];
|
|
126
|
+
padding: BN[];
|
|
127
|
+
|
|
128
|
+
constructor(params: {
|
|
129
|
+
initialized: boolean,
|
|
130
|
+
recentEpoch: BN,
|
|
131
|
+
observationIndex: number,
|
|
132
|
+
poolId: PublicKey,
|
|
133
|
+
observations: Observation[],
|
|
134
|
+
padding: BN[]
|
|
135
|
+
}){
|
|
136
|
+
this.initialized = params.initialized;
|
|
137
|
+
this.recentEpoch = params.recentEpoch;
|
|
138
|
+
this.observationIndex = params.observationIndex;
|
|
139
|
+
this.poolId = params.poolId;
|
|
140
|
+
this.observations = params.observations;
|
|
141
|
+
this.padding = params.padding;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static decode(data: Buffer, offset = 8): [ObservationState, number] {
|
|
145
|
+
let cursor = offset;
|
|
146
|
+
|
|
147
|
+
const initialized = data.readUInt8(cursor) !== 0;
|
|
148
|
+
cursor += 1;
|
|
149
|
+
|
|
150
|
+
const recentEpoch = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
151
|
+
cursor += 8;
|
|
152
|
+
|
|
153
|
+
const observationIndex = data.readUInt16LE(cursor);
|
|
154
|
+
cursor += 2;
|
|
155
|
+
|
|
156
|
+
const poolId = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
157
|
+
cursor += 32;
|
|
158
|
+
|
|
159
|
+
const observations: Observation[] = [];
|
|
160
|
+
for (let i = 0; i < 100; i++) {
|
|
161
|
+
let obs: Observation;
|
|
162
|
+
[obs, cursor] = Observation.decode(data, cursor);
|
|
163
|
+
observations.push(obs);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const padding: BN[] = [];
|
|
167
|
+
for (let i = 0; i < 4; i++) {
|
|
168
|
+
padding.push(new BN(data.subarray(cursor, cursor + 8), "le"));
|
|
169
|
+
cursor += 8;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return [
|
|
173
|
+
new ObservationState({ initialized, recentEpoch, observationIndex, poolId, observations, padding }),
|
|
174
|
+
cursor
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class RewardInfo {
|
|
181
|
+
rewardState: number;
|
|
182
|
+
openTime: BN;
|
|
183
|
+
endTime: BN;
|
|
184
|
+
lastUpdateTime: BN;
|
|
185
|
+
emissionsPerSecondX64: BN;
|
|
186
|
+
rewardTotalEmissioned: BN;
|
|
187
|
+
rewardClaimed: BN;
|
|
188
|
+
tokenMint: PublicKey;
|
|
189
|
+
tokenVault: PublicKey;
|
|
190
|
+
authority: PublicKey;
|
|
191
|
+
rewardGrowthGlobalX64: BN;
|
|
192
|
+
|
|
193
|
+
constructor(params: {
|
|
194
|
+
rewardState: number,
|
|
195
|
+
openTime: BN,
|
|
196
|
+
endTime: BN,
|
|
197
|
+
lastUpdateTime: BN,
|
|
198
|
+
emissionsPerSecondX64: BN,
|
|
199
|
+
rewardTotalEmissioned: BN,
|
|
200
|
+
rewardClaimed: BN,
|
|
201
|
+
tokenMint: PublicKey,
|
|
202
|
+
tokenVault: PublicKey,
|
|
203
|
+
authority: PublicKey,
|
|
204
|
+
rewardGrowthGlobalX64: BN
|
|
205
|
+
}){
|
|
206
|
+
this.rewardState = params.rewardState;
|
|
207
|
+
this.openTime = params.openTime;
|
|
208
|
+
this.endTime = params.endTime;
|
|
209
|
+
this.lastUpdateTime = params.lastUpdateTime;
|
|
210
|
+
this.emissionsPerSecondX64 = params.emissionsPerSecondX64;
|
|
211
|
+
this.rewardTotalEmissioned = params.rewardTotalEmissioned;
|
|
212
|
+
this.rewardClaimed = params.rewardClaimed;
|
|
213
|
+
this.tokenMint = params.tokenMint;
|
|
214
|
+
this.tokenVault = params.tokenVault;
|
|
215
|
+
this.authority = params.authority;
|
|
216
|
+
this.rewardGrowthGlobalX64 = params.rewardGrowthGlobalX64;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static decode(data: Buffer, offset: number = 0): [RewardInfo, number] {
|
|
220
|
+
let cursor = offset;
|
|
221
|
+
|
|
222
|
+
const rewardState = data.readUInt8(cursor);
|
|
223
|
+
cursor += 1;
|
|
224
|
+
|
|
225
|
+
const openTime = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
226
|
+
cursor += 8;
|
|
227
|
+
|
|
228
|
+
const endTime = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
229
|
+
cursor += 8;
|
|
230
|
+
|
|
231
|
+
const lastUpdateTime = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
232
|
+
cursor += 8;
|
|
233
|
+
|
|
234
|
+
const emissionsPerSecondX64 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
235
|
+
cursor += 16;
|
|
236
|
+
|
|
237
|
+
const rewardTotalEmissioned = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
238
|
+
cursor += 8;
|
|
239
|
+
|
|
240
|
+
const rewardClaimed = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
241
|
+
cursor += 8;
|
|
242
|
+
|
|
243
|
+
const tokenMint = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
244
|
+
cursor += 32;
|
|
245
|
+
|
|
246
|
+
const tokenVault = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
247
|
+
cursor += 32;
|
|
248
|
+
|
|
249
|
+
const authority = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
250
|
+
cursor += 32;
|
|
251
|
+
|
|
252
|
+
const rewardGrowthGlobalX64 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
253
|
+
cursor += 16;
|
|
254
|
+
|
|
255
|
+
return [ new RewardInfo({
|
|
256
|
+
rewardState,
|
|
257
|
+
openTime,
|
|
258
|
+
endTime,
|
|
259
|
+
lastUpdateTime,
|
|
260
|
+
emissionsPerSecondX64,
|
|
261
|
+
rewardTotalEmissioned,
|
|
262
|
+
rewardClaimed,
|
|
263
|
+
tokenMint,
|
|
264
|
+
tokenVault,
|
|
265
|
+
authority,
|
|
266
|
+
rewardGrowthGlobalX64
|
|
267
|
+
}), cursor];
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
export class PoolState {
|
|
275
|
+
bump: number[];
|
|
276
|
+
ammConfig: PublicKey;
|
|
277
|
+
owner: PublicKey;
|
|
278
|
+
tokenMint0: PublicKey;
|
|
279
|
+
tokenMint1: PublicKey;
|
|
280
|
+
tokenVault0: PublicKey;
|
|
281
|
+
tokenVault1: PublicKey;
|
|
282
|
+
observationKey: PublicKey;
|
|
283
|
+
mintDecimals0: number;
|
|
284
|
+
mintDecimals1: number;
|
|
285
|
+
tickSpacing: number;
|
|
286
|
+
liquidity: BN;
|
|
287
|
+
sqrtPriceX64: BN;
|
|
288
|
+
tickCurrent: number;
|
|
289
|
+
padding3: number;
|
|
290
|
+
padding4: number;
|
|
291
|
+
feeGrowthGlobal0X64: BN;
|
|
292
|
+
feeGrowthGlobal1X64: BN;
|
|
293
|
+
protocolFeesToken0: BN;
|
|
294
|
+
protocolFeesToken1: BN;
|
|
295
|
+
swapInAmountToken0: BN;
|
|
296
|
+
swapOutAmountToken1: BN;
|
|
297
|
+
swapInAmountToken1: BN;
|
|
298
|
+
swapOutAmountToken0: BN;
|
|
299
|
+
status: number;
|
|
300
|
+
padding: number[];
|
|
301
|
+
rewardInfos: RewardInfo[];
|
|
302
|
+
tickArrayBitmap: BN[];
|
|
303
|
+
totalFeesToken0: BN;
|
|
304
|
+
totalFeesClaimedToken0: BN;
|
|
305
|
+
totalFeesToken1: BN;
|
|
306
|
+
totalFeesClaimedToken1: BN;
|
|
307
|
+
fundFeesToken0: BN;
|
|
308
|
+
fundFeesToken1: BN;
|
|
309
|
+
openTime: BN;
|
|
310
|
+
recentEpoch: BN;
|
|
311
|
+
padding1: BN[];
|
|
312
|
+
padding2: BN[];
|
|
313
|
+
|
|
314
|
+
constructor(
|
|
315
|
+
params: {
|
|
316
|
+
bump: number[],
|
|
317
|
+
ammConfig: PublicKey,
|
|
318
|
+
owner: PublicKey,
|
|
319
|
+
tokenMint0: PublicKey,
|
|
320
|
+
tokenMint1: PublicKey,
|
|
321
|
+
tokenVault0: PublicKey,
|
|
322
|
+
tokenVault1: PublicKey,
|
|
323
|
+
observationKey: PublicKey,
|
|
324
|
+
mintDecimals0: number,
|
|
325
|
+
mintDecimals1: number,
|
|
326
|
+
tickSpacing: number,
|
|
327
|
+
liquidity: BN,
|
|
328
|
+
sqrtPriceX64: BN,
|
|
329
|
+
tickCurrent: number,
|
|
330
|
+
padding3: number,
|
|
331
|
+
padding4: number,
|
|
332
|
+
feeGrowthGlobal0X64: BN,
|
|
333
|
+
feeGrowthGlobal1X64: BN,
|
|
334
|
+
protocolFeesToken0: BN,
|
|
335
|
+
protocolFeesToken1: BN,
|
|
336
|
+
swapInAmountToken0: BN,
|
|
337
|
+
swapOutAmountToken1: BN,
|
|
338
|
+
swapInAmountToken1: BN,
|
|
339
|
+
swapOutAmountToken0: BN,
|
|
340
|
+
status: number,
|
|
341
|
+
padding: number[],
|
|
342
|
+
rewardInfos: RewardInfo[],
|
|
343
|
+
tickArrayBitmap: BN[],
|
|
344
|
+
totalFeesToken0: BN,
|
|
345
|
+
totalFeesClaimedToken0: BN,
|
|
346
|
+
totalFeesToken1: BN,
|
|
347
|
+
totalFeesClaimedToken1: BN,
|
|
348
|
+
fundFeesToken0: BN,
|
|
349
|
+
fundFeesToken1: BN,
|
|
350
|
+
openTime: BN,
|
|
351
|
+
recentEpoch: BN,
|
|
352
|
+
padding1: BN[],
|
|
353
|
+
padding2: BN[]
|
|
354
|
+
}
|
|
355
|
+
){
|
|
356
|
+
this.bump = params.bump;
|
|
357
|
+
this.ammConfig = params.ammConfig;
|
|
358
|
+
this.owner = params.owner;
|
|
359
|
+
this.tokenMint0 = params.tokenMint0;
|
|
360
|
+
this.tokenMint1 = params.tokenMint1;
|
|
361
|
+
this.tokenVault0 = params.tokenVault0;
|
|
362
|
+
this.tokenVault1 = params.tokenVault1;
|
|
363
|
+
this.observationKey = params.observationKey;
|
|
364
|
+
this.mintDecimals0 = params.mintDecimals0;
|
|
365
|
+
this.mintDecimals1 = params.mintDecimals1;
|
|
366
|
+
this.tickSpacing = params.tickSpacing;
|
|
367
|
+
this.liquidity = params.liquidity;
|
|
368
|
+
this.sqrtPriceX64 = params.sqrtPriceX64;
|
|
369
|
+
this.tickCurrent = params.tickCurrent;
|
|
370
|
+
this.padding3 = params.padding3;
|
|
371
|
+
this.padding4 = params.padding4;
|
|
372
|
+
this.feeGrowthGlobal0X64 = params.feeGrowthGlobal0X64;
|
|
373
|
+
this.feeGrowthGlobal1X64 = params.feeGrowthGlobal1X64;
|
|
374
|
+
this.protocolFeesToken0 = params.protocolFeesToken0;
|
|
375
|
+
this.protocolFeesToken1 = params.protocolFeesToken1;
|
|
376
|
+
this.swapInAmountToken0 = params.swapInAmountToken0;
|
|
377
|
+
this.swapOutAmountToken1 = params.swapOutAmountToken1;
|
|
378
|
+
this.swapInAmountToken1 = params.swapInAmountToken1;
|
|
379
|
+
this.swapOutAmountToken0 = params.swapOutAmountToken0;
|
|
380
|
+
this.status = params.status;
|
|
381
|
+
this.padding = params.padding;
|
|
382
|
+
this.rewardInfos = params.rewardInfos;
|
|
383
|
+
this.tickArrayBitmap = params.tickArrayBitmap;
|
|
384
|
+
this.totalFeesToken0 = params.totalFeesToken0;
|
|
385
|
+
this.totalFeesClaimedToken0 = params.totalFeesClaimedToken0;
|
|
386
|
+
this.totalFeesToken1 = params.totalFeesToken1;
|
|
387
|
+
this.totalFeesClaimedToken1 = params.totalFeesClaimedToken1;
|
|
388
|
+
this.fundFeesToken0 = params.fundFeesToken0;
|
|
389
|
+
this.fundFeesToken1 = params.fundFeesToken1;
|
|
390
|
+
this.openTime = params.openTime;
|
|
391
|
+
this.recentEpoch = params.recentEpoch;
|
|
392
|
+
this.padding1 = params.padding1;
|
|
393
|
+
this.padding2 = params.padding2;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
static decode(data: Buffer, offset: number = 8): [PoolState, number] {
|
|
397
|
+
let cursor = offset;
|
|
398
|
+
|
|
399
|
+
const bump = [data.readUInt8(cursor)];
|
|
400
|
+
cursor += 1;
|
|
401
|
+
|
|
402
|
+
const ammConfig = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
403
|
+
cursor += 32;
|
|
404
|
+
|
|
405
|
+
const owner = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
406
|
+
cursor += 32;
|
|
407
|
+
|
|
408
|
+
const tokenMint0 = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
409
|
+
cursor += 32;
|
|
410
|
+
|
|
411
|
+
const tokenMint1 = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
412
|
+
cursor += 32;
|
|
413
|
+
|
|
414
|
+
const tokenVault0 = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
415
|
+
cursor += 32;
|
|
416
|
+
|
|
417
|
+
const tokenVault1 = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
418
|
+
cursor += 32;
|
|
419
|
+
|
|
420
|
+
const observationKey = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
421
|
+
cursor += 32;
|
|
422
|
+
|
|
423
|
+
const mintDecimals0 = data.readUInt8(cursor);
|
|
424
|
+
cursor += 1;
|
|
425
|
+
|
|
426
|
+
const mintDecimals1 = data.readUInt8(cursor);
|
|
427
|
+
cursor += 1;
|
|
428
|
+
|
|
429
|
+
const tickSpacing = data.readUInt16LE(cursor);
|
|
430
|
+
cursor += 2;
|
|
431
|
+
|
|
432
|
+
const liquidity = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
433
|
+
cursor += 16;
|
|
434
|
+
|
|
435
|
+
const sqrtPriceX64 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
436
|
+
cursor += 16;
|
|
437
|
+
|
|
438
|
+
const tickCurrent = data.readInt32LE(cursor);
|
|
439
|
+
cursor += 4;
|
|
440
|
+
|
|
441
|
+
const padding3 = data.readUInt16LE(cursor);
|
|
442
|
+
cursor += 2;
|
|
443
|
+
|
|
444
|
+
const padding4 = data.readUInt16LE(cursor);
|
|
445
|
+
cursor += 2;
|
|
446
|
+
|
|
447
|
+
const feeGrowthGlobal0X64 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
448
|
+
cursor += 16;
|
|
449
|
+
|
|
450
|
+
const feeGrowthGlobal1X64 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
451
|
+
cursor += 16;
|
|
452
|
+
|
|
453
|
+
const protocolFeesToken0 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
454
|
+
cursor += 8;
|
|
455
|
+
|
|
456
|
+
const protocolFeesToken1 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
457
|
+
cursor += 8;
|
|
458
|
+
|
|
459
|
+
const swapInAmountToken0 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
460
|
+
cursor += 16;
|
|
461
|
+
|
|
462
|
+
const swapOutAmountToken1 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
463
|
+
cursor += 16;
|
|
464
|
+
|
|
465
|
+
const swapInAmountToken1 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
466
|
+
cursor += 16;
|
|
467
|
+
|
|
468
|
+
const swapOutAmountToken0 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
469
|
+
cursor += 16;
|
|
470
|
+
|
|
471
|
+
const status = data.readUInt8(cursor);
|
|
472
|
+
cursor += 1;
|
|
473
|
+
|
|
474
|
+
const padding: number[] = [];
|
|
475
|
+
for (let i = 0; i < 7; i++) {
|
|
476
|
+
padding.push(data.readUInt8(cursor));
|
|
477
|
+
cursor += 1;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// RewardInfos
|
|
481
|
+
const rewardInfos: RewardInfo[] = [];
|
|
482
|
+
for (let i = 0; i < 3; i++) {
|
|
483
|
+
const decoded = RewardInfo.decode(data, cursor);
|
|
484
|
+
rewardInfos.push(decoded[0]);
|
|
485
|
+
cursor = decoded[1];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// tickArrayBitmap [u64;16]
|
|
489
|
+
const tickArrayBitmap: BN[] = [];
|
|
490
|
+
for (let i = 0; i < 16; i++) {
|
|
491
|
+
tickArrayBitmap.push(new BN(data.subarray(cursor, cursor + 8), "le"));
|
|
492
|
+
cursor += 8;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const totalFeesToken0 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
496
|
+
cursor += 8;
|
|
497
|
+
|
|
498
|
+
const totalFeesClaimedToken0 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
499
|
+
cursor += 8;
|
|
500
|
+
|
|
501
|
+
const totalFeesToken1 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
502
|
+
cursor += 8;
|
|
503
|
+
|
|
504
|
+
const totalFeesClaimedToken1 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
505
|
+
cursor += 8;
|
|
506
|
+
|
|
507
|
+
const fundFeesToken0 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
508
|
+
cursor += 8;
|
|
509
|
+
|
|
510
|
+
const fundFeesToken1 = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
511
|
+
cursor += 8;
|
|
512
|
+
|
|
513
|
+
const openTime = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
514
|
+
cursor += 8;
|
|
515
|
+
|
|
516
|
+
const recentEpoch = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
517
|
+
cursor += 8;
|
|
518
|
+
|
|
519
|
+
const padding1: BN[] = [];
|
|
520
|
+
for (let i = 0; i < 24; i++) {
|
|
521
|
+
padding1.push(new BN(data.subarray(cursor, cursor + 8), "le"));
|
|
522
|
+
cursor += 8;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const padding2: BN[] = [];
|
|
526
|
+
for (let i = 0; i < 32; i++) {
|
|
527
|
+
padding2.push(new BN(data.subarray(cursor, cursor + 8), "le"));
|
|
528
|
+
cursor += 8;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return [
|
|
532
|
+
new PoolState({
|
|
533
|
+
bump,
|
|
534
|
+
ammConfig,
|
|
535
|
+
owner,
|
|
536
|
+
tokenMint0,
|
|
537
|
+
tokenMint1,
|
|
538
|
+
tokenVault0,
|
|
539
|
+
tokenVault1,
|
|
540
|
+
observationKey,
|
|
541
|
+
mintDecimals0,
|
|
542
|
+
mintDecimals1,
|
|
543
|
+
tickSpacing,
|
|
544
|
+
liquidity,
|
|
545
|
+
sqrtPriceX64,
|
|
546
|
+
tickCurrent,
|
|
547
|
+
padding3,
|
|
548
|
+
padding4,
|
|
549
|
+
feeGrowthGlobal0X64,
|
|
550
|
+
feeGrowthGlobal1X64,
|
|
551
|
+
protocolFeesToken0,
|
|
552
|
+
protocolFeesToken1,
|
|
553
|
+
swapInAmountToken0,
|
|
554
|
+
swapOutAmountToken1,
|
|
555
|
+
swapInAmountToken1,
|
|
556
|
+
swapOutAmountToken0,
|
|
557
|
+
status,
|
|
558
|
+
padding,
|
|
559
|
+
rewardInfos,
|
|
560
|
+
tickArrayBitmap,
|
|
561
|
+
totalFeesToken0,
|
|
562
|
+
totalFeesClaimedToken0,
|
|
563
|
+
totalFeesToken1,
|
|
564
|
+
totalFeesClaimedToken1,
|
|
565
|
+
fundFeesToken0,
|
|
566
|
+
fundFeesToken1,
|
|
567
|
+
openTime,
|
|
568
|
+
recentEpoch,
|
|
569
|
+
padding1,
|
|
570
|
+
padding2,
|
|
571
|
+
}),
|
|
572
|
+
cursor,
|
|
573
|
+
];
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const TICK_ARRAY_SIZE_USIZE = 60;
|
|
580
|
+
const REWARD_NUM = 3;
|
|
581
|
+
|
|
582
|
+
class TickState {
|
|
583
|
+
tick: number; // i32
|
|
584
|
+
liquidityNet: BN; // i128 (signed)
|
|
585
|
+
liquidityGross: BN; // u128
|
|
586
|
+
feeGrowthOutside0X64: BN; // u128
|
|
587
|
+
feeGrowthOutside1X64: BN; // u128
|
|
588
|
+
rewardGrowthsOutsideX64: BN[]; // [u128; REWARD_NUM]
|
|
589
|
+
padding: number[]; // [u32;13]
|
|
590
|
+
|
|
591
|
+
constructor(params: {
|
|
592
|
+
tick: number,
|
|
593
|
+
liquidityNet: BN,
|
|
594
|
+
liquidityGross: BN,
|
|
595
|
+
feeGrowthOutside0X64: BN,
|
|
596
|
+
feeGrowthOutside1X64: BN,
|
|
597
|
+
rewardGrowthsOutsideX64: BN[],
|
|
598
|
+
padding: number[]
|
|
599
|
+
}){
|
|
600
|
+
this.tick = params.tick;
|
|
601
|
+
this.liquidityNet = params.liquidityNet;
|
|
602
|
+
this.liquidityGross = params.liquidityGross;
|
|
603
|
+
this.feeGrowthOutside0X64 = params.feeGrowthOutside0X64;
|
|
604
|
+
this.feeGrowthOutside1X64 = params.feeGrowthOutside1X64;
|
|
605
|
+
this.rewardGrowthsOutsideX64 = params.rewardGrowthsOutsideX64;
|
|
606
|
+
this.padding = params.padding;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
static decode(data: Buffer, offset: number = 0): [TickState, number] {
|
|
610
|
+
let cursor = offset;
|
|
611
|
+
|
|
612
|
+
const tick = data.readInt32LE(cursor);
|
|
613
|
+
cursor += 4;
|
|
614
|
+
|
|
615
|
+
// liquidity_net: i128 (signed)
|
|
616
|
+
const liquidityNet = new BN(data.subarray(cursor, cursor + 16), "le").fromTwos(128);
|
|
617
|
+
cursor += 16;
|
|
618
|
+
|
|
619
|
+
// liquidity_gross: u128
|
|
620
|
+
const liquidityGross = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
621
|
+
cursor += 16;
|
|
622
|
+
|
|
623
|
+
const feeGrowthOutside0X64 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
624
|
+
cursor += 16;
|
|
625
|
+
|
|
626
|
+
const feeGrowthOutside1X64 = new BN(data.subarray(cursor, cursor + 16), "le");
|
|
627
|
+
cursor += 16;
|
|
628
|
+
|
|
629
|
+
const rewardGrowthsOutsideX64: BN[] = [];
|
|
630
|
+
for (let i = 0; i < REWARD_NUM; i++) {
|
|
631
|
+
rewardGrowthsOutsideX64.push(new BN(data.subarray(cursor, cursor + 16), "le"));
|
|
632
|
+
cursor += 16;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const padding: number[] = [];
|
|
636
|
+
for (let i = 0; i < 13; i++) {
|
|
637
|
+
padding.push(data.readUInt32LE(cursor));
|
|
638
|
+
cursor += 4;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return [
|
|
642
|
+
new TickState({
|
|
643
|
+
tick,
|
|
644
|
+
liquidityNet,
|
|
645
|
+
liquidityGross,
|
|
646
|
+
feeGrowthOutside0X64,
|
|
647
|
+
feeGrowthOutside1X64,
|
|
648
|
+
rewardGrowthsOutsideX64,
|
|
649
|
+
padding
|
|
650
|
+
}),
|
|
651
|
+
cursor
|
|
652
|
+
];
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
class TickArrayState {
|
|
657
|
+
poolId: PublicKey;
|
|
658
|
+
startTickIndex: number; // i32
|
|
659
|
+
ticks: TickState[]; // length TICK_ARRAY_SIZE_USIZE
|
|
660
|
+
initializedTickCount: number; // u8
|
|
661
|
+
recentEpoch: BN; // u64
|
|
662
|
+
padding: number[]; // u8[107]
|
|
663
|
+
|
|
664
|
+
constructor(params: {
|
|
665
|
+
poolId: PublicKey,
|
|
666
|
+
startTickIndex: number,
|
|
667
|
+
ticks: TickState[],
|
|
668
|
+
initializedTickCount: number,
|
|
669
|
+
recentEpoch: BN,
|
|
670
|
+
padding: number[]
|
|
671
|
+
}){
|
|
672
|
+
this.poolId = params.poolId;
|
|
673
|
+
this.startTickIndex = params.startTickIndex;
|
|
674
|
+
this.ticks = params.ticks;
|
|
675
|
+
this.initializedTickCount = params.initializedTickCount;
|
|
676
|
+
this.recentEpoch = params.recentEpoch;
|
|
677
|
+
this.padding = params.padding;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
static decode(data: Buffer, offset: number = 8): [TickArrayState, number] {
|
|
681
|
+
let cursor = offset;
|
|
682
|
+
|
|
683
|
+
const poolId = new PublicKey(data.subarray(cursor, cursor + 32));
|
|
684
|
+
cursor += 32;
|
|
685
|
+
|
|
686
|
+
const startTickIndex = data.readInt32LE(cursor);
|
|
687
|
+
cursor += 4;
|
|
688
|
+
|
|
689
|
+
const ticks: TickState[] = [];
|
|
690
|
+
for (let i = 0; i < TICK_ARRAY_SIZE_USIZE; i++) {
|
|
691
|
+
const [tickState, next] = TickState.decode(data, cursor);
|
|
692
|
+
ticks.push(tickState);
|
|
693
|
+
cursor = next;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const initializedTickCount = data.readUInt8(cursor);
|
|
697
|
+
cursor += 1;
|
|
698
|
+
|
|
699
|
+
const recentEpoch = new BN(data.subarray(cursor, cursor + 8), "le");
|
|
700
|
+
cursor += 8;
|
|
701
|
+
|
|
702
|
+
const padding: number[] = [];
|
|
703
|
+
for (let i = 0; i < 107; i++) {
|
|
704
|
+
padding.push(data.readUInt8(cursor));
|
|
705
|
+
cursor += 1;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return [
|
|
709
|
+
new TickArrayState({
|
|
710
|
+
poolId,
|
|
711
|
+
startTickIndex,
|
|
712
|
+
ticks,
|
|
713
|
+
initializedTickCount,
|
|
714
|
+
recentEpoch,
|
|
715
|
+
padding
|
|
716
|
+
}),
|
|
717
|
+
cursor
|
|
718
|
+
];
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
export class RaydiumCLMMOracle {
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
static deriveObservationKey(poolId: PublicKey): [PublicKey, number] {
|
|
728
|
+
const seeds = [
|
|
729
|
+
Buffer.from("observation"),
|
|
730
|
+
poolId.toBuffer(),
|
|
731
|
+
];
|
|
732
|
+
return PublicKey.findProgramAddressSync(seeds, CLMM_PROGRAM_ID);
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
static getObservationAtIndex(observations: Observation[], index: number) {
|
|
736
|
+
return observations[index];
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
static getObservationAtTimestamp(timestamp: BN, observations: Observation[], startIndex: number): Observation {
|
|
740
|
+
let observationCurrent = this.getObservationAtIndex(observations, startIndex);
|
|
741
|
+
let index = startIndex;
|
|
742
|
+
|
|
743
|
+
// const obsIndex = this.observationState.observationIndex;
|
|
744
|
+
|
|
745
|
+
// Loop backwards until we find the observation <= timestamp
|
|
746
|
+
while (observationCurrent.blockTimestamp.gt(timestamp)) {
|
|
747
|
+
index = (index - 1 + 100) % 100;
|
|
748
|
+
observationCurrent = this.getObservationAtIndex(observations, index);
|
|
749
|
+
|
|
750
|
+
if (index === startIndex) {
|
|
751
|
+
throw Error("Observations do not go back far enough in time");
|
|
752
|
+
break; // looped all the way around
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Previous index for interpolation
|
|
757
|
+
const prevIndex = (index - 1 + 100) % 100;
|
|
758
|
+
const observationPrev = this.getObservationAtIndex(observations, prevIndex);
|
|
759
|
+
|
|
760
|
+
// Adjust current observation to the target timestamp
|
|
761
|
+
const result = observationCurrent.adjustToTimestamp(timestamp, observationPrev);
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
static getDeltaObservations(currentTime: BN, observations: Observation[], observationIndex: number, primarySeconds: BN, secondarySeconds: BN): Observation[] {
|
|
766
|
+
|
|
767
|
+
const observationCurrent = this.getObservationAtTimestamp(currentTime, observations, observationIndex);
|
|
768
|
+
|
|
769
|
+
const observationPrimary = this.getObservationAtTimestamp(new BN(currentTime.sub(primarySeconds)), observations, observationIndex);
|
|
770
|
+
const deltaPrimary = observationCurrent.sub(observationPrimary);
|
|
771
|
+
|
|
772
|
+
const observationSecondary = this.getObservationAtTimestamp(new BN(currentTime.sub(secondarySeconds)), observations, observationIndex);
|
|
773
|
+
const deltaSecondary = observationCurrent.sub(observationSecondary);
|
|
774
|
+
|
|
775
|
+
return [deltaPrimary,deltaSecondary];
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
static getSqrtPriceX64FromTick(tick: number): BN {
|
|
780
|
+
|
|
781
|
+
if (!Number.isInteger(tick)) {
|
|
782
|
+
throw new Error("tick must be integer");
|
|
783
|
+
}
|
|
784
|
+
if (tick < MIN_TICK || tick > MAX_TICK) {
|
|
785
|
+
throw new Error("tick must be in MIN_TICK and MAX_TICK");
|
|
786
|
+
}
|
|
787
|
+
const tickAbs: number = tick < 0 ? tick * -1 : tick;
|
|
788
|
+
|
|
789
|
+
let ratio: BN = (tickAbs & 0x1) != 0 ? new BN("18445821805675395072") : new BN("18446744073709551616");
|
|
790
|
+
if ((tickAbs & 0x2) != 0) ratio = mulRightShift(ratio, new BN("18444899583751176192"));
|
|
791
|
+
if ((tickAbs & 0x4) != 0) ratio = mulRightShift(ratio, new BN("18443055278223355904"));
|
|
792
|
+
if ((tickAbs & 0x8) != 0) ratio = mulRightShift(ratio, new BN("18439367220385607680"));
|
|
793
|
+
if ((tickAbs & 0x10) != 0) ratio = mulRightShift(ratio, new BN("18431993317065453568"));
|
|
794
|
+
if ((tickAbs & 0x20) != 0) ratio = mulRightShift(ratio, new BN("18417254355718170624"));
|
|
795
|
+
if ((tickAbs & 0x40) != 0) ratio = mulRightShift(ratio, new BN("18387811781193609216"));
|
|
796
|
+
if ((tickAbs & 0x80) != 0) ratio = mulRightShift(ratio, new BN("18329067761203558400"));
|
|
797
|
+
if ((tickAbs & 0x100) != 0) ratio = mulRightShift(ratio, new BN("18212142134806163456"));
|
|
798
|
+
if ((tickAbs & 0x200) != 0) ratio = mulRightShift(ratio, new BN("17980523815641700352"));
|
|
799
|
+
if ((tickAbs & 0x400) != 0) ratio = mulRightShift(ratio, new BN("17526086738831433728"));
|
|
800
|
+
if ((tickAbs & 0x800) != 0) ratio = mulRightShift(ratio, new BN("16651378430235570176"));
|
|
801
|
+
if ((tickAbs & 0x1000) != 0) ratio = mulRightShift(ratio, new BN("15030750278694412288"));
|
|
802
|
+
if ((tickAbs & 0x2000) != 0) ratio = mulRightShift(ratio, new BN("12247334978884435968"));
|
|
803
|
+
if ((tickAbs & 0x4000) != 0) ratio = mulRightShift(ratio, new BN("8131365268886854656"));
|
|
804
|
+
if ((tickAbs & 0x8000) != 0) ratio = mulRightShift(ratio, new BN("3584323654725218816"));
|
|
805
|
+
if ((tickAbs & 0x10000) != 0) ratio = mulRightShift(ratio, new BN("696457651848324352"));
|
|
806
|
+
if ((tickAbs & 0x20000) != 0) ratio = mulRightShift(ratio, new BN("26294789957507116"));
|
|
807
|
+
if ((tickAbs & 0x40000) != 0) ratio = mulRightShift(ratio, new BN("37481735321082"));
|
|
808
|
+
|
|
809
|
+
if (tick > 0) ratio = U128_MAX.div(ratio);
|
|
810
|
+
return ratio;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
static sqrtPriceX64ToPrice(sqrtPriceX64: BN, decimalsA: number, decimalsB: number): Decimal {
|
|
814
|
+
|
|
815
|
+
return this.x64ToDecimal(sqrtPriceX64)
|
|
816
|
+
.pow(2)
|
|
817
|
+
.mul(Decimal.pow(10, decimalsA - decimalsB));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
static x64ToDecimal(num: BN, decimalPlaces?: number): Decimal {
|
|
821
|
+
|
|
822
|
+
return new Decimal(num.toString()).div(Decimal.pow(2, 64)).toDecimalPlaces(decimalPlaces);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
static getSpotPrice(tickCurrent: number, mintDecimals0: number, mintDecimals1: number): Decimal {
|
|
826
|
+
let price = this.sqrtPriceX64ToPrice(this.getSqrtPriceX64FromTick(tickCurrent),
|
|
827
|
+
mintDecimals0,
|
|
828
|
+
mintDecimals1);
|
|
829
|
+
return price;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
static getTwapPrimary(
|
|
833
|
+
observations: Observation[], observationIndex: number, primarySeconds: BN, secondarySeconds: BN,
|
|
834
|
+
mintDecimals0: number, mintDecimals1: number, currentTime: BN): Decimal {
|
|
835
|
+
|
|
836
|
+
let observation = this.getDeltaObservations(currentTime, observations, observationIndex, primarySeconds, secondarySeconds)[0];
|
|
837
|
+
|
|
838
|
+
let tick = observation.tickCumulative.div(observation.blockTimestamp).toNumber();
|
|
839
|
+
let twap = this.sqrtPriceX64ToPrice(this.getSqrtPriceX64FromTick(tick),
|
|
840
|
+
mintDecimals0,
|
|
841
|
+
mintDecimals1);
|
|
842
|
+
return twap;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
static getTwapSecondary(
|
|
846
|
+
observations: Observation[], observationIndex: number, primarySeconds: BN, secondarySeconds: BN,
|
|
847
|
+
mintDecimals0: number, mintDecimals1: number, currentTime: BN): Decimal {
|
|
848
|
+
|
|
849
|
+
let observation = this.getDeltaObservations(currentTime, observations, observationIndex, primarySeconds, secondarySeconds)[1];
|
|
850
|
+
let tick = observation.tickCumulative.div(observation.blockTimestamp).toNumber();
|
|
851
|
+
let twap = this.sqrtPriceX64ToPrice(this.getSqrtPriceX64FromTick(tick),
|
|
852
|
+
mintDecimals0,
|
|
853
|
+
mintDecimals1);
|
|
854
|
+
return twap;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
static getConfidence(
|
|
858
|
+
observations: Observation[], observationIndex: number,
|
|
859
|
+
primarySeconds: BN, secondarySeconds: BN, tickCurrent: number,
|
|
860
|
+
mintDecimals0: number, mintDecimals1: number, currentTime: BN
|
|
861
|
+
): Decimal {
|
|
862
|
+
|
|
863
|
+
let primary = this.getTwapPrimary(
|
|
864
|
+
observations, observationIndex, primarySeconds, secondarySeconds,
|
|
865
|
+
mintDecimals0, mintDecimals1, currentTime);
|
|
866
|
+
let secondary = this.getTwapSecondary(
|
|
867
|
+
observations, observationIndex, primarySeconds, secondarySeconds,
|
|
868
|
+
mintDecimals0, mintDecimals1, currentTime);
|
|
869
|
+
let spot = this.getSpotPrice(tickCurrent, mintDecimals0, mintDecimals1);
|
|
870
|
+
|
|
871
|
+
let first = primary.sub(spot).abs();
|
|
872
|
+
let second = secondary.sub(spot).abs();
|
|
873
|
+
let third = primary.sub(secondary).abs();
|
|
874
|
+
|
|
875
|
+
let conf = Decimal.max(first, second, third);
|
|
876
|
+
|
|
877
|
+
return conf;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
static calculateMaxPriceImpactForMinLiquidity(
|
|
881
|
+
oracleParams: OracleSettings,
|
|
882
|
+
poolState: PoolState,
|
|
883
|
+
prevTickArray: TickArrayState,
|
|
884
|
+
currentTickArray: TickArrayState,
|
|
885
|
+
nextTickArray: TickArrayState,
|
|
886
|
+
solPrice: OraclePrice,
|
|
887
|
+
usdPrice: OraclePrice,
|
|
888
|
+
): Decimal {
|
|
889
|
+
// local helpers / aliases
|
|
890
|
+
const sStart = this.x64ToDecimal(poolState.sqrtPriceX64);
|
|
891
|
+
const Lstart = new Decimal(poolState.liquidity.toString());
|
|
892
|
+
const spotPrice = this.sqrtPriceX64ToPrice(poolState.sqrtPriceX64, poolState.mintDecimals0, poolState.mintDecimals1);
|
|
893
|
+
const minLiquidityUsd = new Decimal(oracleParams.minLiquidity.toString());
|
|
894
|
+
let amountQuote: Decimal;
|
|
895
|
+
if (oracleParams.quote === Quote.Usdc) {
|
|
896
|
+
amountQuote = minLiquidityUsd.mul(new Decimal(10).pow(6)).div(usdPrice.price);
|
|
897
|
+
} else {
|
|
898
|
+
amountQuote = minLiquidityUsd.mul(new Decimal(10).pow(9)).div(solPrice.price);
|
|
899
|
+
}
|
|
900
|
+
const tickSpacing = poolState.tickSpacing;
|
|
901
|
+
|
|
902
|
+
// Build fast lookup structures once:
|
|
903
|
+
// - tickMap: absoluteTick -> TickState
|
|
904
|
+
// - arrays of initialized tick indices per region (prev/current/next)
|
|
905
|
+
const tickMap = new Map<number, TickState>();
|
|
906
|
+
const prevIdxs: number[] = [];
|
|
907
|
+
const currIdxs: number[] = [];
|
|
908
|
+
const nextIdxs: number[] = [];
|
|
909
|
+
|
|
910
|
+
const pushTicks = (ta: TickArrayState, outArr: number[]) => {
|
|
911
|
+
const start = ta.startTickIndex;
|
|
912
|
+
for (let i = 0; i < ta.ticks.length; i++) {
|
|
913
|
+
const tickVal = start + i * tickSpacing;
|
|
914
|
+
const t = ta.ticks[i];
|
|
915
|
+
// store in map regardless, to speed up findTickState
|
|
916
|
+
tickMap.set(tickVal, t);
|
|
917
|
+
if (t.liquidityGross.gt(new BN(0))) outArr.push(tickVal);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
pushTicks(prevTickArray, prevIdxs);
|
|
922
|
+
pushTicks(currentTickArray, currIdxs);
|
|
923
|
+
pushTicks(nextTickArray, nextIdxs);
|
|
924
|
+
|
|
925
|
+
// findTickState map lookup
|
|
926
|
+
const findTickState = (idx: number | null): TickState | null => {
|
|
927
|
+
if (idx === null) return null;
|
|
928
|
+
return tickMap.get(idx) ?? null;
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
// find next initialized tick index scanning only relevant arrays:
|
|
932
|
+
// forward === true => check current then next
|
|
933
|
+
// forward === false => check current then prev
|
|
934
|
+
const findNextInitializedTickIndex = (startTick: number, forward: boolean): number | null => {
|
|
935
|
+
const lists = forward ? [currIdxs, nextIdxs] : [currIdxs, prevIdxs];
|
|
936
|
+
let best: number | null = null;
|
|
937
|
+
for (const list of lists) {
|
|
938
|
+
// for forward: find smallest tick > startTick
|
|
939
|
+
// for backward: find largest tick < startTick
|
|
940
|
+
if (forward) {
|
|
941
|
+
for (let i = 0; i < list.length; i++) {
|
|
942
|
+
const v = list[i];
|
|
943
|
+
if (v > startTick) {
|
|
944
|
+
best = best === null ? v : Math.min(best, v);
|
|
945
|
+
// since list ascending we can break early for this list
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (best !== null) break; // we already found closest in current list
|
|
950
|
+
} else {
|
|
951
|
+
// backward: list ascending, scan from end for speed
|
|
952
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
953
|
+
const v = list[i];
|
|
954
|
+
if (v < startTick) {
|
|
955
|
+
best = best === null ? v : Math.max(best, v);
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (best !== null) break;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return best;
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
const sFor = (tickIdx: number | null) =>
|
|
966
|
+
tickIdx === null ? null : this.x64ToDecimal(this.getSqrtPriceX64FromTick(tickIdx));
|
|
967
|
+
|
|
968
|
+
// simulate quote -> base (s increases). returns avg quote-per-base or null
|
|
969
|
+
const simulateIncrease = (amountQ: Decimal): Decimal | null => {
|
|
970
|
+
let remaining = amountQ;
|
|
971
|
+
let s = sStart;
|
|
972
|
+
let L = Lstart;
|
|
973
|
+
let totalBaseOut = new Decimal(0);
|
|
974
|
+
let currentTick = poolState.tickCurrent;
|
|
975
|
+
let boundary = findNextInitializedTickIndex(currentTick, true);
|
|
976
|
+
|
|
977
|
+
let steps = 0;
|
|
978
|
+
while (remaining.gt(0) && steps++ < 500) {
|
|
979
|
+
const sBoundary = sFor(boundary);
|
|
980
|
+
if (!sBoundary) {
|
|
981
|
+
// no known boundary -> consume all in one segment
|
|
982
|
+
const sEnd = s.add(remaining.div(L));
|
|
983
|
+
totalBaseOut = totalBaseOut.add(L.mul(new Decimal(1).div(s).sub(new Decimal(1).div(sEnd))));
|
|
984
|
+
remaining = new Decimal(0);
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const deltaQToBoundary = L.mul(sBoundary.sub(s));
|
|
989
|
+
if (remaining.lte(deltaQToBoundary)) {
|
|
990
|
+
const sEnd = s.add(remaining.div(L));
|
|
991
|
+
totalBaseOut = totalBaseOut.add(L.mul(new Decimal(1).div(s).sub(new Decimal(1).div(sEnd))));
|
|
992
|
+
remaining = new Decimal(0);
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// consume whole segment to boundary
|
|
997
|
+
totalBaseOut = totalBaseOut.add(L.mul(new Decimal(1).div(s).sub(new Decimal(1).div(sBoundary))));
|
|
998
|
+
remaining = remaining.sub(deltaQToBoundary);
|
|
999
|
+
|
|
1000
|
+
// cross tick: safe to assert boundary non-null because sBoundary existed
|
|
1001
|
+
const bt = boundary!;
|
|
1002
|
+
const ts = findTickState(bt);
|
|
1003
|
+
if (ts) L = L.add(new Decimal(ts.liquidityNet.toString()));
|
|
1004
|
+
s = sBoundary;
|
|
1005
|
+
currentTick = bt + tickSpacing;
|
|
1006
|
+
boundary = findNextInitializedTickIndex(currentTick, true);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (totalBaseOut.lte(0)) return null;
|
|
1010
|
+
return amountQ.div(totalBaseOut);
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
// simulate base -> quote (s decreases). amountQuote is converted to approximate amountBase via spot
|
|
1014
|
+
const simulateDecrease = (amountQ: Decimal): Decimal | null => {
|
|
1015
|
+
const amountBase = amountQ.div(spotPrice);
|
|
1016
|
+
let remainingBase = amountBase;
|
|
1017
|
+
let s = sStart;
|
|
1018
|
+
let L = Lstart;
|
|
1019
|
+
let totalQuoteOut = new Decimal(0);
|
|
1020
|
+
let currentTick = poolState.tickCurrent;
|
|
1021
|
+
let boundary = findNextInitializedTickIndex(currentTick, false);
|
|
1022
|
+
|
|
1023
|
+
let steps = 0;
|
|
1024
|
+
while (remainingBase.gt(0) && steps++ < 500) {
|
|
1025
|
+
const sBoundary = sFor(boundary);
|
|
1026
|
+
if (!sBoundary) {
|
|
1027
|
+
const denom = L.add(remainingBase.mul(s));
|
|
1028
|
+
if (denom.eq(0)) return null;
|
|
1029
|
+
const sEnd = L.mul(s).div(denom);
|
|
1030
|
+
totalQuoteOut = totalQuoteOut.add(L.mul(s.sub(sEnd)));
|
|
1031
|
+
remainingBase = new Decimal(0);
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const deltaAtoBoundary = L.mul(new Decimal(1).div(sBoundary).sub(new Decimal(1).div(s)));
|
|
1036
|
+
if (remainingBase.lte(deltaAtoBoundary)) {
|
|
1037
|
+
const denom = L.add(remainingBase.mul(s));
|
|
1038
|
+
const sEnd = L.mul(s).div(denom);
|
|
1039
|
+
totalQuoteOut = totalQuoteOut.add(L.mul(s.sub(sEnd)));
|
|
1040
|
+
remainingBase = new Decimal(0);
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// consume to boundary
|
|
1045
|
+
const denom = L.add(deltaAtoBoundary.mul(s));
|
|
1046
|
+
const sEnd = L.mul(s).div(denom);
|
|
1047
|
+
totalQuoteOut = totalQuoteOut.add(L.mul(s.sub(sEnd)));
|
|
1048
|
+
remainingBase = remainingBase.sub(deltaAtoBoundary);
|
|
1049
|
+
|
|
1050
|
+
// cross tick going down
|
|
1051
|
+
const bt = boundary!;
|
|
1052
|
+
const ts = findTickState(bt);
|
|
1053
|
+
if (ts) L = L.sub(new Decimal(ts.liquidityNet.toString()));
|
|
1054
|
+
s = sEnd;
|
|
1055
|
+
currentTick = bt - tickSpacing;
|
|
1056
|
+
boundary = findNextInitializedTickIndex(currentTick, false);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (totalQuoteOut.lte(0)) return null;
|
|
1060
|
+
if (amountBase.eq(0)) return null;
|
|
1061
|
+
return totalQuoteOut.div(amountBase);
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
const up = simulateIncrease(amountQuote);
|
|
1065
|
+
const down = simulateDecrease(amountQuote);
|
|
1066
|
+
|
|
1067
|
+
const toImpact = (avg: Decimal | null) =>
|
|
1068
|
+
!avg || avg.lte(0) ? new Decimal(0) : avg.sub(spotPrice).abs().div(spotPrice).mul(new Decimal(10000));
|
|
1069
|
+
|
|
1070
|
+
return Decimal.max(toImpact(up), toImpact(down));
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
static fetch(
|
|
1076
|
+
oracleParams: OracleSettings,
|
|
1077
|
+
accountInfos: AccountInfo<Buffer>[],
|
|
1078
|
+
solPrice: OraclePrice,
|
|
1079
|
+
usdPrice: OraclePrice,
|
|
1080
|
+
): OraclePrice {
|
|
1081
|
+
|
|
1082
|
+
const poolState = accountInfos[0];
|
|
1083
|
+
const observationState = accountInfos[1];
|
|
1084
|
+
const prevTickArrayState = accountInfos[2];
|
|
1085
|
+
const currentTickArrayState = accountInfos[3];
|
|
1086
|
+
const nextTickArrayState = accountInfos[4];
|
|
1087
|
+
const [decodedPoolState, num] = PoolState.decode(poolState.data, 8);
|
|
1088
|
+
const [decodedObservationState, _] = ObservationState.decode(observationState.data, 8);
|
|
1089
|
+
const [decodedPrevTickArrayState, __] = TickArrayState.decode(prevTickArrayState.data, 8);
|
|
1090
|
+
const [decodedCurrentTickArrayState, ___] = TickArrayState.decode(currentTickArrayState.data, 8);
|
|
1091
|
+
const [decodedNextTickArrayState, ____] = TickArrayState.decode(nextTickArrayState.data, 8);
|
|
1092
|
+
|
|
1093
|
+
const currentTime = new BN(Math.floor(Date.now() / 1000));
|
|
1094
|
+
|
|
1095
|
+
let primaryPrice = RaydiumCLMMOracle.getTwapPrimary(
|
|
1096
|
+
decodedObservationState.observations, decodedObservationState.observationIndex,
|
|
1097
|
+
oracleParams.twapSecondsAgo, oracleParams.twapSecondarySecondsAgo,
|
|
1098
|
+
decodedPoolState.mintDecimals0, decodedPoolState.mintDecimals1,
|
|
1099
|
+
currentTime);
|
|
1100
|
+
|
|
1101
|
+
let secondaryPrice = RaydiumCLMMOracle.getTwapSecondary(
|
|
1102
|
+
decodedObservationState.observations, decodedObservationState.observationIndex,
|
|
1103
|
+
oracleParams.twapSecondsAgo, oracleParams.twapSecondarySecondsAgo,
|
|
1104
|
+
decodedPoolState.mintDecimals0, decodedPoolState.mintDecimals1,
|
|
1105
|
+
currentTime);
|
|
1106
|
+
|
|
1107
|
+
let spotPrice = RaydiumCLMMOracle.getSpotPrice(
|
|
1108
|
+
decodedPoolState.tickCurrent,
|
|
1109
|
+
decodedPoolState.mintDecimals0,
|
|
1110
|
+
decodedPoolState.mintDecimals1);
|
|
1111
|
+
|
|
1112
|
+
// Validate primary TWAP is not zero
|
|
1113
|
+
if (primaryPrice.eq(0)) {
|
|
1114
|
+
return new OraclePrice(new Decimal(0), new Decimal(0), 0);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// === 1. Min liquidity and price impact validation (FIRST) ===
|
|
1118
|
+
if (oracleParams.minLiquidity.gt(new BN(0))) {
|
|
1119
|
+
const impactBps = RaydiumCLMMOracle.calculateMaxPriceImpactForMinLiquidity(
|
|
1120
|
+
oracleParams,
|
|
1121
|
+
decodedPoolState,
|
|
1122
|
+
decodedPrevTickArrayState,
|
|
1123
|
+
decodedCurrentTickArrayState,
|
|
1124
|
+
decodedNextTickArrayState,
|
|
1125
|
+
solPrice,
|
|
1126
|
+
usdPrice
|
|
1127
|
+
);
|
|
1128
|
+
if (oracleParams.maxSlippageBps > 0 && impactBps.gt(new Decimal(oracleParams.maxSlippageBps))) {
|
|
1129
|
+
return new OraclePrice(new Decimal(0), new Decimal(0), 0);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// === 2. Confidence calculation ===
|
|
1134
|
+
let maxPrice = Decimal.max(primaryPrice, secondaryPrice, spotPrice);
|
|
1135
|
+
let minPrice = Decimal.min(primaryPrice, secondaryPrice, spotPrice);
|
|
1136
|
+
let confidence = maxPrice.gt(minPrice) ? maxPrice.sub(minPrice) : new Decimal(0);
|
|
1137
|
+
const lastUpdateTimestamp = decodedObservationState.observations[decodedObservationState.observationIndex].blockTimestamp;
|
|
1138
|
+
|
|
1139
|
+
// === 3. Inflate confidence by staleness ===
|
|
1140
|
+
const deltaSecondsBN = currentTime.sub(lastUpdateTimestamp);
|
|
1141
|
+
const deltaSeconds = new Decimal(deltaSecondsBN.toString());
|
|
1142
|
+
if (oracleParams.stalenessConfRateBps > 0 && deltaSeconds.gt(0)) {
|
|
1143
|
+
const stalenessRate = new Decimal(oracleParams.stalenessConfRateBps).div(new Decimal(10000));
|
|
1144
|
+
const inflateFactor = new Decimal(1).add(deltaSeconds.mul(stalenessRate));
|
|
1145
|
+
confidence = confidence.mul(inflateFactor);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// === 4. Validate confidence threshold ===
|
|
1149
|
+
const confRatioBps = confidence.div(primaryPrice).mul(new Decimal(HUNDRED_PERCENT_BPS));
|
|
1150
|
+
if (confRatioBps.gt(new Decimal(oracleParams.confThreshBps))) {
|
|
1151
|
+
return new OraclePrice(new Decimal(0), new Decimal(0), 0);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// === 5. Validate staleness threshold ===
|
|
1155
|
+
if (oracleParams.stalenessThresh.gt(new BN(0))) {
|
|
1156
|
+
if (currentTime.sub(lastUpdateTimestamp).gt(oracleParams.stalenessThresh)) {
|
|
1157
|
+
return new OraclePrice(new Decimal(0), new Decimal(0), 0);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// === 6. Validate volatility threshold ===
|
|
1162
|
+
if (!minPrice.eq(0)) {
|
|
1163
|
+
const volRatioBps = maxPrice.sub(minPrice).div(minPrice).mul(new Decimal(HUNDRED_PERCENT_BPS));
|
|
1164
|
+
if (volRatioBps.gt(new Decimal(oracleParams.volatilityThreshBps))) {
|
|
1165
|
+
return new OraclePrice(new Decimal(0), new Decimal(0), 0);
|
|
1166
|
+
}
|
|
1167
|
+
} else {
|
|
1168
|
+
return new OraclePrice(new Decimal(0), new Decimal(0), 0);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// === 7. Convert to USD ===
|
|
1172
|
+
if (oracleParams.quote === Quote.Usdc) {
|
|
1173
|
+
primaryPrice = primaryPrice.mul(usdPrice.price);
|
|
1174
|
+
confidence = confidence.mul(usdPrice.price);
|
|
1175
|
+
} else {
|
|
1176
|
+
primaryPrice = primaryPrice.mul(solPrice.price);
|
|
1177
|
+
confidence = confidence.mul(solPrice.price);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return new OraclePrice(primaryPrice, confidence, currentTime.toNumber());
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function mulRightShift(val: BN, mulBy: BN): BN {
|
|
1186
|
+
return signedRightShift(val.mul(mulBy), 64, 256);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function signedRightShift(n0: BN, shiftBy: number, bitWidth: number): BN {
|
|
1190
|
+
const twoN0 = n0.toTwos(bitWidth).shrn(shiftBy);
|
|
1191
|
+
twoN0.imaskn(bitWidth - shiftBy + 1);
|
|
1192
|
+
return twoN0.fromTwos(bitWidth - shiftBy);
|
|
1193
|
+
}
|