@zebec-network/zebec-stake-sdk 1.3.0 → 1.3.1

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/README.md CHANGED
@@ -1,35 +1,685 @@
1
- # ZBCN Stake Sdk
1
+ # Zebec Stake Sdk
2
+
3
+ An SDK for interacting with the Zebec Network staking program on Solana. Supports creating and managing staking lockup pools, staking/unstaking tokens, and querying on-chain staking data.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Setup](#quick-setup)
9
+ - [API Documentation](#api-documentation)
10
+ - [StakeServiceBuilder](#stakeservicebuilder)
11
+ - [StakeService](#stakeservice)
12
+ - [Providers](#providers)
13
+ - [PDA Utilities](#pda-utilities)
14
+ - [Types](#types)
15
+ - [Constants](#constants)
16
+ - [Usage Examples](#usage-examples)
17
+ - [Read-only queries](#read-only-queries)
18
+ - [Initialize a lockup pool](#initialize-a-lockup-pool)
19
+ - [Update a lockup pool](#update-a-lockup-pool)
20
+ - [Stake tokens](#stake-tokens)
21
+ - [Unstake tokens](#unstake-tokens)
22
+ - [Fetch stake data](#fetch-stake-data)
23
+ - [Development](#development)
24
+
25
+ ---
2
26
 
3
27
  ## Installation
4
28
 
29
+ ```bash
30
+ npm install @zebec-network/zebec-stake-sdk
5
31
  ```
32
+
33
+ ```bash
6
34
  yarn add @zebec-network/zebec-stake-sdk
7
35
  ```
8
36
 
37
+ ```bash
38
+ pnpm add @zebec-network/zebec-stake-sdk
9
39
  ```
10
- npm install @zebec-network/zebec-stake-sdk
40
+
41
+ ---
42
+
43
+ ## Quick Setup
44
+
45
+ ### Read-only (no wallet required)
46
+
47
+ Use this setup for querying on-chain data without signing transactions.
48
+
49
+ ```ts
50
+ import { Connection } from "@solana/web3.js";
51
+ import { createReadonlyProvider, StakeServiceBuilder } from "@zebec-network/zebec-stake-sdk";
52
+
53
+ const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
54
+ const provider = createReadonlyProvider(connection);
55
+
56
+ const service = new StakeServiceBuilder()
57
+ .setNetwork("mainnet-beta")
58
+ .setProvider(provider)
59
+ .setProgram()
60
+ .build();
11
61
  ```
12
62
 
13
- ## Development
63
+ ### With wallet (for signing transactions)
64
+
65
+ Use this setup when you need to send transactions (stake, unstake, create/update lockups).
66
+
67
+ ```ts
68
+ import { Connection, Keypair } from "@solana/web3.js";
69
+ import { Wallet } from "@coral-xyz/anchor";
70
+ import {
71
+ createAnchorProvider,
72
+ StakeServiceBuilder,
73
+ } from "@zebec-network/zebec-stake-sdk";
74
+
75
+ const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
76
+ const keypair = Keypair.fromSecretKey(/* your secret key bytes */);
77
+ const wallet = new Wallet(keypair);
78
+
79
+ const provider = createAnchorProvider(connection, wallet, { commitment: "confirmed" });
80
+
81
+ const service = new StakeServiceBuilder()
82
+ .setNetwork("mainnet-beta")
83
+ .setProvider(provider)
84
+ .setProgram()
85
+ .build();
86
+ ```
87
+
88
+ ### Default setup (mainnet, no wallet)
89
+
90
+ All builder methods are optional — calling them without arguments uses sensible defaults.
91
+
92
+ ```ts
93
+ import { StakeServiceBuilder } from "@zebec-network/zebec-stake-sdk";
94
+
95
+ // Defaults: mainnet-beta network, ReadonlyProvider with public RPC
96
+ const service = new StakeServiceBuilder()
97
+ .setNetwork()
98
+ .setProvider()
99
+ .setProgram()
100
+ .build();
101
+ ```
102
+
103
+ ---
104
+
105
+ ## API Documentation
106
+
107
+ ### StakeServiceBuilder
108
+
109
+ A fluent builder for constructing a `StakeService`. Methods must be called in order: `setNetwork` → `setProvider` → `setProgram` → `build`.
110
+
111
+ ```ts
112
+ class StakeServiceBuilder {
113
+ setNetwork(network?: "mainnet-beta" | "devnet"): StakeServiceBuilder
114
+ setProvider(provider?: ReadonlyProvider | AnchorProvider): StakeServiceBuilder
115
+ setProgram(createProgram?: (provider) => Program<ZebecStakeIdlV1>): StakeServiceBuilder
116
+ build(): StakeService
117
+ }
118
+ ```
119
+
120
+ | Method | Description |
121
+ | ------ | ----------- |
122
+ | `setNetwork(network?)` | Set the target network. Defaults to `"mainnet-beta"`. Must be called before `setProvider`. |
123
+ | `setProvider(provider?)` | Set the provider. Accepts `ReadonlyProvider` or `AnchorProvider`. Defaults to `ReadonlyProvider` using the public cluster RPC. Must be called before `setProgram`. |
124
+ | `setProgram(createProgram?)` | Set the Anchor program. Optionally accepts a factory `(provider) => Program`. Defaults to the built-in IDL. |
125
+ | `build()` | Validates all settings and returns a `StakeService` instance. Throws if any step was skipped. |
126
+
127
+ **Errors thrown by the builder:**
128
+
129
+ - `"InvalidOperation: Network is set twice."` — `setNetwork` called more than once.
130
+ - `"InvalidOperation: Provider is set twice."` — `setProvider` called more than once.
131
+ - `"InvalidOperation: Program is set twice."` — `setProgram` called more than once.
132
+ - `"InvalidOperation: Network is not set."` — `setProvider`/`setProgram` called before `setNetwork`.
133
+ - `"InvalidOperation: Provider is not set."` — `setProgram` called before `setProvider`.
134
+ - Network mismatch error if the provider's RPC endpoint does not match the set network.
135
+
136
+ ---
137
+
138
+ ### StakeService
139
+
140
+ The main service class. Exposes methods for managing lockup pools, staking, and reading on-chain data. All transaction methods return a `TransactionPayload` that must be executed separately.
141
+
142
+ #### `initLockup(params)`
143
+
144
+ Creates a new staking lockup pool. Only the `creator` can later update it.
145
+
146
+ ```ts
147
+ service.initLockup(params: {
148
+ stakeToken: Address; // Mint address of the token to be staked
149
+ rewardToken: Address; // Mint address of the reward token
150
+ name: string; // Unique name for the lockup (used to derive its address)
151
+ fee: Numeric; // Fee amount per stake (in token units, e.g. 5 = 5 tokens)
152
+ feeVault: Address; // Public key of the account that collects fees
153
+ rewardSchemes: RewardScheme[]; // Array of lock durations and annual reward rates
154
+ minimumStake: Numeric; // Minimum stake amount (in token units)
155
+ creator?: Address; // Defaults to provider.publicKey
156
+ }): Promise<TransactionPayload>
157
+ ```
158
+
159
+ #### `updateLockup(params)`
160
+
161
+ Updates an existing lockup pool. Only callable by the original creator.
162
+
163
+ ```ts
164
+ service.updateLockup(params: {
165
+ lockupName: string; // Name of the lockup to update
166
+ fee: Numeric; // New fee amount
167
+ feeVault: Address; // New fee vault address
168
+ rewardSchemes: RewardScheme[]; // Updated reward schemes
169
+ minimumStake: Numeric; // Updated minimum stake amount
170
+ updater?: Address; // Defaults to provider.publicKey
171
+ }): Promise<TransactionPayload>
172
+ ```
173
+
174
+ #### `stake(params)`
175
+
176
+ Stakes tokens into a lockup pool for a specified lock period.
177
+
178
+ ```ts
179
+ service.stake(params: {
180
+ lockupName: string; // Name of the lockup to stake into
181
+ amount: Numeric; // Amount to stake (in token units, e.g. 100 = 100 tokens)
182
+ lockPeriod: number; // Lock duration in seconds — must match an existing reward scheme
183
+ nonce: bigint; // Current user nonce (use getUserNonceInfo to retrieve)
184
+ feePayer?: Address; // Defaults to staker
185
+ staker?: Address; // Defaults to provider.publicKey
186
+ }): Promise<TransactionPayload>
187
+ ```
188
+
189
+ #### `unstake(params)`
190
+
191
+ Unstakes tokens and claims the accrued reward after the lock period has elapsed.
192
+
193
+ ```ts
194
+ service.unstake(params: {
195
+ lockupName: string; // Name of the lockup
196
+ nonce: bigint; // Nonce of the stake to unstake
197
+ feePayer?: Address; // Defaults to staker
198
+ staker?: Address; // Defaults to provider.publicKey
199
+ }): Promise<TransactionPayload>
200
+ ```
201
+
202
+ #### `getLockupInfo(lockupAddress)`
203
+
204
+ Fetches and returns human-readable information about a lockup pool.
205
+
206
+ ```ts
207
+ service.getLockupInfo(lockupAddress: Address): Promise<LockupInfo | null>
208
+ ```
209
+
210
+ Returns `null` if the lockup does not exist.
211
+
212
+ #### `getStakeInfo(stakeAddress, lockupAddress)`
213
+
214
+ Fetches information about a specific stake position.
215
+
216
+ ```ts
217
+ service.getStakeInfo(stakeAddress: Address, lockupAddress: Address): Promise<StakeInfo | null>
218
+ ```
219
+
220
+ Returns `null` if the stake account does not exist. Throws if the lockup does not exist.
221
+
222
+ #### `getUserNonceInfo(userNonceAddress)`
223
+
224
+ Returns the user's current nonce for a given lockup. The nonce is used to derive stake addresses and must be passed when calling `stake()`.
225
+
226
+ ```ts
227
+ service.getUserNonceInfo(userNonceAddress: Address): Promise<UserNonceInfo | null>
228
+ ```
229
+
230
+ Returns `null` if the user has never staked in this lockup (nonce is implicitly `0n`).
231
+
232
+ #### `getAllStakesInfoOfUser(userAddress, lockupAddress, options?)`
233
+
234
+ Fetches all historical stake positions for a user in a given lockup pool, including the transaction signature for each stake.
235
+
236
+ ```ts
237
+ service.getAllStakesInfoOfUser(
238
+ userAddress: Address,
239
+ lockupAddress: Address,
240
+ options?: {
241
+ minDelayMs?: number; // Minimum ms between RPC calls (default: 400)
242
+ maxConcurrent?: number; // Max concurrent RPC calls (default: 3)
243
+ }
244
+ ): Promise<StakeInfoWithHash[]>
245
+ ```
246
+
247
+ #### `getAllStakesInfo(lockupAddress)`
248
+
249
+ Fetches all stake positions across all users in a lockup pool.
250
+
251
+ ```ts
252
+ service.getAllStakesInfo(lockupAddress: Address): Promise<StakeInfo[]>
253
+ ```
254
+
255
+ #### `getAllStakesCount(lockupAddress)`
256
+
257
+ Returns the total number of stake accounts associated with a lockup pool.
258
+
259
+ ```ts
260
+ service.getAllStakesCount(lockupAddress: Address): Promise<number>
261
+ ```
262
+
263
+ #### `getStakeSignatureForStake(stakeInfo)`
264
+
265
+ Retrieves the on-chain transaction signature for a given stake position.
266
+
267
+ ```ts
268
+ service.getStakeSignatureForStake(stakeInfo: StakeInfo): Promise<string | null>
269
+ ```
270
+
271
+ #### Properties
272
+
273
+ | Property | Type | Description |
274
+ | -------- | ---- | ----------- |
275
+ | `programId` | `PublicKey` | The deployed staking program's public key |
276
+ | `connection` | `Connection` | The active Solana RPC connection |
277
+ | `provider` | `Provider` | The underlying Anchor/Readonly provider |
278
+ | `program` | `Program<ZebecStakeIdlV1>` | The Anchor program instance |
279
+
280
+ ---
14
281
 
15
- To build the package
282
+ ### Providers
16
283
 
284
+ Two provider types are available depending on your use case.
285
+
286
+ #### `createReadonlyProvider(connection, walletAddress?)`
287
+
288
+ Creates a lightweight provider for read-only operations. Does not require a wallet.
289
+
290
+ ```ts
291
+ import { createReadonlyProvider } from "@zebec-network/zebec-stake-sdk";
292
+
293
+ const provider = createReadonlyProvider(connection);
294
+ // or with an optional wallet address for context
295
+ const provider = createReadonlyProvider(connection, "YourWalletPublicKey...");
296
+ ```
297
+
298
+ #### `createAnchorProvider(connection, wallet, options?)`
299
+
300
+ Creates a full Anchor provider capable of signing and sending transactions.
301
+
302
+ ```ts
303
+ import { createAnchorProvider } from "@zebec-network/zebec-stake-sdk";
304
+
305
+ const provider = createAnchorProvider(connection, wallet, {
306
+ commitment: "confirmed",
307
+ });
308
+ ```
309
+
310
+ The `wallet` must implement the `AnchorWallet` interface:
311
+
312
+ ```ts
313
+ interface AnchorWallet {
314
+ publicKey: PublicKey;
315
+ signTransaction<T extends Transaction | VersionedTransaction>(tx: T): Promise<T>;
316
+ signAllTransactions<T extends Transaction | VersionedTransaction>(txs: T[]): Promise<T[]>;
317
+ }
318
+ ```
319
+
320
+ ---
321
+
322
+ ### PDA Utilities
323
+
324
+ Helper functions for deriving program-derived addresses. All `programId` parameters default to the mainnet program ID.
325
+
326
+ ```ts
327
+ import {
328
+ deriveLockupAddress,
329
+ deriveStakeAddress,
330
+ deriveUserNonceAddress,
331
+ deriveStakeVaultAddress,
332
+ deriveRewardVaultAddress,
333
+ } from "@zebec-network/zebec-stake-sdk";
334
+ ```
335
+
336
+ | Function | Description |
337
+ | -------- | ----------- |
338
+ | `deriveLockupAddress(name, programId?)` | Derives the lockup PDA from its unique name |
339
+ | `deriveStakeAddress(staker, lockup, nonce, programId?)` | Derives a stake PDA for a given staker, lockup, and nonce |
340
+ | `deriveUserNonceAddress(user, lockup, programId?)` | Derives the user nonce PDA tracking a user's total stake count |
341
+ | `deriveStakeVaultAddress(lockup, programId?)` | Derives the vault PDA that holds staked tokens |
342
+ | `deriveRewardVaultAddress(lockup, programId?)` | Derives the vault PDA that holds reward tokens |
343
+
344
+ ---
345
+
346
+ ### Types
347
+
348
+ ```ts
349
+ // Human-readable reward scheme: duration in seconds and annual rate as a percentage
350
+ type RewardScheme = {
351
+ duration: number; // Lock period in seconds (e.g., 2592000 = 30 days)
352
+ rewardRate: Numeric; // Annual reward rate as a percentage (e.g., "5.00" = 5%)
353
+ };
354
+
355
+ // Returned by getLockupInfo()
356
+ type LockupInfo = {
357
+ address: string;
358
+ feeInfo: {
359
+ fee: string; // Fee amount in token units
360
+ feeVault: string; // Fee collector address
361
+ };
362
+ rewardToken: {
363
+ tokenAddress: string;
364
+ };
365
+ stakeToken: {
366
+ tokenAdress: string; // Note: single 'd' in 'adress' (matches on-chain field)
367
+ totalStaked: string; // Total tokens currently staked in this lockup
368
+ };
369
+ stakeInfo: {
370
+ name: string;
371
+ creator: string;
372
+ rewardSchemes: RewardScheme[];
373
+ minimumStake: string;
374
+ };
375
+ };
376
+
377
+ // Returned by getStakeInfo() and getAllStakesInfo()
378
+ type StakeInfo = {
379
+ address: string; // Stake PDA address
380
+ nonce: bigint; // Stake nonce (index within this user's stakes)
381
+ createdTime: number; // Unix timestamp of when the stake was created
382
+ stakedAmount: string; // Amount staked in token units
383
+ rewardAmount: string; // Accrued reward in reward token units
384
+ stakeClaimed: boolean; // Whether the stake has been unstaked
385
+ lockPeriod: number; // Lock duration in seconds
386
+ staker: string; // Staker's public key
387
+ lockup: string; // Lockup PDA address
388
+ };
389
+
390
+ // Returned by getAllStakesInfoOfUser()
391
+ type StakeInfoWithHash = StakeInfo & {
392
+ hash: string; // Transaction signature of the original stake
393
+ };
394
+
395
+ // Returned by getUserNonceInfo()
396
+ type UserNonceInfo = {
397
+ address: string; // User nonce PDA address
398
+ nonce: bigint; // Current nonce (equals total number of stakes made)
399
+ };
400
+
401
+ // Accepted wherever amounts are passed
402
+ type Numeric = string | number;
403
+ ```
404
+
405
+ ---
406
+
407
+ ### Constants
408
+
409
+ ```ts
410
+ import { ZEBEC_STAKE_PROGRAM, STAKE_LOOKUP_TABLE_ADDRESS } from "@zebec-network/zebec-stake-sdk";
411
+
412
+ // Program IDs
413
+ ZEBEC_STAKE_PROGRAM.mainnet // "zSTKzGLiN6T6EVzhBiL6sjULXMahDavAS2p4R62afGv"
414
+ ZEBEC_STAKE_PROGRAM.devnet // "zSTKzGLiN6T6EVzhBiL6sjULXMahDavAS2p4R62afGv"
415
+
416
+ // Address Lookup Table accounts for versioned transactions
417
+ STAKE_LOOKUP_TABLE_ADDRESS["mainnet-beta"] // "EoKjJejKr4XsBdtUuYwzZcYd6tpGNijxCGgQocxtxQ8t"
418
+ STAKE_LOOKUP_TABLE_ADDRESS["devnet"] // "C4R2sL6yj7bzKfbdfwCfH68DZZ3QnzdmedE9wQqTfAAA"
419
+ ```
420
+
421
+ ---
422
+
423
+ ## Usage Examples
424
+
425
+ ### Read-only queries
426
+
427
+ ```ts
428
+ import { Connection } from "@solana/web3.js";
429
+ import {
430
+ createReadonlyProvider,
431
+ deriveLockupAddress,
432
+ deriveUserNonceAddress,
433
+ StakeServiceBuilder,
434
+ } from "@zebec-network/zebec-stake-sdk";
435
+
436
+ const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
437
+ const provider = createReadonlyProvider(connection);
438
+
439
+ const service = new StakeServiceBuilder()
440
+ .setNetwork("mainnet-beta")
441
+ .setProvider(provider)
442
+ .setProgram()
443
+ .build();
444
+
445
+ // Derive the lockup address from its name
446
+ const lockupName = "ZBCN_Lockup_003";
447
+ const lockupAddress = deriveLockupAddress(lockupName, service.programId);
448
+
449
+ // Fetch lockup pool info
450
+ const lockupInfo = await service.getLockupInfo(lockupAddress);
451
+ console.log(lockupInfo);
452
+ // {
453
+ // address: "...",
454
+ // feeInfo: { fee: "5", feeVault: "..." },
455
+ // rewardToken: { tokenAddress: "..." },
456
+ // stakeToken: { tokenAdress: "...", totalStaked: "1234567" },
457
+ // stakeInfo: {
458
+ // name: "ZBCN_Lockup_003",
459
+ // creator: "...",
460
+ // rewardSchemes: [
461
+ // { duration: 2592000, rewardRate: "3.00" },
462
+ // { duration: 7776000, rewardRate: "5.00" },
463
+ // ],
464
+ // minimumStake: "1"
465
+ // }
466
+ // }
467
+
468
+ // Fetch all stakes in a lockup
469
+ const allStakes = await service.getAllStakesInfo(lockupAddress);
470
+ console.log(`Total active positions: ${allStakes.length}`);
471
+
472
+ // Fetch total stake count
473
+ const count = await service.getAllStakesCount(lockupAddress);
474
+ console.log(`Total stake accounts: ${count}`);
17
475
  ```
18
- yarn build
476
+
477
+ ### Initialize a lockup pool
478
+
479
+ Requires an `AnchorProvider` (wallet with signing capability).
480
+
481
+ ```ts
482
+ import { Connection, Keypair } from "@solana/web3.js";
483
+ import { Wallet } from "@coral-xyz/anchor";
484
+ import {
485
+ createAnchorProvider,
486
+ deriveLockupAddress,
487
+ StakeServiceBuilder,
488
+ } from "@zebec-network/zebec-stake-sdk";
489
+
490
+ const connection = new Connection("https://api.devnet.solana.com", "confirmed");
491
+ const keypair = Keypair.fromSecretKey(/* your secret key bytes */);
492
+ const wallet = new Wallet(keypair);
493
+ const provider = createAnchorProvider(connection, wallet, { commitment: "confirmed" });
494
+
495
+ const service = new StakeServiceBuilder()
496
+ .setNetwork("devnet")
497
+ .setProvider(provider)
498
+ .setProgram()
499
+ .build();
500
+
501
+ const ZBCN_MINT = "ZBCNpuD7YMXzTHB2fhGkGi78MNsHGLRXUhRewNRm9RU";
502
+
503
+ const payload = await service.initLockup({
504
+ stakeToken: ZBCN_MINT,
505
+ rewardToken: ZBCN_MINT,
506
+ name: "My_Lockup_001",
507
+ fee: 0, // 0 token fee per stake
508
+ feeVault: "FeeVaultPublicKey...",
509
+ minimumStake: 1, // Minimum 1 token to stake
510
+ rewardSchemes: [
511
+ { duration: 2592000, rewardRate: "3.00" }, // 30 days @ 3% APR
512
+ { duration: 7776000, rewardRate: "5.00" }, // 90 days @ 5% APR
513
+ { duration: 15552000, rewardRate: "7.00" }, // 180 days @ 7% APR
514
+ ],
515
+ });
516
+
517
+ const signature = await payload.execute({ commitment: "confirmed" });
518
+ console.log("Lockup created:", signature);
519
+
520
+ const lockupAddress = deriveLockupAddress("My_Lockup_001", service.programId);
521
+ const lockupInfo = await service.getLockupInfo(lockupAddress);
522
+ console.log(lockupInfo);
523
+ ```
524
+
525
+ ### Update a lockup pool
526
+
527
+ Only the original creator can update a lockup.
528
+
529
+ ```ts
530
+ const payload = await service.updateLockup({
531
+ lockupName: "My_Lockup_001",
532
+ fee: 5,
533
+ feeVault: "NewFeeVaultPublicKey...",
534
+ minimumStake: 10,
535
+ rewardSchemes: [
536
+ { duration: 2592000, rewardRate: "5.00" }, // 30 days @ 5% APR
537
+ { duration: 7776000, rewardRate: "8.00" }, // 90 days @ 8% APR
538
+ { duration: 15552000, rewardRate: "12.00" }, // 180 days @ 12% APR
539
+ ],
540
+ });
541
+
542
+ const signature = await payload.execute({ commitment: "confirmed" });
543
+ console.log("Lockup updated:", signature);
544
+ ```
545
+
546
+ ### Stake tokens
547
+
548
+ ```ts
549
+ import {
550
+ deriveUserNonceAddress,
551
+ deriveLockupAddress,
552
+ } from "@zebec-network/zebec-stake-sdk";
553
+
554
+ const lockupName = "My_Lockup_001";
555
+ const lockupAddress = deriveLockupAddress(lockupName, service.programId);
556
+
557
+ // Get the user's current nonce (determines the next stake account address)
558
+ const userNonceAddress = deriveUserNonceAddress(
559
+ wallet.publicKey,
560
+ lockupAddress,
561
+ service.programId,
562
+ );
563
+ const nonceInfo = await service.getUserNonceInfo(userNonceAddress);
564
+ const nonce = nonceInfo ? nonceInfo.nonce : 0n;
565
+
566
+ // Stake 1000 tokens for 30 days (2592000 seconds)
567
+ const payload = await service.stake({
568
+ lockupName,
569
+ amount: 1000,
570
+ lockPeriod: 2592000, // must match a duration in the lockup's rewardSchemes
571
+ nonce, // current nonce; incremented on-chain after staking
572
+ });
573
+
574
+ const signature = await payload.execute({ commitment: "confirmed" });
575
+ console.log("Stake signature:", signature);
576
+ ```
577
+
578
+ ### Unstake tokens
579
+
580
+ Unstaking returns the original staked tokens plus any accrued reward. Can only be called after the lock period has elapsed.
581
+
582
+ ```ts
583
+ import { deriveStakeAddress } from "@zebec-network/zebec-stake-sdk";
584
+
585
+ // The nonce used when staking identifies which stake position to unstake
586
+ const stakeNonce = 0n;
587
+
588
+ const payload = await service.unstake({
589
+ lockupName: "My_Lockup_001",
590
+ nonce: stakeNonce,
591
+ });
592
+
593
+ const signature = await payload.execute({ commitment: "confirmed" });
594
+ console.log("Unstake signature:", signature);
595
+
596
+ // Verify the stake was claimed
597
+ const lockupAddress = deriveLockupAddress("My_Lockup_001", service.programId);
598
+ const stakeAddress = deriveStakeAddress(
599
+ wallet.publicKey,
600
+ lockupAddress,
601
+ stakeNonce,
602
+ service.programId,
603
+ );
604
+ const stakeInfo = await service.getStakeInfo(stakeAddress, lockupAddress);
605
+ console.log("Stake claimed:", stakeInfo?.stakeClaimed); // true
606
+ console.log("Reward received:", stakeInfo?.rewardAmount);
19
607
  ```
20
608
 
21
- To run specific test filess
609
+ ### Fetch stake data
610
+
611
+ ```ts
612
+ // All stakes for a specific user in a lockup
613
+ const userStakes = await service.getAllStakesInfoOfUser(
614
+ wallet.publicKey,
615
+ lockupAddress,
616
+ {
617
+ maxConcurrent: 3, // max parallel RPC calls (default: 3)
618
+ minDelayMs: 400, // ms between requests to avoid rate limits (default: 400)
619
+ }
620
+ );
621
+
622
+ for (const stake of userStakes) {
623
+ console.log({
624
+ nonce: stake.nonce.toString(),
625
+ amount: stake.stakedAmount,
626
+ reward: stake.rewardAmount,
627
+ claimed: stake.stakeClaimed,
628
+ lockPeriod: stake.lockPeriod,
629
+ txHash: stake.hash,
630
+ });
631
+ }
22
632
 
633
+ // Single stake by address
634
+ const stakeAddress = deriveStakeAddress(
635
+ wallet.publicKey,
636
+ lockupAddress,
637
+ 0n,
638
+ service.programId,
639
+ );
640
+ const stakeInfo = await service.getStakeInfo(stakeAddress, lockupAddress);
23
641
  ```
24
- yarn test <test file path> -f "<regex for test name>"
25
- // example:
26
- // yarn test ./test/e2e/getLockupInfo.test.ts
642
+
643
+ ---
644
+
645
+ ## Development
646
+
647
+ ### Environment setup
648
+
649
+ Create a `.env` file at the project root for running tests:
650
+
651
+ ```env
652
+ RPC_URL=https://your-mainnet-rpc-url
653
+ DEVNET_RPC_URL=https://your-devnet-rpc-url
654
+
655
+ # JSON arrays of base58-encoded secret keys
656
+ MAINNET_SECRET_KEYS=["base58SecretKey1","base58SecretKey2"]
657
+ DEVNET_SECRET_KEYS=["base58SecretKey1","base58SecretKey2"]
27
658
  ```
28
659
 
29
- ## publish
660
+ ### Commands
661
+
662
+ ```bash
663
+ # Build the package
664
+ npm run build
30
665
 
31
- Build package and bump package version to specific need and publish
666
+ # Run all tests
667
+ npm test
32
668
 
669
+ # Run a single test file
670
+ npm run test:single ./test/e2e/getLockupInfo.test.ts
671
+
672
+ # Run a specific test by name pattern
673
+ npm run test:single ./test/e2e/stakeAndUnstake.test.ts -- -f "stake()"
674
+
675
+ # Format code
676
+ npm run format
33
677
  ```
678
+
679
+ ### Publish
680
+
681
+ Build and bump the version in `package.json`, then:
682
+
683
+ ```bash
34
684
  npm publish --access public
35
685
  ```
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./pda";
4
4
  export * from "./providers";
5
5
  export * from "./rateLimitQueue";
6
6
  export * from "./service";
7
+ export * from "./types";
package/dist/index.js CHANGED
@@ -20,3 +20,4 @@ __exportStar(require("./pda"), exports);
20
20
  __exportStar(require("./providers"), exports);
21
21
  __exportStar(require("./rateLimitQueue"), exports);
22
22
  __exportStar(require("./service"), exports);
23
+ __exportStar(require("./types"), exports);
package/package.json CHANGED
@@ -43,5 +43,5 @@
43
43
  "test:single": "ts-mocha -p ./tsconfig.json -t 1000000000"
44
44
  },
45
45
  "types": "dist/index.d.ts",
46
- "version": "1.3.0"
46
+ "version": "1.3.1"
47
47
  }