@soltracer/nft-staking 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,892 @@
1
+ import { BN, Program } from "@coral-xyz/anchor";
2
+ import { PublicKey, SystemProgram } from "@solana/web3.js";
3
+ import { getStakeConfigPda, getStakePoolPda, getStakeEntryPda, getStakerAccountPda, getCollectionPda, getPoolAuthorityPda, getPoolSecondaryRewardsPda, getStakerSecondaryRewardsPda, getProjectPda, getAta, decodeAccount, getFeeConfigPda, getTreasuryPda, PROJECT_MANAGEMENT_PROGRAM_ID, ADMIN_PROGRAM_ID, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, MPL_CORE_PROGRAM_ID, } from "@soltracer/core";
4
+ import NftStakingIDL from "./idl.json";
5
+ /** Well-known program IDs for cNFT operations. */
6
+ const BUBBLEGUM_PROGRAM_ID = new PublicKey("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY");
7
+ const SPL_ACCOUNT_COMPRESSION_PROGRAM_ID = new PublicKey("cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK");
8
+ const SPL_NOOP_PROGRAM_ID = new PublicKey("noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV");
9
+ const TOKEN_METADATA_PROGRAM_ID = new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
10
+ /** Derive the Token Metadata PDA for a given mint. */
11
+ function getMetadataPda(mint) {
12
+ const [pda] = PublicKey.findProgramAddressSync([new TextEncoder().encode("metadata"), TOKEN_METADATA_PROGRAM_ID.toBytes(), mint.toBytes()], TOKEN_METADATA_PROGRAM_ID);
13
+ return pda;
14
+ }
15
+ /** Derive the Master Edition PDA for a given mint. */
16
+ function getEditionPda(mint) {
17
+ const [pda] = PublicKey.findProgramAddressSync([
18
+ new TextEncoder().encode("metadata"),
19
+ TOKEN_METADATA_PROGRAM_ID.toBytes(),
20
+ mint.toBytes(),
21
+ new TextEncoder().encode("edition"),
22
+ ], TOKEN_METADATA_PROGRAM_ID);
23
+ return pda;
24
+ }
25
+ /** Sysvar Instructions address. */
26
+ const SYSVAR_INSTRUCTIONS_ID = new PublicKey("Sysvar1nstructions1111111111111111111111111");
27
+ /** Decode a base58-encoded 32-byte hash into a number array. */
28
+ function base58HashToArray(hash) {
29
+ return Array.from(new PublicKey(hash).toBytes());
30
+ }
31
+ /** Derive the Bubblegum tree config PDA from a merkle tree address. */
32
+ function getTreeConfigPda(merkleTree) {
33
+ return PublicKey.findProgramAddressSync([merkleTree.toBytes()], BUBBLEGUM_PROGRAM_ID);
34
+ }
35
+ export class NftStakingClient {
36
+ program;
37
+ provider;
38
+ tokenProgramCache = new Map();
39
+ mintDecimalsCache = new Map();
40
+ constructor(program, provider) {
41
+ this.program = program;
42
+ this.provider = provider;
43
+ }
44
+ static create(provider) {
45
+ const program = new Program(NftStakingIDL, provider);
46
+ return new NftStakingClient(program, provider);
47
+ }
48
+ static fromIdl(idl, provider) {
49
+ const program = new Program(idl, provider);
50
+ return new NftStakingClient(program, provider);
51
+ }
52
+ /** Detect whether a mint is Token-2022 or SPL Token (cached). */
53
+ async resolveTokenProgram(mint) {
54
+ const key = mint.toBase58();
55
+ const cached = this.tokenProgramCache.get(key);
56
+ if (cached)
57
+ return cached;
58
+ const acct = await this.provider.connection.getAccountInfo(mint);
59
+ const result = acct?.owner.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
60
+ this.tokenProgramCache.set(key, result);
61
+ return result;
62
+ }
63
+ /** Fetch the decimal precision of a token mint (cached). */
64
+ async getMintDecimals(mint) {
65
+ const key = mint.toBase58();
66
+ const cached = this.mintDecimalsCache.get(key);
67
+ if (cached !== undefined)
68
+ return cached;
69
+ const acct = await this.provider.connection.getAccountInfo(mint);
70
+ const decimals = acct ? (acct.data[44] ?? 0) : 0;
71
+ this.mintDecimalsCache.set(key, decimals);
72
+ return decimals;
73
+ }
74
+ /** Convert raw token amounts in a decoded StakePool to human-readable values. */
75
+ applyPoolDecimals(pool, decimals) {
76
+ const divisor = 10 ** decimals;
77
+ return {
78
+ ...pool,
79
+ rewardDecimals: decimals,
80
+ rewardConfig: {
81
+ ...pool.rewardConfig,
82
+ baseRate: pool.rewardConfig.baseRate / divisor,
83
+ },
84
+ totalClaimed: pool.totalClaimed / divisor,
85
+ lockConfigs: pool.lockConfigs.map((lc) => ({
86
+ ...lc,
87
+ rewardRate: lc.rewardRate / divisor,
88
+ })),
89
+ };
90
+ }
91
+ /** Hardcoded Pyth SOL/USD push oracle address (PriceUpdateV2). */
92
+ static PYTH_SOL_USD_FEED = new PublicKey("7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE");
93
+ /** Resolve fee PDA accounts for fee CPI. */
94
+ resolveFeeAccounts(fee) {
95
+ const programId = this.program.programId;
96
+ const [feeConfig] = getFeeConfigPda(programId);
97
+ const [treasury] = getTreasuryPda();
98
+ let referralAccount = ADMIN_PROGRAM_ID;
99
+ if (fee?.referralAccount) {
100
+ try {
101
+ referralAccount = new PublicKey(fee.referralAccount);
102
+ }
103
+ catch {
104
+ }
105
+ }
106
+ return {
107
+ feeConfig,
108
+ treasury,
109
+ referralAccount,
110
+ solUsdPriceFeed: NftStakingClient.PYTH_SOL_USD_FEED,
111
+ adminProgram: ADMIN_PROGRAM_ID,
112
+ };
113
+ }
114
+ async fetchStakeConfig(projectId) {
115
+ const [pda] = getStakeConfigPda(projectId);
116
+ const raw = await this.program.account.stakePoolConfig.fetchNullable(pda);
117
+ if (!raw)
118
+ return null;
119
+ return decodeAccount(raw);
120
+ }
121
+ async fetchStakePool(projectId, poolId) {
122
+ const [pda] = getStakePoolPda(projectId, poolId);
123
+ const raw = await this.program.account.stakePool.fetchNullable(pda);
124
+ if (!raw)
125
+ return null;
126
+ const pool = decodeAccount(raw);
127
+ const decimals = await this.getMintDecimals(new PublicKey(pool.rewardConfig.rewardMint));
128
+ return this.applyPoolDecimals(pool, decimals);
129
+ }
130
+ async fetchStakeEntry(poolId, nftMint) {
131
+ const [pda] = getStakeEntryPda(poolId, nftMint);
132
+ const raw = await this.program.account.stakeEntry.fetchNullable(pda);
133
+ if (!raw)
134
+ return null;
135
+ return decodeAccount(raw);
136
+ }
137
+ async fetchStakerAccount(poolId, wallet, rewardDecimals) {
138
+ const [pda] = getStakerAccountPda(poolId, wallet);
139
+ const raw = await this.program.account.stakerAccount.fetchNullable(pda);
140
+ if (!raw)
141
+ return null;
142
+ const account = decodeAccount(raw);
143
+ if (rewardDecimals !== undefined && rewardDecimals > 0) {
144
+ const divisor = 10 ** rewardDecimals;
145
+ return {
146
+ ...account,
147
+ effectiveRate: account.effectiveRate / divisor,
148
+ accruedRewards: account.accruedRewards / divisor,
149
+ totalClaimed: account.totalClaimed / divisor,
150
+ };
151
+ }
152
+ return account;
153
+ }
154
+ /** Fetch all stake pools for a project by scanning pool IDs 0..totalPools-1. */
155
+ async fetchAllPools(projectId) {
156
+ const config = await this.fetchStakeConfig(projectId);
157
+ if (!config)
158
+ return [];
159
+ const pools = [];
160
+ for (let i = 0; i < config.totalPools; i++) {
161
+ const pool = await this.fetchStakePool(projectId, i);
162
+ if (pool)
163
+ pools.push(pool);
164
+ }
165
+ return pools;
166
+ }
167
+ /** Fetch all active stake entries for a wallet in a specific pool via gPA. */
168
+ async fetchStakeEntriesByOwner(_poolId, owner) {
169
+ const entries = await this.program.account.stakeEntry.all([
170
+ { memcmp: { offset: 72, bytes: owner.toBase58() } },
171
+ ]);
172
+ return entries
173
+ .map(({ account }) => decodeAccount(account))
174
+ .filter((e) => e.isActive);
175
+ }
176
+ /**
177
+ * Fetch active stake entries for specific NFT mints by deriving their PDAs.
178
+ * More reliable than gPA in browser environments.
179
+ */
180
+ async fetchStakeEntriesForMints(poolId, nftMints) {
181
+ if (nftMints.length === 0)
182
+ return [];
183
+ const pdas = nftMints.map((mint) => getStakeEntryPda(poolId, mint)[0]);
184
+ const accounts = await this.program.account.stakeEntry.fetchMultiple(pdas);
185
+ return accounts
186
+ .filter((a) => a !== null)
187
+ .map((a) => decodeAccount(a))
188
+ .filter((e) => e.isActive);
189
+ }
190
+ /** Fetch all stake entries for a pool via gPA with memcmp on pool pubkey. */
191
+ async fetchAllStakeEntriesByPool(projectId, poolId) {
192
+ const [poolPda] = getStakePoolPda(projectId, poolId);
193
+ const entries = await this.program.account.stakeEntry.all([
194
+ { memcmp: { offset: 8, bytes: poolPda.toBase58() } },
195
+ ]);
196
+ return entries.map(({ account }) => decodeAccount(account));
197
+ }
198
+ /**
199
+ * Fetch active stake entries for given mints across multiple pools.
200
+ * Uses PDA derivation + fetchMultiple (reliable in browser).
201
+ */
202
+ async fetchStakeEntriesAcrossPools(poolIds, nftMints) {
203
+ if (nftMints.length === 0 || poolIds.length === 0)
204
+ return [];
205
+ const pdas = [];
206
+ for (const poolId of poolIds) {
207
+ for (const mint of nftMints) {
208
+ pdas.push(getStakeEntryPda(poolId, mint)[0]);
209
+ }
210
+ }
211
+ const accounts = await this.program.account.stakeEntry.fetchMultiple(pdas);
212
+ return accounts
213
+ .filter((a) => a !== null)
214
+ .map((a) => decodeAccount(a))
215
+ .filter((e) => e.isActive);
216
+ }
217
+ /** Fetch all staker accounts for a pool via gPA with memcmp on pool pubkey. */
218
+ async fetchAllStakersByPool(projectId, poolId) {
219
+ const [poolPda] = getStakePoolPda(projectId, poolId);
220
+ const stakers = await this.program.account.stakerAccount.all([
221
+ { memcmp: { offset: 8, bytes: poolPda.toBase58() } },
222
+ ]);
223
+ return stakers.map(({ account }) => decodeAccount(account));
224
+ }
225
+ async closeLegacyCollection(projectId, collectionMint) {
226
+ const [collection] = PublicKey.findProgramAddressSync([Buffer.from("project_collection"), new BN(projectId).toArrayLike(Buffer, "le", 8), collectionMint.toBuffer()], this.program.programId);
227
+ const [project] = getProjectPda(projectId);
228
+ return this.program.methods
229
+ .closeLegacyCollection(new BN(projectId), collectionMint)
230
+ .accountsStrict({
231
+ collection,
232
+ authority: this.provider.wallet.publicKey,
233
+ project,
234
+ projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
235
+ })
236
+ .instruction();
237
+ }
238
+ async initializeStakeConfig(projectId) {
239
+ const [config] = getStakeConfigPda(projectId);
240
+ const [project] = getProjectPda(projectId);
241
+ return this.program.methods
242
+ .initializeStakeConfig(new BN(projectId))
243
+ .accountsStrict({
244
+ config,
245
+ authority: this.provider.wallet.publicKey,
246
+ project,
247
+ projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
248
+ systemProgram: SystemProgram.programId,
249
+ })
250
+ .instruction();
251
+ }
252
+ async createStakePool(projectId, stakingMode, rewardConfig, lockConfigs, collectionMint, rewardEndAt = 0, maxStaked = 0) {
253
+ const config = await this.fetchStakeConfig(projectId);
254
+ const nextPoolId = config ? config.totalPools : 0;
255
+ const tokenProgram = await this.resolveTokenProgram(rewardConfig.rewardMint);
256
+ const decimals = await this.getMintDecimals(rewardConfig.rewardMint);
257
+ const multiplier = 10 ** decimals;
258
+ const rawRewardConfig = {
259
+ rewardType: rewardConfig.rewardType,
260
+ rewardMint: rewardConfig.rewardMint,
261
+ baseRate: new BN(Math.round(rewardConfig.baseRate * multiplier)),
262
+ rateInterval: new BN(rewardConfig.rateInterval),
263
+ traitBonusMode: rewardConfig.traitBonusMode,
264
+ quantityThresholds: rewardConfig.quantityThresholds,
265
+ };
266
+ const rawLockConfigs = lockConfigs.map((lc) => ({
267
+ lockDuration: new BN(lc.lockDuration),
268
+ rewardRate: new BN(Math.round(lc.rewardRate * multiplier)),
269
+ earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
270
+ }));
271
+ const [configPda] = getStakeConfigPda(projectId);
272
+ const [poolPda] = getStakePoolPda(projectId, nextPoolId);
273
+ const [collectionConfigPda] = getCollectionPda(projectId, collectionMint);
274
+ const [poolAuthority] = getPoolAuthorityPda(nextPoolId);
275
+ const rewardVault = getAta(poolAuthority, rewardConfig.rewardMint, tokenProgram);
276
+ return this.program.methods
277
+ .createStakePool(new BN(projectId), stakingMode, rawRewardConfig, rawLockConfigs, new BN(rewardEndAt), new BN(maxStaked))
278
+ .accountsStrict({
279
+ config: configPda,
280
+ pool: poolPda,
281
+ collectionConfig: collectionConfigPda,
282
+ poolAuthority,
283
+ collectionMint,
284
+ rewardMint: rewardConfig.rewardMint,
285
+ rewardVault,
286
+ projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
287
+ authority: this.provider.wallet.publicKey,
288
+ tokenProgram,
289
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
290
+ systemProgram: SystemProgram.programId,
291
+ })
292
+ .instruction();
293
+ }
294
+ async updateStakePool(projectId, poolId, rewardConfig, lockConfigs, isActive, traitAuthority = null, canBurn = null, merkleRoot = null, gateType = null, rewardEndAt = null, maxStaked = null) {
295
+ let rawRewardConfig = null;
296
+ let rawLockConfigs = null;
297
+ if (rewardConfig) {
298
+ const decimals = await this.getMintDecimals(rewardConfig.rewardMint);
299
+ const multiplier = 10 ** decimals;
300
+ rawRewardConfig = {
301
+ rewardType: rewardConfig.rewardType,
302
+ rewardMint: rewardConfig.rewardMint,
303
+ baseRate: new BN(Math.round(rewardConfig.baseRate * multiplier)),
304
+ rateInterval: new BN(rewardConfig.rateInterval),
305
+ traitBonusMode: rewardConfig.traitBonusMode,
306
+ quantityThresholds: rewardConfig.quantityThresholds,
307
+ };
308
+ }
309
+ if (lockConfigs && rewardConfig) {
310
+ const decimals = await this.getMintDecimals(rewardConfig.rewardMint);
311
+ const multiplier = 10 ** decimals;
312
+ rawLockConfigs = lockConfigs.map((lc) => ({
313
+ lockDuration: new BN(lc.lockDuration),
314
+ rewardRate: new BN(Math.round(lc.rewardRate * multiplier)),
315
+ earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
316
+ }));
317
+ }
318
+ else if (lockConfigs) {
319
+ const pool = await this.fetchStakePool(projectId, poolId);
320
+ const decimals = pool ? pool.rewardDecimals : 0;
321
+ const multiplier = 10 ** decimals;
322
+ rawLockConfigs = lockConfigs.map((lc) => ({
323
+ lockDuration: new BN(lc.lockDuration),
324
+ rewardRate: new BN(Math.round(lc.rewardRate * multiplier)),
325
+ earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
326
+ }));
327
+ }
328
+ const [poolPda] = getStakePoolPda(projectId, poolId);
329
+ return this.program.methods
330
+ .updateStakePool(new BN(projectId), new BN(poolId), rawRewardConfig, rawLockConfigs, isActive, traitAuthority, canBurn, merkleRoot, gateType, rewardEndAt, maxStaked)
331
+ .accountsStrict({
332
+ pool: poolPda,
333
+ authority: this.provider.wallet.publicKey,
334
+ })
335
+ .instruction();
336
+ }
337
+ async fundRewardVault(projectId, poolId, amount, rewardMint, funderTokenAccount) {
338
+ const tokenProgram = await this.resolveTokenProgram(rewardMint);
339
+ const [poolPda] = getStakePoolPda(projectId, poolId);
340
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
341
+ const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
342
+ const funderAta = funderTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
343
+ return this.program.methods
344
+ .fundRewardVault(new BN(projectId), new BN(poolId), amount)
345
+ .accountsStrict({
346
+ pool: poolPda,
347
+ poolAuthority,
348
+ rewardVault,
349
+ funderTokenAccount: funderAta,
350
+ rewardMint,
351
+ authority: this.provider.wallet.publicKey,
352
+ tokenProgram,
353
+ })
354
+ .instruction();
355
+ }
356
+ async withdrawRewardVault(projectId, poolId, amount, rewardMint, destinationTokenAccount) {
357
+ const tokenProgram = await this.resolveTokenProgram(rewardMint);
358
+ const [poolPda] = getStakePoolPda(projectId, poolId);
359
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
360
+ const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
361
+ const destAta = destinationTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
362
+ return this.program.methods
363
+ .withdrawRewardVault(new BN(projectId), new BN(poolId), amount)
364
+ .accountsStrict({
365
+ pool: poolPda,
366
+ poolAuthority,
367
+ rewardVault,
368
+ destinationTokenAccount: destAta,
369
+ rewardMint,
370
+ authority: this.provider.wallet.publicKey,
371
+ tokenProgram,
372
+ })
373
+ .instruction();
374
+ }
375
+ async closeStakePool(projectId, poolId) {
376
+ const [poolPda] = getStakePoolPda(projectId, poolId);
377
+ return this.program.methods
378
+ .closeStakePool(new BN(projectId), new BN(poolId))
379
+ .accountsStrict({
380
+ pool: poolPda,
381
+ authority: this.provider.wallet.publicKey,
382
+ })
383
+ .instruction();
384
+ }
385
+ async stakeNft(projectId, poolId, nftMint, stakingMode, lockTierIndex, fee, gateProof = []) {
386
+ const [poolPda] = getStakePoolPda(projectId, poolId);
387
+ const [stakeEntry] = getStakeEntryPda(poolId, nftMint);
388
+ const staker = this.provider.wallet.publicKey;
389
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
390
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
391
+ const nftTokenProgram = await this.resolveTokenProgram(nftMint);
392
+ const stakerNftAccount = getAta(staker, nftMint, nftTokenProgram);
393
+ const isEscrow = stakingMode !== 1;
394
+ const escrowNftAccount = isEscrow
395
+ ? getAta(poolAuthority, nftMint, nftTokenProgram)
396
+ : null;
397
+ const nftMetadata = getMetadataPda(nftMint);
398
+ const nftEdition = getEditionPda(nftMint);
399
+ const feeAccts = this.resolveFeeAccounts(fee);
400
+ return this.program.methods
401
+ .stakeNft(new BN(projectId), new BN(poolId), nftMint, lockTierIndex, gateProof)
402
+ .accountsStrict({
403
+ pool: poolPda,
404
+ stakeEntry,
405
+ stakerAccount,
406
+ poolAuthority,
407
+ nftMint,
408
+ stakerNftAccount,
409
+ escrowNftAccount,
410
+ nftMetadata,
411
+ nftEdition,
412
+ mplTokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
413
+ sysvarInstructions: SYSVAR_INSTRUCTIONS_ID,
414
+ feeConfig: feeAccts.feeConfig,
415
+ treasury: feeAccts.treasury,
416
+ referralAccount: feeAccts.referralAccount,
417
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
418
+ adminProgram: feeAccts.adminProgram,
419
+ staker,
420
+ tokenProgram: nftTokenProgram,
421
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
422
+ systemProgram: SystemProgram.programId,
423
+ })
424
+ .instruction();
425
+ }
426
+ async unstakeNft(projectId, poolId, nftMint, rewardMint, stakingMode, fee) {
427
+ const rewardTokenProgram = await this.resolveTokenProgram(rewardMint);
428
+ const nftTokenProgram = await this.resolveTokenProgram(nftMint);
429
+ const [poolPda] = getStakePoolPda(projectId, poolId);
430
+ const [stakeEntry] = getStakeEntryPda(poolId, nftMint);
431
+ const staker = this.provider.wallet.publicKey;
432
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
433
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
434
+ const stakerNftAccount = getAta(staker, nftMint, nftTokenProgram);
435
+ const isEscrow = stakingMode !== 1;
436
+ const escrowNftAccount = isEscrow
437
+ ? getAta(poolAuthority, nftMint, nftTokenProgram)
438
+ : null;
439
+ const rewardVault = getAta(poolAuthority, rewardMint, rewardTokenProgram);
440
+ const stakerRewardAccount = getAta(staker, rewardMint, rewardTokenProgram);
441
+ const nftMetadata = getMetadataPda(nftMint);
442
+ const nftEdition = getEditionPda(nftMint);
443
+ const feeAccts = this.resolveFeeAccounts(fee);
444
+ return this.program.methods
445
+ .unstakeNft(new BN(projectId), new BN(poolId), nftMint)
446
+ .accountsStrict({
447
+ pool: poolPda,
448
+ stakeEntry,
449
+ stakerAccount,
450
+ poolAuthority,
451
+ nftMint,
452
+ stakerNftAccount,
453
+ escrowNftAccount,
454
+ rewardVault,
455
+ stakerRewardAccount,
456
+ rewardMint,
457
+ nftMetadata,
458
+ nftEdition,
459
+ mplTokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
460
+ sysvarInstructions: SYSVAR_INSTRUCTIONS_ID,
461
+ feeConfig: feeAccts.feeConfig,
462
+ treasury: feeAccts.treasury,
463
+ referralAccount: feeAccts.referralAccount,
464
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
465
+ adminProgram: feeAccts.adminProgram,
466
+ staker,
467
+ tokenProgram: nftTokenProgram,
468
+ rewardTokenProgram,
469
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
470
+ systemProgram: SystemProgram.programId,
471
+ })
472
+ .instruction();
473
+ }
474
+ async claimRewards(projectId, poolId, rewardMint, traitBonusRate = null, fee) {
475
+ const tokenProgram = await this.resolveTokenProgram(rewardMint);
476
+ const [poolPda] = getStakePoolPda(projectId, poolId);
477
+ const staker = this.provider.wallet.publicKey;
478
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
479
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
480
+ const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
481
+ const stakerRewardAccount = getAta(staker, rewardMint, tokenProgram);
482
+ const [stakeConfig] = getStakeConfigPda(projectId);
483
+ const feeAccts = this.resolveFeeAccounts(fee);
484
+ return this.program.methods
485
+ .claimRewards(new BN(projectId), new BN(poolId), traitBonusRate)
486
+ .accountsStrict({
487
+ pool: poolPda,
488
+ stakerAccount,
489
+ poolAuthority,
490
+ rewardVault,
491
+ stakerRewardAccount,
492
+ rewardMint,
493
+ stakeConfig,
494
+ instructionsSysvar: new PublicKey("Sysvar1nstructions1111111111111111111111111"),
495
+ feeConfig: feeAccts.feeConfig,
496
+ treasury: feeAccts.treasury,
497
+ referralAccount: feeAccts.referralAccount,
498
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
499
+ adminProgram: feeAccts.adminProgram,
500
+ staker,
501
+ tokenProgram,
502
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
503
+ systemProgram: SystemProgram.programId,
504
+ })
505
+ .instruction();
506
+ }
507
+ async stakeCoreNft(projectId, poolId, nftAsset, collection, lockTierIndex, fee, gateProof = []) {
508
+ const [poolPda] = getStakePoolPda(projectId, poolId);
509
+ const [stakeEntry] = getStakeEntryPda(poolId, nftAsset);
510
+ const staker = this.provider.wallet.publicKey;
511
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
512
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
513
+ const feeAccts = this.resolveFeeAccounts(fee);
514
+ return this.program.methods
515
+ .stakeCoreNft(new BN(projectId), new BN(poolId), nftAsset, lockTierIndex, gateProof)
516
+ .accountsStrict({
517
+ pool: poolPda,
518
+ stakeEntry,
519
+ stakerAccount,
520
+ poolAuthority,
521
+ nftAsset,
522
+ collection,
523
+ mplCoreProgram: MPL_CORE_PROGRAM_ID,
524
+ feeConfig: feeAccts.feeConfig,
525
+ treasury: feeAccts.treasury,
526
+ referralAccount: feeAccts.referralAccount,
527
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
528
+ adminProgram: feeAccts.adminProgram,
529
+ staker,
530
+ systemProgram: SystemProgram.programId,
531
+ })
532
+ .instruction();
533
+ }
534
+ async unstakeCoreNft(projectId, poolId, nftAsset, collection, rewardMint, fee) {
535
+ const rewardTokenProgram = await this.resolveTokenProgram(rewardMint);
536
+ const [poolPda] = getStakePoolPda(projectId, poolId);
537
+ const [stakeEntry] = getStakeEntryPda(poolId, nftAsset);
538
+ const staker = this.provider.wallet.publicKey;
539
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
540
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
541
+ const rewardVault = getAta(poolAuthority, rewardMint, rewardTokenProgram);
542
+ const stakerRewardAccount = getAta(staker, rewardMint, rewardTokenProgram);
543
+ const feeAccts = this.resolveFeeAccounts(fee);
544
+ return this.program.methods
545
+ .unstakeCoreNft(new BN(projectId), new BN(poolId), nftAsset)
546
+ .accountsStrict({
547
+ pool: poolPda,
548
+ stakeEntry,
549
+ stakerAccount,
550
+ poolAuthority,
551
+ nftAsset,
552
+ collection,
553
+ mplCoreProgram: MPL_CORE_PROGRAM_ID,
554
+ rewardVault,
555
+ stakerRewardAccount,
556
+ rewardMint,
557
+ feeConfig: feeAccts.feeConfig,
558
+ treasury: feeAccts.treasury,
559
+ referralAccount: feeAccts.referralAccount,
560
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
561
+ adminProgram: feeAccts.adminProgram,
562
+ staker,
563
+ rewardTokenProgram,
564
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
565
+ systemProgram: SystemProgram.programId,
566
+ })
567
+ .instruction();
568
+ }
569
+ /** Stake a compressed NFT (cNFT). All address/hash params accepted as strings. */
570
+ async stakeCnft(projectId, poolId, nftAssetId, merkleTree, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes, fee, gateProof = []) {
571
+ const nftAssetIdPk = new PublicKey(nftAssetId);
572
+ const merkleTreePk = new PublicKey(merkleTree);
573
+ const [treeConfigPk] = getTreeConfigPda(merkleTreePk);
574
+ const cnftRootArr = base58HashToArray(cnftRoot);
575
+ const cnftDataHashArr = base58HashToArray(cnftDataHash);
576
+ const cnftCreatorHashArr = base58HashToArray(cnftCreatorHash);
577
+ const [poolPda] = getStakePoolPda(projectId, poolId);
578
+ const [stakeEntry] = getStakeEntryPda(poolId, nftAssetIdPk);
579
+ const staker = this.provider.wallet.publicKey;
580
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
581
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
582
+ const feeAccts = this.resolveFeeAccounts(fee);
583
+ const remainingAccounts = proofNodes.map((node) => ({
584
+ pubkey: new PublicKey(node),
585
+ isSigner: false,
586
+ isWritable: false,
587
+ }));
588
+ return this.program.methods
589
+ .stakeCnft(new BN(projectId), new BN(poolId), nftAssetIdPk, cnftRootArr, cnftDataHashArr, cnftCreatorHashArr, new BN(cnftNonce), cnftIndex, gateProof)
590
+ .accountsStrict({
591
+ pool: poolPda,
592
+ stakeEntry,
593
+ stakerAccount,
594
+ poolAuthority,
595
+ treeConfig: treeConfigPk,
596
+ merkleTree: merkleTreePk,
597
+ logWrapper: SPL_NOOP_PROGRAM_ID,
598
+ compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
599
+ bubblegumProgram: BUBBLEGUM_PROGRAM_ID,
600
+ feeConfig: feeAccts.feeConfig,
601
+ treasury: feeAccts.treasury,
602
+ referralAccount: feeAccts.referralAccount,
603
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
604
+ adminProgram: feeAccts.adminProgram,
605
+ staker,
606
+ systemProgram: SystemProgram.programId,
607
+ })
608
+ .remainingAccounts(remainingAccounts)
609
+ .instruction();
610
+ }
611
+ /** Unstake a compressed NFT (cNFT). Derives tree config and program IDs internally. */
612
+ async unstakeCnft(projectId, poolId, nftAssetId, merkleTree, rewardMint, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes, fee) {
613
+ const nftAssetIdPk = new PublicKey(nftAssetId);
614
+ const merkleTreePk = new PublicKey(merkleTree);
615
+ const [treeConfigPk] = getTreeConfigPda(merkleTreePk);
616
+ const rewardMintPk = new PublicKey(rewardMint);
617
+ const cnftRootArr = base58HashToArray(cnftRoot);
618
+ const cnftDataHashArr = base58HashToArray(cnftDataHash);
619
+ const cnftCreatorHashArr = base58HashToArray(cnftCreatorHash);
620
+ const [poolPda] = getStakePoolPda(projectId, poolId);
621
+ const [stakeEntry] = getStakeEntryPda(poolId, nftAssetIdPk);
622
+ const staker = this.provider.wallet.publicKey;
623
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
624
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
625
+ const feeAccts = this.resolveFeeAccounts(fee);
626
+ const rewardVaultPk = getAta(poolAuthority, rewardMintPk);
627
+ const stakerRewardAccountPk = getAta(staker, rewardMintPk);
628
+ const remainingAccounts = proofNodes.map((node) => ({
629
+ pubkey: new PublicKey(node),
630
+ isSigner: false,
631
+ isWritable: false,
632
+ }));
633
+ return this.program.methods
634
+ .unstakeCnft(new BN(projectId), new BN(poolId), nftAssetIdPk, cnftRootArr, cnftDataHashArr, cnftCreatorHashArr, new BN(cnftNonce), cnftIndex)
635
+ .accountsStrict({
636
+ pool: poolPda,
637
+ stakeEntry,
638
+ stakerAccount,
639
+ poolAuthority,
640
+ treeConfig: treeConfigPk,
641
+ merkleTree: merkleTreePk,
642
+ logWrapper: SPL_NOOP_PROGRAM_ID,
643
+ compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
644
+ bubblegumProgram: BUBBLEGUM_PROGRAM_ID,
645
+ rewardVault: rewardVaultPk,
646
+ stakerRewardAccount: stakerRewardAccountPk,
647
+ rewardMint: rewardMintPk,
648
+ feeConfig: feeAccts.feeConfig,
649
+ treasury: feeAccts.treasury,
650
+ referralAccount: feeAccts.referralAccount,
651
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
652
+ adminProgram: feeAccts.adminProgram,
653
+ staker,
654
+ tokenProgram: TOKEN_PROGRAM_ID,
655
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
656
+ systemProgram: SystemProgram.programId,
657
+ })
658
+ .remainingAccounts(remainingAccounts)
659
+ .instruction();
660
+ }
661
+ /** Burn a permanently-locked legacy/pNFT. Destroys the NFT and closes the stake entry.
662
+ * For Escrow mode, burns from escrow. For WalletLock, unfreezes then burns from wallet. */
663
+ async burnStakedNft(projectId, poolId, nftMint, stakingMode, fee) {
664
+ const nftTokenProgram = await this.resolveTokenProgram(nftMint);
665
+ const [poolPda] = getStakePoolPda(projectId, poolId);
666
+ const [stakeEntry] = getStakeEntryPda(poolId, nftMint);
667
+ const staker = this.provider.wallet.publicKey;
668
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
669
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
670
+ const feeAccts = this.resolveFeeAccounts(fee);
671
+ const isWalletLock = stakingMode === 1;
672
+ const escrowNftAccount = isWalletLock ? null : getAta(poolAuthority, nftMint, nftTokenProgram);
673
+ const stakerNftAccount = isWalletLock ? getAta(staker, nftMint, nftTokenProgram) : null;
674
+ const nftMetadata = isWalletLock ? getMetadataPda(nftMint) : null;
675
+ const nftEdition = isWalletLock ? getEditionPda(nftMint) : null;
676
+ return this.program.methods
677
+ .burnStakedNft(new BN(projectId), new BN(poolId), nftMint)
678
+ .accountsStrict({
679
+ pool: poolPda,
680
+ stakeEntry,
681
+ stakerAccount,
682
+ poolAuthority,
683
+ nftMint,
684
+ escrowNftAccount,
685
+ stakerNftAccount,
686
+ nftMetadata,
687
+ nftEdition,
688
+ mplTokenMetadataProgram: isWalletLock ? TOKEN_METADATA_PROGRAM_ID : null,
689
+ sysvarInstructions: isWalletLock ? SYSVAR_INSTRUCTIONS_ID : null,
690
+ feeConfig: feeAccts.feeConfig,
691
+ treasury: feeAccts.treasury,
692
+ referralAccount: feeAccts.referralAccount,
693
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
694
+ adminProgram: feeAccts.adminProgram,
695
+ staker,
696
+ tokenProgram: nftTokenProgram,
697
+ systemProgram: SystemProgram.programId,
698
+ })
699
+ .instruction();
700
+ }
701
+ /** Burn a permanently-locked Core NFT. Destroys the asset and closes the stake entry. */
702
+ async burnStakedCoreNft(projectId, poolId, nftAsset, collection, fee) {
703
+ const [poolPda] = getStakePoolPda(projectId, poolId);
704
+ const [stakeEntry] = getStakeEntryPda(poolId, nftAsset);
705
+ const staker = this.provider.wallet.publicKey;
706
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
707
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
708
+ const feeAccts = this.resolveFeeAccounts(fee);
709
+ return this.program.methods
710
+ .burnStakedCoreNft(new BN(projectId), new BN(poolId), nftAsset)
711
+ .accountsStrict({
712
+ pool: poolPda,
713
+ stakeEntry,
714
+ stakerAccount,
715
+ poolAuthority,
716
+ nftAsset,
717
+ collection,
718
+ mplCoreProgram: MPL_CORE_PROGRAM_ID,
719
+ feeConfig: feeAccts.feeConfig,
720
+ treasury: feeAccts.treasury,
721
+ referralAccount: feeAccts.referralAccount,
722
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
723
+ adminProgram: feeAccts.adminProgram,
724
+ staker,
725
+ systemProgram: SystemProgram.programId,
726
+ })
727
+ .instruction();
728
+ }
729
+ /** Spend accrued points from a Points reward type pool. */
730
+ async spendPoints(projectId, poolId, amount, fee) {
731
+ const [poolPda] = getStakePoolPda(projectId, poolId);
732
+ const staker = this.provider.wallet.publicKey;
733
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
734
+ const feeAccts = this.resolveFeeAccounts(fee);
735
+ return this.program.methods
736
+ .spendPoints(new BN(projectId), new BN(poolId), amount)
737
+ .accountsStrict({
738
+ pool: poolPda,
739
+ stakerAccount,
740
+ feeConfig: feeAccts.feeConfig,
741
+ treasury: feeAccts.treasury,
742
+ referralAccount: feeAccts.referralAccount,
743
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
744
+ adminProgram: feeAccts.adminProgram,
745
+ staker,
746
+ systemProgram: SystemProgram.programId,
747
+ })
748
+ .instruction();
749
+ }
750
+ async fetchPoolSecondaryRewards(poolId) {
751
+ const [pda] = getPoolSecondaryRewardsPda(poolId);
752
+ const raw = await this.program.account.poolSecondaryRewards.fetchNullable(pda);
753
+ if (!raw)
754
+ return null;
755
+ return decodeAccount(raw);
756
+ }
757
+ async fetchStakerSecondaryRewards(poolId, wallet) {
758
+ const [pda] = getStakerSecondaryRewardsPda(poolId, wallet);
759
+ const raw = await this.program.account.stakerSecondaryRewards.fetchNullable(pda);
760
+ if (!raw)
761
+ return null;
762
+ return decodeAccount(raw);
763
+ }
764
+ async addPoolSecondaryReward(projectId, poolId, rewardMint, baseRate, lockTierRates) {
765
+ const [poolPda] = getStakePoolPda(projectId, poolId);
766
+ const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
767
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
768
+ const tokenProgram = await this.resolveTokenProgram(rewardMint);
769
+ const vault = getAta(poolAuthority, rewardMint, tokenProgram);
770
+ return this.program.methods
771
+ .addPoolSecondaryReward(new BN(projectId), new BN(poolId), baseRate, lockTierRates)
772
+ .accountsStrict({
773
+ pool: poolPda,
774
+ poolSecondary,
775
+ poolAuthority,
776
+ secondaryRewardMint: rewardMint,
777
+ secondaryVault: vault,
778
+ authority: this.provider.wallet.publicKey,
779
+ tokenProgram,
780
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
781
+ systemProgram: SystemProgram.programId,
782
+ })
783
+ .instruction();
784
+ }
785
+ async removePoolSecondaryReward(projectId, poolId, rewardIndex) {
786
+ const [poolPda] = getStakePoolPda(projectId, poolId);
787
+ const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
788
+ return this.program.methods
789
+ .removePoolSecondaryReward(new BN(projectId), new BN(poolId), rewardIndex)
790
+ .accountsStrict({
791
+ pool: poolPda,
792
+ poolSecondary,
793
+ authority: this.provider.wallet.publicKey,
794
+ })
795
+ .instruction();
796
+ }
797
+ async fundSecondaryVault(projectId, poolId, rewardIndex, amount, rewardMint, funderTokenAccount) {
798
+ const tokenProgram = await this.resolveTokenProgram(rewardMint);
799
+ const [poolPda] = getStakePoolPda(projectId, poolId);
800
+ const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
801
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
802
+ const vault = getAta(poolAuthority, rewardMint, tokenProgram);
803
+ const funderAta = funderTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
804
+ return this.program.methods
805
+ .fundSecondaryVault(new BN(projectId), new BN(poolId), rewardIndex, amount)
806
+ .accountsStrict({
807
+ pool: poolPda,
808
+ poolSecondary,
809
+ poolAuthority,
810
+ secondaryVault: vault,
811
+ funderTokenAccount: funderAta,
812
+ secondaryRewardMint: rewardMint,
813
+ authority: this.provider.wallet.publicKey,
814
+ tokenProgram,
815
+ })
816
+ .instruction();
817
+ }
818
+ async withdrawSecondaryVault(projectId, poolId, rewardIndex, amount, rewardMint, destinationTokenAccount) {
819
+ const tokenProgram = await this.resolveTokenProgram(rewardMint);
820
+ const [poolPda] = getStakePoolPda(projectId, poolId);
821
+ const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
822
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
823
+ const vault = getAta(poolAuthority, rewardMint, tokenProgram);
824
+ const destAta = destinationTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
825
+ return this.program.methods
826
+ .withdrawSecondaryVault(new BN(projectId), new BN(poolId), rewardIndex, amount)
827
+ .accountsStrict({
828
+ pool: poolPda,
829
+ poolSecondary,
830
+ poolAuthority,
831
+ secondaryVault: vault,
832
+ destinationTokenAccount: destAta,
833
+ secondaryRewardMint: rewardMint,
834
+ authority: this.provider.wallet.publicKey,
835
+ tokenProgram,
836
+ })
837
+ .instruction();
838
+ }
839
+ async initStakerSecondary(projectId, poolId) {
840
+ const [poolPda] = getStakePoolPda(projectId, poolId);
841
+ const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
842
+ const staker = this.provider.wallet.publicKey;
843
+ const [stakerSecondary] = getStakerSecondaryRewardsPda(poolId, staker);
844
+ return this.program.methods
845
+ .initStakerSecondary(new BN(projectId), new BN(poolId))
846
+ .accountsStrict({
847
+ pool: poolPda,
848
+ poolSecondary,
849
+ stakerSecondary,
850
+ staker,
851
+ systemProgram: SystemProgram.programId,
852
+ })
853
+ .instruction();
854
+ }
855
+ async claimSecondaryRewards(projectId, poolId, secondaryRewards, fee) {
856
+ const [poolPda] = getStakePoolPda(projectId, poolId);
857
+ const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
858
+ const staker = this.provider.wallet.publicKey;
859
+ const [stakerAccount] = getStakerAccountPda(poolId, staker);
860
+ const [stakerSecondary] = getStakerSecondaryRewardsPda(poolId, staker);
861
+ const [poolAuthority] = getPoolAuthorityPda(poolId);
862
+ const feeAccts = this.resolveFeeAccounts(fee);
863
+ const remainingAccounts = [];
864
+ for (const sr of secondaryRewards) {
865
+ const tokenProgram = await this.resolveTokenProgram(sr.mint);
866
+ const vault = getAta(poolAuthority, sr.mint, tokenProgram);
867
+ const stakerAta = getAta(staker, sr.mint, tokenProgram);
868
+ remainingAccounts.push({ pubkey: vault, isSigner: false, isWritable: true }, { pubkey: stakerAta, isSigner: false, isWritable: true }, { pubkey: sr.mint, isSigner: false, isWritable: false }, { pubkey: tokenProgram, isSigner: false, isWritable: false });
869
+ }
870
+ return this.program.methods
871
+ .claimSecondaryRewards(new BN(projectId), new BN(poolId))
872
+ .accountsStrict({
873
+ pool: poolPda,
874
+ poolSecondary,
875
+ stakerAccount,
876
+ stakerSecondary,
877
+ poolAuthority,
878
+ feeConfig: feeAccts.feeConfig,
879
+ treasury: feeAccts.treasury,
880
+ referralAccount: feeAccts.referralAccount,
881
+ solUsdPriceFeed: feeAccts.solUsdPriceFeed,
882
+ adminProgram: feeAccts.adminProgram,
883
+ staker,
884
+ tokenProgram: TOKEN_PROGRAM_ID,
885
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
886
+ systemProgram: SystemProgram.programId,
887
+ })
888
+ .remainingAccounts(remainingAccounts)
889
+ .instruction();
890
+ }
891
+ }
892
+ //# sourceMappingURL=client.js.map