@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,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
|
+
}
|