@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 +661 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,35 +1,685 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
660
|
+
### Commands
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
# Build the package
|
|
664
|
+
npm run build
|
|
30
665
|
|
|
31
|
-
|
|
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
package/dist/index.js
CHANGED
package/package.json
CHANGED