perk-protocol 0.1.0

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/client.js ADDED
@@ -0,0 +1,690 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PerkClient = void 0;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ const spl_token_1 = require("@solana/spl-token");
9
+ const anchor_1 = require("@coral-xyz/anchor");
10
+ const constants_1 = require("./constants");
11
+ const pda_1 = require("./pda");
12
+ const types_1 = require("./types");
13
+ const idl_json_1 = __importDefault(require("./idl.json"));
14
+ // ── Anchor enum serialization maps ──
15
+ const SIDE_MAP = {
16
+ [types_1.Side.Long]: { long: {} },
17
+ [types_1.Side.Short]: { short: {} },
18
+ };
19
+ const ORACLE_SOURCE_MAP = {
20
+ [types_1.OracleSource.Pyth]: { pyth: {} },
21
+ [types_1.OracleSource.PerkOracle]: { perkOracle: {} },
22
+ [types_1.OracleSource.DexPool]: { dexPool: {} },
23
+ };
24
+ const ORDER_TYPE_MAP = {
25
+ [types_1.TriggerOrderType.Limit]: { limit: {} },
26
+ [types_1.TriggerOrderType.StopLoss]: { stopLoss: {} },
27
+ [types_1.TriggerOrderType.TakeProfit]: { takeProfit: {} },
28
+ };
29
+ class PerkClient {
30
+ constructor(config) {
31
+ this.connection = config.connection;
32
+ this.wallet = config.wallet;
33
+ this.programId = config.programId ?? constants_1.PERK_PROGRAM_ID;
34
+ this.preInstructions = config.preInstructions ?? [];
35
+ this.provider = new anchor_1.AnchorProvider(config.connection, config.wallet, { commitment: config.commitment ?? "confirmed" });
36
+ this.program = new anchor_1.Program(idl_json_1.default, this.provider);
37
+ this.accounts = this.program.account;
38
+ }
39
+ // ═══════════════════════════════════════════════
40
+ // PDA Helpers
41
+ // ═══════════════════════════════════════════════
42
+ getProtocolAddress() {
43
+ return (0, pda_1.findProtocolAddress)(this.programId)[0];
44
+ }
45
+ getMarketAddress(tokenMint) {
46
+ return (0, pda_1.findMarketAddress)(tokenMint, this.programId)[0];
47
+ }
48
+ getPositionAddress(market, user) {
49
+ return (0, pda_1.findPositionAddress)(market, user, this.programId)[0];
50
+ }
51
+ getVaultAddress(market) {
52
+ return (0, pda_1.findVaultAddress)(market, this.programId)[0];
53
+ }
54
+ getTriggerOrderAddress(market, user, orderId) {
55
+ return (0, pda_1.findTriggerOrderAddress)(market, user, orderId, this.programId)[0];
56
+ }
57
+ getPerkOracleAddress(tokenMint) {
58
+ return (0, pda_1.findPerkOracleAddress)(tokenMint, this.programId)[0];
59
+ }
60
+ // ═══════════════════════════════════════════════
61
+ // Account Fetchers
62
+ // ═══════════════════════════════════════════════
63
+ async fetchProtocol() {
64
+ const address = this.getProtocolAddress();
65
+ return (await this.accounts.protocol.fetch(address));
66
+ }
67
+ async fetchMarket(tokenMint) {
68
+ const address = this.getMarketAddress(tokenMint);
69
+ return (await this.accounts.market.fetch(address));
70
+ }
71
+ async fetchMarketByAddress(address) {
72
+ return (await this.accounts.market.fetch(address));
73
+ }
74
+ async fetchAllMarkets() {
75
+ const accounts = await this.accounts.market.all();
76
+ return accounts.map((a) => ({
77
+ address: a.publicKey,
78
+ account: a.account,
79
+ }));
80
+ }
81
+ async fetchPosition(market, user) {
82
+ const address = this.getPositionAddress(market, user);
83
+ return (await this.accounts.userPosition.fetch(address));
84
+ }
85
+ async fetchPositionByAddress(address) {
86
+ return (await this.accounts.userPosition.fetch(address));
87
+ }
88
+ async fetchAllPositions(user) {
89
+ const accounts = await this.accounts.userPosition.all([
90
+ { memcmp: { offset: 8, bytes: user.toBase58() } },
91
+ ]);
92
+ return accounts.map((a) => ({
93
+ address: a.publicKey,
94
+ account: a.account,
95
+ }));
96
+ }
97
+ async fetchTriggerOrder(address) {
98
+ return (await this.accounts.triggerOrder.fetch(address));
99
+ }
100
+ async fetchTriggerOrders(market, user) {
101
+ const accounts = await this.accounts.triggerOrder.all([
102
+ { memcmp: { offset: 8, bytes: user.toBase58() } }, // authority at offset 8
103
+ { memcmp: { offset: 8 + 32, bytes: market.toBase58() } }, // market at offset 40
104
+ ]);
105
+ return accounts
106
+ .map((a) => ({
107
+ address: a.publicKey,
108
+ account: a.account,
109
+ }));
110
+ }
111
+ /** Fetch a PerkOracle account. */
112
+ async fetchPerkOracle(tokenMint) {
113
+ const address = this.getPerkOracleAddress(tokenMint);
114
+ return (await this.accounts.perkOraclePrice.fetch(address));
115
+ }
116
+ /** Fetch a PerkOracle account, returning null if not found. */
117
+ async fetchPerkOracleNullable(tokenMint) {
118
+ const address = this.getPerkOracleAddress(tokenMint);
119
+ return (await this.accounts.perkOraclePrice.fetchNullable(address));
120
+ }
121
+ // ═══════════════════════════════════════════════
122
+ // Admin Instructions
123
+ // ═══════════════════════════════════════════════
124
+ /** Initialize the protocol (admin only, once). */
125
+ async initializeProtocol(protocolFeeVault) {
126
+ const protocol = this.getProtocolAddress();
127
+ return this.program.methods
128
+ .initializeProtocol()
129
+ .accounts({
130
+ protocol,
131
+ admin: this.wallet.publicKey,
132
+ protocolFeeVault,
133
+ systemProgram: web3_js_1.SystemProgram.programId,
134
+ })
135
+ .preInstructions(this.preInstructions).rpc();
136
+ }
137
+ /** Propose a new admin (current admin only). */
138
+ async proposeAdmin(newAdmin) {
139
+ const protocol = this.getProtocolAddress();
140
+ return this.program.methods
141
+ .proposeAdmin(newAdmin)
142
+ .accounts({
143
+ protocol,
144
+ admin: this.wallet.publicKey,
145
+ })
146
+ .preInstructions(this.preInstructions).rpc();
147
+ }
148
+ /** Accept admin transfer (pending admin only). */
149
+ async acceptAdmin() {
150
+ const protocol = this.getProtocolAddress();
151
+ return this.program.methods
152
+ .acceptAdmin()
153
+ .accounts({
154
+ protocol,
155
+ newAdmin: this.wallet.publicKey,
156
+ })
157
+ .preInstructions(this.preInstructions).rpc();
158
+ }
159
+ /** Pause/unpause the protocol. */
160
+ async adminPause(paused) {
161
+ const protocol = this.getProtocolAddress();
162
+ return this.program.methods
163
+ .adminPause(paused)
164
+ .accounts({
165
+ protocol,
166
+ admin: this.wallet.publicKey,
167
+ })
168
+ .preInstructions(this.preInstructions).rpc();
169
+ }
170
+ /** Update market parameters (admin only). */
171
+ async adminUpdateMarket(tokenMint, oracle, params) {
172
+ const protocol = this.getProtocolAddress();
173
+ const market = this.getMarketAddress(tokenMint);
174
+ return this.program.methods
175
+ .adminUpdateMarket(params)
176
+ .accounts({
177
+ protocol,
178
+ market,
179
+ oracle: oracle ?? this.programId, // Anchor optional-absent sentinel
180
+ admin: this.wallet.publicKey,
181
+ })
182
+ .preInstructions(this.preInstructions).rpc();
183
+ }
184
+ /** Withdraw SOL from protocol PDA (admin only). */
185
+ async adminWithdrawSol(amount) {
186
+ const protocol = this.getProtocolAddress();
187
+ return this.program.methods
188
+ .adminWithdrawSol(amount)
189
+ .accounts({
190
+ protocol,
191
+ admin: this.wallet.publicKey,
192
+ systemProgram: web3_js_1.SystemProgram.programId,
193
+ })
194
+ .preInstructions(this.preInstructions).rpc();
195
+ }
196
+ // ═══════════════════════════════════════════════
197
+ // Market Instructions
198
+ // ═══════════════════════════════════════════════
199
+ /** Create a new perpetual futures market. */
200
+ async createMarket(tokenMint, oracle, params) {
201
+ const protocol = this.getProtocolAddress();
202
+ const [market] = (0, pda_1.findMarketAddress)(tokenMint, this.programId);
203
+ const [vault] = (0, pda_1.findVaultAddress)(market, this.programId);
204
+ return this.program.methods
205
+ .createMarket({
206
+ oracleSource: ORACLE_SOURCE_MAP[params.oracleSource],
207
+ maxLeverage: params.maxLeverage,
208
+ tradingFeeBps: params.tradingFeeBps,
209
+ initialK: params.initialK,
210
+ })
211
+ .accounts({
212
+ protocol,
213
+ market,
214
+ tokenMint,
215
+ oracle,
216
+ vault,
217
+ creator: this.wallet.publicKey,
218
+ systemProgram: web3_js_1.SystemProgram.programId,
219
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
220
+ rent: web3_js_1.SYSVAR_RENT_PUBKEY,
221
+ })
222
+ .preInstructions(this.preInstructions).rpc();
223
+ }
224
+ // ═══════════════════════════════════════════════
225
+ // Position Instructions
226
+ // ═══════════════════════════════════════════════
227
+ /** Initialize a position account for a user on a market. */
228
+ async initializePosition(tokenMint) {
229
+ const protocol = this.getProtocolAddress();
230
+ const market = this.getMarketAddress(tokenMint);
231
+ const position = this.getPositionAddress(market, this.wallet.publicKey);
232
+ return this.program.methods
233
+ .initializePosition()
234
+ .accounts({
235
+ protocol,
236
+ market,
237
+ userPosition: position,
238
+ user: this.wallet.publicKey,
239
+ systemProgram: web3_js_1.SystemProgram.programId,
240
+ })
241
+ .preInstructions(this.preInstructions).rpc();
242
+ }
243
+ /** Deposit collateral into a position. */
244
+ async deposit(tokenMint, oracle, amount, fallbackOracle) {
245
+ const protocol = this.getProtocolAddress();
246
+ const market = this.getMarketAddress(tokenMint);
247
+ const position = this.getPositionAddress(market, this.wallet.publicKey);
248
+ const [vault] = (0, pda_1.findVaultAddress)(market, this.programId);
249
+ const userAta = await (0, spl_token_1.getAssociatedTokenAddress)(tokenMint, this.wallet.publicKey);
250
+ return this.program.methods
251
+ .deposit(amount)
252
+ .accounts({
253
+ protocol,
254
+ market,
255
+ userPosition: position,
256
+ oracle,
257
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
258
+ userTokenAccount: userAta,
259
+ vault,
260
+ user: this.wallet.publicKey,
261
+ systemProgram: web3_js_1.SystemProgram.programId,
262
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
263
+ })
264
+ .preInstructions(this.preInstructions).rpc();
265
+ }
266
+ /** Withdraw collateral from a position. */
267
+ async withdraw(tokenMint, oracle, amount, fallbackOracle) {
268
+ const protocol = this.getProtocolAddress();
269
+ const market = this.getMarketAddress(tokenMint);
270
+ const position = this.getPositionAddress(market, this.wallet.publicKey);
271
+ const [vault] = (0, pda_1.findVaultAddress)(market, this.programId);
272
+ const userAta = await (0, spl_token_1.getAssociatedTokenAddress)(tokenMint, this.wallet.publicKey);
273
+ return this.program.methods
274
+ .withdraw(amount)
275
+ .accounts({
276
+ protocol,
277
+ market,
278
+ userPosition: position,
279
+ oracle,
280
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
281
+ userTokenAccount: userAta,
282
+ vault,
283
+ authority: this.wallet.publicKey,
284
+ user: this.wallet.publicKey,
285
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
286
+ })
287
+ .preInstructions(this.preInstructions).rpc();
288
+ }
289
+ /** Open a leveraged position. */
290
+ async openPosition(tokenMint, oracle, side, baseSize, leverage, maxSlippageBps = 500, fallbackOracle) {
291
+ // Input validation — catch bad values before they hit Borsh serialization
292
+ if (leverage < constants_1.MIN_LEVERAGE || leverage > constants_1.MAX_LEVERAGE) {
293
+ throw new Error(`leverage must be ${constants_1.MIN_LEVERAGE}-${constants_1.MAX_LEVERAGE} (${constants_1.MIN_LEVERAGE / constants_1.LEVERAGE_SCALE}x-${constants_1.MAX_LEVERAGE / constants_1.LEVERAGE_SCALE}x), got ${leverage}`);
294
+ }
295
+ if (maxSlippageBps < 0 || maxSlippageBps > 65535) {
296
+ throw new Error(`maxSlippageBps must be 0-65535 (u16), got ${maxSlippageBps}`);
297
+ }
298
+ if (!Number.isInteger(leverage) || !Number.isInteger(maxSlippageBps)) {
299
+ throw new Error("leverage and maxSlippageBps must be integers");
300
+ }
301
+ const protocol = this.getProtocolAddress();
302
+ const market = this.getMarketAddress(tokenMint);
303
+ const position = this.getPositionAddress(market, this.wallet.publicKey);
304
+ return this.program.methods
305
+ .openPosition(SIDE_MAP[side], baseSize, leverage, maxSlippageBps)
306
+ .accounts({
307
+ protocol,
308
+ market,
309
+ userPosition: position,
310
+ oracle,
311
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
312
+ authority: this.wallet.publicKey,
313
+ user: this.wallet.publicKey,
314
+ })
315
+ .preInstructions(this.preInstructions).rpc();
316
+ }
317
+ /** Close a position (full or partial). */
318
+ async closePosition(tokenMint, oracle, baseSizeToClose, fallbackOracle) {
319
+ const protocol = this.getProtocolAddress();
320
+ const market = this.getMarketAddress(tokenMint);
321
+ const position = this.getPositionAddress(market, this.wallet.publicKey);
322
+ return this.program.methods
323
+ .closePosition(baseSizeToClose ?? null)
324
+ .accounts({
325
+ protocol,
326
+ market,
327
+ userPosition: position,
328
+ oracle,
329
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
330
+ authority: this.wallet.publicKey,
331
+ user: this.wallet.publicKey,
332
+ })
333
+ .preInstructions(this.preInstructions).rpc();
334
+ }
335
+ // ═══════════════════════════════════════════════
336
+ // Trigger Orders
337
+ // ═══════════════════════════════════════════════
338
+ /** Place a trigger order (limit, stop-loss, take-profit). */
339
+ async placeTriggerOrder(tokenMint, params) {
340
+ const market = this.getMarketAddress(tokenMint);
341
+ const position = this.getPositionAddress(market, this.wallet.publicKey);
342
+ // Fetch position to get next order ID
343
+ const pos = await this.fetchPositionByAddress(position);
344
+ const orderId = pos.nextOrderId;
345
+ const triggerOrder = this.getTriggerOrderAddress(market, this.wallet.publicKey, orderId);
346
+ return this.program.methods
347
+ .placeTriggerOrder({
348
+ orderType: ORDER_TYPE_MAP[params.orderType],
349
+ side: SIDE_MAP[params.side],
350
+ size: params.size,
351
+ triggerPrice: params.triggerPrice,
352
+ leverage: params.leverage,
353
+ reduceOnly: params.reduceOnly,
354
+ expiry: params.expiry,
355
+ })
356
+ .accounts({
357
+ market,
358
+ userPosition: position,
359
+ triggerOrder,
360
+ authority: this.wallet.publicKey,
361
+ user: this.wallet.publicKey,
362
+ systemProgram: web3_js_1.SystemProgram.programId,
363
+ })
364
+ .preInstructions(this.preInstructions).rpc();
365
+ }
366
+ /** Cancel a trigger order. */
367
+ async cancelTriggerOrder(tokenMint, orderId) {
368
+ const market = this.getMarketAddress(tokenMint);
369
+ const position = this.getPositionAddress(market, this.wallet.publicKey);
370
+ const triggerOrder = this.getTriggerOrderAddress(market, this.wallet.publicKey, orderId);
371
+ return this.program.methods
372
+ .cancelTriggerOrder()
373
+ .accounts({
374
+ market,
375
+ userPosition: position,
376
+ triggerOrder,
377
+ authority: this.wallet.publicKey,
378
+ user: this.wallet.publicKey,
379
+ })
380
+ .preInstructions(this.preInstructions).rpc();
381
+ }
382
+ // ═══════════════════════════════════════════════
383
+ // Fee Claims
384
+ // ═══════════════════════════════════════════════
385
+ /** Claim accumulated fees (creator or protocol). */
386
+ async claimFees(tokenMint, recipientTokenAccount) {
387
+ const protocol = this.getProtocolAddress();
388
+ const market = this.getMarketAddress(tokenMint);
389
+ const [vault] = (0, pda_1.findVaultAddress)(market, this.programId);
390
+ return this.program.methods
391
+ .claimFees()
392
+ .accounts({
393
+ protocol,
394
+ market,
395
+ vault,
396
+ recipientTokenAccount,
397
+ claimer: this.wallet.publicKey,
398
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
399
+ })
400
+ .preInstructions(this.preInstructions).rpc();
401
+ }
402
+ // ═══════════════════════════════════════════════
403
+ // Cranker Instructions
404
+ // ═══════════════════════════════════════════════
405
+ /** Crank funding rate update. */
406
+ async crankFunding(marketAddress, oracle, fallbackOracle) {
407
+ return this.program.methods
408
+ .crankFunding()
409
+ .accounts({
410
+ market: marketAddress,
411
+ oracle,
412
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
413
+ cranker: this.wallet.publicKey,
414
+ })
415
+ .preInstructions(this.preInstructions).rpc();
416
+ }
417
+ /** Liquidate an underwater position. */
418
+ async liquidate(marketAddress, oracle, targetUser, liquidatorTokenAccount, fallbackOracle) {
419
+ const protocol = this.getProtocolAddress();
420
+ const position = this.getPositionAddress(marketAddress, targetUser);
421
+ const [vault] = (0, pda_1.findVaultAddress)(marketAddress, this.programId);
422
+ return this.program.methods
423
+ .liquidate()
424
+ .accounts({
425
+ protocol,
426
+ market: marketAddress,
427
+ userPosition: position,
428
+ oracle,
429
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
430
+ targetUser,
431
+ liquidatorTokenAccount,
432
+ vault,
433
+ liquidator: this.wallet.publicKey,
434
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
435
+ })
436
+ .preInstructions(this.preInstructions).rpc();
437
+ }
438
+ /** Execute a trigger order that has been triggered. */
439
+ async executeTriggerOrder(marketAddress, oracle, targetUser, orderId, executorTokenAccount, fallbackOracle) {
440
+ const protocol = this.getProtocolAddress();
441
+ const position = this.getPositionAddress(marketAddress, targetUser);
442
+ const triggerOrder = this.getTriggerOrderAddress(marketAddress, targetUser, orderId);
443
+ const [vault] = (0, pda_1.findVaultAddress)(marketAddress, this.programId);
444
+ return this.program.methods
445
+ .executeTriggerOrder()
446
+ .accounts({
447
+ protocol,
448
+ market: marketAddress,
449
+ userPosition: position,
450
+ triggerOrder,
451
+ oracle,
452
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
453
+ executorTokenAccount,
454
+ vault,
455
+ executor: this.wallet.publicKey,
456
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
457
+ })
458
+ .preInstructions(this.preInstructions).rpc();
459
+ }
460
+ /** Update the vAMM peg multiplier (permissionless). */
461
+ async updateAmm(marketAddress, oracle, fallbackOracle) {
462
+ return this.program.methods
463
+ .updateAmm()
464
+ .accounts({
465
+ market: marketAddress,
466
+ oracle,
467
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
468
+ caller: this.wallet.publicKey,
469
+ })
470
+ .preInstructions(this.preInstructions).rpc();
471
+ }
472
+ /** Reclaim an empty/abandoned position account (permissionless). */
473
+ async reclaimEmptyAccount(marketAddress, oracle, positionOwner, fallbackOracle) {
474
+ const position = this.getPositionAddress(marketAddress, positionOwner);
475
+ return this.program.methods
476
+ .reclaimEmptyAccount()
477
+ .accounts({
478
+ market: marketAddress,
479
+ userPosition: position,
480
+ oracle,
481
+ fallbackOracle: fallbackOracle ?? web3_js_1.SystemProgram.programId,
482
+ positionOwner,
483
+ rentReceiver: positionOwner, // On-chain enforces rentReceiver == position.authority
484
+ caller: this.wallet.publicKey,
485
+ })
486
+ .preInstructions(this.preInstructions).rpc();
487
+ }
488
+ // ═══════════════════════════════════════════════
489
+ // PerkOracle Instructions
490
+ // ═══════════════════════════════════════════════
491
+ /** Initialize a PerkOracle price feed. Admin only. */
492
+ async initializePerkOracle(tokenMint, oracleAuthority, params) {
493
+ // ── Client-side validation (ATK-01) ──
494
+ // Type safety: reject NaN, Infinity, floats
495
+ for (const [name, val] of Object.entries({
496
+ circuitBreakerDeviationBps: params.circuitBreakerDeviationBps,
497
+ maxPriceChangeBps: params.maxPriceChangeBps,
498
+ minSources: params.minSources,
499
+ maxStalenessSeconds: params.maxStalenessSeconds,
500
+ })) {
501
+ if (!Number.isFinite(val) || !Number.isInteger(val)) {
502
+ throw new Error(`${name} must be a finite integer, got ${val}`);
503
+ }
504
+ }
505
+ // u16 range checks
506
+ if (params.circuitBreakerDeviationBps < 0 || params.circuitBreakerDeviationBps > 65535) {
507
+ throw new Error(`circuitBreakerDeviationBps out of u16 range`);
508
+ }
509
+ if (params.maxPriceChangeBps < 0 || params.maxPriceChangeBps > 65535) {
510
+ throw new Error(`maxPriceChangeBps out of u16 range`);
511
+ }
512
+ // Bounds checks
513
+ if (params.circuitBreakerDeviationBps !== 0) {
514
+ if (params.circuitBreakerDeviationBps < 500 || params.circuitBreakerDeviationBps > 9999) {
515
+ throw new Error(`circuitBreakerDeviationBps must be 0 (disabled) or between 500 and 9999, got ${params.circuitBreakerDeviationBps}`);
516
+ }
517
+ }
518
+ if (params.maxPriceChangeBps !== 0) {
519
+ if (params.maxPriceChangeBps < 100 || params.maxPriceChangeBps > 9999) {
520
+ throw new Error(`maxPriceChangeBps must be 0 (disabled) or between 100 and 9999, got ${params.maxPriceChangeBps}`);
521
+ }
522
+ }
523
+ if (params.minSources < 1 || params.minSources > 10) {
524
+ throw new Error(`minSources must be between 1 and 10, got ${params.minSources}`);
525
+ }
526
+ if (params.maxStalenessSeconds < 5 || params.maxStalenessSeconds > 300) {
527
+ throw new Error(`maxStalenessSeconds must be between 5 and 300, got ${params.maxStalenessSeconds}`);
528
+ }
529
+ const protocol = this.getProtocolAddress();
530
+ const perkOracle = this.getPerkOracleAddress(tokenMint);
531
+ return this.program.methods
532
+ .initializePerkOracle({
533
+ minSources: params.minSources,
534
+ maxStalenessSeconds: params.maxStalenessSeconds,
535
+ maxPriceChangeBps: params.maxPriceChangeBps,
536
+ circuitBreakerDeviationBps: params.circuitBreakerDeviationBps,
537
+ })
538
+ .accounts({
539
+ protocol,
540
+ perkOracle,
541
+ tokenMint,
542
+ oracleAuthority,
543
+ admin: this.wallet.publicKey,
544
+ systemProgram: web3_js_1.SystemProgram.programId,
545
+ })
546
+ .preInstructions(this.preInstructions).rpc();
547
+ }
548
+ /** Update a PerkOracle price feed. Authorized cranker only. */
549
+ async updatePerkOracle(tokenMint, params) {
550
+ const perkOracle = this.getPerkOracleAddress(tokenMint);
551
+ return this.program.methods
552
+ .updatePerkOracle({
553
+ price: params.price,
554
+ confidence: params.confidence,
555
+ numSources: params.numSources,
556
+ })
557
+ .accounts({
558
+ perkOracle,
559
+ authority: this.wallet.publicKey,
560
+ })
561
+ .preInstructions(this.preInstructions).rpc();
562
+ }
563
+ /** Build updatePerkOracle instructions without sending. Used for Jito bundle submission. */
564
+ async buildUpdatePerkOracleIx(tokenMint, params) {
565
+ const perkOracle = this.getPerkOracleAddress(tokenMint);
566
+ const mainIx = await this.program.methods
567
+ .updatePerkOracle({
568
+ price: params.price,
569
+ confidence: params.confidence,
570
+ numSources: params.numSources,
571
+ })
572
+ .accounts({
573
+ perkOracle,
574
+ authority: this.wallet.publicKey,
575
+ })
576
+ .instruction();
577
+ return [...this.preInstructions, mainIx];
578
+ }
579
+ /** Freeze or unfreeze a PerkOracle. Admin only. */
580
+ async freezePerkOracle(tokenMint, frozen) {
581
+ const protocol = this.getProtocolAddress();
582
+ const perkOracle = this.getPerkOracleAddress(tokenMint);
583
+ return this.program.methods
584
+ .freezePerkOracle(frozen)
585
+ .accounts({
586
+ protocol,
587
+ perkOracle,
588
+ admin: this.wallet.publicKey,
589
+ })
590
+ .preInstructions(this.preInstructions).rpc();
591
+ }
592
+ /** Transfer PerkOracle authority. Current authority or admin. */
593
+ async transferOracleAuthority(tokenMint, newAuthority) {
594
+ const protocol = this.getProtocolAddress();
595
+ const perkOracle = this.getPerkOracleAddress(tokenMint);
596
+ return this.program.methods
597
+ .transferOracleAuthority()
598
+ .accounts({
599
+ protocol,
600
+ perkOracle,
601
+ signer: this.wallet.publicKey,
602
+ newAuthority,
603
+ })
604
+ .preInstructions(this.preInstructions).rpc();
605
+ }
606
+ /** Update PerkOracle config (price banding). Admin only. */
607
+ async updateOracleConfig(tokenMint, params) {
608
+ // ── Client-side validation (ATK-01) ──
609
+ // Type safety: reject NaN, Infinity, floats for non-null fields
610
+ for (const [name, val] of Object.entries(params)) {
611
+ if (val !== null && typeof val === 'number' && (!Number.isFinite(val) || !Number.isInteger(val))) {
612
+ throw new Error(`${name} must be null or a finite integer, got ${val}`);
613
+ }
614
+ }
615
+ // u16 range checks
616
+ if (params.circuitBreakerDeviationBps !== null) {
617
+ if (params.circuitBreakerDeviationBps < 0 || params.circuitBreakerDeviationBps > 65535) {
618
+ throw new Error(`circuitBreakerDeviationBps out of u16 range`);
619
+ }
620
+ }
621
+ if (params.maxPriceChangeBps !== null) {
622
+ if (params.maxPriceChangeBps < 0 || params.maxPriceChangeBps > 65535) {
623
+ throw new Error(`maxPriceChangeBps out of u16 range`);
624
+ }
625
+ }
626
+ // Bounds checks
627
+ if (params.circuitBreakerDeviationBps !== null && params.circuitBreakerDeviationBps !== 0) {
628
+ if (params.circuitBreakerDeviationBps < 500 || params.circuitBreakerDeviationBps > 9999) {
629
+ throw new Error(`circuitBreakerDeviationBps must be 0, null, or between 500 and 9999`);
630
+ }
631
+ }
632
+ if (params.maxPriceChangeBps !== null && params.maxPriceChangeBps !== 0) {
633
+ if (params.maxPriceChangeBps < 100 || params.maxPriceChangeBps > 9999) {
634
+ throw new Error(`maxPriceChangeBps must be 0, null, or between 100 and 9999`);
635
+ }
636
+ }
637
+ if (params.minSources !== null) {
638
+ if (params.minSources < 1 || params.minSources > 10) {
639
+ throw new Error(`minSources must be null or between 1 and 10`);
640
+ }
641
+ }
642
+ if (params.maxStalenessSeconds !== null) {
643
+ if (params.maxStalenessSeconds < 5 || params.maxStalenessSeconds > 300) {
644
+ throw new Error(`maxStalenessSeconds must be null or between 5 and 300`);
645
+ }
646
+ }
647
+ const protocol = this.getProtocolAddress();
648
+ const perkOracle = this.getPerkOracleAddress(tokenMint);
649
+ return this.program.methods
650
+ .updateOracleConfig({
651
+ maxPriceChangeBps: params.maxPriceChangeBps,
652
+ minSources: params.minSources,
653
+ maxStalenessSeconds: params.maxStalenessSeconds,
654
+ circuitBreakerDeviationBps: params.circuitBreakerDeviationBps,
655
+ })
656
+ .accounts({
657
+ protocol,
658
+ perkOracle,
659
+ admin: this.wallet.publicKey,
660
+ })
661
+ .preInstructions(this.preInstructions).rpc();
662
+ }
663
+ /** Set or remove fallback oracle on a market. Admin only. */
664
+ async adminSetFallbackOracle(tokenMint, params) {
665
+ const protocol = this.getProtocolAddress();
666
+ const market = this.getMarketAddress(tokenMint);
667
+ // When removing fallback (address = default/zeros), pass SystemProgram as the
668
+ // account since Solana won't accept the null address as a transaction account.
669
+ // The on-chain handler checks params.fallback_oracle_address == default and
670
+ // short-circuits before reading the account, so the sentinel is never dereferenced.
671
+ const isRemoving = params.fallbackOracleAddress.equals(web3_js_1.PublicKey.default);
672
+ const fallbackAccount = isRemoving
673
+ ? web3_js_1.SystemProgram.programId
674
+ : params.fallbackOracleAddress;
675
+ return this.program.methods
676
+ .adminSetFallbackOracle({
677
+ fallbackOracleSource: ORACLE_SOURCE_MAP[params.fallbackOracleSource],
678
+ fallbackOracleAddress: params.fallbackOracleAddress,
679
+ })
680
+ .accounts({
681
+ protocol,
682
+ market,
683
+ fallbackOracle: fallbackAccount,
684
+ admin: this.wallet.publicKey,
685
+ })
686
+ .preInstructions(this.preInstructions).rpc();
687
+ }
688
+ }
689
+ exports.PerkClient = PerkClient;
690
+ //# sourceMappingURL=client.js.map