@soltracer/nft-staking 0.1.0 → 0.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BN, Program } from "@coral-xyz/anchor";
2
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";
3
+ import { getStakeConfigPda, getStakePoolPda, getStakeEntryPda, getStakerAccountPda, getCollectionPda, getPoolAuthorityPda, getPoolSecondaryRewardsPda, getStakerSecondaryRewardsPda, getProjectPda, getUtilityConfigPda, 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
4
  import NftStakingIDL from "./idl.json";
5
5
  /** Well-known program IDs for cNFT operations. */
6
6
  const BUBBLEGUM_PROGRAM_ID = new PublicKey("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY");
@@ -37,17 +37,59 @@ export class NftStakingClient {
37
37
  provider;
38
38
  tokenProgramCache = new Map();
39
39
  mintDecimalsCache = new Map();
40
- constructor(program, provider) {
40
+ poolCache = new Map();
41
+ static POOL_CACHE_TTL = 30_000; // 30s
42
+ /** Project ID bound to this client. Set via `create()` options or `setProjectId()`. */
43
+ projectId;
44
+ constructor(program, provider, projectId) {
41
45
  this.program = program;
42
46
  this.provider = provider;
47
+ this.projectId = projectId;
43
48
  }
44
- static create(provider) {
49
+ static create(provider, opts) {
45
50
  const program = new Program(NftStakingIDL, provider);
46
- return new NftStakingClient(program, provider);
51
+ return new NftStakingClient(program, provider, opts?.projectId);
47
52
  }
48
- static fromIdl(idl, provider) {
53
+ static fromIdl(idl, provider, opts) {
49
54
  const program = new Program(idl, provider);
50
- return new NftStakingClient(program, provider);
55
+ return new NftStakingClient(program, provider, opts?.projectId);
56
+ }
57
+ /** Set the project ID for this client instance. Returns `this` for chaining. */
58
+ setProjectId(projectId) {
59
+ this.projectId = projectId;
60
+ return this;
61
+ }
62
+ /** Resolve projectId: use explicit param if given, else fall back to instance-level. Throws if neither is set. */
63
+ resolveProjectId(projectId) {
64
+ const pid = projectId ?? this.projectId;
65
+ if (pid === undefined)
66
+ throw new Error("projectId required: pass it explicitly or set via create()/setProjectId()");
67
+ return pid;
68
+ }
69
+ /** Fetch pool data with caching. Used internally to auto-resolve rewardMint, stakingMode, collectionMint. */
70
+ async getPoolData(projectId, poolId) {
71
+ const key = `${projectId}-${poolId}`;
72
+ const cached = this.poolCache.get(key);
73
+ if (cached && Date.now() - cached.fetchedAt < NftStakingClient.POOL_CACHE_TTL) {
74
+ return cached.data;
75
+ }
76
+ const pool = await this.fetchStakePool(projectId, poolId);
77
+ if (!pool)
78
+ throw new Error(`Pool ${poolId} not found for project ${projectId}`);
79
+ this.poolCache.set(key, { data: pool, fetchedAt: Date.now() });
80
+ return pool;
81
+ }
82
+ /** Invalidate cached pool data (call after pool mutations). */
83
+ invalidatePoolCache(poolId) {
84
+ if (poolId !== undefined) {
85
+ for (const key of this.poolCache.keys()) {
86
+ if (key.endsWith(`-${poolId}`))
87
+ this.poolCache.delete(key);
88
+ }
89
+ }
90
+ else {
91
+ this.poolCache.clear();
92
+ }
51
93
  }
52
94
  /** Detect whether a mint is Token-2022 or SPL Token (cached). */
53
95
  async resolveTokenProgram(mint) {
@@ -112,14 +154,16 @@ export class NftStakingClient {
112
154
  };
113
155
  }
114
156
  async fetchStakeConfig(projectId) {
115
- const [pda] = getStakeConfigPda(projectId);
157
+ const pid = this.resolveProjectId(projectId);
158
+ const [pda] = getStakeConfigPda(pid);
116
159
  const raw = await this.program.account.stakePoolConfig.fetchNullable(pda);
117
160
  if (!raw)
118
161
  return null;
119
162
  return decodeAccount(raw);
120
163
  }
121
164
  async fetchStakePool(projectId, poolId) {
122
- const [pda] = getStakePoolPda(projectId, poolId);
165
+ const pid = this.resolveProjectId(projectId);
166
+ const [pda] = getStakePoolPda(pid, poolId);
123
167
  const raw = await this.program.account.stakePool.fetchNullable(pda);
124
168
  if (!raw)
125
169
  return null;
@@ -153,12 +197,13 @@ export class NftStakingClient {
153
197
  }
154
198
  /** Fetch all stake pools for a project by scanning pool IDs 0..totalPools-1. */
155
199
  async fetchAllPools(projectId) {
156
- const config = await this.fetchStakeConfig(projectId);
200
+ const pid = this.resolveProjectId(projectId);
201
+ const config = await this.fetchStakeConfig(pid);
157
202
  if (!config)
158
203
  return [];
159
204
  const pools = [];
160
205
  for (let i = 0; i < config.totalPools; i++) {
161
- const pool = await this.fetchStakePool(projectId, i);
206
+ const pool = await this.fetchStakePool(pid, i);
162
207
  if (pool)
163
208
  pools.push(pool);
164
209
  }
@@ -188,8 +233,9 @@ export class NftStakingClient {
188
233
  .filter((e) => e.isActive);
189
234
  }
190
235
  /** 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);
236
+ async fetchAllStakeEntriesByPool(poolId, projectId) {
237
+ const pid = this.resolveProjectId(projectId);
238
+ const [poolPda] = getStakePoolPda(pid, poolId);
193
239
  const entries = await this.program.account.stakeEntry.all([
194
240
  { memcmp: { offset: 8, bytes: poolPda.toBase58() } },
195
241
  ]);
@@ -215,18 +261,20 @@ export class NftStakingClient {
215
261
  .filter((e) => e.isActive);
216
262
  }
217
263
  /** 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);
264
+ async fetchAllStakersByPool(poolId, projectId) {
265
+ const pid = this.resolveProjectId(projectId);
266
+ const [poolPda] = getStakePoolPda(pid, poolId);
220
267
  const stakers = await this.program.account.stakerAccount.all([
221
268
  { memcmp: { offset: 8, bytes: poolPda.toBase58() } },
222
269
  ]);
223
270
  return stakers.map(({ account }) => decodeAccount(account));
224
271
  }
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);
272
+ async closeLegacyCollection(collectionMint, projectId) {
273
+ const pid = this.resolveProjectId(projectId);
274
+ const [collection] = PublicKey.findProgramAddressSync([Buffer.from("project_collection"), new BN(pid).toArrayLike(Buffer, "le", 8), collectionMint.toBuffer()], this.program.programId);
275
+ const [project] = getProjectPda(pid);
228
276
  return this.program.methods
229
- .closeLegacyCollection(new BN(projectId), collectionMint)
277
+ .closeLegacyCollection(new BN(pid), collectionMint)
230
278
  .accountsStrict({
231
279
  collection,
232
280
  authority: this.provider.wallet.publicKey,
@@ -236,10 +284,11 @@ export class NftStakingClient {
236
284
  .instruction();
237
285
  }
238
286
  async initializeStakeConfig(projectId) {
239
- const [config] = getStakeConfigPda(projectId);
240
- const [project] = getProjectPda(projectId);
287
+ const pid = this.resolveProjectId(projectId);
288
+ const [config] = getStakeConfigPda(pid);
289
+ const [project] = getProjectPda(pid);
241
290
  return this.program.methods
242
- .initializeStakeConfig(new BN(projectId))
291
+ .initializeStakeConfig(new BN(pid))
243
292
  .accountsStrict({
244
293
  config,
245
294
  authority: this.provider.wallet.publicKey,
@@ -249,8 +298,11 @@ export class NftStakingClient {
249
298
  })
250
299
  .instruction();
251
300
  }
252
- async createStakePool(projectId, stakingMode, rewardConfig, lockConfigs, collectionMint, rewardEndAt = 0, maxStaked = 0) {
253
- const config = await this.fetchStakeConfig(projectId);
301
+ async createStakePool(stakingMode, rewardConfig, lockConfigs, collectionMint, opts) {
302
+ const pid = this.resolveProjectId(opts?.projectId);
303
+ const rewardEndAt = opts?.rewardEndAt ?? 0;
304
+ const maxStaked = opts?.maxStaked ?? 0;
305
+ const config = await this.fetchStakeConfig(pid);
254
306
  const nextPoolId = config ? config.totalPools : 0;
255
307
  const tokenProgram = await this.resolveTokenProgram(rewardConfig.rewardMint);
256
308
  const decimals = await this.getMintDecimals(rewardConfig.rewardMint);
@@ -268,13 +320,14 @@ export class NftStakingClient {
268
320
  rewardRate: new BN(Math.round(lc.rewardRate * multiplier)),
269
321
  earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
270
322
  }));
271
- const [configPda] = getStakeConfigPda(projectId);
272
- const [poolPda] = getStakePoolPda(projectId, nextPoolId);
273
- const [collectionConfigPda] = getCollectionPda(projectId, collectionMint);
323
+ const [configPda] = getStakeConfigPda(pid);
324
+ const [poolPda] = getStakePoolPda(pid, nextPoolId);
325
+ const [collectionConfigPda] = getCollectionPda(pid, collectionMint);
274
326
  const [poolAuthority] = getPoolAuthorityPda(nextPoolId);
275
327
  const rewardVault = getAta(poolAuthority, rewardConfig.rewardMint, tokenProgram);
328
+ const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
276
329
  return this.program.methods
277
- .createStakePool(new BN(projectId), stakingMode, rawRewardConfig, rawLockConfigs, new BN(rewardEndAt), new BN(maxStaked))
330
+ .createStakePool(new BN(pid), stakingMode, rawRewardConfig, rawLockConfigs, new BN(rewardEndAt), new BN(maxStaked))
278
331
  .accountsStrict({
279
332
  config: configPda,
280
333
  pool: poolPda,
@@ -283,6 +336,7 @@ export class NftStakingClient {
283
336
  collectionMint,
284
337
  rewardMint: rewardConfig.rewardMint,
285
338
  rewardVault,
339
+ utilityConfig,
286
340
  projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
287
341
  authority: this.provider.wallet.publicKey,
288
342
  tokenProgram,
@@ -291,7 +345,17 @@ export class NftStakingClient {
291
345
  })
292
346
  .instruction();
293
347
  }
294
- async updateStakePool(projectId, poolId, rewardConfig, lockConfigs, isActive, traitAuthority = null, canBurn = null, merkleRoot = null, gateType = null, rewardEndAt = null, maxStaked = null) {
348
+ async updateStakePool(poolId, updates = {}) {
349
+ const pid = this.resolveProjectId(updates.projectId);
350
+ const rewardConfig = updates.rewardConfig ?? null;
351
+ const lockConfigs = updates.lockConfigs ?? null;
352
+ const isActive = updates.isActive ?? null;
353
+ const traitAuthority = updates.traitAuthority ?? null;
354
+ const canBurn = updates.canBurn ?? null;
355
+ const merkleRoot = updates.merkleRoot ?? null;
356
+ const gateType = updates.gateType ?? null;
357
+ const rewardEndAt = updates.rewardEndAt != null ? new BN(updates.rewardEndAt) : null;
358
+ const maxStaked = updates.maxStaked != null ? new BN(updates.maxStaked) : null;
295
359
  let rawRewardConfig = null;
296
360
  let rawLockConfigs = null;
297
361
  if (rewardConfig) {
@@ -316,7 +380,7 @@ export class NftStakingClient {
316
380
  }));
317
381
  }
318
382
  else if (lockConfigs) {
319
- const pool = await this.fetchStakePool(projectId, poolId);
383
+ const pool = await this.fetchStakePool(pid, poolId);
320
384
  const decimals = pool ? pool.rewardDecimals : 0;
321
385
  const multiplier = 10 ** decimals;
322
386
  rawLockConfigs = lockConfigs.map((lc) => ({
@@ -325,23 +389,27 @@ export class NftStakingClient {
325
389
  earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
326
390
  }));
327
391
  }
328
- const [poolPda] = getStakePoolPda(projectId, poolId);
392
+ const [poolPda] = getStakePoolPda(pid, poolId);
329
393
  return this.program.methods
330
- .updateStakePool(new BN(projectId), new BN(poolId), rawRewardConfig, rawLockConfigs, isActive, traitAuthority, canBurn, merkleRoot, gateType, rewardEndAt, maxStaked)
394
+ .updateStakePool(new BN(pid), new BN(poolId), rawRewardConfig, rawLockConfigs, isActive, traitAuthority, canBurn, merkleRoot, gateType, rewardEndAt, maxStaked)
331
395
  .accountsStrict({
332
396
  pool: poolPda,
333
397
  authority: this.provider.wallet.publicKey,
334
398
  })
335
399
  .instruction();
336
400
  }
337
- async fundRewardVault(projectId, poolId, amount, rewardMint, funderTokenAccount) {
401
+ /** Fund the primary reward vault. Auto-resolves rewardMint from pool if not provided. */
402
+ async fundRewardVault(poolId, amount, opts) {
403
+ const pid = this.resolveProjectId(opts?.projectId);
404
+ const pool = await this.getPoolData(pid, poolId);
405
+ const rewardMint = opts?.rewardMint ?? new PublicKey(pool.rewardConfig.rewardMint);
338
406
  const tokenProgram = await this.resolveTokenProgram(rewardMint);
339
- const [poolPda] = getStakePoolPda(projectId, poolId);
407
+ const [poolPda] = getStakePoolPda(pid, poolId);
340
408
  const [poolAuthority] = getPoolAuthorityPda(poolId);
341
409
  const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
342
- const funderAta = funderTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
410
+ const funderAta = opts?.funderTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
343
411
  return this.program.methods
344
- .fundRewardVault(new BN(projectId), new BN(poolId), amount)
412
+ .fundRewardVault(new BN(pid), new BN(poolId), amount)
345
413
  .accountsStrict({
346
414
  pool: poolPda,
347
415
  poolAuthority,
@@ -353,14 +421,18 @@ export class NftStakingClient {
353
421
  })
354
422
  .instruction();
355
423
  }
356
- async withdrawRewardVault(projectId, poolId, amount, rewardMint, destinationTokenAccount) {
424
+ /** Withdraw from the primary reward vault. Auto-resolves rewardMint from pool if not provided. */
425
+ async withdrawRewardVault(poolId, amount, opts) {
426
+ const pid = this.resolveProjectId(opts?.projectId);
427
+ const pool = await this.getPoolData(pid, poolId);
428
+ const rewardMint = opts?.rewardMint ?? new PublicKey(pool.rewardConfig.rewardMint);
357
429
  const tokenProgram = await this.resolveTokenProgram(rewardMint);
358
- const [poolPda] = getStakePoolPda(projectId, poolId);
430
+ const [poolPda] = getStakePoolPda(pid, poolId);
359
431
  const [poolAuthority] = getPoolAuthorityPda(poolId);
360
432
  const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
361
- const destAta = destinationTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
433
+ const destAta = opts?.destinationTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
362
434
  return this.program.methods
363
- .withdrawRewardVault(new BN(projectId), new BN(poolId), amount)
435
+ .withdrawRewardVault(new BN(pid), new BN(poolId), amount)
364
436
  .accountsStrict({
365
437
  pool: poolPda,
366
438
  poolAuthority,
@@ -372,18 +444,25 @@ export class NftStakingClient {
372
444
  })
373
445
  .instruction();
374
446
  }
375
- async closeStakePool(projectId, poolId) {
376
- const [poolPda] = getStakePoolPda(projectId, poolId);
447
+ async closeStakePool(poolId, projectId) {
448
+ const pid = this.resolveProjectId(projectId);
449
+ const [poolPda] = getStakePoolPda(pid, poolId);
377
450
  return this.program.methods
378
- .closeStakePool(new BN(projectId), new BN(poolId))
451
+ .closeStakePool(new BN(pid), new BN(poolId))
379
452
  .accountsStrict({
380
453
  pool: poolPda,
381
454
  authority: this.provider.wallet.publicKey,
382
455
  })
383
456
  .instruction();
384
457
  }
385
- async stakeNft(projectId, poolId, nftMint, stakingMode, lockTierIndex, fee, gateProof = []) {
386
- const [poolPda] = getStakePoolPda(projectId, poolId);
458
+ /** Stake a legacy/pNFT. Auto-resolves stakingMode from pool. */
459
+ async stakeNft(poolId, nftMint, opts) {
460
+ const pid = this.resolveProjectId(opts?.projectId);
461
+ const pool = await this.getPoolData(pid, poolId);
462
+ const stakingMode = pool.stakingMode;
463
+ const lockTierIndex = opts?.lockTierIndex ?? null;
464
+ const gateProof = opts?.gateProof ?? [];
465
+ const [poolPda] = getStakePoolPda(pid, poolId);
387
466
  const [stakeEntry] = getStakeEntryPda(poolId, nftMint);
388
467
  const staker = this.provider.wallet.publicKey;
389
468
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
@@ -396,9 +475,10 @@ export class NftStakingClient {
396
475
  : null;
397
476
  const nftMetadata = getMetadataPda(nftMint);
398
477
  const nftEdition = getEditionPda(nftMint);
399
- const feeAccts = this.resolveFeeAccounts(fee);
478
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
479
+ const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
400
480
  return this.program.methods
401
- .stakeNft(new BN(projectId), new BN(poolId), nftMint, lockTierIndex, gateProof)
481
+ .stakeNft(new BN(pid), new BN(poolId), nftMint, lockTierIndex, gateProof)
402
482
  .accountsStrict({
403
483
  pool: poolPda,
404
484
  stakeEntry,
@@ -411,6 +491,9 @@ export class NftStakingClient {
411
491
  nftEdition,
412
492
  mplTokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
413
493
  sysvarInstructions: SYSVAR_INSTRUCTIONS_ID,
494
+ tokenRecord: null,
495
+ destinationTokenRecord: null,
496
+ utilityConfig,
414
497
  feeConfig: feeAccts.feeConfig,
415
498
  treasury: feeAccts.treasury,
416
499
  referralAccount: feeAccts.referralAccount,
@@ -423,10 +506,15 @@ export class NftStakingClient {
423
506
  })
424
507
  .instruction();
425
508
  }
426
- async unstakeNft(projectId, poolId, nftMint, rewardMint, stakingMode, fee) {
509
+ /** Unstake a legacy/pNFT. Auto-resolves rewardMint and stakingMode from pool. */
510
+ async unstakeNft(poolId, nftMint, opts) {
511
+ const pid = this.resolveProjectId(opts?.projectId);
512
+ const pool = await this.getPoolData(pid, poolId);
513
+ const rewardMint = new PublicKey(pool.rewardConfig.rewardMint);
514
+ const stakingMode = pool.stakingMode;
427
515
  const rewardTokenProgram = await this.resolveTokenProgram(rewardMint);
428
516
  const nftTokenProgram = await this.resolveTokenProgram(nftMint);
429
- const [poolPda] = getStakePoolPda(projectId, poolId);
517
+ const [poolPda] = getStakePoolPda(pid, poolId);
430
518
  const [stakeEntry] = getStakeEntryPda(poolId, nftMint);
431
519
  const staker = this.provider.wallet.publicKey;
432
520
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
@@ -440,9 +528,9 @@ export class NftStakingClient {
440
528
  const stakerRewardAccount = getAta(staker, rewardMint, rewardTokenProgram);
441
529
  const nftMetadata = getMetadataPda(nftMint);
442
530
  const nftEdition = getEditionPda(nftMint);
443
- const feeAccts = this.resolveFeeAccounts(fee);
531
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
444
532
  return this.program.methods
445
- .unstakeNft(new BN(projectId), new BN(poolId), nftMint)
533
+ .unstakeNft(new BN(pid), new BN(poolId), nftMint)
446
534
  .accountsStrict({
447
535
  pool: poolPda,
448
536
  stakeEntry,
@@ -458,6 +546,8 @@ export class NftStakingClient {
458
546
  nftEdition,
459
547
  mplTokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
460
548
  sysvarInstructions: SYSVAR_INSTRUCTIONS_ID,
549
+ tokenRecord: null,
550
+ destinationTokenRecord: null,
461
551
  feeConfig: feeAccts.feeConfig,
462
552
  treasury: feeAccts.treasury,
463
553
  referralAccount: feeAccts.referralAccount,
@@ -471,18 +561,23 @@ export class NftStakingClient {
471
561
  })
472
562
  .instruction();
473
563
  }
474
- async claimRewards(projectId, poolId, rewardMint, traitBonusRate = null, fee) {
564
+ /** Claim accrued rewards. Auto-resolves rewardMint from pool. */
565
+ async claimRewards(poolId, opts) {
566
+ const pid = this.resolveProjectId(opts?.projectId);
567
+ const pool = await this.getPoolData(pid, poolId);
568
+ const rewardMint = new PublicKey(pool.rewardConfig.rewardMint);
569
+ const traitBonusRate = opts?.traitBonusRate ?? null;
475
570
  const tokenProgram = await this.resolveTokenProgram(rewardMint);
476
- const [poolPda] = getStakePoolPda(projectId, poolId);
571
+ const [poolPda] = getStakePoolPda(pid, poolId);
477
572
  const staker = this.provider.wallet.publicKey;
478
573
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
479
574
  const [poolAuthority] = getPoolAuthorityPda(poolId);
480
575
  const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
481
576
  const stakerRewardAccount = getAta(staker, rewardMint, tokenProgram);
482
- const [stakeConfig] = getStakeConfigPda(projectId);
483
- const feeAccts = this.resolveFeeAccounts(fee);
577
+ const [stakeConfig] = getStakeConfigPda(pid);
578
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
484
579
  return this.program.methods
485
- .claimRewards(new BN(projectId), new BN(poolId), traitBonusRate)
580
+ .claimRewards(new BN(pid), new BN(poolId), traitBonusRate)
486
581
  .accountsStrict({
487
582
  pool: poolPda,
488
583
  stakerAccount,
@@ -504,15 +599,22 @@ export class NftStakingClient {
504
599
  })
505
600
  .instruction();
506
601
  }
507
- async stakeCoreNft(projectId, poolId, nftAsset, collection, lockTierIndex, fee, gateProof = []) {
508
- const [poolPda] = getStakePoolPda(projectId, poolId);
602
+ /** Stake a Core NFT. Auto-resolves collection from pool. */
603
+ async stakeCoreNft(poolId, nftAsset, opts) {
604
+ const pid = this.resolveProjectId(opts?.projectId);
605
+ const pool = await this.getPoolData(pid, poolId);
606
+ const collection = new PublicKey(pool.collectionMint);
607
+ const lockTierIndex = opts?.lockTierIndex ?? null;
608
+ const gateProof = opts?.gateProof ?? [];
609
+ const [poolPda] = getStakePoolPda(pid, poolId);
509
610
  const [stakeEntry] = getStakeEntryPda(poolId, nftAsset);
510
611
  const staker = this.provider.wallet.publicKey;
511
612
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
512
613
  const [poolAuthority] = getPoolAuthorityPda(poolId);
513
- const feeAccts = this.resolveFeeAccounts(fee);
614
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
615
+ const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
514
616
  return this.program.methods
515
- .stakeCoreNft(new BN(projectId), new BN(poolId), nftAsset, lockTierIndex, gateProof)
617
+ .stakeCoreNft(new BN(pid), new BN(poolId), nftAsset, lockTierIndex, gateProof)
516
618
  .accountsStrict({
517
619
  pool: poolPda,
518
620
  stakeEntry,
@@ -521,6 +623,7 @@ export class NftStakingClient {
521
623
  nftAsset,
522
624
  collection,
523
625
  mplCoreProgram: MPL_CORE_PROGRAM_ID,
626
+ utilityConfig,
524
627
  feeConfig: feeAccts.feeConfig,
525
628
  treasury: feeAccts.treasury,
526
629
  referralAccount: feeAccts.referralAccount,
@@ -531,18 +634,23 @@ export class NftStakingClient {
531
634
  })
532
635
  .instruction();
533
636
  }
534
- async unstakeCoreNft(projectId, poolId, nftAsset, collection, rewardMint, fee) {
637
+ /** Unstake a Core NFT. Auto-resolves collection and rewardMint from pool. */
638
+ async unstakeCoreNft(poolId, nftAsset, opts) {
639
+ const pid = this.resolveProjectId(opts?.projectId);
640
+ const pool = await this.getPoolData(pid, poolId);
641
+ const collection = new PublicKey(pool.collectionMint);
642
+ const rewardMint = new PublicKey(pool.rewardConfig.rewardMint);
535
643
  const rewardTokenProgram = await this.resolveTokenProgram(rewardMint);
536
- const [poolPda] = getStakePoolPda(projectId, poolId);
644
+ const [poolPda] = getStakePoolPda(pid, poolId);
537
645
  const [stakeEntry] = getStakeEntryPda(poolId, nftAsset);
538
646
  const staker = this.provider.wallet.publicKey;
539
647
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
540
648
  const [poolAuthority] = getPoolAuthorityPda(poolId);
541
649
  const rewardVault = getAta(poolAuthority, rewardMint, rewardTokenProgram);
542
650
  const stakerRewardAccount = getAta(staker, rewardMint, rewardTokenProgram);
543
- const feeAccts = this.resolveFeeAccounts(fee);
651
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
544
652
  return this.program.methods
545
- .unstakeCoreNft(new BN(projectId), new BN(poolId), nftAsset)
653
+ .unstakeCoreNft(new BN(pid), new BN(poolId), nftAsset)
546
654
  .accountsStrict({
547
655
  pool: poolPda,
548
656
  stakeEntry,
@@ -567,26 +675,30 @@ export class NftStakingClient {
567
675
  .instruction();
568
676
  }
569
677
  /** 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 = []) {
678
+ async stakeCnft(poolId, cnftParams, opts) {
679
+ const pid = this.resolveProjectId(opts?.projectId);
680
+ const gateProof = opts?.gateProof ?? [];
681
+ const { nftAssetId, merkleTree, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes } = cnftParams;
571
682
  const nftAssetIdPk = new PublicKey(nftAssetId);
572
683
  const merkleTreePk = new PublicKey(merkleTree);
573
684
  const [treeConfigPk] = getTreeConfigPda(merkleTreePk);
574
685
  const cnftRootArr = base58HashToArray(cnftRoot);
575
686
  const cnftDataHashArr = base58HashToArray(cnftDataHash);
576
687
  const cnftCreatorHashArr = base58HashToArray(cnftCreatorHash);
577
- const [poolPda] = getStakePoolPda(projectId, poolId);
688
+ const [poolPda] = getStakePoolPda(pid, poolId);
578
689
  const [stakeEntry] = getStakeEntryPda(poolId, nftAssetIdPk);
579
690
  const staker = this.provider.wallet.publicKey;
580
691
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
581
692
  const [poolAuthority] = getPoolAuthorityPda(poolId);
582
- const feeAccts = this.resolveFeeAccounts(fee);
693
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
694
+ const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
583
695
  const remainingAccounts = proofNodes.map((node) => ({
584
696
  pubkey: new PublicKey(node),
585
697
  isSigner: false,
586
698
  isWritable: false,
587
699
  }));
588
700
  return this.program.methods
589
- .stakeCnft(new BN(projectId), new BN(poolId), nftAssetIdPk, cnftRootArr, cnftDataHashArr, cnftCreatorHashArr, new BN(cnftNonce), cnftIndex, gateProof)
701
+ .stakeCnft(new BN(pid), new BN(poolId), nftAssetIdPk, cnftRootArr, cnftDataHashArr, cnftCreatorHashArr, new BN(cnftNonce), cnftIndex, gateProof)
590
702
  .accountsStrict({
591
703
  pool: poolPda,
592
704
  stakeEntry,
@@ -597,6 +709,7 @@ export class NftStakingClient {
597
709
  logWrapper: SPL_NOOP_PROGRAM_ID,
598
710
  compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
599
711
  bubblegumProgram: BUBBLEGUM_PROGRAM_ID,
712
+ utilityConfig,
600
713
  feeConfig: feeAccts.feeConfig,
601
714
  treasury: feeAccts.treasury,
602
715
  referralAccount: feeAccts.referralAccount,
@@ -608,21 +721,24 @@ export class NftStakingClient {
608
721
  .remainingAccounts(remainingAccounts)
609
722
  .instruction();
610
723
  }
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) {
724
+ /** Unstake a compressed NFT (cNFT). Auto-resolves rewardMint from pool. */
725
+ async unstakeCnft(poolId, cnftParams, opts) {
726
+ const pid = this.resolveProjectId(opts?.projectId);
727
+ const pool = await this.getPoolData(pid, poolId);
728
+ const { nftAssetId, merkleTree, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes } = cnftParams;
613
729
  const nftAssetIdPk = new PublicKey(nftAssetId);
614
730
  const merkleTreePk = new PublicKey(merkleTree);
615
731
  const [treeConfigPk] = getTreeConfigPda(merkleTreePk);
616
- const rewardMintPk = new PublicKey(rewardMint);
732
+ const rewardMintPk = new PublicKey(pool.rewardConfig.rewardMint);
617
733
  const cnftRootArr = base58HashToArray(cnftRoot);
618
734
  const cnftDataHashArr = base58HashToArray(cnftDataHash);
619
735
  const cnftCreatorHashArr = base58HashToArray(cnftCreatorHash);
620
- const [poolPda] = getStakePoolPda(projectId, poolId);
736
+ const [poolPda] = getStakePoolPda(pid, poolId);
621
737
  const [stakeEntry] = getStakeEntryPda(poolId, nftAssetIdPk);
622
738
  const staker = this.provider.wallet.publicKey;
623
739
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
624
740
  const [poolAuthority] = getPoolAuthorityPda(poolId);
625
- const feeAccts = this.resolveFeeAccounts(fee);
741
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
626
742
  const rewardVaultPk = getAta(poolAuthority, rewardMintPk);
627
743
  const stakerRewardAccountPk = getAta(staker, rewardMintPk);
628
744
  const remainingAccounts = proofNodes.map((node) => ({
@@ -631,7 +747,7 @@ export class NftStakingClient {
631
747
  isWritable: false,
632
748
  }));
633
749
  return this.program.methods
634
- .unstakeCnft(new BN(projectId), new BN(poolId), nftAssetIdPk, cnftRootArr, cnftDataHashArr, cnftCreatorHashArr, new BN(cnftNonce), cnftIndex)
750
+ .unstakeCnft(new BN(pid), new BN(poolId), nftAssetIdPk, cnftRootArr, cnftDataHashArr, cnftCreatorHashArr, new BN(cnftNonce), cnftIndex)
635
751
  .accountsStrict({
636
752
  pool: poolPda,
637
753
  stakeEntry,
@@ -658,23 +774,25 @@ export class NftStakingClient {
658
774
  .remainingAccounts(remainingAccounts)
659
775
  .instruction();
660
776
  }
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) {
777
+ /** Burn a permanently-locked legacy/pNFT. Auto-resolves stakingMode from pool. */
778
+ async burnStakedNft(poolId, nftMint, opts) {
779
+ const pid = this.resolveProjectId(opts?.projectId);
780
+ const pool = await this.getPoolData(pid, poolId);
781
+ const stakingMode = pool.stakingMode;
664
782
  const nftTokenProgram = await this.resolveTokenProgram(nftMint);
665
- const [poolPda] = getStakePoolPda(projectId, poolId);
783
+ const [poolPda] = getStakePoolPda(pid, poolId);
666
784
  const [stakeEntry] = getStakeEntryPda(poolId, nftMint);
667
785
  const staker = this.provider.wallet.publicKey;
668
786
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
669
787
  const [poolAuthority] = getPoolAuthorityPda(poolId);
670
- const feeAccts = this.resolveFeeAccounts(fee);
788
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
671
789
  const isWalletLock = stakingMode === 1;
672
790
  const escrowNftAccount = isWalletLock ? null : getAta(poolAuthority, nftMint, nftTokenProgram);
673
791
  const stakerNftAccount = isWalletLock ? getAta(staker, nftMint, nftTokenProgram) : null;
674
792
  const nftMetadata = isWalletLock ? getMetadataPda(nftMint) : null;
675
793
  const nftEdition = isWalletLock ? getEditionPda(nftMint) : null;
676
794
  return this.program.methods
677
- .burnStakedNft(new BN(projectId), new BN(poolId), nftMint)
795
+ .burnStakedNft(new BN(pid), new BN(poolId), nftMint)
678
796
  .accountsStrict({
679
797
  pool: poolPda,
680
798
  stakeEntry,
@@ -687,6 +805,8 @@ export class NftStakingClient {
687
805
  nftEdition,
688
806
  mplTokenMetadataProgram: isWalletLock ? TOKEN_METADATA_PROGRAM_ID : null,
689
807
  sysvarInstructions: isWalletLock ? SYSVAR_INSTRUCTIONS_ID : null,
808
+ tokenRecord: null,
809
+ collectionMetadata: null,
690
810
  feeConfig: feeAccts.feeConfig,
691
811
  treasury: feeAccts.treasury,
692
812
  referralAccount: feeAccts.referralAccount,
@@ -698,16 +818,19 @@ export class NftStakingClient {
698
818
  })
699
819
  .instruction();
700
820
  }
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);
821
+ /** Burn a permanently-locked Core NFT. Auto-resolves collection from pool. */
822
+ async burnStakedCoreNft(poolId, nftAsset, opts) {
823
+ const pid = this.resolveProjectId(opts?.projectId);
824
+ const pool = await this.getPoolData(pid, poolId);
825
+ const collection = new PublicKey(pool.collectionMint);
826
+ const [poolPda] = getStakePoolPda(pid, poolId);
704
827
  const [stakeEntry] = getStakeEntryPda(poolId, nftAsset);
705
828
  const staker = this.provider.wallet.publicKey;
706
829
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
707
830
  const [poolAuthority] = getPoolAuthorityPda(poolId);
708
- const feeAccts = this.resolveFeeAccounts(fee);
831
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
709
832
  return this.program.methods
710
- .burnStakedCoreNft(new BN(projectId), new BN(poolId), nftAsset)
833
+ .burnStakedCoreNft(new BN(pid), new BN(poolId), nftAsset)
711
834
  .accountsStrict({
712
835
  pool: poolPda,
713
836
  stakeEntry,
@@ -727,13 +850,14 @@ export class NftStakingClient {
727
850
  .instruction();
728
851
  }
729
852
  /** Spend accrued points from a Points reward type pool. */
730
- async spendPoints(projectId, poolId, amount, fee) {
731
- const [poolPda] = getStakePoolPda(projectId, poolId);
853
+ async spendPoints(poolId, amount, opts) {
854
+ const pid = this.resolveProjectId(opts?.projectId);
855
+ const [poolPda] = getStakePoolPda(pid, poolId);
732
856
  const staker = this.provider.wallet.publicKey;
733
857
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
734
- const feeAccts = this.resolveFeeAccounts(fee);
858
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
735
859
  return this.program.methods
736
- .spendPoints(new BN(projectId), new BN(poolId), amount)
860
+ .spendPoints(new BN(pid), new BN(poolId), amount)
737
861
  .accountsStrict({
738
862
  pool: poolPda,
739
863
  stakerAccount,
@@ -761,14 +885,15 @@ export class NftStakingClient {
761
885
  return null;
762
886
  return decodeAccount(raw);
763
887
  }
764
- async addPoolSecondaryReward(projectId, poolId, rewardMint, baseRate, lockTierRates) {
765
- const [poolPda] = getStakePoolPda(projectId, poolId);
888
+ async addPoolSecondaryReward(poolId, rewardMint, baseRate, lockTierRates, projectId) {
889
+ const pid = this.resolveProjectId(projectId);
890
+ const [poolPda] = getStakePoolPda(pid, poolId);
766
891
  const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
767
892
  const [poolAuthority] = getPoolAuthorityPda(poolId);
768
893
  const tokenProgram = await this.resolveTokenProgram(rewardMint);
769
894
  const vault = getAta(poolAuthority, rewardMint, tokenProgram);
770
895
  return this.program.methods
771
- .addPoolSecondaryReward(new BN(projectId), new BN(poolId), baseRate, lockTierRates)
896
+ .addPoolSecondaryReward(new BN(pid), new BN(poolId), baseRate, lockTierRates)
772
897
  .accountsStrict({
773
898
  pool: poolPda,
774
899
  poolSecondary,
@@ -782,11 +907,12 @@ export class NftStakingClient {
782
907
  })
783
908
  .instruction();
784
909
  }
785
- async removePoolSecondaryReward(projectId, poolId, rewardIndex) {
786
- const [poolPda] = getStakePoolPda(projectId, poolId);
910
+ async removePoolSecondaryReward(poolId, rewardIndex, projectId) {
911
+ const pid = this.resolveProjectId(projectId);
912
+ const [poolPda] = getStakePoolPda(pid, poolId);
787
913
  const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
788
914
  return this.program.methods
789
- .removePoolSecondaryReward(new BN(projectId), new BN(poolId), rewardIndex)
915
+ .removePoolSecondaryReward(new BN(pid), new BN(poolId), rewardIndex)
790
916
  .accountsStrict({
791
917
  pool: poolPda,
792
918
  poolSecondary,
@@ -794,15 +920,16 @@ export class NftStakingClient {
794
920
  })
795
921
  .instruction();
796
922
  }
797
- async fundSecondaryVault(projectId, poolId, rewardIndex, amount, rewardMint, funderTokenAccount) {
923
+ async fundSecondaryVault(poolId, rewardIndex, amount, rewardMint, opts) {
924
+ const pid = this.resolveProjectId(opts?.projectId);
798
925
  const tokenProgram = await this.resolveTokenProgram(rewardMint);
799
- const [poolPda] = getStakePoolPda(projectId, poolId);
926
+ const [poolPda] = getStakePoolPda(pid, poolId);
800
927
  const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
801
928
  const [poolAuthority] = getPoolAuthorityPda(poolId);
802
929
  const vault = getAta(poolAuthority, rewardMint, tokenProgram);
803
- const funderAta = funderTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
930
+ const funderAta = opts?.funderTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
804
931
  return this.program.methods
805
- .fundSecondaryVault(new BN(projectId), new BN(poolId), rewardIndex, amount)
932
+ .fundSecondaryVault(new BN(pid), new BN(poolId), rewardIndex, amount)
806
933
  .accountsStrict({
807
934
  pool: poolPda,
808
935
  poolSecondary,
@@ -815,15 +942,16 @@ export class NftStakingClient {
815
942
  })
816
943
  .instruction();
817
944
  }
818
- async withdrawSecondaryVault(projectId, poolId, rewardIndex, amount, rewardMint, destinationTokenAccount) {
945
+ async withdrawSecondaryVault(poolId, rewardIndex, amount, rewardMint, opts) {
946
+ const pid = this.resolveProjectId(opts?.projectId);
819
947
  const tokenProgram = await this.resolveTokenProgram(rewardMint);
820
- const [poolPda] = getStakePoolPda(projectId, poolId);
948
+ const [poolPda] = getStakePoolPda(pid, poolId);
821
949
  const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
822
950
  const [poolAuthority] = getPoolAuthorityPda(poolId);
823
951
  const vault = getAta(poolAuthority, rewardMint, tokenProgram);
824
- const destAta = destinationTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
952
+ const destAta = opts?.destinationTokenAccount ?? getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
825
953
  return this.program.methods
826
- .withdrawSecondaryVault(new BN(projectId), new BN(poolId), rewardIndex, amount)
954
+ .withdrawSecondaryVault(new BN(pid), new BN(poolId), rewardIndex, amount)
827
955
  .accountsStrict({
828
956
  pool: poolPda,
829
957
  poolSecondary,
@@ -836,13 +964,14 @@ export class NftStakingClient {
836
964
  })
837
965
  .instruction();
838
966
  }
839
- async initStakerSecondary(projectId, poolId) {
840
- const [poolPda] = getStakePoolPda(projectId, poolId);
967
+ async initStakerSecondary(poolId, projectId) {
968
+ const pid = this.resolveProjectId(projectId);
969
+ const [poolPda] = getStakePoolPda(pid, poolId);
841
970
  const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
842
971
  const staker = this.provider.wallet.publicKey;
843
972
  const [stakerSecondary] = getStakerSecondaryRewardsPda(poolId, staker);
844
973
  return this.program.methods
845
- .initStakerSecondary(new BN(projectId), new BN(poolId))
974
+ .initStakerSecondary(new BN(pid), new BN(poolId))
846
975
  .accountsStrict({
847
976
  pool: poolPda,
848
977
  poolSecondary,
@@ -852,14 +981,15 @@ export class NftStakingClient {
852
981
  })
853
982
  .instruction();
854
983
  }
855
- async claimSecondaryRewards(projectId, poolId, secondaryRewards, fee) {
856
- const [poolPda] = getStakePoolPda(projectId, poolId);
984
+ async claimSecondaryRewards(poolId, secondaryRewards, opts) {
985
+ const pid = this.resolveProjectId(opts?.projectId);
986
+ const [poolPda] = getStakePoolPda(pid, poolId);
857
987
  const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
858
988
  const staker = this.provider.wallet.publicKey;
859
989
  const [stakerAccount] = getStakerAccountPda(poolId, staker);
860
990
  const [stakerSecondary] = getStakerSecondaryRewardsPda(poolId, staker);
861
991
  const [poolAuthority] = getPoolAuthorityPda(poolId);
862
- const feeAccts = this.resolveFeeAccounts(fee);
992
+ const feeAccts = this.resolveFeeAccounts(opts?.fee);
863
993
  const remainingAccounts = [];
864
994
  for (const sr of secondaryRewards) {
865
995
  const tokenProgram = await this.resolveTokenProgram(sr.mint);
@@ -868,7 +998,7 @@ export class NftStakingClient {
868
998
  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
999
  }
870
1000
  return this.program.methods
871
- .claimSecondaryRewards(new BN(projectId), new BN(poolId))
1001
+ .claimSecondaryRewards(new BN(pid), new BN(poolId))
872
1002
  .accountsStrict({
873
1003
  pool: poolPda,
874
1004
  poolSecondary,