@soltracer/nft-staking 0.2.1 → 0.2.4
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 +157 -31
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +624 -132
- 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 +3252 -1457
- package/dist/idl.d.ts.map +1 -1
- package/dist/idl.json +3252 -1457
- 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 +57 -0
- package/dist/merkle.d.ts.map +1 -0
- package/dist/merkle.js +110 -0
- package/dist/merkle.js.map +1 -0
- package/dist/traitProof.d.ts +61 -0
- package/dist/traitProof.d.ts.map +1 -0
- package/dist/traitProof.js +72 -0
- package/dist/traitProof.js.map +1 -0
- package/package.json +4 -2
package/INTEGRATION.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# @soltracer/nft-staking
|
|
2
2
|
|
|
3
|
-
SDK client for the solTracer NFT Staking program. Supports Legacy, pNFT, Core, and cNFT staking with escrow or
|
|
3
|
+
SDK client for the solTracer NFT Staking program. Supports Legacy, pNFT, Core, and cNFT staking with escrow or
|
|
4
|
+
wallet-lock modes, primary and secondary rewards, trait bonuses, lock tiers, and permanent burn mechanics.
|
|
4
5
|
|
|
5
|
-
The SDK auto-resolves pool state (rewardMint, stakingMode, collectionMint) and all PDA/ATA derivations internally —
|
|
6
|
+
The SDK auto-resolves pool state (rewardMint, stakingMode, collectionMint) and all PDA/ATA derivations internally —
|
|
7
|
+
callers only need to provide the minimum required parameters.
|
|
8
|
+
|
|
9
|
+
Staking wallets must have a user-management profile before staking, claiming, unstaking, or burning. The SDK derives the
|
|
10
|
+
profile PDA automatically, but the profile account must already exist. Banned profiles cannot create new stakes; they
|
|
11
|
+
may still claim or exit positions, with accrual capped at the profile ban timestamp.
|
|
6
12
|
|
|
7
13
|
## Installation
|
|
8
14
|
|
|
@@ -13,46 +19,47 @@ npm install @soltracer/nft-staking @soltracer/core @coral-xyz/anchor @solana/web
|
|
|
13
19
|
## Quick Start
|
|
14
20
|
|
|
15
21
|
```ts
|
|
16
|
-
import { NftStakingClient } from "@soltracer/nft-staking"
|
|
17
|
-
import { AnchorProvider } from "@coral-xyz/anchor"
|
|
22
|
+
import { NftStakingClient } from "@soltracer/nft-staking"
|
|
23
|
+
import { AnchorProvider } from "@coral-xyz/anchor"
|
|
18
24
|
|
|
19
|
-
const provider = AnchorProvider.env()
|
|
20
|
-
const client = NftStakingClient.create(provider, { projectId: 1 })
|
|
25
|
+
const provider = AnchorProvider.env()
|
|
26
|
+
const client = NftStakingClient.create(provider, { projectId: 1 })
|
|
21
27
|
|
|
22
28
|
// Fetch a staking pool
|
|
23
|
-
const pool = await client.fetchStakePool(undefined, poolId)
|
|
29
|
+
const pool = await client.fetchStakePool(undefined, poolId)
|
|
24
30
|
|
|
25
31
|
// Stake an NFT (stakingMode auto-resolved from pool)
|
|
26
|
-
const ix = await client.stakeNft(poolId, nftMint)
|
|
32
|
+
const ix = await client.stakeNft(poolId, nftMint)
|
|
27
33
|
|
|
28
34
|
// Unstake (rewardMint + stakingMode auto-resolved)
|
|
29
|
-
const ix = await client.unstakeNft(poolId, nftMint)
|
|
35
|
+
const ix = await client.unstakeNft(poolId, nftMint)
|
|
30
36
|
|
|
31
37
|
// Claim rewards (rewardMint auto-resolved)
|
|
32
|
-
const ix = await client.claimRewards(poolId)
|
|
38
|
+
const ix = await client.claimRewards(poolId)
|
|
33
39
|
```
|
|
34
40
|
|
|
35
41
|
## Client Setup
|
|
36
42
|
|
|
37
43
|
```ts
|
|
38
44
|
// With project ID bound (recommended — eliminates projectId from every call)
|
|
39
|
-
const client = NftStakingClient.create(provider, { projectId: 1 })
|
|
45
|
+
const client = NftStakingClient.create(provider, { projectId: 1 })
|
|
40
46
|
|
|
41
47
|
// Without project ID (must pass it per-call, or set later)
|
|
42
|
-
const client = NftStakingClient.create(provider)
|
|
43
|
-
client.setProjectId(1)
|
|
48
|
+
const client = NftStakingClient.create(provider)
|
|
49
|
+
client.setProjectId(1) // set later, chainable
|
|
44
50
|
|
|
45
51
|
// From custom IDL
|
|
46
|
-
const client = NftStakingClient.fromIdl(customIdl, provider, { projectId: 1 })
|
|
52
|
+
const client = NftStakingClient.fromIdl(customIdl, provider, { projectId: 1 })
|
|
47
53
|
```
|
|
48
54
|
|
|
49
55
|
## Fee Parameters
|
|
50
56
|
|
|
51
|
-
Optional referral account for fee distribution. Fee PDA accounts (feeConfig, treasury, priceFeed) are resolved
|
|
57
|
+
Optional referral account for fee distribution. Fee PDA accounts (feeConfig, treasury, priceFeed) are resolved
|
|
58
|
+
automatically.
|
|
52
59
|
|
|
53
60
|
```ts
|
|
54
61
|
interface FeeParams {
|
|
55
|
-
referralAccount?: string
|
|
62
|
+
referralAccount?: string // base58 pubkey
|
|
56
63
|
}
|
|
57
64
|
```
|
|
58
65
|
|
|
@@ -61,34 +68,53 @@ interface FeeParams {
|
|
|
61
68
|
### Pool & Config
|
|
62
69
|
|
|
63
70
|
```ts
|
|
64
|
-
const config = await client.fetchStakeConfig()
|
|
65
|
-
const config = await client.fetchStakeConfig(2)
|
|
66
|
-
const pool = await client.fetchStakePool(undefined, poolId)
|
|
67
|
-
const pools = await client.fetchAllPools()
|
|
71
|
+
const config = await client.fetchStakeConfig() // uses bound projectId
|
|
72
|
+
const config = await client.fetchStakeConfig(2) // explicit projectId
|
|
73
|
+
const pool = await client.fetchStakePool(undefined, poolId)
|
|
74
|
+
const pools = await client.fetchAllPools()
|
|
68
75
|
```
|
|
69
76
|
|
|
70
77
|
### Stake Entries
|
|
71
78
|
|
|
72
79
|
```ts
|
|
73
|
-
const entry = await client.fetchStakeEntry(poolId, nftMint)
|
|
74
|
-
const entries = await client.fetchStakeEntriesByOwner(poolId, owner)
|
|
75
|
-
const batch = await client.fetchStakeEntriesForMints(poolId, [mint1, mint2])
|
|
76
|
-
const all = await client.fetchAllStakeEntriesByPool(poolId)
|
|
77
|
-
const cross = await client.fetchStakeEntriesAcrossPools([poolId1, poolId2], [mint1, mint2])
|
|
80
|
+
const entry = await client.fetchStakeEntry(poolId, nftMint)
|
|
81
|
+
const entries = await client.fetchStakeEntriesByOwner(poolId, owner)
|
|
82
|
+
const batch = await client.fetchStakeEntriesForMints(poolId, [mint1, mint2])
|
|
83
|
+
const all = await client.fetchAllStakeEntriesByPool(poolId)
|
|
84
|
+
const cross = await client.fetchStakeEntriesAcrossPools([poolId1, poolId2], [mint1, mint2])
|
|
78
85
|
```
|
|
79
86
|
|
|
80
87
|
### Staker Accounts
|
|
81
88
|
|
|
82
89
|
```ts
|
|
83
|
-
const staker = await client.fetchStakerAccount(poolId, wallet)
|
|
84
|
-
const stakers = await client.fetchAllStakersByPool(poolId)
|
|
90
|
+
const staker = await client.fetchStakerAccount(poolId, wallet)
|
|
91
|
+
const stakers = await client.fetchAllStakersByPool(poolId)
|
|
92
|
+
|
|
93
|
+
const summary = await client.fetchStakerRewardSummary(poolId, wallet, {
|
|
94
|
+
rewardDecimals: 6, // optional: avoids a mint-account decimals fetch
|
|
95
|
+
secondaryRewardDecimals: { [secondaryMint.toBase58()]: 6 },
|
|
96
|
+
includeVaultBalances: true,
|
|
97
|
+
})
|
|
85
98
|
```
|
|
86
99
|
|
|
100
|
+
`fetchStakerRewardSummary()` returns display-ready raw and UI amounts for primary and secondary rewards:
|
|
101
|
+
|
|
102
|
+
- `primary.totalAccrued`, `primary.claimable`, `primary.unclaimable`.
|
|
103
|
+
- `secondary[i].totalAccrued`, `secondary[i].claimable`, `secondary[i].unclaimable`.
|
|
104
|
+
- `claimableBase` / `unclaimableBase` before quantity bonuses.
|
|
105
|
+
- `vaultBalance` when `includeVaultBalances` is true.
|
|
106
|
+
- `entries.claimableActive` and `entries.unclaimableActive` for explaining how many NFTs are currently claimable versus
|
|
107
|
+
claim-at-end locked.
|
|
108
|
+
|
|
109
|
+
Raw amounts are base units. UI amounts are divided by the returned `decimals`. The SDK caches mint decimals per client
|
|
110
|
+
instance, and callers can pass `rewardDecimals` / `secondaryRewardDecimals` from project metadata or a token registry to
|
|
111
|
+
avoid recurring mint-account RPC reads.
|
|
112
|
+
|
|
87
113
|
### Secondary Rewards
|
|
88
114
|
|
|
89
115
|
```ts
|
|
90
|
-
const poolSecondary = await client.fetchPoolSecondaryRewards(poolId)
|
|
91
|
-
const stakerSecondary = await client.fetchStakerSecondaryRewards(poolId, wallet)
|
|
116
|
+
const poolSecondary = await client.fetchPoolSecondaryRewards(poolId)
|
|
117
|
+
const stakerSecondary = await client.fetchStakerSecondaryRewards(poolId, wallet)
|
|
92
118
|
```
|
|
93
119
|
|
|
94
120
|
## Admin Instructions
|
|
@@ -96,7 +122,7 @@ const stakerSecondary = await client.fetchStakerSecondaryRewards(poolId, wallet)
|
|
|
96
122
|
### Initialize & Create Pools
|
|
97
123
|
|
|
98
124
|
```ts
|
|
99
|
-
const ix = await client.initializeStakeConfig()
|
|
125
|
+
const ix = await client.initializeStakeConfig()
|
|
100
126
|
|
|
101
127
|
const ix = await client.createStakePool(
|
|
102
128
|
0, // stakingMode: 0 = Escrow, 1 = WalletLock
|
|
@@ -108,12 +134,15 @@ const ix = await client.createStakePool(
|
|
|
108
134
|
traitBonusMode: 0,
|
|
109
135
|
quantityThresholds: [],
|
|
110
136
|
},
|
|
111
|
-
[{ lockDuration:
|
|
137
|
+
[{ lockDuration: 30 * 86400, rewardRate: 150, earlyUnstakePenaltyBps: 500, claimOnlyAtEnd: false }],
|
|
112
138
|
collectionMint,
|
|
113
|
-
{ rewardEndAt: 0, maxStaked: 0 } // optional
|
|
114
|
-
)
|
|
139
|
+
{ rewardEndAt: 0, maxStaked: 0, allowUnlockedStaking: true }, // optional
|
|
140
|
+
)
|
|
115
141
|
```
|
|
116
142
|
|
|
143
|
+
Lock tier reward rates override the base rate for NFTs staked into that tier. Set `allowUnlockedStaking: false` for
|
|
144
|
+
pools where every stake must choose a lock tier.
|
|
145
|
+
|
|
117
146
|
### Update Pool
|
|
118
147
|
|
|
119
148
|
```ts
|
|
@@ -134,57 +163,68 @@ const ix = await client.updateStakePool(poolId, {
|
|
|
134
163
|
RewardMint is auto-resolved from pool state:
|
|
135
164
|
|
|
136
165
|
```ts
|
|
137
|
-
const fund = await client.fundRewardVault(poolId, amount)
|
|
138
|
-
const withdraw = await client.withdrawRewardVault(poolId, amount)
|
|
166
|
+
const fund = await client.fundRewardVault(poolId, amount)
|
|
167
|
+
const withdraw = await client.withdrawRewardVault(poolId, amount)
|
|
139
168
|
|
|
140
169
|
// Or explicit rewardMint if needed:
|
|
141
|
-
const fund = await client.fundRewardVault(poolId, amount, { rewardMint: mintPk })
|
|
170
|
+
const fund = await client.fundRewardVault(poolId, amount, { rewardMint: mintPk })
|
|
142
171
|
```
|
|
143
172
|
|
|
144
173
|
### Close Pool
|
|
145
174
|
|
|
146
175
|
```ts
|
|
147
|
-
const ix = await client.closeStakePool(poolId)
|
|
176
|
+
const ix = await client.closeStakePool(poolId)
|
|
148
177
|
```
|
|
149
178
|
|
|
150
179
|
### Secondary Rewards Management
|
|
151
180
|
|
|
152
181
|
```ts
|
|
153
|
-
const add = await client.addPoolSecondaryReward(poolId, rewardMint, baseRate, lockTierRates)
|
|
154
|
-
const remove = await client.removePoolSecondaryReward(poolId, rewardIndex)
|
|
155
|
-
const fund = await client.fundSecondaryVault(poolId, rewardIndex, amount, rewardMint)
|
|
156
|
-
const withdraw = await client.withdrawSecondaryVault(poolId, rewardIndex, amount, rewardMint)
|
|
182
|
+
const add = await client.addPoolSecondaryReward(poolId, rewardMint, baseRate, lockTierRates)
|
|
183
|
+
const remove = await client.removePoolSecondaryReward(poolId, rewardIndex)
|
|
184
|
+
const fund = await client.fundSecondaryVault(poolId, rewardIndex, amount, rewardMint)
|
|
185
|
+
const withdraw = await client.withdrawSecondaryVault(poolId, rewardIndex, amount, rewardMint)
|
|
157
186
|
```
|
|
158
187
|
|
|
159
188
|
## User Instructions
|
|
160
189
|
|
|
161
190
|
### Staking Legacy/pNFT
|
|
162
191
|
|
|
163
|
-
StakingMode is auto-resolved from pool
|
|
192
|
+
StakingMode is auto-resolved from pool. Programmable-NFT token-record PDAs (`tokenRecord` + `destinationTokenRecord`)
|
|
193
|
+
are auto-detected on-chain by probing the source token-record account — legacy NFTs pass `null` for both, pNFTs pass the
|
|
194
|
+
derived PDAs. No client flag required.
|
|
164
195
|
|
|
165
196
|
```ts
|
|
166
|
-
const stake = await client.stakeNft(poolId, nftMint, { lockTierIndex: 0, fee, gateProof })
|
|
167
|
-
const unstake = await client.unstakeNft(poolId, nftMint, { fee })
|
|
197
|
+
const stake = await client.stakeNft(poolId, nftMint, { lockTierIndex: 0, fee, gateProof })
|
|
198
|
+
const unstake = await client.unstakeNft(poolId, nftMint, { fee })
|
|
168
199
|
```
|
|
169
200
|
|
|
201
|
+
An NFT can only be actively staked once across all pools. The program maintains a global asset lock PDA for each staked
|
|
202
|
+
mint/asset ID and closes it when the asset is unstaked or burned.
|
|
203
|
+
|
|
170
204
|
### Staking Core NFTs
|
|
171
205
|
|
|
172
206
|
Collection is auto-resolved from pool:
|
|
173
207
|
|
|
174
208
|
```ts
|
|
175
|
-
const stake = await client.stakeCoreNft(poolId, nftAsset, { lockTierIndex: 0, fee })
|
|
176
|
-
const unstake = await client.unstakeCoreNft(poolId, nftAsset, { fee })
|
|
209
|
+
const stake = await client.stakeCoreNft(poolId, nftAsset, { lockTierIndex: 0, fee })
|
|
210
|
+
const unstake = await client.unstakeCoreNft(poolId, nftAsset, { fee })
|
|
177
211
|
```
|
|
178
212
|
|
|
179
213
|
### Staking cNFTs
|
|
180
214
|
|
|
181
215
|
```ts
|
|
182
216
|
const cnftParams = {
|
|
183
|
-
nftAssetId,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
217
|
+
nftAssetId,
|
|
218
|
+
merkleTree,
|
|
219
|
+
cnftRoot,
|
|
220
|
+
cnftDataHash,
|
|
221
|
+
cnftCreatorHash,
|
|
222
|
+
cnftNonce,
|
|
223
|
+
cnftIndex,
|
|
224
|
+
proofNodes,
|
|
225
|
+
}
|
|
226
|
+
const stake = await client.stakeCnft(poolId, cnftParams, { fee, gateProof })
|
|
227
|
+
const unstake = await client.unstakeCnft(poolId, cnftParams, { fee })
|
|
188
228
|
```
|
|
189
229
|
|
|
190
230
|
### Claiming Rewards
|
|
@@ -192,53 +232,152 @@ const unstake = await client.unstakeCnft(poolId, cnftParams, { fee });
|
|
|
192
232
|
RewardMint is auto-resolved from pool:
|
|
193
233
|
|
|
194
234
|
```ts
|
|
195
|
-
const claim = await client.claimRewards(poolId, { traitBonusRate, fee })
|
|
235
|
+
const claim = await client.claimRewards(poolId, { traitBonusRate, fee })
|
|
196
236
|
```
|
|
197
237
|
|
|
238
|
+
Current on-chain behavior: if any active stake entry in the pool has a live `claimOnlyAtEnd` lock, primary and secondary
|
|
239
|
+
claim instructions are blocked until `claimLockedUntil`. Use `fetchStakerRewardSummary()` to show claimable versus
|
|
240
|
+
unclaimable buckets for the intended per-NFT UX. The summary identifies base/unlocked rewards separately from
|
|
241
|
+
claim-at-end locked rewards, but the current claim instruction does not yet support a partial claim of only the unlocked
|
|
242
|
+
bucket.
|
|
243
|
+
|
|
244
|
+
Early-unstake penalties apply on exit when configured.
|
|
245
|
+
|
|
246
|
+
### Trait Bonus Claims
|
|
247
|
+
|
|
248
|
+
`RewardConfig.traitBonusMode` controls how a signed `traitBonusRate` is interpreted:
|
|
249
|
+
|
|
250
|
+
- `0` / `None`: trait bonus proofs are disabled; nonzero `traitBonusRate` is rejected.
|
|
251
|
+
- `1` / `FixedExtra`: `traitBonusRate` is a raw extra reward rate per interval, added temporarily for the claim accrual
|
|
252
|
+
window.
|
|
253
|
+
- `2` / `BaseMultiplier`: `traitBonusRate` is bonus basis points over the staker's current base effective rate. `10_000`
|
|
254
|
+
means +100%, so total accrual for that window is 2x base.
|
|
255
|
+
|
|
256
|
+
Trait proof messages bind the pool, wallet, signed rate, and the staker account's strictly-monotonic `totalClaimed`
|
|
257
|
+
counter (audit fix F-H2 — replaces the prior `lastUpdateAt` nonce to eliminate same-second replay). A new proof is
|
|
258
|
+
required after every successful claim because `totalClaimed` advances.
|
|
259
|
+
|
|
260
|
+
Use the SDK's `buildTraitProofMessage` helper to assemble the exact 80-byte payload the on-chain verifier rebuilds
|
|
261
|
+
(`pool ‖ wallet ‖ rate u64 LE ‖ totalClaimed u64 LE`):
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import nacl from "tweetnacl"
|
|
265
|
+
import { Ed25519Program, Transaction } from "@solana/web3.js"
|
|
266
|
+
import { buildTraitProofMessage } from "@soltracer/nft-staking"
|
|
267
|
+
|
|
268
|
+
const stakerAccount = await client.fetchStakerAccount(poolId, wallet)
|
|
269
|
+
const message = buildTraitProofMessage({
|
|
270
|
+
pool,
|
|
271
|
+
wallet,
|
|
272
|
+
traitBonusRate, // BN
|
|
273
|
+
totalClaimed: stakerAccount.totalClaimed,
|
|
274
|
+
})
|
|
275
|
+
const signature = nacl.sign.detached(message, traitAuthority.secretKey)
|
|
276
|
+
const ed25519Ix = Ed25519Program.createInstructionWithPublicKey({
|
|
277
|
+
publicKey: traitAuthority.publicKey.toBytes(),
|
|
278
|
+
message,
|
|
279
|
+
signature,
|
|
280
|
+
})
|
|
281
|
+
const claimIx = await client.claimRewards(poolId, { traitBonusRate, fee })
|
|
282
|
+
await provider.sendAndConfirm(new Transaction().add(ed25519Ix, claimIx))
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The Ed25519 verifier instruction MUST be in the same transaction as `claimRewards` and the signing pubkey MUST match
|
|
286
|
+
`pool.traitAuthority` (or the global trait authority if configured).
|
|
287
|
+
|
|
288
|
+
### Merkle Gate Proofs
|
|
289
|
+
|
|
290
|
+
Pools with a non-zero `merkleRoot` enforce wallet- or mint-allowlist gating at stake time. The on-chain verifier uses
|
|
291
|
+
`sha256(leaf)` for leaves and `sha256(min(a,b) ‖ max(a,b))` for internal nodes (canonical ordering — proofs do not carry
|
|
292
|
+
position bits). Build the tree once when seeding the root via `updateStakePool`, then derive per-stake proofs:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import {
|
|
296
|
+
GATE_TYPE_WALLET,
|
|
297
|
+
buildMerkleTree,
|
|
298
|
+
getMerkleProof,
|
|
299
|
+
proofToAnchorArg,
|
|
300
|
+
rootToAnchorArg,
|
|
301
|
+
} from "@soltracer/nft-staking"
|
|
302
|
+
|
|
303
|
+
// One-time: seed pool gate
|
|
304
|
+
const tree = buildMerkleTree(allowedWallets) // PublicKey[]
|
|
305
|
+
await client.updateStakePool(poolId, {
|
|
306
|
+
merkleRoot: rootToAnchorArg(tree.root),
|
|
307
|
+
gateType: GATE_TYPE_WALLET,
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// Per-stake call
|
|
311
|
+
const gateProof = proofToAnchorArg(getMerkleProof(tree, staker))
|
|
312
|
+
await client.stakeNft(poolId, nftMint, { gateProof })
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
For `GATE_TYPE_TOKEN_MINT` pools, pass the gated mints to `buildMerkleTree` and supply `getMerkleProof(tree, nftMint)`.
|
|
316
|
+
|
|
198
317
|
### Secondary Reward Claims
|
|
199
318
|
|
|
200
319
|
```ts
|
|
201
|
-
const init = await client.initStakerSecondary(poolId)
|
|
202
|
-
const claim = await client.claimSecondaryRewards(poolId, [{ mint: mintPk }], { fee })
|
|
320
|
+
const init = await client.initStakerSecondary(poolId)
|
|
321
|
+
const claim = await client.claimSecondaryRewards(poolId, [{ mint: mintPk }], { fee })
|
|
203
322
|
```
|
|
204
323
|
|
|
324
|
+
Secondary reward claims use the same profile and claim-lock checks as primary rewards. Quantity thresholds also apply to
|
|
325
|
+
secondary payouts.
|
|
326
|
+
|
|
327
|
+
All secondary mints claimed in one transaction must use the same token program. If a pool uses both SPL Token and
|
|
328
|
+
Token-2022 secondary mints, claim them in separate transactions.
|
|
329
|
+
|
|
330
|
+
### Reward Exhaustion
|
|
331
|
+
|
|
332
|
+
Primary token claims are all-or-nothing: if the reward vault balance is below the computed payout after trait and
|
|
333
|
+
quantity bonuses, the claim fails with `InsufficientRewardBalance`. The program does not do partial primary claims.
|
|
334
|
+
Primary vault withdrawals are protected by `totalObligationPending`, but trait and quantity bonus surplus still needs
|
|
335
|
+
operator funding beyond base obligations.
|
|
336
|
+
|
|
337
|
+
Secondary token claims are also all-or-nothing for the supplied secondary set. Secondary rewards do not currently
|
|
338
|
+
maintain a pool-wide obligation mirror, so operators should keep secondary vaults funded above the displayed claimable
|
|
339
|
+
totals from `fetchStakerRewardSummary({ includeVaultBalances: true })` and avoid withdrawing below pending user-facing
|
|
340
|
+
liabilities.
|
|
341
|
+
|
|
205
342
|
### Burn Permanently-Locked Assets
|
|
206
343
|
|
|
207
344
|
StakingMode/collection auto-resolved from pool:
|
|
208
345
|
|
|
209
346
|
```ts
|
|
210
|
-
const burnNft = await client.burnStakedNft(poolId, nftMint, { fee })
|
|
211
|
-
const burnCore = await client.burnStakedCoreNft(poolId, nftAsset, { fee })
|
|
347
|
+
const burnNft = await client.burnStakedNft(poolId, nftMint, { fee })
|
|
348
|
+
const burnCore = await client.burnStakedCoreNft(poolId, nftAsset, { fee })
|
|
212
349
|
```
|
|
213
350
|
|
|
214
351
|
### Spend Points
|
|
215
352
|
|
|
216
353
|
```ts
|
|
217
|
-
const spend = await client.spendPoints(poolId, amount, { fee })
|
|
354
|
+
const spend = await client.spendPoints(poolId, amount, { fee })
|
|
218
355
|
```
|
|
219
356
|
|
|
220
357
|
## What the SDK Resolves Internally
|
|
221
358
|
|
|
222
|
-
| Parameter
|
|
223
|
-
|
|
224
|
-
| `rewardMint`
|
|
225
|
-
| `stakingMode`
|
|
226
|
-
| `collectionMint`
|
|
227
|
-
| `projectId`
|
|
228
|
-
| Fee accounts
|
|
229
|
-
| Token program
|
|
230
|
-
| ATAs
|
|
231
|
-
|
|
|
232
|
-
|
|
|
359
|
+
| Parameter | Auto-resolved from |
|
|
360
|
+
| --------------------- | -------------------------------------------------------- |
|
|
361
|
+
| `rewardMint` | Pool `rewardConfig.rewardMint` |
|
|
362
|
+
| `stakingMode` | Pool `stakingMode` |
|
|
363
|
+
| `collectionMint` | Pool `collectionMint` |
|
|
364
|
+
| `projectId` | Client instance (set via `create()` or `setProjectId()`) |
|
|
365
|
+
| Fee accounts | PDA derivation from program ID |
|
|
366
|
+
| Token program | Mint account owner detection (SPL vs Token-2022) |
|
|
367
|
+
| ATAs | Derived from wallet + mint + token program |
|
|
368
|
+
| User profile | Derived from wallet via user-management PDA |
|
|
369
|
+
| Global stake lock | Derived from NFT mint/Core asset/cNFT asset ID |
|
|
370
|
+
| All PDAs | Anchor seed derivation |
|
|
371
|
+
| Decimal normalization | Mint account data byte 44 |
|
|
233
372
|
|
|
234
373
|
Pool data is cached for 30s to minimize redundant RPC calls.
|
|
235
374
|
|
|
236
375
|
## Exports
|
|
237
376
|
|
|
238
|
-
| Export
|
|
239
|
-
|
|
240
|
-
| `NftStakingClient`
|
|
241
|
-
| `FeeParams`
|
|
377
|
+
| Export | Type |
|
|
378
|
+
| ------------------------ | ------------------------------------------------ |
|
|
379
|
+
| `NftStakingClient` | Class |
|
|
380
|
+
| `FeeParams` | TypeScript interface |
|
|
242
381
|
| `NFT_STAKING_PROGRAM_ID` | `PublicKey` (re-exported from `@soltracer/core`) |
|
|
243
|
-
| `NftStakingIDLType`
|
|
244
|
-
| `NftStakingIDL`
|
|
382
|
+
| `NftStakingIDLType` | IDL type |
|
|
383
|
+
| `NftStakingIDL` | IDL JSON |
|