@soltracer/nft-staking 0.2.0 → 0.2.3
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/INTEGRATION.md +213 -74
- package/dist/client.d.ts +181 -49
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +737 -196
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +60 -0
- package/dist/errors.js.map +1 -0
- package/dist/helpers.d.ts +50 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +88 -0
- package/dist/helpers.js.map +1 -0
- package/dist/idl.d.ts +5278 -1295
- package/dist/idl.d.ts.map +1 -1
- package/dist/idl.json +4696 -713
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/merkle.d.ts +58 -0
- package/dist/merkle.d.ts.map +1 -0
- package/dist/merkle.js +130 -0
- package/dist/merkle.js.map +1 -0
- package/dist/traitProof.d.ts +65 -0
- package/dist/traitProof.d.ts.map +1 -0
- package/dist/traitProof.js +55 -0
- package/dist/traitProof.js.map +1 -0
- package/package.json +4 -2
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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, getUtilityConfigPda, getAta, decodeAccount,
|
|
3
|
+
import { getStakeConfigPda, getStakePoolPda, getStakeEntryPda, getNftStakeLockPda, getStakerAccountPda, getUserProfilePda, getCollectionPda, getPoolAuthorityPda, getPoolSecondaryRewardsPda, getStakerSecondaryRewardsPda, getProjectPda, getUtilityConfigPda, getAta, decodeAccount, PROJECT_MANAGEMENT_PROGRAM_ID, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, MPL_CORE_PROGRAM_ID, } from "@soltracer/core";
|
|
4
|
+
import { ProjectReferralCache, resolveFeeAccounts, } from "@soltracer/cpi-accounts";
|
|
4
5
|
import NftStakingIDL from "./idl.json";
|
|
5
6
|
/** Well-known program IDs for cNFT operations. */
|
|
6
7
|
const BUBBLEGUM_PROGRAM_ID = new PublicKey("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY");
|
|
@@ -22,6 +23,23 @@ function getEditionPda(mint) {
|
|
|
22
23
|
], TOKEN_METADATA_PROGRAM_ID);
|
|
23
24
|
return pda;
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Derive the Token Record PDA for a programmable NFT (pNFT). The token
|
|
28
|
+
* record exists per (mint, tokenAccount) pair and is required as a
|
|
29
|
+
* mutable account for pNFT lock/unlock CPIs.
|
|
30
|
+
*
|
|
31
|
+
* seeds = ["metadata", MPL_TOKEN_METADATA, mint, "token_record", tokenAccount]
|
|
32
|
+
*/
|
|
33
|
+
function getTokenRecordPda(mint, tokenAccount) {
|
|
34
|
+
const [pda] = PublicKey.findProgramAddressSync([
|
|
35
|
+
new TextEncoder().encode("metadata"),
|
|
36
|
+
TOKEN_METADATA_PROGRAM_ID.toBytes(),
|
|
37
|
+
mint.toBytes(),
|
|
38
|
+
new TextEncoder().encode("token_record"),
|
|
39
|
+
tokenAccount.toBytes(),
|
|
40
|
+
], TOKEN_METADATA_PROGRAM_ID);
|
|
41
|
+
return pda;
|
|
42
|
+
}
|
|
25
43
|
/** Sysvar Instructions address. */
|
|
26
44
|
const SYSVAR_INSTRUCTIONS_ID = new PublicKey("Sysvar1nstructions1111111111111111111111111");
|
|
27
45
|
/** Decode a base58-encoded 32-byte hash into a number array. */
|
|
@@ -32,13 +50,59 @@ function base58HashToArray(hash) {
|
|
|
32
50
|
function getTreeConfigPda(merkleTree) {
|
|
33
51
|
return PublicKey.findProgramAddressSync([merkleTree.toBytes()], BUBBLEGUM_PROGRAM_ID);
|
|
34
52
|
}
|
|
53
|
+
function toPk(v) {
|
|
54
|
+
return typeof v === "string" ? new PublicKey(v) : v;
|
|
55
|
+
}
|
|
56
|
+
function toBN(v) {
|
|
57
|
+
return typeof v === "number" ? new BN(v) : v;
|
|
58
|
+
}
|
|
59
|
+
function normalizeQuantityBonus(input) {
|
|
60
|
+
const bonusType = input.bonusType ?? 0;
|
|
61
|
+
return {
|
|
62
|
+
minCount: input.minCount,
|
|
63
|
+
bonusType,
|
|
64
|
+
target: input.target ?? 0,
|
|
65
|
+
bonusBps: input.bonusBps ?? 0,
|
|
66
|
+
fixedAmount: toBN(input.fixedAmount ?? 0),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function amountWithDecimals(raw, decimals) {
|
|
70
|
+
const divisor = 10 ** decimals;
|
|
71
|
+
return { raw, ui: decimals > 0 ? raw / divisor : raw, decimals };
|
|
72
|
+
}
|
|
73
|
+
function applyQuantityBonusRaw(reward, thresholds, stakedCount, target) {
|
|
74
|
+
if (reward <= 0)
|
|
75
|
+
return 0;
|
|
76
|
+
let best = null;
|
|
77
|
+
for (const threshold of thresholds) {
|
|
78
|
+
if ((threshold.target ?? 0) !== target || stakedCount < threshold.minCount)
|
|
79
|
+
continue;
|
|
80
|
+
if (!best) {
|
|
81
|
+
best = threshold;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const bestFixed = Number(best.fixedAmount ?? 0);
|
|
85
|
+
const nextFixed = Number(threshold.fixedAmount ?? 0);
|
|
86
|
+
if (threshold.minCount > best.minCount ||
|
|
87
|
+
(threshold.minCount === best.minCount &&
|
|
88
|
+
nextFixed >= bestFixed &&
|
|
89
|
+
(threshold.bonusBps ?? 0) >= (best.bonusBps ?? 0))) {
|
|
90
|
+
best = threshold;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!best)
|
|
94
|
+
return reward;
|
|
95
|
+
if ((best.bonusType ?? 0) === 1)
|
|
96
|
+
return reward + Number(best.fixedAmount ?? 0);
|
|
97
|
+
return Math.floor((reward * (10_000 + (best.bonusBps ?? 0))) / 10_000);
|
|
98
|
+
}
|
|
35
99
|
export class NftStakingClient {
|
|
36
100
|
program;
|
|
37
101
|
provider;
|
|
38
102
|
tokenProgramCache = new Map();
|
|
39
103
|
mintDecimalsCache = new Map();
|
|
40
104
|
poolCache = new Map();
|
|
41
|
-
static POOL_CACHE_TTL = 30_000;
|
|
105
|
+
static POOL_CACHE_TTL = 30_000;
|
|
42
106
|
/** Project ID bound to this client. Set via `create()` options or `setProjectId()`. */
|
|
43
107
|
projectId;
|
|
44
108
|
constructor(program, provider, projectId) {
|
|
@@ -79,6 +143,13 @@ export class NftStakingClient {
|
|
|
79
143
|
this.poolCache.set(key, { data: pool, fetchedAt: Date.now() });
|
|
80
144
|
return pool;
|
|
81
145
|
}
|
|
146
|
+
async fetchRawStakePool(projectId, poolId) {
|
|
147
|
+
const [pda] = getStakePoolPda(projectId, poolId);
|
|
148
|
+
const raw = await this.program.account.stakePool.fetchNullable(pda);
|
|
149
|
+
if (!raw)
|
|
150
|
+
return null;
|
|
151
|
+
return decodeAccount(raw);
|
|
152
|
+
}
|
|
82
153
|
/** Invalidate cached pool data (call after pool mutations). */
|
|
83
154
|
invalidatePoolCache(poolId) {
|
|
84
155
|
if (poolId !== undefined) {
|
|
@@ -98,10 +169,30 @@ export class NftStakingClient {
|
|
|
98
169
|
if (cached)
|
|
99
170
|
return cached;
|
|
100
171
|
const acct = await this.provider.connection.getAccountInfo(mint);
|
|
101
|
-
const result = acct?.owner.equals(TOKEN_2022_PROGRAM_ID)
|
|
172
|
+
const result = acct?.owner.equals(TOKEN_2022_PROGRAM_ID)
|
|
173
|
+
? TOKEN_2022_PROGRAM_ID
|
|
174
|
+
: TOKEN_PROGRAM_ID;
|
|
102
175
|
this.tokenProgramCache.set(key, result);
|
|
103
176
|
return result;
|
|
104
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Resolve programmable-NFT token-record PDAs for a (mint, source, dest)
|
|
180
|
+
* triple. Detection: derive the SOURCE token record PDA and probe the
|
|
181
|
+
* account on-chain. A token-record account only exists for pNFTs, so its
|
|
182
|
+
* presence is a reliable, version-agnostic detector (and is what the
|
|
183
|
+
* mpl-token-metadata clients use). Returns `{ tokenRecord: null,
|
|
184
|
+
* destinationTokenRecord: null }` for legacy NFTs, otherwise the pair of
|
|
185
|
+
* PDAs. `destination` may be null for transfer-mode stakes (no escrow).
|
|
186
|
+
*/
|
|
187
|
+
async resolvePNftTokenRecords(mint, source, destination) {
|
|
188
|
+
const sourceRecord = getTokenRecordPda(mint, source);
|
|
189
|
+
const info = await this.provider.connection.getAccountInfo(sourceRecord);
|
|
190
|
+
if (!info) {
|
|
191
|
+
return { tokenRecord: null, destinationTokenRecord: null };
|
|
192
|
+
}
|
|
193
|
+
const destinationRecord = destination ? getTokenRecordPda(mint, destination) : null;
|
|
194
|
+
return { tokenRecord: sourceRecord, destinationTokenRecord: destinationRecord };
|
|
195
|
+
}
|
|
105
196
|
/** Fetch the decimal precision of a token mint (cached). */
|
|
106
197
|
async getMintDecimals(mint) {
|
|
107
198
|
const key = mint.toBase58();
|
|
@@ -130,28 +221,17 @@ export class NftStakingClient {
|
|
|
130
221
|
})),
|
|
131
222
|
};
|
|
132
223
|
}
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
/** Resolve fee PDA accounts for fee CPI. */
|
|
136
|
-
resolveFeeAccounts(fee) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
feeConfig,
|
|
150
|
-
treasury,
|
|
151
|
-
referralAccount,
|
|
152
|
-
solUsdPriceFeed: NftStakingClient.PYTH_SOL_USD_FEED,
|
|
153
|
-
adminProgram: ADMIN_PROGRAM_ID,
|
|
154
|
-
};
|
|
224
|
+
/** Cache of project_id -> referral pubkey (or null = no referral). */
|
|
225
|
+
projectReferralCache = new ProjectReferralCache();
|
|
226
|
+
/** Resolve fee PDA accounts for fee CPI. Auto-supplies referral from project if not provided. */
|
|
227
|
+
async resolveFeeAccounts(pid, fee) {
|
|
228
|
+
return resolveFeeAccounts({
|
|
229
|
+
connection: this.provider.connection,
|
|
230
|
+
utilityProgramId: this.program.programId,
|
|
231
|
+
projectId: pid,
|
|
232
|
+
fee,
|
|
233
|
+
referralCache: this.projectReferralCache,
|
|
234
|
+
});
|
|
155
235
|
}
|
|
156
236
|
async fetchStakeConfig(projectId) {
|
|
157
237
|
const pid = this.resolveProjectId(projectId);
|
|
@@ -171,15 +251,17 @@ export class NftStakingClient {
|
|
|
171
251
|
const decimals = await this.getMintDecimals(new PublicKey(pool.rewardConfig.rewardMint));
|
|
172
252
|
return this.applyPoolDecimals(pool, decimals);
|
|
173
253
|
}
|
|
174
|
-
async fetchStakeEntry(poolId, nftMint) {
|
|
175
|
-
const
|
|
254
|
+
async fetchStakeEntry(poolId, nftMint, projectId) {
|
|
255
|
+
const pid = this.resolveProjectId(projectId);
|
|
256
|
+
const [pda] = getStakeEntryPda(pid, poolId, toPk(nftMint));
|
|
176
257
|
const raw = await this.program.account.stakeEntry.fetchNullable(pda);
|
|
177
258
|
if (!raw)
|
|
178
259
|
return null;
|
|
179
260
|
return decodeAccount(raw);
|
|
180
261
|
}
|
|
181
|
-
async fetchStakerAccount(poolId, wallet, rewardDecimals) {
|
|
182
|
-
const
|
|
262
|
+
async fetchStakerAccount(poolId, wallet, rewardDecimals, projectId) {
|
|
263
|
+
const pid = this.resolveProjectId(projectId);
|
|
264
|
+
const [pda] = getStakerAccountPda(pid, poolId, toPk(wallet));
|
|
183
265
|
const raw = await this.program.account.stakerAccount.fetchNullable(pda);
|
|
184
266
|
if (!raw)
|
|
185
267
|
return null;
|
|
@@ -210,22 +292,224 @@ export class NftStakingClient {
|
|
|
210
292
|
return pools;
|
|
211
293
|
}
|
|
212
294
|
/** Fetch all active stake entries for a wallet in a specific pool via gPA. */
|
|
213
|
-
async fetchStakeEntriesByOwner(
|
|
295
|
+
async fetchStakeEntriesByOwner(poolId, owner, projectId) {
|
|
296
|
+
const pid = this.resolveProjectId(projectId);
|
|
297
|
+
const _owner = toPk(owner);
|
|
298
|
+
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
214
299
|
const entries = await this.program.account.stakeEntry.all([
|
|
215
|
-
{ memcmp: { offset: 72, bytes:
|
|
300
|
+
{ memcmp: { offset: 72, bytes: _owner.toBase58() } },
|
|
216
301
|
]);
|
|
217
302
|
return entries
|
|
218
303
|
.map(({ account }) => decodeAccount(account))
|
|
219
|
-
.filter((e) => e.isActive);
|
|
304
|
+
.filter((e) => e.isActive && new PublicKey(e.pool).equals(poolPda));
|
|
305
|
+
}
|
|
306
|
+
async fetchTokenAccountAmount(account) {
|
|
307
|
+
try {
|
|
308
|
+
const balance = await this.provider.connection.getTokenAccountBalance(account);
|
|
309
|
+
return Number(balance.value.amount);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
isEntryClaimDeferred(pool, entry, now) {
|
|
316
|
+
if (entry.lockTierIndex === 255 || entry.lockExpiresAt <= 0 || now >= entry.lockExpiresAt) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return Boolean(pool.lockConfigs[entry.lockTierIndex]?.claimOnlyAtEnd);
|
|
320
|
+
}
|
|
321
|
+
splitAccruedByRates(accrued, claimableRate, totalRate) {
|
|
322
|
+
if (accrued <= 0)
|
|
323
|
+
return [0, 0];
|
|
324
|
+
if (totalRate <= 0)
|
|
325
|
+
return [accrued, 0];
|
|
326
|
+
const claimable = Math.floor((accrued * claimableRate) / totalRate);
|
|
327
|
+
return [claimable, accrued - claimable];
|
|
328
|
+
}
|
|
329
|
+
pendingFromRate(rate, interval, rewardEndAt, lastUpdateAt, now) {
|
|
330
|
+
if (rate <= 0 || interval <= 0)
|
|
331
|
+
return 0;
|
|
332
|
+
const effectiveNow = rewardEndAt > 0 && now > rewardEndAt ? rewardEndAt : now;
|
|
333
|
+
const elapsed = Math.max(0, effectiveNow - lastUpdateAt);
|
|
334
|
+
return Math.floor((elapsed * rate) / interval);
|
|
335
|
+
}
|
|
336
|
+
resolveSecondaryDecimals(mint, index, overrides) {
|
|
337
|
+
if (!overrides)
|
|
338
|
+
return undefined;
|
|
339
|
+
if (Array.isArray(overrides))
|
|
340
|
+
return overrides[index];
|
|
341
|
+
return overrides[mint];
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Build a display-ready reward summary for a staker in one pool.
|
|
345
|
+
*
|
|
346
|
+
* Current on-chain primary and secondary claims are still blocked while
|
|
347
|
+
* `claimLockedUntil` is active. This helper partitions estimated accruals
|
|
348
|
+
* into claimable versus claim-at-end buckets from active StakeEntry rates so
|
|
349
|
+
* UIs can show what is available now and what remains locked.
|
|
350
|
+
*/
|
|
351
|
+
async fetchStakerRewardSummary(poolId, wallet, opts = {}) {
|
|
352
|
+
const pid = this.resolveProjectId(opts.projectId);
|
|
353
|
+
const _wallet = toPk(wallet);
|
|
354
|
+
const now = opts.now ?? Math.floor(Date.now() / 1000);
|
|
355
|
+
const pool = await this.fetchRawStakePool(pid, poolId);
|
|
356
|
+
if (!pool)
|
|
357
|
+
return null;
|
|
358
|
+
const rewardMint = new PublicKey(pool.rewardConfig.rewardMint);
|
|
359
|
+
const rewardDecimals = opts.rewardDecimals ?? (await this.getMintDecimals(rewardMint));
|
|
360
|
+
const staker = await this.fetchStakerAccount(poolId, _wallet, undefined, pid);
|
|
361
|
+
const entries = opts.entries ?? (await this.fetchStakeEntriesByOwner(poolId, _wallet, pid));
|
|
362
|
+
const activeEntries = entries.filter((entry) => entry.isActive);
|
|
363
|
+
const claimableEntries = activeEntries.filter((entry) => !this.isEntryClaimDeferred(pool, entry, now));
|
|
364
|
+
const unclaimableEntries = activeEntries.filter((entry) => this.isEntryClaimDeferred(pool, entry, now));
|
|
365
|
+
const claimableRate = claimableEntries.reduce((sum, entry) => sum + entry.rateContribution, 0);
|
|
366
|
+
const unclaimableRate = unclaimableEntries.reduce((sum, entry) => sum + entry.rateContribution, 0);
|
|
367
|
+
const totalRate = claimableRate + unclaimableRate;
|
|
368
|
+
const stakerRate = staker?.effectiveRate ?? 0;
|
|
369
|
+
const settledPrimary = staker?.accruedRewards ?? 0;
|
|
370
|
+
const [claimableSettledPrimary, unclaimableSettledPrimary] = this.splitAccruedByRates(settledPrimary, claimableRate, totalRate || stakerRate);
|
|
371
|
+
const pendingClaimablePrimary = staker
|
|
372
|
+
? this.pendingFromRate(claimableRate, pool.rewardConfig.rateInterval, pool.rewardEndAt, staker.lastUpdateAt, now)
|
|
373
|
+
: 0;
|
|
374
|
+
const pendingUnclaimablePrimary = staker
|
|
375
|
+
? this.pendingFromRate(unclaimableRate, pool.rewardConfig.rateInterval, pool.rewardEndAt, staker.lastUpdateAt, now)
|
|
376
|
+
: 0;
|
|
377
|
+
const claimableBasePrimary = claimableSettledPrimary + pendingClaimablePrimary;
|
|
378
|
+
const unclaimableBasePrimary = unclaimableSettledPrimary + pendingUnclaimablePrimary;
|
|
379
|
+
const quantityThresholds = pool.rewardConfig.quantityThresholds;
|
|
380
|
+
const stakedCount = staker?.stakedCount ?? activeEntries.length;
|
|
381
|
+
const claimablePayoutPrimary = applyQuantityBonusRaw(claimableBasePrimary, quantityThresholds, stakedCount, 0);
|
|
382
|
+
const unclaimablePayoutPrimary = applyQuantityBonusRaw(unclaimableBasePrimary, quantityThresholds, stakedCount, 0);
|
|
383
|
+
let primaryVaultBalance;
|
|
384
|
+
if (opts.includeVaultBalances) {
|
|
385
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
386
|
+
const tokenProgram = await this.resolveTokenProgram(rewardMint);
|
|
387
|
+
const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
|
|
388
|
+
const rawBalance = await this.fetchTokenAccountAmount(rewardVault);
|
|
389
|
+
primaryVaultBalance =
|
|
390
|
+
rawBalance == null ? null : amountWithDecimals(rawBalance, rewardDecimals);
|
|
391
|
+
}
|
|
392
|
+
const secondary = [];
|
|
393
|
+
const poolSecondary = await this.fetchPoolSecondaryRewards(poolId, pid);
|
|
394
|
+
const stakerSecondary = await this.fetchStakerSecondaryRewards(poolId, _wallet, pid);
|
|
395
|
+
if (poolSecondary && stakerSecondary) {
|
|
396
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
397
|
+
for (let index = 0; index < poolSecondary.rewards.length; index++) {
|
|
398
|
+
const reward = poolSecondary.rewards[index];
|
|
399
|
+
if (!reward)
|
|
400
|
+
continue;
|
|
401
|
+
const mint = reward.rewardMint;
|
|
402
|
+
const decimals = this.resolveSecondaryDecimals(mint, index, opts.secondaryRewardDecimals) ??
|
|
403
|
+
(await this.getMintDecimals(new PublicKey(mint)));
|
|
404
|
+
const secondaryClaimableRate = claimableEntries.reduce((sum, entry) => sum + (entry.secondaryRateContributions[index] ?? 0), 0);
|
|
405
|
+
const secondaryUnclaimableRate = unclaimableEntries.reduce((sum, entry) => sum + (entry.secondaryRateContributions[index] ?? 0), 0);
|
|
406
|
+
const secondaryTotalRate = secondaryClaimableRate + secondaryUnclaimableRate;
|
|
407
|
+
const settledSecondary = stakerSecondary.accruedRewards[index] ?? 0;
|
|
408
|
+
const [claimableSettledSecondary, unclaimableSettledSecondary] = this.splitAccruedByRates(settledSecondary, secondaryClaimableRate, secondaryTotalRate || stakerSecondary.effectiveRate[index] || 0);
|
|
409
|
+
const isRetired = Boolean(reward.isRetired);
|
|
410
|
+
const retiredAtRaw = reward.retiredAt;
|
|
411
|
+
const retiredAt = typeof retiredAtRaw === "number"
|
|
412
|
+
? retiredAtRaw
|
|
413
|
+
: retiredAtRaw &&
|
|
414
|
+
typeof retiredAtRaw.toNumber === "function"
|
|
415
|
+
? retiredAtRaw.toNumber()
|
|
416
|
+
: Number(retiredAtRaw ?? 0);
|
|
417
|
+
// STAKE-FRESH-2026-05-11 (F-H3): for retired slots, pending accrues
|
|
418
|
+
// only up to `retiredAt`. Pre-fix the SDK zeroed pending entirely,
|
|
419
|
+
// hiding the legitimately-owed pre-retire window from the UI; now we
|
|
420
|
+
// mirror the on-chain cap so the displayed claimable matches what
|
|
421
|
+
// `claim_secondary_rewards` will actually pay out.
|
|
422
|
+
const cappedNow = isRetired && retiredAt > 0 && retiredAt < now ? retiredAt : now;
|
|
423
|
+
const pendingClaimableSecondary = staker
|
|
424
|
+
? this.pendingFromRate(secondaryClaimableRate, pool.rewardConfig.rateInterval, pool.rewardEndAt, staker.lastUpdateAt, cappedNow)
|
|
425
|
+
: 0;
|
|
426
|
+
const pendingUnclaimableSecondary = staker
|
|
427
|
+
? this.pendingFromRate(secondaryUnclaimableRate, pool.rewardConfig.rateInterval, pool.rewardEndAt, staker.lastUpdateAt, cappedNow)
|
|
428
|
+
: 0;
|
|
429
|
+
const claimableBaseSecondary = claimableSettledSecondary + pendingClaimableSecondary;
|
|
430
|
+
const unclaimableBaseSecondary = unclaimableSettledSecondary + pendingUnclaimableSecondary;
|
|
431
|
+
const claimablePayoutSecondary = applyQuantityBonusRaw(claimableBaseSecondary, quantityThresholds, stakedCount, 1);
|
|
432
|
+
const unclaimablePayoutSecondary = applyQuantityBonusRaw(unclaimableBaseSecondary, quantityThresholds, stakedCount, 1);
|
|
433
|
+
// STAKE-FRESH-2026-05-11 (F-H3): a retired slot is "tombstoned" once
|
|
434
|
+
// there is nothing left to claim AND nothing left to accrue (true by
|
|
435
|
+
// definition while retired). Hide from default output so the UI
|
|
436
|
+
// "deprecated reward disappears after claim" naturally.
|
|
437
|
+
const tombstoned = isRetired &&
|
|
438
|
+
claimablePayoutSecondary === 0 &&
|
|
439
|
+
unclaimablePayoutSecondary === 0 &&
|
|
440
|
+
(stakerSecondary.accruedRewards[index] ?? 0) === 0;
|
|
441
|
+
if (tombstoned && !opts.includeTombstoned) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
let secondaryVaultBalance;
|
|
445
|
+
if (opts.includeVaultBalances) {
|
|
446
|
+
const mintPk = new PublicKey(mint);
|
|
447
|
+
const tokenProgram = await this.resolveTokenProgram(mintPk);
|
|
448
|
+
const vault = getAta(poolAuthority, mintPk, tokenProgram);
|
|
449
|
+
const rawBalance = await this.fetchTokenAccountAmount(vault);
|
|
450
|
+
secondaryVaultBalance =
|
|
451
|
+
rawBalance == null ? null : amountWithDecimals(rawBalance, decimals);
|
|
452
|
+
}
|
|
453
|
+
secondary.push({
|
|
454
|
+
index,
|
|
455
|
+
rewardMint: mint,
|
|
456
|
+
rewardDecimals: decimals,
|
|
457
|
+
isRetired,
|
|
458
|
+
retiredAt,
|
|
459
|
+
tombstoned,
|
|
460
|
+
totalAccrued: amountWithDecimals(claimablePayoutSecondary + unclaimablePayoutSecondary, decimals),
|
|
461
|
+
claimable: amountWithDecimals(claimablePayoutSecondary, decimals),
|
|
462
|
+
unclaimable: amountWithDecimals(unclaimablePayoutSecondary, decimals),
|
|
463
|
+
claimableBase: amountWithDecimals(claimableBaseSecondary, decimals),
|
|
464
|
+
unclaimableBase: amountWithDecimals(unclaimableBaseSecondary, decimals),
|
|
465
|
+
claimableRate: secondaryClaimableRate,
|
|
466
|
+
unclaimableRate: secondaryUnclaimableRate,
|
|
467
|
+
totalRate: secondaryTotalRate,
|
|
468
|
+
vaultBalance: secondaryVaultBalance,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
poolId,
|
|
474
|
+
wallet: _wallet.toBase58(),
|
|
475
|
+
generatedAt: now,
|
|
476
|
+
rewardMint: rewardMint.toBase58(),
|
|
477
|
+
rewardDecimals,
|
|
478
|
+
primary: {
|
|
479
|
+
totalAccrued: amountWithDecimals(claimablePayoutPrimary + unclaimablePayoutPrimary, rewardDecimals),
|
|
480
|
+
claimable: amountWithDecimals(claimablePayoutPrimary, rewardDecimals),
|
|
481
|
+
unclaimable: amountWithDecimals(unclaimablePayoutPrimary, rewardDecimals),
|
|
482
|
+
claimableBase: amountWithDecimals(claimableBasePrimary, rewardDecimals),
|
|
483
|
+
unclaimableBase: amountWithDecimals(unclaimableBasePrimary, rewardDecimals),
|
|
484
|
+
claimableRate,
|
|
485
|
+
unclaimableRate,
|
|
486
|
+
totalRate,
|
|
487
|
+
claimLockedUntil: staker?.claimLockedUntil ?? 0,
|
|
488
|
+
claimTransactionBlocked: Boolean(staker?.claimLockedUntil && now < staker.claimLockedUntil),
|
|
489
|
+
vaultBalance: primaryVaultBalance,
|
|
490
|
+
},
|
|
491
|
+
secondary,
|
|
492
|
+
entries: {
|
|
493
|
+
totalActive: activeEntries.length,
|
|
494
|
+
claimableActive: claimableEntries.length,
|
|
495
|
+
unclaimableActive: unclaimableEntries.length,
|
|
496
|
+
},
|
|
497
|
+
notes: [
|
|
498
|
+
"Raw amounts are base units; ui amounts are divided by rewardDecimals.",
|
|
499
|
+
"claimable/unclaimable splits are estimated from active StakeEntry rate contributions.",
|
|
500
|
+
"Current on-chain primary and secondary claim instructions are still blocked while claimLockedUntil is active.",
|
|
501
|
+
],
|
|
502
|
+
};
|
|
220
503
|
}
|
|
221
504
|
/**
|
|
222
505
|
* Fetch active stake entries for specific NFT mints by deriving their PDAs.
|
|
223
506
|
* More reliable than gPA in browser environments.
|
|
224
507
|
*/
|
|
225
|
-
async fetchStakeEntriesForMints(poolId, nftMints) {
|
|
508
|
+
async fetchStakeEntriesForMints(poolId, nftMints, projectId) {
|
|
226
509
|
if (nftMints.length === 0)
|
|
227
510
|
return [];
|
|
228
|
-
const
|
|
511
|
+
const pid = this.resolveProjectId(projectId);
|
|
512
|
+
const pdas = nftMints.map((mint) => getStakeEntryPda(pid, poolId, toPk(mint))[0]);
|
|
229
513
|
const accounts = await this.program.account.stakeEntry.fetchMultiple(pdas);
|
|
230
514
|
return accounts
|
|
231
515
|
.filter((a) => a !== null)
|
|
@@ -245,13 +529,14 @@ export class NftStakingClient {
|
|
|
245
529
|
* Fetch active stake entries for given mints across multiple pools.
|
|
246
530
|
* Uses PDA derivation + fetchMultiple (reliable in browser).
|
|
247
531
|
*/
|
|
248
|
-
async fetchStakeEntriesAcrossPools(poolIds, nftMints) {
|
|
532
|
+
async fetchStakeEntriesAcrossPools(poolIds, nftMints, projectId) {
|
|
249
533
|
if (nftMints.length === 0 || poolIds.length === 0)
|
|
250
534
|
return [];
|
|
535
|
+
const pid = this.resolveProjectId(projectId);
|
|
251
536
|
const pdas = [];
|
|
252
537
|
for (const poolId of poolIds) {
|
|
253
538
|
for (const mint of nftMints) {
|
|
254
|
-
pdas.push(getStakeEntryPda(poolId, mint)[0]);
|
|
539
|
+
pdas.push(getStakeEntryPda(pid, poolId, mint)[0]);
|
|
255
540
|
}
|
|
256
541
|
}
|
|
257
542
|
const accounts = await this.program.account.stakeEntry.fetchMultiple(pdas);
|
|
@@ -270,11 +555,16 @@ export class NftStakingClient {
|
|
|
270
555
|
return stakers.map(({ account }) => decodeAccount(account));
|
|
271
556
|
}
|
|
272
557
|
async closeLegacyCollection(collectionMint, projectId) {
|
|
558
|
+
const _collMint = toPk(collectionMint);
|
|
273
559
|
const pid = this.resolveProjectId(projectId);
|
|
274
|
-
const [collection] = PublicKey.findProgramAddressSync([
|
|
560
|
+
const [collection] = PublicKey.findProgramAddressSync([
|
|
561
|
+
Buffer.from("project_collection"),
|
|
562
|
+
new BN(pid).toArrayLike(Buffer, "le", 8),
|
|
563
|
+
_collMint.toBuffer(),
|
|
564
|
+
], this.program.programId);
|
|
275
565
|
const [project] = getProjectPda(pid);
|
|
276
566
|
return this.program.methods
|
|
277
|
-
.closeLegacyCollection(new BN(pid),
|
|
567
|
+
.closeLegacyCollection(new BN(pid), _collMint)
|
|
278
568
|
.accountsStrict({
|
|
279
569
|
collection,
|
|
280
570
|
authority: this.provider.wallet.publicKey,
|
|
@@ -299,44 +589,50 @@ export class NftStakingClient {
|
|
|
299
589
|
.instruction();
|
|
300
590
|
}
|
|
301
591
|
async createStakePool(stakingMode, rewardConfig, lockConfigs, collectionMint, opts) {
|
|
592
|
+
const _collMint = toPk(collectionMint);
|
|
593
|
+
const _rewardMint = toPk(rewardConfig.rewardMint);
|
|
302
594
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
303
595
|
const rewardEndAt = opts?.rewardEndAt ?? 0;
|
|
304
596
|
const maxStaked = opts?.maxStaked ?? 0;
|
|
597
|
+
const allowUnlockedStaking = opts?.allowUnlockedStaking ?? true;
|
|
305
598
|
const config = await this.fetchStakeConfig(pid);
|
|
306
599
|
const nextPoolId = config ? config.totalPools : 0;
|
|
307
|
-
const tokenProgram = await this.resolveTokenProgram(
|
|
308
|
-
const decimals = await this.getMintDecimals(
|
|
600
|
+
const tokenProgram = await this.resolveTokenProgram(_rewardMint);
|
|
601
|
+
const decimals = await this.getMintDecimals(_rewardMint);
|
|
309
602
|
const multiplier = 10 ** decimals;
|
|
310
603
|
const rawRewardConfig = {
|
|
311
604
|
rewardType: rewardConfig.rewardType,
|
|
312
|
-
rewardMint:
|
|
605
|
+
rewardMint: _rewardMint,
|
|
313
606
|
baseRate: new BN(Math.round(rewardConfig.baseRate * multiplier)),
|
|
314
607
|
rateInterval: new BN(rewardConfig.rateInterval),
|
|
315
608
|
traitBonusMode: rewardConfig.traitBonusMode,
|
|
316
|
-
quantityThresholds: rewardConfig.quantityThresholds,
|
|
609
|
+
quantityThresholds: rewardConfig.quantityThresholds.map(normalizeQuantityBonus),
|
|
317
610
|
};
|
|
318
611
|
const rawLockConfigs = lockConfigs.map((lc) => ({
|
|
319
612
|
lockDuration: new BN(lc.lockDuration),
|
|
320
613
|
rewardRate: new BN(Math.round(lc.rewardRate * multiplier)),
|
|
321
614
|
earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
|
|
615
|
+
claimOnlyAtEnd: lc.claimOnlyAtEnd,
|
|
322
616
|
}));
|
|
323
617
|
const [configPda] = getStakeConfigPda(pid);
|
|
324
618
|
const [poolPda] = getStakePoolPda(pid, nextPoolId);
|
|
325
|
-
const [collectionConfigPda] = getCollectionPda(pid,
|
|
326
|
-
const [poolAuthority] = getPoolAuthorityPda(nextPoolId);
|
|
327
|
-
const rewardVault = getAta(poolAuthority,
|
|
619
|
+
const [collectionConfigPda] = getCollectionPda(pid, _collMint);
|
|
620
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, nextPoolId);
|
|
621
|
+
const rewardVault = getAta(poolAuthority, _rewardMint, tokenProgram);
|
|
622
|
+
const [project] = getProjectPda(pid);
|
|
328
623
|
const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
|
|
329
624
|
return this.program.methods
|
|
330
|
-
.createStakePool(new BN(pid), stakingMode, rawRewardConfig, rawLockConfigs, new BN(rewardEndAt), new BN(maxStaked))
|
|
625
|
+
.createStakePool(new BN(pid), stakingMode, rawRewardConfig, rawLockConfigs, new BN(rewardEndAt), new BN(maxStaked), allowUnlockedStaking)
|
|
331
626
|
.accountsStrict({
|
|
332
627
|
config: configPda,
|
|
333
628
|
pool: poolPda,
|
|
334
629
|
collectionConfig: collectionConfigPda,
|
|
335
630
|
poolAuthority,
|
|
336
|
-
collectionMint,
|
|
337
|
-
rewardMint:
|
|
631
|
+
collectionMint: _collMint,
|
|
632
|
+
rewardMint: _rewardMint,
|
|
338
633
|
rewardVault,
|
|
339
634
|
utilityConfig,
|
|
635
|
+
project,
|
|
340
636
|
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
341
637
|
authority: this.provider.wallet.publicKey,
|
|
342
638
|
tokenProgram,
|
|
@@ -350,33 +646,36 @@ export class NftStakingClient {
|
|
|
350
646
|
const rewardConfig = updates.rewardConfig ?? null;
|
|
351
647
|
const lockConfigs = updates.lockConfigs ?? null;
|
|
352
648
|
const isActive = updates.isActive ?? null;
|
|
353
|
-
const traitAuthority = updates.traitAuthority
|
|
649
|
+
const traitAuthority = updates.traitAuthority != null ? toPk(updates.traitAuthority) : null;
|
|
354
650
|
const canBurn = updates.canBurn ?? null;
|
|
355
651
|
const merkleRoot = updates.merkleRoot ?? null;
|
|
356
652
|
const gateType = updates.gateType ?? null;
|
|
357
653
|
const rewardEndAt = updates.rewardEndAt != null ? new BN(updates.rewardEndAt) : null;
|
|
358
654
|
const maxStaked = updates.maxStaked != null ? new BN(updates.maxStaked) : null;
|
|
655
|
+
const allowUnlockedStaking = updates.allowUnlockedStaking ?? null;
|
|
359
656
|
let rawRewardConfig = null;
|
|
360
657
|
let rawLockConfigs = null;
|
|
361
658
|
if (rewardConfig) {
|
|
362
|
-
const
|
|
659
|
+
const _rMint = toPk(rewardConfig.rewardMint);
|
|
660
|
+
const decimals = await this.getMintDecimals(_rMint);
|
|
363
661
|
const multiplier = 10 ** decimals;
|
|
364
662
|
rawRewardConfig = {
|
|
365
663
|
rewardType: rewardConfig.rewardType,
|
|
366
|
-
rewardMint:
|
|
664
|
+
rewardMint: _rMint,
|
|
367
665
|
baseRate: new BN(Math.round(rewardConfig.baseRate * multiplier)),
|
|
368
666
|
rateInterval: new BN(rewardConfig.rateInterval),
|
|
369
667
|
traitBonusMode: rewardConfig.traitBonusMode,
|
|
370
|
-
quantityThresholds: rewardConfig.quantityThresholds,
|
|
668
|
+
quantityThresholds: rewardConfig.quantityThresholds.map(normalizeQuantityBonus),
|
|
371
669
|
};
|
|
372
670
|
}
|
|
373
671
|
if (lockConfigs && rewardConfig) {
|
|
374
|
-
const decimals = await this.getMintDecimals(rewardConfig.rewardMint);
|
|
672
|
+
const decimals = await this.getMintDecimals(toPk(rewardConfig.rewardMint));
|
|
375
673
|
const multiplier = 10 ** decimals;
|
|
376
674
|
rawLockConfigs = lockConfigs.map((lc) => ({
|
|
377
675
|
lockDuration: new BN(lc.lockDuration),
|
|
378
676
|
rewardRate: new BN(Math.round(lc.rewardRate * multiplier)),
|
|
379
677
|
earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
|
|
678
|
+
claimOnlyAtEnd: lc.claimOnlyAtEnd,
|
|
380
679
|
}));
|
|
381
680
|
}
|
|
382
681
|
else if (lockConfigs) {
|
|
@@ -387,13 +686,17 @@ export class NftStakingClient {
|
|
|
387
686
|
lockDuration: new BN(lc.lockDuration),
|
|
388
687
|
rewardRate: new BN(Math.round(lc.rewardRate * multiplier)),
|
|
389
688
|
earlyUnstakePenaltyBps: lc.earlyUnstakePenaltyBps,
|
|
689
|
+
claimOnlyAtEnd: lc.claimOnlyAtEnd,
|
|
390
690
|
}));
|
|
391
691
|
}
|
|
392
692
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
693
|
+
const [project] = getProjectPda(pid);
|
|
393
694
|
return this.program.methods
|
|
394
|
-
.updateStakePool(new BN(pid), new BN(poolId), rawRewardConfig, rawLockConfigs, isActive, traitAuthority, canBurn, merkleRoot, gateType, rewardEndAt, maxStaked)
|
|
695
|
+
.updateStakePool(new BN(pid), new BN(poolId), rawRewardConfig, rawLockConfigs, isActive, traitAuthority, canBurn, merkleRoot, gateType, rewardEndAt, maxStaked, allowUnlockedStaking)
|
|
395
696
|
.accountsStrict({
|
|
396
697
|
pool: poolPda,
|
|
698
|
+
project,
|
|
699
|
+
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
397
700
|
authority: this.provider.wallet.publicKey,
|
|
398
701
|
})
|
|
399
702
|
.instruction();
|
|
@@ -402,14 +705,18 @@ export class NftStakingClient {
|
|
|
402
705
|
async fundRewardVault(poolId, amount, opts) {
|
|
403
706
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
404
707
|
const pool = await this.getPoolData(pid, poolId);
|
|
405
|
-
const rewardMint = opts?.rewardMint
|
|
708
|
+
const rewardMint = opts?.rewardMint
|
|
709
|
+
? toPk(opts.rewardMint)
|
|
710
|
+
: new PublicKey(pool.rewardConfig.rewardMint);
|
|
406
711
|
const tokenProgram = await this.resolveTokenProgram(rewardMint);
|
|
407
712
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
408
|
-
const [poolAuthority] = getPoolAuthorityPda(poolId);
|
|
713
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
409
714
|
const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
|
|
410
|
-
const funderAta = opts?.funderTokenAccount
|
|
715
|
+
const funderAta = opts?.funderTokenAccount
|
|
716
|
+
? toPk(opts.funderTokenAccount)
|
|
717
|
+
: getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
|
|
411
718
|
return this.program.methods
|
|
412
|
-
.fundRewardVault(new BN(pid), new BN(poolId), amount)
|
|
719
|
+
.fundRewardVault(new BN(pid), new BN(poolId), toBN(amount))
|
|
413
720
|
.accountsStrict({
|
|
414
721
|
pool: poolPda,
|
|
415
722
|
poolAuthority,
|
|
@@ -425,16 +732,23 @@ export class NftStakingClient {
|
|
|
425
732
|
async withdrawRewardVault(poolId, amount, opts) {
|
|
426
733
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
427
734
|
const pool = await this.getPoolData(pid, poolId);
|
|
428
|
-
const rewardMint = opts?.rewardMint
|
|
735
|
+
const rewardMint = opts?.rewardMint
|
|
736
|
+
? toPk(opts.rewardMint)
|
|
737
|
+
: new PublicKey(pool.rewardConfig.rewardMint);
|
|
429
738
|
const tokenProgram = await this.resolveTokenProgram(rewardMint);
|
|
430
739
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
431
|
-
const [
|
|
740
|
+
const [project] = getProjectPda(pid);
|
|
741
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
432
742
|
const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
|
|
433
|
-
const destAta = opts?.destinationTokenAccount
|
|
743
|
+
const destAta = opts?.destinationTokenAccount
|
|
744
|
+
? toPk(opts.destinationTokenAccount)
|
|
745
|
+
: getAta(this.provider.wallet.publicKey, rewardMint, tokenProgram);
|
|
434
746
|
return this.program.methods
|
|
435
|
-
.withdrawRewardVault(new BN(pid), new BN(poolId), amount)
|
|
747
|
+
.withdrawRewardVault(new BN(pid), new BN(poolId), toBN(amount))
|
|
436
748
|
.accountsStrict({
|
|
437
749
|
pool: poolPda,
|
|
750
|
+
project,
|
|
751
|
+
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
438
752
|
poolAuthority,
|
|
439
753
|
rewardVault,
|
|
440
754
|
destinationTokenAccount: destAta,
|
|
@@ -447,58 +761,70 @@ export class NftStakingClient {
|
|
|
447
761
|
async closeStakePool(poolId, projectId) {
|
|
448
762
|
const pid = this.resolveProjectId(projectId);
|
|
449
763
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
764
|
+
const [project] = getProjectPda(pid);
|
|
450
765
|
return this.program.methods
|
|
451
766
|
.closeStakePool(new BN(pid), new BN(poolId))
|
|
452
767
|
.accountsStrict({
|
|
453
768
|
pool: poolPda,
|
|
769
|
+
project,
|
|
770
|
+
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
771
|
+
rewardVault: null,
|
|
772
|
+
tokenProgram: null,
|
|
454
773
|
authority: this.provider.wallet.publicKey,
|
|
455
774
|
})
|
|
456
775
|
.instruction();
|
|
457
776
|
}
|
|
458
777
|
/** Stake a legacy/pNFT. Auto-resolves stakingMode from pool. */
|
|
459
778
|
async stakeNft(poolId, nftMint, opts) {
|
|
779
|
+
const _nftMint = toPk(nftMint);
|
|
460
780
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
461
781
|
const pool = await this.getPoolData(pid, poolId);
|
|
462
782
|
const stakingMode = pool.stakingMode;
|
|
463
783
|
const lockTierIndex = opts?.lockTierIndex ?? null;
|
|
464
784
|
const gateProof = opts?.gateProof ?? [];
|
|
465
785
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
466
|
-
const [stakeEntry] = getStakeEntryPda(poolId,
|
|
786
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, _nftMint);
|
|
787
|
+
const [stakeLock] = getNftStakeLockPda(_nftMint);
|
|
467
788
|
const staker = this.provider.wallet.publicKey;
|
|
468
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
469
|
-
const [
|
|
470
|
-
const
|
|
471
|
-
const
|
|
789
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
790
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
791
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
792
|
+
const nftTokenProgram = await this.resolveTokenProgram(_nftMint);
|
|
793
|
+
const stakerNftAccount = getAta(staker, _nftMint, nftTokenProgram);
|
|
472
794
|
const isEscrow = stakingMode !== 1;
|
|
473
|
-
const escrowNftAccount = isEscrow
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
const
|
|
795
|
+
const escrowNftAccount = isEscrow ? getAta(poolAuthority, _nftMint, nftTokenProgram) : null;
|
|
796
|
+
const nftMetadata = getMetadataPda(_nftMint);
|
|
797
|
+
const nftEdition = getEditionPda(_nftMint);
|
|
798
|
+
const { tokenRecord, destinationTokenRecord } = await this.resolvePNftTokenRecords(_nftMint, stakerNftAccount, escrowNftAccount);
|
|
799
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
800
|
+
const [project] = getProjectPda(pid);
|
|
479
801
|
const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
|
|
480
802
|
return this.program.methods
|
|
481
|
-
.stakeNft(new BN(pid), new BN(poolId),
|
|
803
|
+
.stakeNft(new BN(pid), new BN(poolId), _nftMint, lockTierIndex, gateProof)
|
|
482
804
|
.accountsStrict({
|
|
483
805
|
pool: poolPda,
|
|
484
806
|
stakeEntry,
|
|
807
|
+
stakeLock,
|
|
485
808
|
stakerAccount,
|
|
486
809
|
poolAuthority,
|
|
487
|
-
nftMint,
|
|
810
|
+
nftMint: _nftMint,
|
|
488
811
|
stakerNftAccount,
|
|
489
812
|
escrowNftAccount,
|
|
490
813
|
nftMetadata,
|
|
491
814
|
nftEdition,
|
|
492
815
|
mplTokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
|
|
493
816
|
sysvarInstructions: SYSVAR_INSTRUCTIONS_ID,
|
|
494
|
-
tokenRecord
|
|
495
|
-
destinationTokenRecord
|
|
817
|
+
tokenRecord,
|
|
818
|
+
destinationTokenRecord,
|
|
819
|
+
userProfile,
|
|
496
820
|
utilityConfig,
|
|
497
821
|
feeConfig: feeAccts.feeConfig,
|
|
822
|
+
programRegistry: feeAccts.programRegistry,
|
|
498
823
|
treasury: feeAccts.treasury,
|
|
499
824
|
referralAccount: feeAccts.referralAccount,
|
|
500
825
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
501
826
|
adminProgram: feeAccts.adminProgram,
|
|
827
|
+
project,
|
|
502
828
|
staker,
|
|
503
829
|
tokenProgram: nftTokenProgram,
|
|
504
830
|
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
@@ -506,37 +832,99 @@ export class NftStakingClient {
|
|
|
506
832
|
})
|
|
507
833
|
.instruction();
|
|
508
834
|
}
|
|
509
|
-
/**
|
|
835
|
+
/**
|
|
836
|
+
* Resolve the staker's remaining StakeEntry PDAs (excluding the entry being
|
|
837
|
+
* closed) for the on-chain claim-lock recompute. Used by `unstakeNft`,
|
|
838
|
+
* `unstakeCoreNft`, `unstakeCnft`, `burnStakedNft`, `burnStakedCoreNft`.
|
|
839
|
+
*
|
|
840
|
+
* - `undefined`: auto-discover via `fetchAllStakeEntriesByPool` (one RPC).
|
|
841
|
+
* - `null`: skip recompute (existing `claim_locked_until` is left as-is).
|
|
842
|
+
* - `PublicKey[]`: use exactly this list (filtered to exclude the closed entry).
|
|
843
|
+
*/
|
|
844
|
+
async resolveRemainingStakeEntries(poolId, pid, staker, excludeStakeEntry, explicit) {
|
|
845
|
+
if (explicit === null)
|
|
846
|
+
return null;
|
|
847
|
+
if (explicit !== undefined) {
|
|
848
|
+
return explicit.filter((pk) => !pk.equals(excludeStakeEntry));
|
|
849
|
+
}
|
|
850
|
+
try {
|
|
851
|
+
const all = await this.fetchAllStakeEntriesByPool(poolId, pid);
|
|
852
|
+
return all
|
|
853
|
+
.filter((e) => e.isActive && new PublicKey(e.owner).equals(staker))
|
|
854
|
+
.map((e) => getStakeEntryPda(pid, poolId, new PublicKey(e.nftMint))[0])
|
|
855
|
+
.filter((pk) => !pk.equals(excludeStakeEntry));
|
|
856
|
+
}
|
|
857
|
+
catch {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Unstake a previously staked NFT.
|
|
863
|
+
*
|
|
864
|
+
* Pass `opts.remainingStakeEntries` (the staker's other active StakeEntry
|
|
865
|
+
* PDAs in this pool, NOT including the one being unstaked) so the on-chain
|
|
866
|
+
* handler can recompute `claim_locked_until` from scratch. If omitted, the
|
|
867
|
+
* existing `claim_locked_until` is left unchanged on the staker account —
|
|
868
|
+
* the staker stays behind their lock until either it expires naturally or
|
|
869
|
+
* they retry the call with the full set of remaining entries. This default
|
|
870
|
+
* is intentionally conservative; client UIs should always supply the list
|
|
871
|
+
* to avoid stranding stakers behind a phantom lock.
|
|
872
|
+
*
|
|
873
|
+
* If `opts.remainingStakeEntries === undefined`, the SDK will attempt to
|
|
874
|
+
* auto-discover them via `fetchAllStakeEntriesByPool` (one extra RPC
|
|
875
|
+
* call). Pass `null` to opt out of discovery entirely.
|
|
876
|
+
*/
|
|
510
877
|
async unstakeNft(poolId, nftMint, opts) {
|
|
878
|
+
const _nftMint = toPk(nftMint);
|
|
511
879
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
512
880
|
const pool = await this.getPoolData(pid, poolId);
|
|
513
881
|
const rewardMint = new PublicKey(pool.rewardConfig.rewardMint);
|
|
514
882
|
const stakingMode = pool.stakingMode;
|
|
515
883
|
const rewardTokenProgram = await this.resolveTokenProgram(rewardMint);
|
|
516
|
-
const nftTokenProgram = await this.resolveTokenProgram(
|
|
884
|
+
const nftTokenProgram = await this.resolveTokenProgram(_nftMint);
|
|
517
885
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
518
|
-
const [stakeEntry] = getStakeEntryPda(poolId,
|
|
886
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, _nftMint);
|
|
887
|
+
const [stakeLock] = getNftStakeLockPda(_nftMint);
|
|
519
888
|
const staker = this.provider.wallet.publicKey;
|
|
520
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
521
|
-
const [
|
|
522
|
-
const
|
|
889
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
890
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
891
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
892
|
+
const stakerNftAccount = getAta(staker, _nftMint, nftTokenProgram);
|
|
523
893
|
const isEscrow = stakingMode !== 1;
|
|
524
|
-
const escrowNftAccount = isEscrow
|
|
525
|
-
? getAta(poolAuthority, nftMint, nftTokenProgram)
|
|
526
|
-
: null;
|
|
894
|
+
const escrowNftAccount = isEscrow ? getAta(poolAuthority, _nftMint, nftTokenProgram) : null;
|
|
527
895
|
const rewardVault = getAta(poolAuthority, rewardMint, rewardTokenProgram);
|
|
528
896
|
const stakerRewardAccount = getAta(staker, rewardMint, rewardTokenProgram);
|
|
529
|
-
const nftMetadata = getMetadataPda(
|
|
530
|
-
const nftEdition = getEditionPda(
|
|
531
|
-
const
|
|
897
|
+
const nftMetadata = getMetadataPda(_nftMint);
|
|
898
|
+
const nftEdition = getEditionPda(_nftMint);
|
|
899
|
+
const { tokenRecord, destinationTokenRecord } = await this.resolvePNftTokenRecords(_nftMint,
|
|
900
|
+
// For unstake, the *source* of the NFT is the escrow when escrowed,
|
|
901
|
+
// otherwise the staker's wallet ATA (with the staker_account PDA as
|
|
902
|
+
// delegate).
|
|
903
|
+
escrowNftAccount ?? stakerNftAccount, escrowNftAccount ? stakerNftAccount : null);
|
|
904
|
+
// entry has secondary rates), then the remaining StakeEntry PDAs for
|
|
905
|
+
// the claim-lock recompute.
|
|
906
|
+
const entry = await this.fetchStakeEntry(poolId, _nftMint, pid);
|
|
907
|
+
const hasSecondary = entry?.secondaryRateContributions?.some((r) => r > 0n) ?? false;
|
|
908
|
+
const remainingAccounts = [];
|
|
909
|
+
if (hasSecondary) {
|
|
910
|
+
const [stakerSecondary] = getStakerSecondaryRewardsPda(pid, poolId, staker);
|
|
911
|
+
remainingAccounts.push({ pubkey: stakerSecondary, isSigner: false, isWritable: true });
|
|
912
|
+
}
|
|
913
|
+
let stakeEntryList = await this.resolveRemainingStakeEntries(poolId, pid, staker, stakeEntry, opts?.remainingStakeEntries);
|
|
914
|
+
if (stakeEntryList) {
|
|
915
|
+
for (const pk of stakeEntryList) {
|
|
916
|
+
remainingAccounts.push({ pubkey: pk, isSigner: false, isWritable: false });
|
|
917
|
+
}
|
|
918
|
+
}
|
|
532
919
|
return this.program.methods
|
|
533
|
-
.unstakeNft(new BN(pid), new BN(poolId),
|
|
920
|
+
.unstakeNft(new BN(pid), new BN(poolId), _nftMint)
|
|
534
921
|
.accountsStrict({
|
|
535
922
|
pool: poolPda,
|
|
536
923
|
stakeEntry,
|
|
924
|
+
stakeLock,
|
|
537
925
|
stakerAccount,
|
|
538
926
|
poolAuthority,
|
|
539
|
-
nftMint,
|
|
927
|
+
nftMint: _nftMint,
|
|
540
928
|
stakerNftAccount,
|
|
541
929
|
escrowNftAccount,
|
|
542
930
|
rewardVault,
|
|
@@ -546,19 +934,16 @@ export class NftStakingClient {
|
|
|
546
934
|
nftEdition,
|
|
547
935
|
mplTokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
|
|
548
936
|
sysvarInstructions: SYSVAR_INSTRUCTIONS_ID,
|
|
549
|
-
tokenRecord
|
|
550
|
-
destinationTokenRecord
|
|
551
|
-
feeConfig: feeAccts.feeConfig,
|
|
552
|
-
treasury: feeAccts.treasury,
|
|
553
|
-
referralAccount: feeAccts.referralAccount,
|
|
554
|
-
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
555
|
-
adminProgram: feeAccts.adminProgram,
|
|
937
|
+
tokenRecord,
|
|
938
|
+
destinationTokenRecord,
|
|
556
939
|
staker,
|
|
940
|
+
userProfile,
|
|
557
941
|
tokenProgram: nftTokenProgram,
|
|
558
942
|
rewardTokenProgram,
|
|
559
943
|
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
560
944
|
systemProgram: SystemProgram.programId,
|
|
561
945
|
})
|
|
946
|
+
.remainingAccounts(remainingAccounts)
|
|
562
947
|
.instruction();
|
|
563
948
|
}
|
|
564
949
|
/** Claim accrued rewards. Auto-resolves rewardMint from pool. */
|
|
@@ -570,12 +955,14 @@ export class NftStakingClient {
|
|
|
570
955
|
const tokenProgram = await this.resolveTokenProgram(rewardMint);
|
|
571
956
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
572
957
|
const staker = this.provider.wallet.publicKey;
|
|
573
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
574
|
-
const [
|
|
958
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
959
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
960
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
575
961
|
const rewardVault = getAta(poolAuthority, rewardMint, tokenProgram);
|
|
576
962
|
const stakerRewardAccount = getAta(staker, rewardMint, tokenProgram);
|
|
577
963
|
const [stakeConfig] = getStakeConfigPda(pid);
|
|
578
|
-
const feeAccts = this.resolveFeeAccounts(opts?.fee);
|
|
964
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
965
|
+
const [project] = getProjectPda(pid);
|
|
579
966
|
return this.program.methods
|
|
580
967
|
.claimRewards(new BN(pid), new BN(poolId), traitBonusRate)
|
|
581
968
|
.accountsStrict({
|
|
@@ -588,11 +975,14 @@ export class NftStakingClient {
|
|
|
588
975
|
stakeConfig,
|
|
589
976
|
instructionsSysvar: new PublicKey("Sysvar1nstructions1111111111111111111111111"),
|
|
590
977
|
feeConfig: feeAccts.feeConfig,
|
|
978
|
+
programRegistry: feeAccts.programRegistry,
|
|
591
979
|
treasury: feeAccts.treasury,
|
|
592
980
|
referralAccount: feeAccts.referralAccount,
|
|
593
981
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
594
982
|
adminProgram: feeAccts.adminProgram,
|
|
983
|
+
project,
|
|
595
984
|
staker,
|
|
985
|
+
userProfile,
|
|
596
986
|
tokenProgram,
|
|
597
987
|
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
598
988
|
systemProgram: SystemProgram.programId,
|
|
@@ -601,84 +991,107 @@ export class NftStakingClient {
|
|
|
601
991
|
}
|
|
602
992
|
/** Stake a Core NFT. Auto-resolves collection from pool. */
|
|
603
993
|
async stakeCoreNft(poolId, nftAsset, opts) {
|
|
994
|
+
const _nftAsset = toPk(nftAsset);
|
|
604
995
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
605
996
|
const pool = await this.getPoolData(pid, poolId);
|
|
606
997
|
const collection = new PublicKey(pool.collectionMint);
|
|
607
998
|
const lockTierIndex = opts?.lockTierIndex ?? null;
|
|
608
999
|
const gateProof = opts?.gateProof ?? [];
|
|
609
1000
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
610
|
-
const [stakeEntry] = getStakeEntryPda(poolId,
|
|
1001
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, _nftAsset);
|
|
1002
|
+
const [stakeLock] = getNftStakeLockPda(_nftAsset);
|
|
611
1003
|
const staker = this.provider.wallet.publicKey;
|
|
612
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
613
|
-
const [
|
|
614
|
-
const
|
|
1004
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1005
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1006
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1007
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
1008
|
+
const [project] = getProjectPda(pid);
|
|
615
1009
|
const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
|
|
616
1010
|
return this.program.methods
|
|
617
|
-
.stakeCoreNft(new BN(pid), new BN(poolId),
|
|
1011
|
+
.stakeCoreNft(new BN(pid), new BN(poolId), _nftAsset, lockTierIndex, gateProof)
|
|
618
1012
|
.accountsStrict({
|
|
619
1013
|
pool: poolPda,
|
|
620
1014
|
stakeEntry,
|
|
1015
|
+
stakeLock,
|
|
621
1016
|
stakerAccount,
|
|
622
1017
|
poolAuthority,
|
|
623
|
-
nftAsset,
|
|
1018
|
+
nftAsset: _nftAsset,
|
|
624
1019
|
collection,
|
|
625
1020
|
mplCoreProgram: MPL_CORE_PROGRAM_ID,
|
|
626
1021
|
utilityConfig,
|
|
627
1022
|
feeConfig: feeAccts.feeConfig,
|
|
1023
|
+
programRegistry: feeAccts.programRegistry,
|
|
628
1024
|
treasury: feeAccts.treasury,
|
|
629
1025
|
referralAccount: feeAccts.referralAccount,
|
|
630
1026
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
631
1027
|
adminProgram: feeAccts.adminProgram,
|
|
1028
|
+
project,
|
|
632
1029
|
staker,
|
|
1030
|
+
userProfile,
|
|
633
1031
|
systemProgram: SystemProgram.programId,
|
|
634
1032
|
})
|
|
635
1033
|
.instruction();
|
|
636
1034
|
}
|
|
637
1035
|
/** Unstake a Core NFT. Auto-resolves collection and rewardMint from pool. */
|
|
638
1036
|
async unstakeCoreNft(poolId, nftAsset, opts) {
|
|
1037
|
+
const _nftAsset = toPk(nftAsset);
|
|
639
1038
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
640
1039
|
const pool = await this.getPoolData(pid, poolId);
|
|
641
1040
|
const collection = new PublicKey(pool.collectionMint);
|
|
642
1041
|
const rewardMint = new PublicKey(pool.rewardConfig.rewardMint);
|
|
643
1042
|
const rewardTokenProgram = await this.resolveTokenProgram(rewardMint);
|
|
644
1043
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
645
|
-
const [stakeEntry] = getStakeEntryPda(poolId,
|
|
1044
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, _nftAsset);
|
|
1045
|
+
const [stakeLock] = getNftStakeLockPda(_nftAsset);
|
|
646
1046
|
const staker = this.provider.wallet.publicKey;
|
|
647
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
648
|
-
const [
|
|
1047
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1048
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1049
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
649
1050
|
const rewardVault = getAta(poolAuthority, rewardMint, rewardTokenProgram);
|
|
650
1051
|
const stakerRewardAccount = getAta(staker, rewardMint, rewardTokenProgram);
|
|
651
|
-
|
|
1052
|
+
// Build remaining_accounts (optional secondary + staker's other active
|
|
1053
|
+
// StakeEntry PDAs).
|
|
1054
|
+
const entry = await this.fetchStakeEntry(poolId, _nftAsset, pid);
|
|
1055
|
+
const hasSecondary = entry?.secondaryRateContributions?.some((r) => r > 0n) ?? false;
|
|
1056
|
+
const remainingAccounts = [];
|
|
1057
|
+
if (hasSecondary) {
|
|
1058
|
+
const [stakerSecondary] = getStakerSecondaryRewardsPda(pid, poolId, staker);
|
|
1059
|
+
remainingAccounts.push({ pubkey: stakerSecondary, isSigner: false, isWritable: true });
|
|
1060
|
+
}
|
|
1061
|
+
const stakeEntryList = await this.resolveRemainingStakeEntries(poolId, pid, staker, stakeEntry, opts?.remainingStakeEntries);
|
|
1062
|
+
if (stakeEntryList) {
|
|
1063
|
+
for (const pk of stakeEntryList) {
|
|
1064
|
+
remainingAccounts.push({ pubkey: pk, isSigner: false, isWritable: false });
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
652
1067
|
return this.program.methods
|
|
653
|
-
.unstakeCoreNft(new BN(pid), new BN(poolId),
|
|
1068
|
+
.unstakeCoreNft(new BN(pid), new BN(poolId), _nftAsset)
|
|
654
1069
|
.accountsStrict({
|
|
655
1070
|
pool: poolPda,
|
|
656
1071
|
stakeEntry,
|
|
1072
|
+
stakeLock,
|
|
657
1073
|
stakerAccount,
|
|
658
1074
|
poolAuthority,
|
|
659
|
-
nftAsset,
|
|
1075
|
+
nftAsset: _nftAsset,
|
|
660
1076
|
collection,
|
|
661
1077
|
mplCoreProgram: MPL_CORE_PROGRAM_ID,
|
|
662
1078
|
rewardVault,
|
|
663
1079
|
stakerRewardAccount,
|
|
664
1080
|
rewardMint,
|
|
665
|
-
feeConfig: feeAccts.feeConfig,
|
|
666
|
-
treasury: feeAccts.treasury,
|
|
667
|
-
referralAccount: feeAccts.referralAccount,
|
|
668
|
-
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
669
|
-
adminProgram: feeAccts.adminProgram,
|
|
670
1081
|
staker,
|
|
1082
|
+
userProfile,
|
|
671
1083
|
rewardTokenProgram,
|
|
672
1084
|
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
673
1085
|
systemProgram: SystemProgram.programId,
|
|
674
1086
|
})
|
|
1087
|
+
.remainingAccounts(remainingAccounts)
|
|
675
1088
|
.instruction();
|
|
676
1089
|
}
|
|
677
1090
|
/** Stake a compressed NFT (cNFT). All address/hash params accepted as strings. */
|
|
678
1091
|
async stakeCnft(poolId, cnftParams, opts) {
|
|
679
1092
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
680
1093
|
const gateProof = opts?.gateProof ?? [];
|
|
681
|
-
const { nftAssetId, merkleTree, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes } = cnftParams;
|
|
1094
|
+
const { nftAssetId, merkleTree, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes, } = cnftParams;
|
|
682
1095
|
const nftAssetIdPk = new PublicKey(nftAssetId);
|
|
683
1096
|
const merkleTreePk = new PublicKey(merkleTree);
|
|
684
1097
|
const [treeConfigPk] = getTreeConfigPda(merkleTreePk);
|
|
@@ -686,11 +1099,14 @@ export class NftStakingClient {
|
|
|
686
1099
|
const cnftDataHashArr = base58HashToArray(cnftDataHash);
|
|
687
1100
|
const cnftCreatorHashArr = base58HashToArray(cnftCreatorHash);
|
|
688
1101
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
689
|
-
const [stakeEntry] = getStakeEntryPda(poolId, nftAssetIdPk);
|
|
1102
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, nftAssetIdPk);
|
|
1103
|
+
const [stakeLock] = getNftStakeLockPda(nftAssetIdPk);
|
|
690
1104
|
const staker = this.provider.wallet.publicKey;
|
|
691
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
692
|
-
const [
|
|
693
|
-
const
|
|
1105
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1106
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1107
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1108
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
1109
|
+
const [project] = getProjectPda(pid);
|
|
694
1110
|
const [utilityConfig] = getUtilityConfigPda(pid, this.program.programId);
|
|
695
1111
|
const remainingAccounts = proofNodes.map((node) => ({
|
|
696
1112
|
pubkey: new PublicKey(node),
|
|
@@ -702,6 +1118,7 @@ export class NftStakingClient {
|
|
|
702
1118
|
.accountsStrict({
|
|
703
1119
|
pool: poolPda,
|
|
704
1120
|
stakeEntry,
|
|
1121
|
+
stakeLock,
|
|
705
1122
|
stakerAccount,
|
|
706
1123
|
poolAuthority,
|
|
707
1124
|
treeConfig: treeConfigPk,
|
|
@@ -711,11 +1128,14 @@ export class NftStakingClient {
|
|
|
711
1128
|
bubblegumProgram: BUBBLEGUM_PROGRAM_ID,
|
|
712
1129
|
utilityConfig,
|
|
713
1130
|
feeConfig: feeAccts.feeConfig,
|
|
1131
|
+
programRegistry: feeAccts.programRegistry,
|
|
714
1132
|
treasury: feeAccts.treasury,
|
|
715
1133
|
referralAccount: feeAccts.referralAccount,
|
|
716
1134
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
717
1135
|
adminProgram: feeAccts.adminProgram,
|
|
1136
|
+
project,
|
|
718
1137
|
staker,
|
|
1138
|
+
userProfile,
|
|
719
1139
|
systemProgram: SystemProgram.programId,
|
|
720
1140
|
})
|
|
721
1141
|
.remainingAccounts(remainingAccounts)
|
|
@@ -725,7 +1145,7 @@ export class NftStakingClient {
|
|
|
725
1145
|
async unstakeCnft(poolId, cnftParams, opts) {
|
|
726
1146
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
727
1147
|
const pool = await this.getPoolData(pid, poolId);
|
|
728
|
-
const { nftAssetId, merkleTree, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes } = cnftParams;
|
|
1148
|
+
const { nftAssetId, merkleTree, cnftRoot, cnftDataHash, cnftCreatorHash, cnftNonce, cnftIndex, proofNodes, } = cnftParams;
|
|
729
1149
|
const nftAssetIdPk = new PublicKey(nftAssetId);
|
|
730
1150
|
const merkleTreePk = new PublicKey(merkleTree);
|
|
731
1151
|
const [treeConfigPk] = getTreeConfigPda(merkleTreePk);
|
|
@@ -734,23 +1154,45 @@ export class NftStakingClient {
|
|
|
734
1154
|
const cnftDataHashArr = base58HashToArray(cnftDataHash);
|
|
735
1155
|
const cnftCreatorHashArr = base58HashToArray(cnftCreatorHash);
|
|
736
1156
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
737
|
-
const [stakeEntry] = getStakeEntryPda(poolId, nftAssetIdPk);
|
|
1157
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, nftAssetIdPk);
|
|
1158
|
+
const [stakeLock] = getNftStakeLockPda(nftAssetIdPk);
|
|
738
1159
|
const staker = this.provider.wallet.publicKey;
|
|
739
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
740
|
-
const [
|
|
741
|
-
const
|
|
1160
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1161
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1162
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
742
1163
|
const rewardVaultPk = getAta(poolAuthority, rewardMintPk);
|
|
743
1164
|
const stakerRewardAccountPk = getAta(staker, rewardMintPk);
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1165
|
+
// cNFT layout for remaining_accounts is
|
|
1166
|
+
// [<staker_secondary if has_secondary>, <proof nodes...>, <trailing
|
|
1167
|
+
// StakeEntry PDAs for claim-lock recompute>]
|
|
1168
|
+
// The on-chain handler tail-walks for program-owned accounts to find
|
|
1169
|
+
// stake entries.
|
|
1170
|
+
const entry = await this.fetchStakeEntry(poolId, nftAssetIdPk, pid);
|
|
1171
|
+
const hasSecondary = entry?.secondaryRateContributions?.some((r) => r > 0n) ?? false;
|
|
1172
|
+
const remainingAccounts = [];
|
|
1173
|
+
if (hasSecondary) {
|
|
1174
|
+
const [stakerSecondary] = getStakerSecondaryRewardsPda(pid, poolId, staker);
|
|
1175
|
+
remainingAccounts.push({ pubkey: stakerSecondary, isSigner: false, isWritable: true });
|
|
1176
|
+
}
|
|
1177
|
+
for (const node of proofNodes) {
|
|
1178
|
+
remainingAccounts.push({
|
|
1179
|
+
pubkey: new PublicKey(node),
|
|
1180
|
+
isSigner: false,
|
|
1181
|
+
isWritable: false,
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
const stakeEntryList = await this.resolveRemainingStakeEntries(poolId, pid, staker, stakeEntry, opts?.remainingStakeEntries);
|
|
1185
|
+
if (stakeEntryList) {
|
|
1186
|
+
for (const pk of stakeEntryList) {
|
|
1187
|
+
remainingAccounts.push({ pubkey: pk, isSigner: false, isWritable: false });
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
749
1190
|
return this.program.methods
|
|
750
1191
|
.unstakeCnft(new BN(pid), new BN(poolId), nftAssetIdPk, cnftRootArr, cnftDataHashArr, cnftCreatorHashArr, new BN(cnftNonce), cnftIndex)
|
|
751
1192
|
.accountsStrict({
|
|
752
1193
|
pool: poolPda,
|
|
753
1194
|
stakeEntry,
|
|
1195
|
+
stakeLock,
|
|
754
1196
|
stakerAccount,
|
|
755
1197
|
poolAuthority,
|
|
756
1198
|
treeConfig: treeConfigPk,
|
|
@@ -761,12 +1203,8 @@ export class NftStakingClient {
|
|
|
761
1203
|
rewardVault: rewardVaultPk,
|
|
762
1204
|
stakerRewardAccount: stakerRewardAccountPk,
|
|
763
1205
|
rewardMint: rewardMintPk,
|
|
764
|
-
feeConfig: feeAccts.feeConfig,
|
|
765
|
-
treasury: feeAccts.treasury,
|
|
766
|
-
referralAccount: feeAccts.referralAccount,
|
|
767
|
-
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
768
|
-
adminProgram: feeAccts.adminProgram,
|
|
769
1206
|
staker,
|
|
1207
|
+
userProfile,
|
|
770
1208
|
tokenProgram: TOKEN_PROGRAM_ID,
|
|
771
1209
|
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
772
1210
|
systemProgram: SystemProgram.programId,
|
|
@@ -774,31 +1212,57 @@ export class NftStakingClient {
|
|
|
774
1212
|
.remainingAccounts(remainingAccounts)
|
|
775
1213
|
.instruction();
|
|
776
1214
|
}
|
|
777
|
-
/**
|
|
1215
|
+
/**
|
|
1216
|
+
* Burn a permanently-locked legacy/pNFT. Auto-resolves stakingMode from pool.
|
|
1217
|
+
*
|
|
1218
|
+
* Pass `opts.remainingStakeEntries` so the on-chain handler can recompute
|
|
1219
|
+
* `claim_locked_until`. See `unstakeNft` for full semantics. Default: SDK
|
|
1220
|
+
* auto-discovers remaining entries via gPA.
|
|
1221
|
+
*/
|
|
778
1222
|
async burnStakedNft(poolId, nftMint, opts) {
|
|
1223
|
+
const _nftMint = toPk(nftMint);
|
|
779
1224
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
780
1225
|
const pool = await this.getPoolData(pid, poolId);
|
|
781
1226
|
const stakingMode = pool.stakingMode;
|
|
782
|
-
const nftTokenProgram = await this.resolveTokenProgram(
|
|
1227
|
+
const nftTokenProgram = await this.resolveTokenProgram(_nftMint);
|
|
783
1228
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
784
|
-
const [stakeEntry] = getStakeEntryPda(poolId,
|
|
1229
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, _nftMint);
|
|
1230
|
+
const [stakeLock] = getNftStakeLockPda(_nftMint);
|
|
785
1231
|
const staker = this.provider.wallet.publicKey;
|
|
786
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
787
|
-
const [
|
|
788
|
-
const
|
|
1232
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1233
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1234
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1235
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
1236
|
+
const [project] = getProjectPda(pid);
|
|
789
1237
|
const isWalletLock = stakingMode === 1;
|
|
790
|
-
const escrowNftAccount = isWalletLock ? null : getAta(poolAuthority,
|
|
791
|
-
const stakerNftAccount = isWalletLock ? getAta(staker,
|
|
792
|
-
const nftMetadata = isWalletLock ? getMetadataPda(
|
|
793
|
-
const nftEdition = isWalletLock ? getEditionPda(
|
|
1238
|
+
const escrowNftAccount = isWalletLock ? null : getAta(poolAuthority, _nftMint, nftTokenProgram);
|
|
1239
|
+
const stakerNftAccount = isWalletLock ? getAta(staker, _nftMint, nftTokenProgram) : null;
|
|
1240
|
+
const nftMetadata = isWalletLock ? getMetadataPda(_nftMint) : null;
|
|
1241
|
+
const nftEdition = isWalletLock ? getEditionPda(_nftMint) : null;
|
|
1242
|
+
// Assemble remaining_accounts (secondary slot first when applicable,
|
|
1243
|
+
// then remaining StakeEntry PDAs).
|
|
1244
|
+
const entry = await this.fetchStakeEntry(poolId, _nftMint, pid);
|
|
1245
|
+
const hasSecondary = entry?.secondaryRateContributions?.some((r) => r > 0n) ?? false;
|
|
1246
|
+
const remainingAccounts = [];
|
|
1247
|
+
if (hasSecondary) {
|
|
1248
|
+
const [stakerSecondary] = getStakerSecondaryRewardsPda(pid, poolId, staker);
|
|
1249
|
+
remainingAccounts.push({ pubkey: stakerSecondary, isSigner: false, isWritable: true });
|
|
1250
|
+
}
|
|
1251
|
+
let stakeEntryList = await this.resolveRemainingStakeEntries(poolId, pid, staker, stakeEntry, opts?.remainingStakeEntries);
|
|
1252
|
+
if (stakeEntryList) {
|
|
1253
|
+
for (const pk of stakeEntryList) {
|
|
1254
|
+
remainingAccounts.push({ pubkey: pk, isSigner: false, isWritable: false });
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
794
1257
|
return this.program.methods
|
|
795
|
-
.burnStakedNft(new BN(pid), new BN(poolId),
|
|
1258
|
+
.burnStakedNft(new BN(pid), new BN(poolId), _nftMint)
|
|
796
1259
|
.accountsStrict({
|
|
797
1260
|
pool: poolPda,
|
|
798
1261
|
stakeEntry,
|
|
1262
|
+
stakeLock,
|
|
799
1263
|
stakerAccount,
|
|
800
1264
|
poolAuthority,
|
|
801
|
-
nftMint,
|
|
1265
|
+
nftMint: _nftMint,
|
|
802
1266
|
escrowNftAccount,
|
|
803
1267
|
stakerNftAccount,
|
|
804
1268
|
nftMetadata,
|
|
@@ -808,45 +1272,72 @@ export class NftStakingClient {
|
|
|
808
1272
|
tokenRecord: null,
|
|
809
1273
|
collectionMetadata: null,
|
|
810
1274
|
feeConfig: feeAccts.feeConfig,
|
|
1275
|
+
programRegistry: feeAccts.programRegistry,
|
|
811
1276
|
treasury: feeAccts.treasury,
|
|
812
1277
|
referralAccount: feeAccts.referralAccount,
|
|
813
1278
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
814
1279
|
adminProgram: feeAccts.adminProgram,
|
|
1280
|
+
project,
|
|
815
1281
|
staker,
|
|
1282
|
+
userProfile,
|
|
816
1283
|
tokenProgram: nftTokenProgram,
|
|
817
1284
|
systemProgram: SystemProgram.programId,
|
|
818
1285
|
})
|
|
1286
|
+
.remainingAccounts(remainingAccounts)
|
|
819
1287
|
.instruction();
|
|
820
1288
|
}
|
|
821
1289
|
/** Burn a permanently-locked Core NFT. Auto-resolves collection from pool. */
|
|
822
1290
|
async burnStakedCoreNft(poolId, nftAsset, opts) {
|
|
1291
|
+
const _nftAsset = toPk(nftAsset);
|
|
823
1292
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
824
1293
|
const pool = await this.getPoolData(pid, poolId);
|
|
825
1294
|
const collection = new PublicKey(pool.collectionMint);
|
|
826
1295
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
827
|
-
const [stakeEntry] = getStakeEntryPda(poolId,
|
|
1296
|
+
const [stakeEntry] = getStakeEntryPda(pid, poolId, _nftAsset);
|
|
1297
|
+
const [stakeLock] = getNftStakeLockPda(_nftAsset);
|
|
828
1298
|
const staker = this.provider.wallet.publicKey;
|
|
829
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
830
|
-
const [
|
|
831
|
-
const
|
|
1299
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1300
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1301
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1302
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
1303
|
+
const [project] = getProjectPda(pid);
|
|
1304
|
+
// Build remaining_accounts (secondary + stake entries).
|
|
1305
|
+
const entry = await this.fetchStakeEntry(poolId, _nftAsset, pid);
|
|
1306
|
+
const hasSecondary = entry?.secondaryRateContributions?.some((r) => r > 0n) ?? false;
|
|
1307
|
+
const remainingAccounts = [];
|
|
1308
|
+
if (hasSecondary) {
|
|
1309
|
+
const [stakerSecondary] = getStakerSecondaryRewardsPda(pid, poolId, staker);
|
|
1310
|
+
remainingAccounts.push({ pubkey: stakerSecondary, isSigner: false, isWritable: true });
|
|
1311
|
+
}
|
|
1312
|
+
const stakeEntryList = await this.resolveRemainingStakeEntries(poolId, pid, staker, stakeEntry, opts?.remainingStakeEntries);
|
|
1313
|
+
if (stakeEntryList) {
|
|
1314
|
+
for (const pk of stakeEntryList) {
|
|
1315
|
+
remainingAccounts.push({ pubkey: pk, isSigner: false, isWritable: false });
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
832
1318
|
return this.program.methods
|
|
833
|
-
.burnStakedCoreNft(new BN(pid), new BN(poolId),
|
|
1319
|
+
.burnStakedCoreNft(new BN(pid), new BN(poolId), _nftAsset)
|
|
834
1320
|
.accountsStrict({
|
|
835
1321
|
pool: poolPda,
|
|
836
1322
|
stakeEntry,
|
|
1323
|
+
stakeLock,
|
|
837
1324
|
stakerAccount,
|
|
838
1325
|
poolAuthority,
|
|
839
|
-
nftAsset,
|
|
1326
|
+
nftAsset: _nftAsset,
|
|
840
1327
|
collection,
|
|
841
1328
|
mplCoreProgram: MPL_CORE_PROGRAM_ID,
|
|
842
1329
|
feeConfig: feeAccts.feeConfig,
|
|
1330
|
+
programRegistry: feeAccts.programRegistry,
|
|
843
1331
|
treasury: feeAccts.treasury,
|
|
844
1332
|
referralAccount: feeAccts.referralAccount,
|
|
845
1333
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
846
1334
|
adminProgram: feeAccts.adminProgram,
|
|
1335
|
+
project,
|
|
847
1336
|
staker,
|
|
1337
|
+
userProfile,
|
|
848
1338
|
systemProgram: SystemProgram.programId,
|
|
849
1339
|
})
|
|
1340
|
+
.remainingAccounts(remainingAccounts)
|
|
850
1341
|
.instruction();
|
|
851
1342
|
}
|
|
852
1343
|
/** Spend accrued points from a Points reward type pool. */
|
|
@@ -854,32 +1345,39 @@ export class NftStakingClient {
|
|
|
854
1345
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
855
1346
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
856
1347
|
const staker = this.provider.wallet.publicKey;
|
|
857
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
858
|
-
const
|
|
1348
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1349
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1350
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
1351
|
+
const [project] = getProjectPda(pid);
|
|
859
1352
|
return this.program.methods
|
|
860
|
-
.spendPoints(new BN(pid), new BN(poolId), amount)
|
|
1353
|
+
.spendPoints(new BN(pid), new BN(poolId), toBN(amount))
|
|
861
1354
|
.accountsStrict({
|
|
862
1355
|
pool: poolPda,
|
|
863
1356
|
stakerAccount,
|
|
864
1357
|
feeConfig: feeAccts.feeConfig,
|
|
1358
|
+
programRegistry: feeAccts.programRegistry,
|
|
865
1359
|
treasury: feeAccts.treasury,
|
|
866
1360
|
referralAccount: feeAccts.referralAccount,
|
|
867
1361
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
868
1362
|
adminProgram: feeAccts.adminProgram,
|
|
1363
|
+
project,
|
|
869
1364
|
staker,
|
|
1365
|
+
userProfile,
|
|
870
1366
|
systemProgram: SystemProgram.programId,
|
|
871
1367
|
})
|
|
872
1368
|
.instruction();
|
|
873
1369
|
}
|
|
874
|
-
async fetchPoolSecondaryRewards(poolId) {
|
|
875
|
-
const
|
|
1370
|
+
async fetchPoolSecondaryRewards(poolId, projectId) {
|
|
1371
|
+
const pid = this.resolveProjectId(projectId);
|
|
1372
|
+
const [pda] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
876
1373
|
const raw = await this.program.account.poolSecondaryRewards.fetchNullable(pda);
|
|
877
1374
|
if (!raw)
|
|
878
1375
|
return null;
|
|
879
1376
|
return decodeAccount(raw);
|
|
880
1377
|
}
|
|
881
|
-
async fetchStakerSecondaryRewards(poolId, wallet) {
|
|
882
|
-
const
|
|
1378
|
+
async fetchStakerSecondaryRewards(poolId, wallet, projectId) {
|
|
1379
|
+
const pid = this.resolveProjectId(projectId);
|
|
1380
|
+
const [pda] = getStakerSecondaryRewardsPda(pid, poolId, toPk(wallet));
|
|
883
1381
|
const raw = await this.program.account.stakerSecondaryRewards.fetchNullable(pda);
|
|
884
1382
|
if (!raw)
|
|
885
1383
|
return null;
|
|
@@ -887,18 +1385,22 @@ export class NftStakingClient {
|
|
|
887
1385
|
}
|
|
888
1386
|
async addPoolSecondaryReward(poolId, rewardMint, baseRate, lockTierRates, projectId) {
|
|
889
1387
|
const pid = this.resolveProjectId(projectId);
|
|
1388
|
+
const _rewardMint = toPk(rewardMint);
|
|
890
1389
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
891
|
-
const [
|
|
892
|
-
const [
|
|
893
|
-
const
|
|
894
|
-
const
|
|
1390
|
+
const [project] = getProjectPda(pid);
|
|
1391
|
+
const [poolSecondary] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
1392
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1393
|
+
const tokenProgram = await this.resolveTokenProgram(_rewardMint);
|
|
1394
|
+
const vault = getAta(poolAuthority, _rewardMint, tokenProgram);
|
|
895
1395
|
return this.program.methods
|
|
896
|
-
.addPoolSecondaryReward(new BN(pid), new BN(poolId), baseRate, lockTierRates)
|
|
1396
|
+
.addPoolSecondaryReward(new BN(pid), new BN(poolId), toBN(baseRate), lockTierRates.map(toBN))
|
|
897
1397
|
.accountsStrict({
|
|
898
1398
|
pool: poolPda,
|
|
1399
|
+
project,
|
|
1400
|
+
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
899
1401
|
poolSecondary,
|
|
900
1402
|
poolAuthority,
|
|
901
|
-
secondaryRewardMint:
|
|
1403
|
+
secondaryRewardMint: _rewardMint,
|
|
902
1404
|
secondaryVault: vault,
|
|
903
1405
|
authority: this.provider.wallet.publicKey,
|
|
904
1406
|
tokenProgram,
|
|
@@ -910,11 +1412,14 @@ export class NftStakingClient {
|
|
|
910
1412
|
async removePoolSecondaryReward(poolId, rewardIndex, projectId) {
|
|
911
1413
|
const pid = this.resolveProjectId(projectId);
|
|
912
1414
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
913
|
-
const [
|
|
1415
|
+
const [project] = getProjectPda(pid);
|
|
1416
|
+
const [poolSecondary] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
914
1417
|
return this.program.methods
|
|
915
1418
|
.removePoolSecondaryReward(new BN(pid), new BN(poolId), rewardIndex)
|
|
916
1419
|
.accountsStrict({
|
|
917
1420
|
pool: poolPda,
|
|
1421
|
+
project,
|
|
1422
|
+
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
918
1423
|
poolSecondary,
|
|
919
1424
|
authority: this.provider.wallet.publicKey,
|
|
920
1425
|
})
|
|
@@ -922,21 +1427,24 @@ export class NftStakingClient {
|
|
|
922
1427
|
}
|
|
923
1428
|
async fundSecondaryVault(poolId, rewardIndex, amount, rewardMint, opts) {
|
|
924
1429
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
925
|
-
const
|
|
1430
|
+
const _rewardMint = toPk(rewardMint);
|
|
1431
|
+
const tokenProgram = await this.resolveTokenProgram(_rewardMint);
|
|
926
1432
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
927
|
-
const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
|
|
928
|
-
const [poolAuthority] = getPoolAuthorityPda(poolId);
|
|
929
|
-
const vault = getAta(poolAuthority,
|
|
930
|
-
const funderAta = opts?.funderTokenAccount
|
|
1433
|
+
const [poolSecondary] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
1434
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1435
|
+
const vault = getAta(poolAuthority, _rewardMint, tokenProgram);
|
|
1436
|
+
const funderAta = opts?.funderTokenAccount
|
|
1437
|
+
? toPk(opts.funderTokenAccount)
|
|
1438
|
+
: getAta(this.provider.wallet.publicKey, _rewardMint, tokenProgram);
|
|
931
1439
|
return this.program.methods
|
|
932
|
-
.fundSecondaryVault(new BN(pid), new BN(poolId), rewardIndex, amount)
|
|
1440
|
+
.fundSecondaryVault(new BN(pid), new BN(poolId), rewardIndex, toBN(amount))
|
|
933
1441
|
.accountsStrict({
|
|
934
1442
|
pool: poolPda,
|
|
935
1443
|
poolSecondary,
|
|
936
1444
|
poolAuthority,
|
|
937
1445
|
secondaryVault: vault,
|
|
938
1446
|
funderTokenAccount: funderAta,
|
|
939
|
-
secondaryRewardMint:
|
|
1447
|
+
secondaryRewardMint: _rewardMint,
|
|
940
1448
|
authority: this.provider.wallet.publicKey,
|
|
941
1449
|
tokenProgram,
|
|
942
1450
|
})
|
|
@@ -944,21 +1452,27 @@ export class NftStakingClient {
|
|
|
944
1452
|
}
|
|
945
1453
|
async withdrawSecondaryVault(poolId, rewardIndex, amount, rewardMint, opts) {
|
|
946
1454
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
947
|
-
const
|
|
1455
|
+
const _rewardMint = toPk(rewardMint);
|
|
1456
|
+
const tokenProgram = await this.resolveTokenProgram(_rewardMint);
|
|
948
1457
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
949
|
-
const [
|
|
950
|
-
const [
|
|
951
|
-
const
|
|
952
|
-
const
|
|
1458
|
+
const [project] = getProjectPda(pid);
|
|
1459
|
+
const [poolSecondary] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
1460
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1461
|
+
const vault = getAta(poolAuthority, _rewardMint, tokenProgram);
|
|
1462
|
+
const destAta = opts?.destinationTokenAccount
|
|
1463
|
+
? toPk(opts.destinationTokenAccount)
|
|
1464
|
+
: getAta(this.provider.wallet.publicKey, _rewardMint, tokenProgram);
|
|
953
1465
|
return this.program.methods
|
|
954
|
-
.withdrawSecondaryVault(new BN(pid), new BN(poolId), rewardIndex, amount)
|
|
1466
|
+
.withdrawSecondaryVault(new BN(pid), new BN(poolId), rewardIndex, toBN(amount))
|
|
955
1467
|
.accountsStrict({
|
|
956
1468
|
pool: poolPda,
|
|
1469
|
+
project,
|
|
1470
|
+
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
957
1471
|
poolSecondary,
|
|
958
1472
|
poolAuthority,
|
|
959
1473
|
secondaryVault: vault,
|
|
960
1474
|
destinationTokenAccount: destAta,
|
|
961
|
-
secondaryRewardMint:
|
|
1475
|
+
secondaryRewardMint: _rewardMint,
|
|
962
1476
|
authority: this.provider.wallet.publicKey,
|
|
963
1477
|
tokenProgram,
|
|
964
1478
|
})
|
|
@@ -967,9 +1481,9 @@ export class NftStakingClient {
|
|
|
967
1481
|
async initStakerSecondary(poolId, projectId) {
|
|
968
1482
|
const pid = this.resolveProjectId(projectId);
|
|
969
1483
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
970
|
-
const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
|
|
1484
|
+
const [poolSecondary] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
971
1485
|
const staker = this.provider.wallet.publicKey;
|
|
972
|
-
const [stakerSecondary] = getStakerSecondaryRewardsPda(poolId, staker);
|
|
1486
|
+
const [stakerSecondary] = getStakerSecondaryRewardsPda(pid, poolId, staker);
|
|
973
1487
|
return this.program.methods
|
|
974
1488
|
.initStakerSecondary(new BN(pid), new BN(poolId))
|
|
975
1489
|
.accountsStrict({
|
|
@@ -984,18 +1498,26 @@ export class NftStakingClient {
|
|
|
984
1498
|
async claimSecondaryRewards(poolId, secondaryRewards, opts) {
|
|
985
1499
|
const pid = this.resolveProjectId(opts?.projectId);
|
|
986
1500
|
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
987
|
-
const [poolSecondary] = getPoolSecondaryRewardsPda(poolId);
|
|
1501
|
+
const [poolSecondary] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
988
1502
|
const staker = this.provider.wallet.publicKey;
|
|
989
|
-
const [stakerAccount] = getStakerAccountPda(poolId, staker);
|
|
990
|
-
const [stakerSecondary] = getStakerSecondaryRewardsPda(poolId, staker);
|
|
991
|
-
const [
|
|
992
|
-
const
|
|
1503
|
+
const [stakerAccount] = getStakerAccountPda(pid, poolId, staker);
|
|
1504
|
+
const [stakerSecondary] = getStakerSecondaryRewardsPda(pid, poolId, staker);
|
|
1505
|
+
const [userProfile] = getUserProfilePda(staker);
|
|
1506
|
+
const [poolAuthority] = getPoolAuthorityPda(pid, poolId);
|
|
1507
|
+
const feeAccts = await this.resolveFeeAccounts(pid, opts?.fee);
|
|
1508
|
+
const [project] = getProjectPda(pid);
|
|
993
1509
|
const remainingAccounts = [];
|
|
1510
|
+
let sharedTokenProgram = null;
|
|
994
1511
|
for (const sr of secondaryRewards) {
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
1512
|
+
const _srMint = toPk(sr.mint);
|
|
1513
|
+
const tokenProgram = await this.resolveTokenProgram(_srMint);
|
|
1514
|
+
if (sharedTokenProgram && !sharedTokenProgram.equals(tokenProgram)) {
|
|
1515
|
+
throw new Error("claimSecondaryRewards requires all secondary mints in one transaction to use the same token program");
|
|
1516
|
+
}
|
|
1517
|
+
sharedTokenProgram = tokenProgram;
|
|
1518
|
+
const vault = getAta(poolAuthority, _srMint, tokenProgram);
|
|
1519
|
+
const stakerAta = getAta(staker, _srMint, tokenProgram);
|
|
1520
|
+
remainingAccounts.push({ pubkey: vault, isSigner: false, isWritable: true }, { pubkey: stakerAta, isSigner: false, isWritable: true }, { pubkey: _srMint, isSigner: false, isWritable: false });
|
|
999
1521
|
}
|
|
1000
1522
|
return this.program.methods
|
|
1001
1523
|
.claimSecondaryRewards(new BN(pid), new BN(poolId))
|
|
@@ -1006,17 +1528,36 @@ export class NftStakingClient {
|
|
|
1006
1528
|
stakerSecondary,
|
|
1007
1529
|
poolAuthority,
|
|
1008
1530
|
feeConfig: feeAccts.feeConfig,
|
|
1531
|
+
programRegistry: feeAccts.programRegistry,
|
|
1009
1532
|
treasury: feeAccts.treasury,
|
|
1010
1533
|
referralAccount: feeAccts.referralAccount,
|
|
1011
1534
|
solUsdPriceFeed: feeAccts.solUsdPriceFeed,
|
|
1012
1535
|
adminProgram: feeAccts.adminProgram,
|
|
1536
|
+
project,
|
|
1013
1537
|
staker,
|
|
1014
|
-
|
|
1538
|
+
userProfile,
|
|
1539
|
+
tokenProgram: sharedTokenProgram ?? TOKEN_PROGRAM_ID,
|
|
1015
1540
|
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1016
1541
|
systemProgram: SystemProgram.programId,
|
|
1017
1542
|
})
|
|
1018
1543
|
.remainingAccounts(remainingAccounts)
|
|
1019
1544
|
.instruction();
|
|
1020
1545
|
}
|
|
1546
|
+
async updatePoolSecondaryReward(poolId, rewardIndex, baseRate, lockTierRates, projectId) {
|
|
1547
|
+
const pid = this.resolveProjectId(projectId);
|
|
1548
|
+
const [poolPda] = getStakePoolPda(pid, poolId);
|
|
1549
|
+
const [project] = getProjectPda(pid);
|
|
1550
|
+
const [poolSecondary] = getPoolSecondaryRewardsPda(pid, poolId);
|
|
1551
|
+
return this.program.methods
|
|
1552
|
+
.updatePoolSecondaryReward(new BN(pid), new BN(poolId), rewardIndex, toBN(baseRate), lockTierRates.map(toBN))
|
|
1553
|
+
.accountsStrict({
|
|
1554
|
+
pool: poolPda,
|
|
1555
|
+
project,
|
|
1556
|
+
projectManagementProgram: PROJECT_MANAGEMENT_PROGRAM_ID,
|
|
1557
|
+
poolSecondary,
|
|
1558
|
+
authority: this.provider.wallet.publicKey,
|
|
1559
|
+
})
|
|
1560
|
+
.instruction();
|
|
1561
|
+
}
|
|
1021
1562
|
}
|
|
1022
1563
|
//# sourceMappingURL=client.js.map
|