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.d.ts +110 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +690 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +59 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +71 -0
- package/dist/constants.js.map +1 -0
- package/dist/cranker.d.ts +71 -0
- package/dist/cranker.d.ts.map +1 -0
- package/dist/cranker.js +441 -0
- package/dist/cranker.js.map +1 -0
- package/dist/idl.json +3740 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/math.d.ts +60 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +330 -0
- package/dist/math.js.map +1 -0
- package/dist/oracle-cranker.d.ts +72 -0
- package/dist/oracle-cranker.d.ts.map +1 -0
- package/dist/oracle-cranker.js +434 -0
- package/dist/oracle-cranker.js.map +1 -0
- package/dist/pda.d.ts +15 -0
- package/dist/pda.d.ts.map +1 -0
- package/dist/pda.js +45 -0
- package/dist/pda.js.map +1 -0
- package/dist/types.d.ts +252 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -0
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
|