• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bmresearch / Solnet / 15458437619

05 Jun 2025 04:27AM UTC coverage: 66.061% (-4.7%) from 70.792%
15458437619

Pull #497

github

web-flow
Merge da1be547c into 632c6bef2
Pull Request #497: Stakepool program

1161 of 2022 branches covered (57.42%)

Branch coverage included in aggregate %.

705 of 1521 new or added lines in 12 files covered. (46.35%)

5823 of 8550 relevant lines covered (68.11%)

987890.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.8
/src/Solnet.Programs/StakePool/StakePoolProgram.cs
1
using Solnet.Rpc.Models;
2
using Solnet.Wallet;
3
using System.Collections.Generic;
4
using Solnet.Programs.Abstract;
5
using System;
6
using System.Text;
7
using Solnet.Programs.StakePool.Models;
8
using static Solnet.Programs.Models.Stake.State;
9
using System.Linq;
10

11
namespace Solnet.Programs.StakePool
12
{
13
    /// <summary>
14
    /// Implements the Stake Pool Program methods.
15
    /// <remarks>
16
    /// For more information see:
17
    /// https://spl.solana.com/stake-pool
18
    /// https://docs.rs/spl-stake-pool/latest/spl_stake_pool/
19
    /// </remarks>
20
    /// </summary>
21
    public class StakePoolProgram: BaseProgram
22
    {
23
        /// <summary>
24
        /// SPL Stake Pool Program ID
25
        /// </summary>
26
        public static readonly PublicKey StakePoolProgramIdKey = new("SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy");
1✔
27

28
        /// <summary>
29
        /// Mpl Token Metadata Program ID
30
        /// </summary>
31
        public static readonly PublicKey MplTokenMetadataProgramIdKey = new("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
1✔
32

33
        /// <summary>
34
        /// SPL Stake Pool Program Name
35
        /// </summary>
36
        public static readonly string StakePoolProgramName = "Stake Pool Program";
1✔
37

38
        // Instance vars
39

40
        /// <summary>
41
        /// The owner key required to use as the fee account owner.  
42
        /// </summary>
NEW
43
        public virtual PublicKey OwnerKey => new("HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN");
×
44

45
        /// <summary>
46
        /// Represents the byte array encoding of the ASCII string "withdraw".
47
        /// </summary>
48
        /// <remarks>This field is used to identify the "withdraw" authority in a byte array format. It is
49
        /// encoded using ASCII encoding.</remarks>
50
        private static readonly byte[] AUTHORITY_WITHDRAW = Encoding.ASCII.GetBytes("withdraw");
1✔
51

52
        /// <summary>
53
        /// Represents the seed for deposit authority.
54
        /// </summary>
55
        /// <remarks>This seed identifies the "deposit" authority for a stake pool in a byte array format.</remarks>
56
        private static readonly byte[] AUTHORITY_DEPOSIT = Encoding.ASCII.GetBytes("deposit");
1✔
57

58
        /// <summary>
59
        /// Represents the prefix used for generating transient stake seeds.
60
        /// </summary>
61
        /// <remarks>This prefix is encoded as an ASCII byte array and is used in conjunction with other
62
        /// data  to generate unique transient stake seeds. The value is constant and cannot be modified.</remarks>
63
        private static readonly byte[] TRANSIENT_STAKE_SEED_PREFIX =
1✔
64
            Encoding.ASCII.GetBytes("transient");
1✔
65

66
        /// <summary>
67
        /// Represents the seed for ephemeral stake account.
68
        /// </summary>
69
        private static readonly byte[] EPHEMERAL_STAKE_SEED_PREFIX = Encoding.ASCII.GetBytes("ephemeral");
1✔
70

71
        /// <summary>
72
        /// Stake Pool account layout size.
73
        /// </summary>
74
        public static readonly ulong StakePoolAccountDataSize = 255;
1✔
75

76
        /// <summary>
77
        /// Minimum amount of staked lamports required in a validator stake account to
78
        /// allow for merges without a mismatch on credits observed.
79
        /// </summary>
80
        public const ulong MINIMUM_ACTIVE_STAKE = 1_000_000;
81

82
        /// <summary>
83
        /// Minimum amount of lamports in the reserve.
84
        /// </summary>
85
        public const ulong MINIMUM_RESERVE_LAMPORTS = 0;
86

87
        /// <summary>
88
        /// Maximum amount of validator stake accounts to update per
89
        /// <c>UpdateValidatorListBalance</c> instruction, based on compute limits.
90
        /// </summary>
91
        public const int MAX_VALIDATORS_TO_UPDATE = 5;
92

93
        /// <summary>
94
        /// Maximum factor by which a withdrawal fee can be increased per epoch,
95
        /// protecting stakers from malicious users.
96
        /// If current fee is 0, WITHDRAWAL_BASELINE_FEE is used as the baseline.
97
        /// </summary>
98
        public static readonly Fee MAX_WITHDRAWAL_FEE_INCREASE = new Fee(3, 2);
1✔
99

100
        /// <summary>
101
        /// Drop-in baseline fee when evaluating withdrawal fee increases when fee is 0.
102
        /// </summary>
103
        public static readonly Fee WITHDRAWAL_BASELINE_FEE = new Fee(1, 1000);
1✔
104

105
        /// <summary>
106
        /// The maximum number of transient stake accounts respecting transaction account limits.
107
        /// </summary>
108
        public const int MAX_TRANSIENT_STAKE_ACCOUNTS = 10;
109

110
        /// <summary>
111
        /// Represents the Stake Pool Program, a specialized program for managing stake pools on the Solana blockchain.
112
        /// </summary>
113
        /// <remarks>This program provides functionality for interacting with stake pools, including
114
        /// creating, managing, and querying stake pool accounts. It is identified by the program ID key and name
115
        /// specific to the Stake Pool Program.</remarks>
116
        public StakePoolProgram() : base(StakePoolProgramIdKey, StakePoolProgramName) { }
18✔
117

118
        /// <summary>
119
        /// Initializes a new instance of the <see cref="StakePoolProgram"/> class with the specified program ID.
120
        /// </summary>
121
        /// <param name="programId">The public key identifying the stake pool program.</param>
NEW
122
        public StakePoolProgram(PublicKey programId) : base(programId, StakePoolProgramName) { }
×
123

124
        #region Transaction Methods
125

126
        /// <summary>
127
        /// Creates an Initialize instruction (initialize a new stake pool).
128
        /// </summary>
129
        /// <param name="stakePoolAccount"></param>
130
        /// <param name="manager"></param>
131
        /// <param name="staker"></param>
132
        /// <param name="withdrawAuthority"></param>
133
        /// <param name="validatorList"></param>
134
        /// <param name="reserveStake"></param>
135
        /// <param name="poolMint"></param>
136
        /// <param name="managerPoolAccount"></param>
137
        /// <param name="tokenProgramId"></param>
138
        /// <param name="fee"></param>
139
        /// <param name="withdrawalFee"></param>
140
        /// <param name="depositFee"></param>
141
        /// <param name="referralFee"></param>
142
        /// <param name="depositAuthority"></param>
143
        /// <param name="maxValidators"></param>
144
        /// <returns></returns>
145
        public virtual TransactionInstruction Initialize(
146
            PublicKey stakePoolAccount,
147
            PublicKey manager,
148
            PublicKey staker,
149
            PublicKey withdrawAuthority,
150
            PublicKey validatorList,
151
            PublicKey reserveStake,
152
            PublicKey poolMint,
153
            PublicKey managerPoolAccount,
154
            PublicKey tokenProgramId,
155
            Fee fee,
156
            Fee withdrawalFee,
157
            Fee depositFee,
158
            Fee referralFee,
159
            PublicKey depositAuthority = null,
160
            uint? maxValidators = null
161
        )
162
        {
163
            // Prepare the instruction data
164
            var data = StakePoolProgramData.EncodeInitializeData(fee, withdrawalFee, depositFee, referralFee, maxValidators);
1✔
165
            // Prepare the accounts for the instruction
166
            var keys = new List<AccountMeta>
1✔
167
            {
1✔
168
                AccountMeta.Writable(stakePoolAccount, false),
1✔
169
                AccountMeta.ReadOnly(manager, true),
1✔
170
                AccountMeta.ReadOnly(staker, false),
1✔
171
                AccountMeta.ReadOnly(withdrawAuthority, false),
1✔
172
                AccountMeta.Writable(validatorList, false),
1✔
173
                AccountMeta.ReadOnly(reserveStake, false),
1✔
174
                AccountMeta.Writable(poolMint, false),
1✔
175
                AccountMeta.Writable(managerPoolAccount, false),
1✔
176
                AccountMeta.ReadOnly(tokenProgramId, false)
1✔
177
            };
1✔
178

179
            if (depositAuthority != null)
1✔
180
            {
181
                keys.Add(AccountMeta.ReadOnly(depositAuthority, false));
1✔
182
            }
183

184
            // Return the transaction instruction
185
            return new TransactionInstruction
1✔
186
            {
1✔
187
                ProgramId = StakePoolProgramIdKey.KeyBytes,
1✔
188
                Keys = keys,
1✔
189
                Data = data
1✔
190
            };
1✔
191
        }
192

193
        /// <summary>
194
        /// Creates an AddValidatorToPool instruction (add validator stake account to the pool).
195
        /// </summary>
196
        /// <param name="stakePoolAccount"></param>
197
        /// <param name="staker"></param>
198
        /// <param name="reserve"></param>
199
        /// <param name="stakePoolWithdraw"></param>
200
        /// <param name="validatorList"></param>
201
        /// <param name="stakeAccount"></param>
202
        /// <param name="validatorAccount"></param>
203
        /// <param name="seed"></param>
204
        /// <returns></returns>
205
        public virtual TransactionInstruction AddValidatorToPool(
206
            PublicKey stakePoolAccount,
207
            PublicKey staker,
208
            PublicKey reserve,
209
            PublicKey stakePoolWithdraw,
210
            PublicKey validatorList,
211
            PublicKey stakeAccount,
212
            PublicKey validatorAccount,
213
            uint? seed = null
214
        )
215
        {
216
            // dont allow zero seed values
217
            if (seed == 0)
1!
NEW
218
                throw new ArgumentException("Value must be nonzero.", nameof(seed));
×
219

220
            // Prepare the instruction data
221
            var data = StakePoolProgramData.EncodeAddValidatorToPoolData(seed);
1✔
222

223
            // Prepare the accounts for the instruction
224
            List<AccountMeta> keys = new()
1✔
225
            {
1✔
226
                AccountMeta.Writable(stakePoolAccount, false),
1✔
227
                AccountMeta.ReadOnly(staker, true),
1✔
228
                AccountMeta.Writable(reserve, false),
1✔
229
                AccountMeta.ReadOnly(stakePoolWithdraw, false),
1✔
230
                AccountMeta.Writable(validatorList, false),
1✔
231
                AccountMeta.Writable(stakeAccount, false),
1✔
232
                AccountMeta.ReadOnly(validatorAccount, false),
1✔
233
                AccountMeta.ReadOnly(SysVars.RentKey, false),
1✔
234
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
1✔
235
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
1✔
236
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
1✔
237
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
1✔
238
            };
1✔
239

240
            return new TransactionInstruction
1✔
241
            {
1✔
242
                ProgramId = ProgramIdKey.KeyBytes,
1✔
243
                Keys = keys,
1✔
244
                Data = data
1✔
245
            };
1✔
246
        }
247

248
        /// <summary>
249
        /// Creates an RemoveValidatorFromPool instruction (remove validator stake account from the pool).
250
        /// </summary>
251
        /// <param name="stakePool"></param>
252
        /// <param name="staker"></param>
253
        /// <param name="stakePoolWithdraw"></param>
254
        /// <param name="validatorList"></param>
255
        /// <param name="stakeAccount"></param>
256
        /// <param name="transientStakeAccount"></param>
257
        /// <returns></returns>
258
        public virtual TransactionInstruction RemoveValidatorFromPool(
259
            PublicKey stakePool,
260
            PublicKey staker,
261
            PublicKey stakePoolWithdraw,
262
            PublicKey validatorList,
263
            PublicKey stakeAccount,
264
            PublicKey transientStakeAccount
265
        )
266
        {
267
            // Prepare the instruction data
268
            var data = StakePoolProgramData.EncodeRemoveValidatorFromPoolData();
1✔
269

270
            // Prepare the accounts that will be involved in this instruction
271
            List<AccountMeta> keys = new()
1✔
272
            {
1✔
273
                AccountMeta.Writable(stakePool, false),
1✔
274
                AccountMeta.ReadOnly(staker, true),
1✔
275
                AccountMeta.ReadOnly(stakePoolWithdraw, false),
1✔
276
                AccountMeta.Writable(validatorList, false),
1✔
277
                AccountMeta.Writable(stakeAccount, false),
1✔
278
                AccountMeta.Writable(transientStakeAccount, false),
1✔
279
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
1✔
280
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
1✔
281
            };
1✔
282

283
            // Return the TransactionInstruction
284
            return new TransactionInstruction
1✔
285
            {
1✔
286
                ProgramId = StakePoolProgramIdKey.KeyBytes,
1✔
287
                Keys = keys,
1✔
288
                Data = data
1✔
289
            };
1✔
290
        }
291

292
        /// <summary>
293
        /// Creates the 'DecreaseValidatorStake' instruction (rebalance from validator account to transient account).
294
        /// </summary>
295
        /// <param name="stakePool"></param>
296
        /// <param name="staker"></param>
297
        /// <param name="stakePoolWithdrawAuthority"></param>
298
        /// <param name="validatorList"></param>
299
        /// <param name="validatorStake"></param>
300
        /// <param name="transientStake"></param>
301
        /// <param name="lamports"></param>
302
        /// <param name="transientStakeSeed"></param>
303
        /// <returns></returns>
304
        [Obsolete("Use 'decrease_validator_stake_with_reserve' instead")]
305
        public virtual TransactionInstruction DecreaseValidatorStake(
306
            PublicKey stakePool,
307
            PublicKey staker,
308
            PublicKey stakePoolWithdrawAuthority,
309
            PublicKey validatorList,
310
            PublicKey validatorStake,
311
            PublicKey transientStake,
312
            ulong lamports,
313
            ulong transientStakeSeed)
314
        {
315
            // Prepare the instruction data
NEW
316
            var data = StakePoolProgramData.EncodeDecreaseValidatorStakeData(lamports, transientStakeSeed);
×
317

318
            // Prepare the accounts for the instruction
NEW
319
            var keys = new List<AccountMeta>
×
NEW
320
            {
×
NEW
321
                AccountMeta.ReadOnly(stakePool, false),
×
NEW
322
                AccountMeta.ReadOnly(staker, true),
×
NEW
323
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
×
NEW
324
                AccountMeta.Writable(validatorList, false),
×
NEW
325
                AccountMeta.Writable(validatorStake, false),
×
NEW
326
                AccountMeta.Writable(transientStake, false),
×
NEW
327
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
×
NEW
328
                AccountMeta.ReadOnly(SysVars.RentKey, false),
×
NEW
329
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
×
NEW
330
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
×
NEW
331
            };
×
332

333
            // Return the transaction instruction
NEW
334
            return new TransactionInstruction
×
NEW
335
            {
×
NEW
336
                ProgramId = StakePoolProgramIdKey.KeyBytes,
×
NEW
337
                Keys = keys,
×
NEW
338
                Data = data
×
NEW
339
            };
×
340
        }
341

342
        /// <summary>
343
        /// Creates the 'DecreaseAdditionalValidatorStake' instruction (rebalance from validator account to transient account).
344
        /// </summary>
345
        /// <param name="stakePool"></param>
346
        /// <param name="staker"></param>
347
        /// <param name="stakePoolWithdrawAuthority"></param>
348
        /// <param name="validatorList"></param>
349
        /// <param name="reserveStake"></param>
350
        /// <param name="validatorStake"></param>
351
        /// <param name="ephemeralStake"></param>
352
        /// <param name="transientStake"></param>
353
        /// <param name="lamports"></param>
354
        /// <param name="transientStakeSeed"></param>
355
        /// <param name="ephemeralStakeSeed"></param>
356
        /// <returns></returns>
357
        public virtual TransactionInstruction DecreaseAdditionalValidatorStake(
358
            PublicKey stakePool,
359
            PublicKey staker,
360
            PublicKey stakePoolWithdrawAuthority,
361
            PublicKey validatorList,
362
            PublicKey reserveStake,
363
            PublicKey validatorStake,
364
            PublicKey ephemeralStake,
365
            PublicKey transientStake,
366
            ulong lamports,
367
            ulong transientStakeSeed,
368
            ulong ephemeralStakeSeed)
369
        {
370
            // Prepare the instruction data
371
            var data = StakePoolProgramData.EncodeDecreaseAdditionalValidatorStakeData(lamports, transientStakeSeed, ephemeralStakeSeed);
1✔
372

373
            // Prepare the accounts for the instruction
374
            var keys = new List<AccountMeta>
1✔
375
        {
1✔
376
            AccountMeta.ReadOnly(stakePool, false),
1✔
377
            AccountMeta.ReadOnly(staker, true),
1✔
378
            AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
1✔
379
            AccountMeta.Writable(validatorList, false),
1✔
380
            AccountMeta.Writable(reserveStake, false),
1✔
381
            AccountMeta.Writable(validatorStake, false),
1✔
382
            AccountMeta.Writable(ephemeralStake, false),
1✔
383
            AccountMeta.Writable(transientStake, false),
1✔
384
            AccountMeta.ReadOnly(SysVars.ClockKey, false),
1✔
385
            AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
1✔
386
            AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
1✔
387
            AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
1✔
388
        };
1✔
389

390
            // Return the transaction instruction
391
            return new TransactionInstruction
1✔
392
            {
1✔
393
                ProgramId = StakePoolProgramIdKey.KeyBytes,
1✔
394
                Keys = keys,
1✔
395
                Data = data
1✔
396
            };
1✔
397
        }
398

399
        /// <summary>
400
        /// Creates the 'DecreaseValidatorStakeWithReserve' instruction (rebalance from validator account to transient account).
401
        /// </summary>
402
        /// <param name="stakePool"></param>
403
        /// <param name="staker"></param>
404
        /// <param name="stakePoolWithdrawAuthority"></param>
405
        /// <param name="validatorList"></param>
406
        /// <param name="reserveStake"></param>
407
        /// <param name="validatorStake"></param>
408
        /// <param name="transientStake"></param>
409
        /// <param name="lamports"></param>
410
        /// <param name="transientStakeSeed"></param>
411
        /// <returns></returns>
412
        public virtual TransactionInstruction DecreaseValidatorStakeWithReserve(
413
            PublicKey stakePool,
414
            PublicKey staker,
415
            PublicKey stakePoolWithdrawAuthority,
416
            PublicKey validatorList,
417
            PublicKey reserveStake,
418
            PublicKey validatorStake,
419
            PublicKey transientStake,
420
            ulong lamports,
421
            ulong transientStakeSeed)
422
        {
423
            // Prepare the instruction data
424
            var data = StakePoolProgramData.EncodeDecreaseValidatorStakeWithReserveData(lamports, transientStakeSeed);
2✔
425

426
            // Prepare the accounts for the instruction
427
            var keys = new List<AccountMeta>
2✔
428
        {
2✔
429
            AccountMeta.ReadOnly(stakePool, false),
2✔
430
            AccountMeta.ReadOnly(staker, true),
2✔
431
            AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
2✔
432
            AccountMeta.Writable(validatorList, false),
2✔
433
            AccountMeta.Writable(reserveStake, false),
2✔
434
            AccountMeta.Writable(validatorStake, false),
2✔
435
            AccountMeta.Writable(transientStake, false),
2✔
436
            AccountMeta.ReadOnly(SysVars.ClockKey, false),
2✔
437
            AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
2✔
438
            AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
2✔
439
            AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
2✔
440
        };
2✔
441

442
            // Return the transaction instruction
443
            return new TransactionInstruction
2✔
444
            {
2✔
445
                ProgramId = StakePoolProgramIdKey.KeyBytes,
2✔
446
                Keys = keys,
2✔
447
                Data = data
2✔
448
            };
2✔
449
        }
450

451
        /// <summary>
452
        /// Creates the 'IncreaseValidatorStake' instruction (rebalance from transient account to validator account).
453
        /// </summary>
454
        /// <param name="stakePool"></param>
455
        /// <param name="staker"></param>
456
        /// <param name="stakePoolWithdrawAuthority"></param>
457
        /// <param name="validatorList"></param>
458
        /// <param name="reserveStake"></param>
459
        /// <param name="transientStake"></param>
460
        /// <param name="validatorStake"></param>
461
        /// <param name="lamports"></param>
462
        /// <param name="transientStakeSeed"></param>
463
        /// <returns></returns>
464
        public virtual TransactionInstruction IncreaseValidatorStake
465
        (
466
            PublicKey stakePool,
467
            PublicKey staker,
468
            PublicKey stakePoolWithdrawAuthority,
469
            PublicKey validatorList,
470
            PublicKey reserveStake,
471
            PublicKey transientStake,
472
            PublicKey validatorStake,
473
            ulong lamports,
474
            ulong transientStakeSeed
475
        )
476
        { 
477
            var data = StakePoolProgramData.EncodeIncreaseValidatorStakeData(lamports, transientStakeSeed);
1✔
478

479
            var keys = new List<AccountMeta>
1✔
480
            {
1✔
481
                AccountMeta.ReadOnly(stakePool, false),
1✔
482
                AccountMeta.ReadOnly(staker, true),
1✔
483
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
1✔
484
                AccountMeta.Writable(validatorList, false),
1✔
485
                AccountMeta.Writable(reserveStake, false),
1✔
486
                AccountMeta.Writable(transientStake, false),
1✔
487
                AccountMeta.ReadOnly(validatorStake, false),
1✔
488
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
1✔
489
                AccountMeta.ReadOnly(SysVars.RentKey, false),
1✔
490
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
1✔
491
                AccountMeta.ReadOnly(StakeProgram.ConfigKey, false),
1✔
492
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
1✔
493
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
1✔
494
            };
1✔
495

496
            return new TransactionInstruction
1✔
497
            {
1✔
498
                ProgramId = StakePoolProgramIdKey.KeyBytes,
1✔
499
                Keys = keys,
1✔
500
                Data = data
1✔
501
            };
1✔
502
        }
503

504
        /// <summary>
505
        /// Creates the 'IncreaseAdditionalValidatorStake' instruction (rebalance from validator account to transient account).
506
        /// </summary>
507
        /// <param name="stakePool"></param>
508
        /// <param name="staker"></param>
509
        /// <param name="stakePoolWithdrawAuthority"></param>
510
        /// <param name="validatorList"></param>
511
        /// <param name="reserveStake"></param>
512
        /// <param name="ephemeralStake"></param>
513
        /// <param name="transientStake"></param>
514
        /// <param name="validatorStake"></param>
515
        /// <param name="validator"></param>
516
        /// <param name="lamports"></param>
517
        /// <param name="transientStakeSeed"></param>
518
        /// <param name="ephemeralStakeSeed"></param>
519
        /// <returns></returns>
520
        public virtual TransactionInstruction IncreaseAdditionalValidatorStake(
521
            PublicKey stakePool,
522
            PublicKey staker,
523
            PublicKey stakePoolWithdrawAuthority,
524
            PublicKey validatorList,
525
            PublicKey reserveStake,
526
            PublicKey ephemeralStake,
527
            PublicKey transientStake,
528
            PublicKey validatorStake,
529
            PublicKey validator,
530
            ulong lamports,
531
            ulong transientStakeSeed,
532
            ulong ephemeralStakeSeed)
533
        {
534
            // Prepare the instruction data
535
            var data = StakePoolProgramData.EncodeIncreaseAdditionalValidatorStakeData(lamports, transientStakeSeed, ephemeralStakeSeed);
1✔
536

537
            // Prepare the accounts for the instruction
538
            var keys = new List<AccountMeta>
1✔
539
        {
1✔
540
            AccountMeta.ReadOnly(stakePool, false),
1✔
541
            AccountMeta.ReadOnly(staker, true),
1✔
542
            AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
1✔
543
            AccountMeta.Writable(validatorList, false),
1✔
544
            AccountMeta.Writable(reserveStake, false),
1✔
545
            AccountMeta.Writable(ephemeralStake, false),
1✔
546
            AccountMeta.Writable(transientStake, false),
1✔
547
            AccountMeta.ReadOnly(validatorStake, false),
1✔
548
            AccountMeta.ReadOnly(validator, false),
1✔
549
            AccountMeta.ReadOnly(SysVars.ClockKey, false),
1✔
550
            AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
1✔
551
            AccountMeta.ReadOnly(StakeProgram.ConfigKey, false),
1✔
552
            AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
1✔
553
            AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
1✔
554
        };
1✔
555

556
            // Return the transaction instruction
557
            return new TransactionInstruction
1✔
558
            {
1✔
559
                ProgramId = StakePoolProgramIdKey.KeyBytes,
1✔
560
                Keys = keys,
1✔
561
                Data = data
1✔
562
            };
1✔
563
        }
564

565

566
        /// <summary>
567
        /// Creates the 'Redelegate' instruction (rebalance from one validator account to another).
568
        /// </summary>
569
        /// <remarks>
570
        /// This instruction is deprecated since 2.0.0. The stake redelegate instruction will not be enabled.
571
        /// </remarks>
572
        [Obsolete("The stake redelegate instruction used in this will not be enabled. Since 2.0.0", true)]
573
        public static TransactionInstruction Redelegate(
574
            PublicKey stakePool,
575
            PublicKey staker,
576
            PublicKey stakePoolWithdrawAuthority,
577
            PublicKey validatorList,
578
            PublicKey reserveStake,
579
            PublicKey sourceValidatorStake,
580
            PublicKey sourceTransientStake,
581
            PublicKey ephemeralStake,
582
            PublicKey destinationTransientStake,
583
            PublicKey destinationValidatorStake,
584
            PublicKey validator,
585
            ulong lamports,
586
            ulong sourceTransientStakeSeed,
587
            ulong ephemeralStakeSeed,
588
            ulong destinationTransientStakeSeed)
589
        {
NEW
590
            var accounts = new List<AccountMeta>
×
NEW
591
            {
×
NEW
592
                AccountMeta.ReadOnly(stakePool, false),
×
NEW
593
                AccountMeta.ReadOnly(staker, true),
×
NEW
594
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
×
NEW
595
                AccountMeta.Writable(validatorList, false),
×
NEW
596
                AccountMeta.Writable(reserveStake, false),
×
NEW
597
                AccountMeta.Writable(sourceValidatorStake, false),
×
NEW
598
                AccountMeta.Writable(sourceTransientStake, false),
×
NEW
599
                AccountMeta.Writable(ephemeralStake, false),
×
NEW
600
                AccountMeta.Writable(destinationTransientStake, false),
×
NEW
601
                AccountMeta.ReadOnly(destinationValidatorStake, false),
×
NEW
602
                AccountMeta.ReadOnly(validator, false),
×
NEW
603
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
×
NEW
604
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
×
NEW
605
                AccountMeta.ReadOnly(StakeProgram.ConfigKey, false),
×
NEW
606
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
×
NEW
607
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false)
×
NEW
608
            };
×
609

NEW
610
            var data = StakePoolProgramData.EncodeRedelegateData(lamports, sourceTransientStakeSeed, ephemeralStakeSeed, destinationTransientStakeSeed);
×
611

NEW
612
            return new TransactionInstruction
×
NEW
613
            {
×
NEW
614
                ProgramId = StakePoolProgramIdKey,
×
NEW
615
                Keys = accounts,
×
NEW
616
                Data = data
×
NEW
617
            };
×
618
        }
619

620
#nullable enable
621

622
        /// <summary>
623
        /// Creates the 'SetPreferredDepositValidator' instruction (set preferred deposit validator).
624
        /// </summary>
625
        /// <param name="stakePoolAddress"></param>
626
        /// <param name="staker"></param>
627
        /// <param name="validatorListAddress"></param>
628
        /// <param name="validatorType"></param>
629
        /// <param name="validatorVoteAddress">Optional public key; if provided, it must be in the keys list.</param>
630
        /// <returns></returns>
631
        public virtual TransactionInstruction SetPreferredDepositValidator(
632
            PublicKey stakePoolAddress,
633
            PublicKey staker,
634
            PublicKey validatorListAddress,
635
            PreferredValidatorType validatorType,
636
            PublicKey? validatorVoteAddress = null)
637
        {
638
            // Prepare Account Metas.
639
            var keys = new List<AccountMeta>
1✔
640
            {
1✔
641
                AccountMeta.Writable(stakePoolAddress, false),
1✔
642
                AccountMeta.ReadOnly(staker, true),
1✔
643
                AccountMeta.ReadOnly(validatorListAddress, false)
1✔
644
            };
1✔
645

646
            // Fix: If a validator vote address is provided, add it to the keys list instead of encoding into data.
647
            if (validatorVoteAddress != null)
1✔
648
            {
649
                keys.Add(AccountMeta.ReadOnly(validatorVoteAddress, false));
1✔
650
            }
651

652
            // Encode only the instruction discriminator and validator type.
653
            var data = StakePoolProgramData.EncodeSetPreferredValidatorData(validatorType);
1✔
654

655
            return new TransactionInstruction
1✔
656
            {
1✔
657
                ProgramId = StakePoolProgramIdKey.KeyBytes,
1✔
658
                Keys = keys,
1✔
659
                Data = data
1✔
660
            };
1✔
661
        }
662

663
        /// <summary>
664
        /// Creates an 'AddValidatorToPoolWithVote' instruction given an existing stake pool and vote account.
665
        /// </summary>
666
        /// <param name="stakePool"></param>
667
        /// <param name="stakePoolAddress"></param>
668
        /// <param name="voteAccountAddress"></param>
669
        /// <param name="seed"></param>
670
        /// <returns></returns>
671
        /// <exception cref="InvalidProgramException"></exception>
672
        public virtual TransactionInstruction AddValidatorToPoolWithVote(
673
            Models.StakePool stakePool,
674
            PublicKey stakePoolAddress,
675
            PublicKey voteAccountAddress,
676
            uint? seed = null)
677
        {
678
            // dont allow zero seed values
NEW
679
            if (seed == 0)
×
NEW
680
                throw new ArgumentException("Value must be nonzero.", nameof(seed));
×
681

682
            // Find the program address for the withdraw authority
NEW
683
            if (!PublicKey.TryFindProgramAddress([stakePoolAddress.KeyBytes], ProgramIdKey, out var poolWithdrawalAuthority, out var nonce))
×
NEW
684
                throw new InvalidProgramException();
×
685
            // Find the stake account address for the validator using the vote account and seed
NEW
686
            if (!PublicKey.TryFindProgramAddress([voteAccountAddress.KeyBytes], ProgramIdKey, out var stakeAccountAddress, out var _))
×
NEW
687
                throw new InvalidProgramException();
×
688

689
            // Generate the instruction to add the validator to the pool
NEW
690
            return AddValidatorToPool(
×
NEW
691
                StakePoolProgramIdKey,
×
NEW
692
                stakePool.Staker,
×
NEW
693
                stakePool.ReserveStake,
×
NEW
694
                poolWithdrawalAuthority,
×
NEW
695
                stakePool.ValidatorList,
×
NEW
696
                stakeAccountAddress,
×
NEW
697
                voteAccountAddress,
×
NEW
698
                seed
×
NEW
699
            );
×
700
        }
701

702

703
        /// <summary>
704
        /// Creates an 'RemoveValidatorFromPoolWithVote' instruction given an existing stake pool and vote account.
705
        /// </summary>
706
        /// <param name="stakePool"></param>
707
        /// <param name="stakePoolAddress"></param>
708
        /// <param name="voteAccountAddress"></param>
709
        /// <param name="validatorStakeSeed"></param>
710
        /// <param name="transientStakeSeed"></param>
711
        /// <returns></returns>
712
        public virtual TransactionInstruction RemoveValidatorFromPoolWithVote(
713
            Models.StakePool stakePool, // Fully qualify the type to avoid ambiguity
714
            PublicKey stakePoolAddress,
715
            PublicKey voteAccountAddress,
716
            uint? validatorStakeSeed = null,
717
            ulong transientStakeSeed = 0)
718
        {
719
            // dont allow zero seed values
NEW
720
            if (validatorStakeSeed == 0)
×
NEW
721
                throw new ArgumentException("Value must be nonzero.", nameof(validatorStakeSeed));
×
722

723
            // Find the program address for the withdraw authority
NEW
724
            if (!PublicKey.TryFindProgramAddress([stakePoolAddress.KeyBytes], ProgramIdKey, out var poolWithdrawalAuthority, out var nonce))
×
NEW
725
                throw new InvalidProgramException();
×
726
            
727
            // Find the stake account address for the validator using the vote account and seed
NEW
728
            if (!PublicKey.TryFindProgramAddress([voteAccountAddress.KeyBytes], ProgramIdKey, out var stakeAccountAddress, out var _))
×
NEW
729
                throw new InvalidProgramException();
×
730

731
            // Find the transient stake account using the vote account, stake pool address, and transient stake seed
NEW
732
            if (!PublicKey.TryFindProgramAddress([voteAccountAddress.KeyBytes], ProgramIdKey, out var transientStakeAccount, out var _))
×
NEW
733
                throw new InvalidProgramException();
×
734

735
            // Create the RemoveValidatorFromPool instruction
NEW
736
            return RemoveValidatorFromPool(
×
NEW
737
                StakePoolProgramIdKey,
×
NEW
738
                stakePool.Staker,
×
NEW
739
                poolWithdrawalAuthority,
×
NEW
740
                stakePool.ValidatorList,
×
NEW
741
                stakeAccountAddress,
×
NEW
742
                transientStakeAccount
×
NEW
743
            );
×
744
        }
745

746
        /// <summary>
747
        /// Creates an 'IncreaseValidatorStakeWithVote' instruction given an existing stake pool and vote account.
748
        /// </summary>
749
        /// <param name="stakePool"></param>
750
        /// <param name="stakePoolAddress"></param>
751
        /// <param name="voteAccountAddress"></param>
752
        /// <param name="lamports"></param>
753
        /// <param name="validatorStakeSeed"></param>
754
        /// <param name="transientStakeSeed"></param>
755
        /// <returns></returns>
756
        public static TransactionInstruction IncreaseValidatorStakeWithVote(
757
            Models.StakePool stakePool,
758
            PublicKey stakePoolAddress,PublicKey voteAccountAddress,
759
            ulong lamports,
760
            uint? validatorStakeSeed,
761
            ulong transientStakeSeed
762
        )
763
        {
764
            // dont allow zero seed values
NEW
765
            if (validatorStakeSeed == 0)
×
NEW
766
                throw new ArgumentException("Value must be nonzero.", nameof(validatorStakeSeed));
×
767

768
            // Find the addresses using helper methods
NEW
769
            var poolWithdrawAuthority = FindWithdrawAuthorityProgramAddress(stakePoolAddress);
×
NEW
770
            var transientStakeAddress = FindTransientStakeProgramAddress(voteAccountAddress, stakePoolAddress, transientStakeSeed);
×
NEW
771
            var validatorStakeAddress = FindStakeProgramAddress(voteAccountAddress, stakePoolAddress, validatorStakeSeed);
×
772

773
            // Create the instruction accounts (same structure as the Rust version)
NEW
774
            var accounts = new List<AccountMeta>
×
NEW
775
            {
×
NEW
776
                AccountMeta.ReadOnly(stakePoolAddress, false),
×
NEW
777
                AccountMeta.Writable(stakePool.Staker, true),
×
NEW
778
                AccountMeta.ReadOnly(poolWithdrawAuthority, false),
×
NEW
779
                AccountMeta.ReadOnly(stakePool.ValidatorList, false),
×
NEW
780
                AccountMeta.ReadOnly(stakePool.ReserveStake, false),
×
NEW
781
                AccountMeta.ReadOnly(transientStakeAddress, false),
×
NEW
782
                AccountMeta.ReadOnly(validatorStakeAddress, false),
×
NEW
783
                AccountMeta.ReadOnly(voteAccountAddress, false)
×
NEW
784
            };
×
785

786
            // Serialize the instruction data (this is similar to `borsh::to_vec` in Rust)
NEW
787
            var data = SerializeIncreaseValidatorStakeData(lamports, transientStakeSeed);
×
788

789
            // Return the instruction
NEW
790
            return new TransactionInstruction
×
NEW
791
            {
×
NEW
792
                ProgramId = StakePoolProgramIdKey,
×
NEW
793
                Keys = accounts,
×
NEW
794
                Data = data
×
NEW
795
            };
×
796
        }
797

798
        /// <summary>
799
        /// Creates a DecreaseValidatorStake instruction (rebalance from validator account to transient account)
800
        /// given an existing stake pool and vote account.
801
        /// </summary>
802
        /// <param name="stakePool">The stake pool model. Provides staker, validator list, and reserve stake.</param>
803
        /// <param name="stakePoolAddress">The address of the stake pool.</param>
804
        /// <param name="voteAccountAddress">The vote account address.</param>
805
        /// <param name="lamports">The amount of lamports.</param>
806
        /// <param name="validatorStakeSeed">
807
        /// An optional nonzero seed for the validator stake account (represented by a uint?; zero is disallowed).
808
        /// </param>
809
        /// <param name="transientStakeSeed">The transient stake seed.</param>
810
        /// <returns>The corresponding transaction instruction.</returns>
811
        public virtual TransactionInstruction DecreaseValidatorStakeWithVote(
812
            Models.StakePool stakePool,
813
            PublicKey stakePoolAddress,
814
            PublicKey voteAccountAddress,
815
            ulong lamports,
816
            uint? validatorStakeSeed,
817
            ulong transientStakeSeed)
818
        {
819
            // Ensure the optional validator stake seed is nonzero.
NEW
820
            if (validatorStakeSeed == 0)
×
NEW
821
                throw new ArgumentException("Value must be nonzero.", nameof(validatorStakeSeed));
×
822

823
            // Find the pool withdrawal authority.
NEW
824
            var poolWithdrawalAuthority = FindWithdrawAuthorityProgramAddress(stakePoolAddress);
×
825

826
            // Find the validator stake address using the vote account and the seed.
NEW
827
            var validatorStakeAddress = FindStakeProgramAddress(voteAccountAddress, stakePoolAddress, validatorStakeSeed);
×
828

829
            // Find the transient stake address.
NEW
830
            var transientStakeAddress = FindTransientStakeProgramAddress(voteAccountAddress, stakePoolAddress, transientStakeSeed);
×
831

832
            // Construct the instruction by calling the already implemented decrease_validator_stake_with_reserve method.
NEW
833
            return DecreaseValidatorStakeWithReserve(
×
NEW
834
                stakePoolAddress,
×
NEW
835
                stakePool.Staker,
×
NEW
836
                poolWithdrawalAuthority,
×
NEW
837
                stakePool.ValidatorList,
×
NEW
838
                stakePool.ReserveStake,
×
NEW
839
                validatorStakeAddress,
×
NEW
840
                transientStakeAddress,
×
NEW
841
                lamports,
×
NEW
842
                transientStakeSeed);
×
843
        }
844
        /// <summary>
845
        /// Finds the program-derived address for the withdraw authority.
846
        /// </summary>
847
        /// <param name="stakePoolAddress"></param>
848
        /// <returns></returns>
849
        public static PublicKey FindWithdrawAuthorityProgramAddress(PublicKey stakePoolAddress)
850
        {
851
            // Seeds must be provided in the exact same order used in Rust.
852
            var seeds = new[]
1✔
853
            {
1✔
854
                stakePoolAddress.KeyBytes, // stake pool pubkey as bytes
1✔
855
                AUTHORITY_WITHDRAW         // "withdraw"
1✔
856
            };
1✔
857

858
            // Solnet exposes FindProgramAddress; it yields the PDA and out‑param bump.
859
            if (!PublicKey.TryFindProgramAddress(seeds, StakePoolProgramIdKey, out PublicKey address, out byte bump))
1!
860
            {
NEW
861
                throw new InvalidProgramException();
×
862
            }
863

864
            return address;
1✔
865
        }
866

867
        /// <summary>
868
        /// Serializes the data for the 'IncreaseValidatorStake' instruction.
869
        /// </summary>
870
        /// <param name="lamports"></param>
871
        /// <param name="transientStakeSeed"></param>
872
        /// <returns></returns>
873
        public static byte[] SerializeIncreaseValidatorStakeData(ulong lamports, ulong transientStakeSeed)
874
        {
875
            // Placeholder: actual serialization would be required based on your program's structure.
NEW
876
            return new byte[] { (byte)lamports, (byte)transientStakeSeed };
×
877
        }
878

879
        /// <summary>
880
        /// Finds the stake program address for a validator's vote account.
881
        /// </summary>
882
        /// <param name="voteAccountAddress"></param>
883
        /// <param name="stakePoolAddress"></param>
884
        /// <param name="validatorStakeSeed"></param>
885
        /// <returns></returns>
886
        public static PublicKey FindStakeProgramAddress(
887
            PublicKey voteAccountAddress,
888
            PublicKey stakePoolAddress,
889
            uint? validatorStakeSeed
890
        )
891
        {
892
            if (validatorStakeSeed.HasValue && validatorStakeSeed.Value == 0)
4!
NEW
893
                throw new ArgumentException("Seed must be non‑zero (Rust NonZeroU32).", nameof(validatorStakeSeed));
×
894

895
            // Convert the seed (if provided) to little‑endian bytes.
896
            byte[] seedBytes = Array.Empty<byte>();
4✔
897
            if (validatorStakeSeed.HasValue)
4✔
898
            {
899
                seedBytes = BitConverter.GetBytes(validatorStakeSeed.Value);              // platform‑endian -> little‑endian
4✔
900
                if (!BitConverter.IsLittleEndian) Array.Reverse(seedBytes); // ensure LE on big‑endian CPUs
4!
901
            }
902

903
            // Seeds must be passed in the exact order used in Rust.
904
            var seeds = new[]
4✔
905
            {
4✔
906
                voteAccountAddress.KeyBytes,    // vote‑account pubkey
4✔
907
                stakePoolAddress.KeyBytes,
4✔
908
                seedBytes                // may be empty
4✔
909
            };
4✔
910

911
            // Fix: Correctly call TryFindProgramAddress with all required parameters
912
            if (!PublicKey.TryFindProgramAddress(seeds, StakePoolProgramIdKey, out PublicKey pda, out byte bump))
4!
913
            {
NEW
914
                throw new InvalidProgramException();
×
915
            }
916

917
            return pda;
4✔
918
        }
919

920
        /// <summary>
921
        /// Finds the transient stake program address for a given vote account and stake pool.
922
        /// </summary>
923
        /// <param name="voteAccountAddress"></param>
924
        /// <param name="stakePoolAddress"></param>
925
        /// <param name="transientStakeSeed"></param>
926
        /// <returns></returns>
927
        public static PublicKey FindTransientStakeProgramAddress(
928
            PublicKey voteAccountAddress,
929
            PublicKey stakePoolAddress,
930
            ulong transientStakeSeed)
931
        {
932
            // Convert the u64 seed to little‑endian bytes (8 bytes).
933
            byte[] seedBytes = BitConverter.GetBytes(transientStakeSeed);
4✔
934
            if (!BitConverter.IsLittleEndian)
4!
NEW
935
                Array.Reverse(seedBytes); // ensure LE on big‑endian machines
×
936

937
            // Build seed list in the exact order used in Rust.
938
            var seeds = new List<byte[]>
4✔
939
            {
4✔
940
                TRANSIENT_STAKE_SEED_PREFIX,
4✔
941
                voteAccountAddress.KeyBytes,
4✔
942
                stakePoolAddress.KeyBytes,
4✔
943
                seedBytes
4✔
944
            };
4✔
945

946
            // Fix: Correctly call TryFindProgramAddress with all required parameters
947
            if (!PublicKey.TryFindProgramAddress(seeds, StakePoolProgramIdKey, out PublicKey pda, out byte bump))
4!
948
            {
NEW
949
                throw new InvalidProgramException();
×
950
            }
951

952
            return pda;
4✔
953
        }
954

955
        /// <summary>
956
        /// Generates the deposit authority program address for the stake pool.
957
        /// </summary>
958
        /// <param name="stakePoolAddress">The stake pool public key.</param>
959
        /// <returns>A tuple of the derived deposit authority public key and the bump seed.</returns>
960
        public static (PublicKey, byte) FindDepositAuthorityProgramAddress(PublicKey stakePoolAddress)
961
        {
962
            // Seeds must be in the exact order: stake pool address bytes followed by AUTHORITY_DEPOSIT.
963
            var seeds = new[] { stakePoolAddress.KeyBytes, AUTHORITY_DEPOSIT };
2✔
964
            if (!PublicKey.TryFindProgramAddress(seeds, StakePoolProgramIdKey, out PublicKey address, out byte bump))
2!
NEW
965
                throw new InvalidProgramException("Unable to find deposit authority program address");
×
966
            return (address, bump);
2✔
967
        }
968

969
        /// <summary>
970
        /// Generates the ephemeral program address for stake pool redelegation.
971
        /// </summary>
972
        /// <param name="stakePoolAddress">The stake pool public key.</param>
973
        /// <param name="seed">The seed used to generate the ephemeral stake address.</param>
974
        /// <returns>A tuple of the derived ephemeral stake public key and the bump seed.</returns>
975
        public static (PublicKey, byte) FindEphemeralStakeProgramAddress(PublicKey stakePoolAddress, ulong seed)
976
        {
NEW
977
            byte[] seedBytes = BitConverter.GetBytes(seed);
×
NEW
978
            if (!BitConverter.IsLittleEndian)
×
979
            {
NEW
980
                Array.Reverse(seedBytes);
×
981
            }
982

NEW
983
            var seeds = new List<byte[]>
×
NEW
984
            {
×
NEW
985
                EPHEMERAL_STAKE_SEED_PREFIX,
×
NEW
986
                stakePoolAddress.KeyBytes,
×
NEW
987
                seedBytes
×
NEW
988
            };
×
989

NEW
990
            if (!PublicKey.TryFindProgramAddress(seeds, StakePoolProgramIdKey, out PublicKey address, out byte bump))
×
991
            {
NEW
992
                throw new InvalidProgramException("Unable to find ephemeral stake program address");
×
993
            }
994

NEW
995
            return (address, bump);
×
996
        }
997

998
        /// <summary>
999
        /// Gets the minimum delegation required by a stake account in a stake pool.
1000
        /// </summary>
1001
        /// <param name="stakeProgramMinimumDelegation">The minimum delegation defined by the stake program.</param>
1002
        /// <returns>The greater value between stakeProgramMinimumDelegation and MINIMUM_ACTIVE_STAKE.</returns>
1003
        public static ulong MinimumDelegation(ulong stakeProgramMinimumDelegation)
1004
        {
NEW
1005
            return Math.Max(stakeProgramMinimumDelegation, MINIMUM_ACTIVE_STAKE);
×
1006
        }
1007

1008
        /// <summary>
1009
        /// Gets the stake amount under consideration when calculating pool token conversions.
1010
        /// </summary>
1011
        /// <param name="meta">The metadata instance containing the rent-exempt reserve value.</param>
1012
        /// <returns>
1013
        /// The sum of <c>meta.RentExemptReserve</c> and <c>MINIMUM_RESERVE_LAMPORTS</c>. If addition overflows,
1014
        /// returns <c>ulong.MaxValue</c>.
1015
        /// </returns>
1016
        public static ulong MinimumReserveLamports(Meta meta)
1017
        {
NEW
1018
            ulong reserve = meta.RentExemptReserve;
×
NEW
1019
            ulong addition = MINIMUM_RESERVE_LAMPORTS;
×
1020
            // Implement saturating addition: if adding addition to reserve would overflow, return ulong.MaxValue.
NEW
1021
            if (ulong.MaxValue - reserve < addition)
×
1022
            {
NEW
1023
                return ulong.MaxValue;
×
1024
            }
1025

NEW
1026
            return reserve + addition;
×
1027
        }
1028

1029
        /// <summary>
1030
        /// Creates an IncreaseAdditionalValidatorStake instruction (rebalance from validator account to transient account)
1031
        /// given an existing stake pool, validator list and vote account.
1032
        /// </summary>
1033
        /// <param name="stakePool">The stake pool model containing staker, validator list, and reserve stake.</param>
1034
        /// <param name="validatorList">The validator list used to locate the corresponding validator info.</param>
1035
        /// <param name="stakePoolAddress">The address of the stake pool.</param>
1036
        /// <param name="voteAccountAddress">The vote account address.</param>
1037
        /// <param name="lamports">The amount of lamports.</param>
1038
        /// <param name="ephemeralStakeSeed">The ephemeral stake seed.</param>
1039
        /// <returns>The transaction instruction to increase additional validator stake.</returns>
1040
        public TransactionInstruction IncreaseAdditionalValidatorStakeWithList(
1041
            Models.StakePool stakePool,
1042
            ValidatorList validatorList,
1043
            PublicKey stakePoolAddress,
1044
            PublicKey voteAccountAddress,
1045
            ulong lamports,
1046
            ulong ephemeralStakeSeed)
1047
        {
NEW
1048
            var validatorInfo = validatorList.Find(voteAccountAddress);
×
NEW
1049
            if (validatorInfo == null)
×
NEW
1050
                throw new ArgumentException("Invalid instruction data: vote account was not found in the validator list.", nameof(voteAccountAddress));
×
1051

NEW
1052
            ulong transientStakeSeed = (ulong)validatorInfo.TransientSeedSuffix;
×
NEW
1053
            uint? validatorStakeSeed = validatorInfo.ValidatorSeedSuffix; // assume this property returns a uint
×
NEW
1054
            if (validatorStakeSeed == 0)
×
NEW
1055
                throw new ArgumentException("Invalid instruction data: validator stake seed cannot be zero.", nameof(validatorStakeSeed));
×
1056

NEW
1057
            return IncreaseAdditionalValidatorStakeWithVote(
×
NEW
1058
                stakePool,
×
NEW
1059
                stakePoolAddress,
×
NEW
1060
                voteAccountAddress,
×
NEW
1061
                lamports,
×
NEW
1062
                validatorStakeSeed,
×
NEW
1063
                transientStakeSeed,
×
NEW
1064
                ephemeralStakeSeed);
×
1065
        }
1066

1067
        /// <summary>
1068
        /// Creates an IncreaseAdditionalValidatorStake instruction given an existing stake pool and vote account.
1069
        /// This helper derives the necessary addresses and serializes the instruction data.
1070
        /// </summary>
1071
        /// <param name="stakePool">The stake pool model.</param>
1072
        /// <param name="stakePoolAddress">The stake pool public key.</param>
1073
        /// <param name="voteAccountAddress">The validator vote account public key.</param>
1074
        /// <param name="lamports">The amount of lamports.</param>
1075
        /// <param name="validatorStakeSeed">
1076
        /// An optional nonzero seed for the validator stake account (zero is disallowed).
1077
        /// </param>
1078
        /// <param name="transientStakeSeed">The transient stake seed.</param>
1079
        /// <param name="ephemeralStakeSeed">The ephemeral stake seed.</param>
1080
        /// <returns>The constructed transaction instruction.</returns>
1081
        public static TransactionInstruction IncreaseAdditionalValidatorStakeWithVote(
1082
            Models.StakePool stakePool,
1083
            PublicKey stakePoolAddress,
1084
            PublicKey voteAccountAddress,
1085
            ulong lamports,
1086
            uint? validatorStakeSeed,
1087
            ulong transientStakeSeed,
1088
            ulong ephemeralStakeSeed)
1089
        {
1090
            // Derive the pool withdraw authority.
NEW
1091
            PublicKey poolWithdrawAuthority = FindWithdrawAuthorityProgramAddress(stakePoolAddress);
×
1092

1093
            // Derive the ephemeral stake address using stake pool address and ephemeral seed.
NEW
1094
            PublicKey ephemeralStakeAddress = FindEphemeralStakeProgramAddress(stakePoolAddress, ephemeralStakeSeed).Item1;
×
1095

1096
            // Derive the transient stake address using the vote account and stake pool address.
NEW
1097
            PublicKey transientStakeAddress = FindTransientStakeProgramAddress(voteAccountAddress, stakePoolAddress, transientStakeSeed);
×
1098

1099
            // Derive the validator stake address using the vote account and stake pool address.
NEW
1100
            PublicKey validatorStakeAddress = FindStakeProgramAddress(voteAccountAddress, stakePoolAddress, validatorStakeSeed);
×
1101

1102
            // Build the account metas.
NEW
1103
            var accounts = new List<AccountMeta>
×
NEW
1104
            {
×
NEW
1105
                AccountMeta.ReadOnly(stakePoolAddress, false),
×
NEW
1106
                AccountMeta.Writable(stakePool.Staker, true),
×
NEW
1107
                AccountMeta.ReadOnly(poolWithdrawAuthority, false),
×
NEW
1108
                AccountMeta.ReadOnly(stakePool.ValidatorList, false),
×
NEW
1109
                AccountMeta.ReadOnly(stakePool.ReserveStake, false),
×
NEW
1110
                AccountMeta.Writable(ephemeralStakeAddress, false),
×
NEW
1111
                AccountMeta.Writable(transientStakeAddress, false),
×
NEW
1112
                AccountMeta.ReadOnly(validatorStakeAddress, false),
×
NEW
1113
                AccountMeta.ReadOnly(voteAccountAddress, false),
×
NEW
1114
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
×
NEW
1115
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
×
NEW
1116
                AccountMeta.ReadOnly(StakeProgram.ConfigKey, false),
×
NEW
1117
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
×
NEW
1118
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
×
NEW
1119
            };
×
1120

1121
            // Serialize instruction data.
NEW
1122
            byte[] data = StakePoolProgramData.EncodeIncreaseAdditionalValidatorStakeData(lamports, transientStakeSeed, ephemeralStakeSeed);
×
1123

NEW
1124
            return new TransactionInstruction
×
NEW
1125
            {
×
NEW
1126
                ProgramId = StakePoolProgramIdKey,
×
NEW
1127
                Keys = accounts,
×
NEW
1128
                Data = data
×
NEW
1129
            };
×
1130
        }
1131

1132
        /// <summary>
1133
        /// Creates a DecreaseAdditionalValidatorStake instruction given an existing stake pool, validator list and vote account.
1134
        /// </summary>
1135
        /// <param name="stakePool">The stake pool model containing staker, validator list, and reserve stake.</param>
1136
        /// <param name="validatorList">The list of validator stake info.</param>
1137
        /// <param name="stakePoolAddress">The stake pool public key.</param>
1138
        /// <param name="voteAccountAddress">The validator vote account public key.</param>
1139
        /// <param name="lamports">The amount of lamports to withdraw.</param>
1140
        /// <param name="ephemeralStakeSeed">The ephemeral stake seed.</param>
1141
        /// <returns>The constructed transaction instruction.</returns>
1142
        public TransactionInstruction DecreaseAdditionalValidatorStakeWithList(
1143
            Models.StakePool stakePool,
1144
            ValidatorList validatorList,
1145
            PublicKey stakePoolAddress,
1146
            PublicKey voteAccountAddress,
1147
            ulong lamports,
1148
            ulong ephemeralStakeSeed)
1149
        {
NEW
1150
            var validatorInfo = validatorList.Find(voteAccountAddress);
×
NEW
1151
            if (validatorInfo == null)
×
NEW
1152
                throw new ArgumentException("Invalid instruction data: vote account was not found in the validator list.", nameof(voteAccountAddress));
×
1153

NEW
1154
            ulong transientStakeSeed = validatorInfo.TransientSeedSuffix;
×
NEW
1155
            uint? validatorStakeSeed = validatorInfo.ValidatorSeedSuffix;
×
NEW
1156
            if (validatorStakeSeed == 0)
×
NEW
1157
                throw new ArgumentException("Invalid instruction data: validator stake seed cannot be zero.", nameof(validatorStakeSeed));
×
1158

NEW
1159
            return DecreaseAdditionalValidatorStakeWithVote(
×
NEW
1160
                stakePool,
×
NEW
1161
                stakePoolAddress,
×
NEW
1162
                voteAccountAddress,
×
NEW
1163
                lamports,
×
NEW
1164
                validatorStakeSeed,
×
NEW
1165
                transientStakeSeed,
×
NEW
1166
                ephemeralStakeSeed);
×
1167
        }
1168

1169
        /// <summary>
1170
        /// Creates a DecreaseAdditionalValidatorStake instruction given an existing stake pool and vote account.
1171
        /// This helper derives the necessary addresses and serializes the instruction data. Its output is analogous to the Rust
1172
        /// function `decrease_additional_validator_stake_with_vote`.
1173
        /// </summary>
1174
        /// <param name="stakePool">The stake pool model.</param>
1175
        /// <param name="stakePoolAddress">The stake pool public key.</param>
1176
        /// <param name="voteAccountAddress">The validator vote account public key.</param>
1177
        /// <param name="lamports">The amount of lamports to withdraw.</param>
1178
        /// <param name="validatorStakeSeed">
1179
        /// An optional nonzero seed for the validator stake account (zero is disallowed).
1180
        /// </param>
1181
        /// <param name="transientStakeSeed">The transient stake seed.</param>
1182
        /// <param name="ephemeralStakeSeed">The ephemeral stake seed.</param>
1183
        /// <returns>The constructed transaction instruction.</returns>
1184
        public static TransactionInstruction DecreaseAdditionalValidatorStakeWithVote(
1185
            Models.StakePool stakePool,
1186
            PublicKey stakePoolAddress,
1187
            PublicKey voteAccountAddress,
1188
            ulong lamports,
1189
            uint? validatorStakeSeed,
1190
            ulong transientStakeSeed,
1191
            ulong ephemeralStakeSeed)
1192
        {
1193
            // Derive the pool withdraw authority.
NEW
1194
            PublicKey poolWithdrawAuthority = FindWithdrawAuthorityProgramAddress(stakePoolAddress);
×
1195

1196
            // Derive the ephemeral stake address using the stake pool address and ephemeral stake seed.
NEW
1197
            PublicKey ephemeralStakeAddress = FindEphemeralStakeProgramAddress(stakePoolAddress, ephemeralStakeSeed).Item1;
×
1198

1199
            // Derive the transient stake address using the vote account and stake pool address.
NEW
1200
            PublicKey transientStakeAddress = FindTransientStakeProgramAddress(voteAccountAddress, stakePoolAddress, transientStakeSeed);
×
1201

1202
            // Derive the validator stake address using the vote account, stake pool address, and validator stake seed.
NEW
1203
            PublicKey validatorStakeAddress = FindStakeProgramAddress(voteAccountAddress, stakePoolAddress, validatorStakeSeed);
×
1204

1205
            // Build the instruction accounts in the same order as in the Rust version.
NEW
1206
            var accounts = new List<AccountMeta>
×
NEW
1207
            {
×
NEW
1208
                AccountMeta.ReadOnly(stakePoolAddress, false),
×
NEW
1209
                AccountMeta.Writable(stakePool.Staker, true),
×
NEW
1210
                AccountMeta.ReadOnly(poolWithdrawAuthority, false),
×
NEW
1211
                AccountMeta.ReadOnly(stakePool.ValidatorList, false),
×
NEW
1212
                AccountMeta.ReadOnly(stakePool.ReserveStake, false),
×
NEW
1213
                AccountMeta.Writable(ephemeralStakeAddress, false),
×
NEW
1214
                AccountMeta.Writable(transientStakeAddress, false),
×
NEW
1215
                AccountMeta.ReadOnly(validatorStakeAddress, false),
×
NEW
1216
                AccountMeta.ReadOnly(voteAccountAddress, false),
×
NEW
1217
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
×
NEW
1218
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
×
NEW
1219
                AccountMeta.ReadOnly(StakeProgram.ConfigKey, false),
×
NEW
1220
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
×
NEW
1221
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
×
NEW
1222
            };
×
1223

1224
            // Serialize instruction data; this method should follow your program's custom layout.
NEW
1225
            byte[] data = StakePoolProgramData.EncodeDecreaseAdditionalValidatorStakeData(lamports, transientStakeSeed, ephemeralStakeSeed);
×
1226

NEW
1227
            return new TransactionInstruction
×
NEW
1228
            {
×
NEW
1229
                ProgramId = StakePoolProgramIdKey,
×
NEW
1230
                Keys = accounts,
×
NEW
1231
                Data = data
×
NEW
1232
            };
×
1233
        }
1234

1235
        /// <summary>
1236
        /// Creates an UpdateValidatorListBalance instruction to update the balance of validators in a stake pool.
1237
        /// </summary>
1238
        /// <param name="stakePool"></param>
1239
        /// <param name="stakePoolWithdrawAuthority"></param>
1240
        /// <param name="validatorListAddress"></param>
1241
        /// <param name="reserveStake"></param>
1242
        /// <param name="validatorList"></param>
1243
        /// <param name="validatorVoteAccounts"></param>
1244
        /// <param name="startIndex"></param>
1245
        /// <param name="noMerge"></param>
1246
        /// <returns></returns>
1247
        [Obsolete("please use UpdateValidatorListBalanceChunk")]
1248
        public static TransactionInstruction UpdateValidatorListBalance(
1249
            PublicKey stakePool,
1250
            PublicKey stakePoolWithdrawAuthority,
1251
            PublicKey validatorListAddress,
1252
            PublicKey reserveStake,
1253
            ValidatorList validatorList,
1254
            IEnumerable<PublicKey> validatorVoteAccounts,
1255
            uint startIndex,
1256
            bool noMerge)
1257
        {
1258
            // Build the fixed part of the account metas.
NEW
1259
            var accounts = new List<AccountMeta>
×
NEW
1260
            {
×
NEW
1261
                AccountMeta.ReadOnly(stakePool, false),
×
NEW
1262
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
×
NEW
1263
                AccountMeta.Writable(validatorListAddress, false),
×
NEW
1264
                AccountMeta.Writable(reserveStake, false),
×
NEW
1265
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
×
NEW
1266
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
×
NEW
1267
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false)
×
NEW
1268
            };
×
1269

1270
            // Append each validator's stake and transient stake accounts if found in the validator list.
NEW
1271
            foreach (var voteAccount in validatorVoteAccounts)
×
1272
            {
NEW
1273
                var validatorStakeInfo = validatorList.Find(voteAccount);
×
NEW
1274
                if (validatorStakeInfo != null)
×
1275
                {
NEW
1276
                    uint? validatorSeed = validatorStakeInfo.ValidatorSeedSuffix != 0 
×
NEW
1277
                        ? (uint?)validatorStakeInfo.ValidatorSeedSuffix 
×
NEW
1278
                        : null;
×
NEW
1279
                    PublicKey validatorStakeAccount = FindStakeProgramAddress(voteAccount, stakePool, validatorSeed);
×
NEW
1280
                    PublicKey transientStakeAccount = FindTransientStakeProgramAddress(voteAccount, stakePool, validatorStakeInfo.TransientSeedSuffix);
×
NEW
1281
                    accounts.Add(AccountMeta.Writable(validatorStakeAccount, false));
×
NEW
1282
                    accounts.Add(AccountMeta.Writable(transientStakeAccount, false));
×
1283
                }
1284
            }
1285

NEW
1286
            byte[] data = StakePoolProgramData.EncodeUpdateValidatorListBalance(startIndex, noMerge);
×
NEW
1287
            return new TransactionInstruction
×
NEW
1288
            {
×
NEW
1289
                ProgramId = StakePoolProgramIdKey,
×
NEW
1290
                Keys = accounts,
×
NEW
1291
                Data = data
×
NEW
1292
            };
×
1293
        }
1294

1295
        /// <summary>
1296
        /// Creates an UpdateValidatorListBalanceChunk instruction to update a chunk of validators in a stake pool.
1297
        /// </summary>
1298
        /// <param name="stakePool"></param>
1299
        /// <param name="stakePoolWithdrawAuthority"></param>
1300
        /// <param name="validatorListAddress"></param>
1301
        /// <param name="reserveStake"></param>
1302
        /// <param name="validatorList"></param>
1303
        /// <param name="len"></param>
1304
        /// <param name="startIndex"></param>
1305
        /// <param name="noMerge"></param>
1306
        /// <returns></returns>
1307
        /// <exception cref="ArgumentException"></exception>
1308
        public static TransactionInstruction UpdateValidatorListBalanceChunk(
1309
            PublicKey stakePool,
1310
            PublicKey stakePoolWithdrawAuthority,
1311
            PublicKey validatorListAddress,
1312
            PublicKey reserveStake,
1313
            ValidatorList validatorList,
1314
            int len,
1315
            int startIndex,
1316
            bool noMerge)
1317
        {
1318
            // Verify slice bounds.
1319
            if (startIndex < 0 || startIndex + len > validatorList.Validators.Count)
2!
NEW
1320
                throw new ArgumentException("Invalid instruction data: slice out of bounds", nameof(validatorList));
×
1321

1322
            // Build the fixed part of the accounts list.
1323
            var accounts = new List<AccountMeta>
2✔
1324
            {
2✔
1325
                AccountMeta.ReadOnly(stakePool, false),
2✔
1326
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
2✔
1327
                AccountMeta.Writable(validatorListAddress, false),
2✔
1328
                AccountMeta.Writable(reserveStake, false),
2✔
1329
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
2✔
1330
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
2✔
1331
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false)
2✔
1332
            };
2✔
1333

1334
            // Get the requested slice of validators.
1335
            var subSlice = validatorList.Validators.GetRange(startIndex, len);
2✔
1336
            foreach (var validator in subSlice)
12✔
1337
            {
1338
                // Ensure the validator stake seed is nonzero.
1339
                uint? seed = validator.ValidatorSeedSuffix;
4✔
1340
                if (seed == 0)
4!
NEW
1341
                    throw new ArgumentException("Invalid instruction data: validator stake seed cannot be zero", nameof(validator));
×
1342

1343
                // Derive the validator stake account.
1344
                PublicKey validatorStakeAccount = FindStakeProgramAddress(
4✔
1345
                    validator.VoteAccountAddress,
4✔
1346
                    stakePool,
4✔
1347
                    seed);
4✔
1348
                
1349
                // Derive the transient stake account.
1350
                PublicKey transientStakeAccount = FindTransientStakeProgramAddress(
4✔
1351
                    validator.VoteAccountAddress,
4✔
1352
                    stakePool,
4✔
1353
                    validator.TransientSeedSuffix);
4✔
1354

1355
                accounts.Add(AccountMeta.Writable(validatorStakeAccount, false));
4✔
1356
                accounts.Add(AccountMeta.Writable(transientStakeAccount, false));
4✔
1357
            }
1358

1359
            // Serialize the instruction data.
1360
            byte[] data = StakePoolProgramData.EncodeUpdateValidatorListBalance((uint)startIndex, noMerge);
2✔
1361

1362
            return new TransactionInstruction
2✔
1363
            {
2✔
1364
                ProgramId = StakePoolProgramIdKey,
2✔
1365
                Keys = accounts,
2✔
1366
                Data = data
2✔
1367
            };
2✔
1368
        }
1369

1370
        /// <summary>
1371
        /// Creates an UpdateStaleValidatorListBalanceChunk instruction to update a chunk of validators in a stake pool
1372
        /// </summary>
1373
        /// <param name="stakePool"></param>
1374
        /// <param name="stakePoolWithdrawAuthority"></param>
1375
        /// <param name="validatorListAddress"></param>
1376
        /// <param name="reserveStake"></param>
1377
        /// <param name="validatorList"></param>
1378
        /// <param name="len"></param>
1379
        /// <param name="startIndex"></param>
1380
        /// <param name="noMerge"></param>
1381
        /// <param name="currentEpoch"></param>
1382
        /// <returns></returns>
1383
        /// <exception cref="ArgumentException"></exception>
1384
        public static TransactionInstruction? UpdateStaleValidatorListBalanceChunk(
1385
            PublicKey stakePool,
1386
            PublicKey stakePoolWithdrawAuthority,
1387
            PublicKey validatorListAddress,
1388
            PublicKey reserveStake,
1389
            ValidatorList validatorList,
1390
            int len,
1391
            int startIndex,
1392
            bool noMerge,
1393
            ulong currentEpoch)
1394
        {
1395
            // Verify the requested range is within the validator list bounds.
1396
            if (startIndex < 0 || startIndex + len > validatorList.Validators.Count)
1!
NEW
1397
                throw new ArgumentException("Invalid instruction data: slice out of bounds", nameof(validatorList));
×
1398

1399
            // Get the sub-slice of validators.
1400
            var subSlice = validatorList.Validators.GetRange(startIndex, len);
1✔
1401

1402
            // Check if every validator's LastUpdateEpoch is greater than or equal to the current epoch.
1403
            if (subSlice.All(info => info.LastUpdateEpoch >= currentEpoch))
3!
1404
            {
NEW
1405
                return null;
×
1406
            }
1407

1408
            // Otherwise, return the update instruction wrapped in a non-null value.
1409
            return UpdateValidatorListBalanceChunk(
1✔
1410
                stakePool,
1✔
1411
                stakePoolWithdrawAuthority,
1✔
1412
                validatorListAddress,
1✔
1413
                reserveStake,
1✔
1414
                validatorList,
1✔
1415
                len,
1✔
1416
                startIndex,
1✔
1417
                noMerge);
1✔
1418
        }
1419

1420
        /// <summary>
1421
        /// Creates an UpdateStakePoolBalance instruction (update the balance of the stake pool).
1422
        /// </summary>
1423
        /// <param name="stakePool"></param>
1424
        /// <param name="withdrawAuthority"></param>
1425
        /// <param name="validatorListStorage"></param>
1426
        /// <param name="reserveStake"></param>
1427
        /// <param name="managerFeeAccount"></param>
1428
        /// <param name="stakePoolMint"></param>
1429
        /// <param name="tokenProgramId"></param>
1430
        /// <returns></returns>
1431
        public static TransactionInstruction UpdateStakePoolBalance(
1432
            PublicKey stakePool,
1433
            PublicKey withdrawAuthority,
1434
            PublicKey validatorListStorage,
1435
            PublicKey reserveStake,
1436
            PublicKey managerFeeAccount,
1437
            PublicKey stakePoolMint,
1438
            PublicKey tokenProgramId)
1439
        {
1440
            var accounts = new List<AccountMeta>
2✔
1441
            {
2✔
1442
                AccountMeta.Writable(stakePool, false),
2✔
1443
                AccountMeta.ReadOnly(withdrawAuthority, false),
2✔
1444
                AccountMeta.Writable(validatorListStorage, false),
2✔
1445
                AccountMeta.ReadOnly(reserveStake, false),
2✔
1446
                AccountMeta.Writable(managerFeeAccount, false),
2✔
1447
                AccountMeta.Writable(stakePoolMint, false),
2✔
1448
                AccountMeta.ReadOnly(tokenProgramId, false)
2✔
1449
            };
2✔
1450

1451
            // Serialize the instruction data. This assumes that the method
1452
            // 'EncodeUpdateStakePoolBalance' encodes the unit variant for this instruction.
1453
            byte[] data = StakePoolProgramData.EncodeUpdateStakePoolBalance();
2✔
1454

1455
            return new TransactionInstruction
2✔
1456
            {
2✔
1457
                ProgramId = StakePoolProgramIdKey,
2✔
1458
                Keys = accounts,
2✔
1459
                Data = data
2✔
1460
            };
2✔
1461
        }
1462

1463
        /// <summary>
1464
        /// Creates a CleanupRemovedValidatorEntries instruction (removes entries from the validator list).
1465
        /// </summary>
1466
        /// <param name="stakePool">The stake pool public key.</param>
1467
        /// <param name="validatorListStorage">The validator list storage public key.</param>
1468
        /// <returns>The constructed transaction instruction.</returns>
1469
        public static TransactionInstruction CleanupRemovedValidatorEntries(
1470
            PublicKey stakePool,
1471
            PublicKey validatorListStorage)
1472
        {
1473
            var accounts = new List<AccountMeta>
2✔
1474
            {
2✔
1475
                AccountMeta.ReadOnly(stakePool, false),
2✔
1476
                AccountMeta.Writable(validatorListStorage, false)
2✔
1477
            };
2✔
1478

1479
            byte[] data = StakePoolProgramData.EncodeCleanupRemovedValidatorEntries();
2✔
1480

1481
            return new TransactionInstruction
2✔
1482
            {
2✔
1483
                ProgramId = StakePoolProgramIdKey,
2✔
1484
                Keys = accounts,
2✔
1485
                Data = data
2✔
1486
            };
2✔
1487
        }
1488

1489
        /// <summary>
1490
        /// Creates all UpdateValidatorListBalance and UpdateStakePoolBalance instructions
1491
        /// for fully updating a stake pool each epoch.
1492
        /// </summary>
1493
        /// <param name="stakePool">The stake pool model.</param>
1494
        /// <param name="validatorList">The validator list.</param>
1495
        /// <param name="stakePoolAddress">The address of the stake pool.</param>
1496
        /// <param name="noMerge">Flag indicating whether merging should be bypassed.</param>
1497
        /// <returns>
1498
        /// A tuple where the first element is the list of update validator list instructions
1499
        /// and the second element is the final list of instructions (update stake pool balance and cleanup).
1500
        /// </returns>
1501
        public static (List<TransactionInstruction> updateListInstructions, List<TransactionInstruction> finalInstructions) UpdateStakePool(
1502
            Models.StakePool stakePool,
1503
            ValidatorList validatorList,
1504
            PublicKey stakePoolAddress,
1505
            bool noMerge)
1506
        {
1507
            // Derive the withdraw authority using the helper method.
NEW
1508
            var withdrawAuthority = FindWithdrawAuthorityProgramAddress(stakePoolAddress);
×
1509

1510
            // Build the update list instructions by processing the validator list in chunks.
NEW
1511
            var updateListInstructions = new List<TransactionInstruction>();
×
1512
            const int maxValidatorsToUpdate = MAX_VALIDATORS_TO_UPDATE; // MAX_VALIDATORS_TO_UPDATE is defined in the class.
1513

1514
            // Iterate over the validators in chunks.
NEW
1515
            for (int i = 0; i < validatorList.Validators.Count; i += maxValidatorsToUpdate)
×
1516
            {
NEW
1517
                int chunkLen = Math.Min(maxValidatorsToUpdate, validatorList.Validators.Count - i);
×
1518
                // The start index for this chunk is simply 'i' as validators are grouped in chunks of maxValidatorsToUpdate.
NEW
1519
                var instruction = UpdateValidatorListBalanceChunk(
×
NEW
1520
                    stakePoolAddress,
×
NEW
1521
                    withdrawAuthority,
×
NEW
1522
                    stakePool.ValidatorList,
×
NEW
1523
                    stakePool.ReserveStake,
×
NEW
1524
                    validatorList,
×
NEW
1525
                    chunkLen,
×
NEW
1526
                    i,
×
NEW
1527
                    noMerge);
×
NEW
1528
                updateListInstructions.Add(instruction);
×
1529
            }
1530

1531
            // Create the final instructions:
1532
            // 1. UpdateStakePoolBalance instruction.
1533
            // 2. CleanupRemovedValidatorEntries instruction.
NEW
1534
            var finalInstructions = new List<TransactionInstruction>
×
NEW
1535
            {
×
NEW
1536
                UpdateStakePoolBalance(
×
NEW
1537
                    stakePoolAddress,
×
NEW
1538
                    withdrawAuthority,
×
NEW
1539
                    stakePool.ValidatorList,
×
NEW
1540
                    stakePool.ReserveStake,
×
NEW
1541
                    stakePool.ManagerFeeAccount,
×
NEW
1542
                    stakePool.PoolMint,
×
NEW
1543
                    stakePool.TokenProgramId),
×
NEW
1544
                CleanupRemovedValidatorEntries(
×
NEW
1545
                    stakePoolAddress,
×
NEW
1546
                    stakePool.ValidatorList)
×
NEW
1547
            };
×
1548

NEW
1549
            return (updateListInstructions, finalInstructions);
×
1550
        }
1551

1552
        /// <summary>
1553
        /// Creates the UpdateValidatorListBalance instructions only for validators on the validator list 
1554
        /// that have not been updated for the current epoch, along with the UpdateStakePoolBalance and 
1555
        /// CleanupRemovedValidatorEntries instructions for fully updating the stake pool.
1556
        /// </summary>
1557
        /// <param name="stakePool">The stake pool model.</param>
1558
        /// <param name="validatorList">The validator list.</param>
1559
        /// <param name="stakePoolAddress">The stake pool address.</param>
1560
        /// <param name="noMerge">Indicates whether merging should be bypassed.</param>
1561
        /// <param name="currentEpoch">The current epoch.</param>
1562
        /// <returns>
1563
        /// A tuple where the first element is the list of update instructions for individual validator groups
1564
        /// and the second element contains the final instructions for updating the stake pool balance and cleaning up.
1565
        /// </returns>
1566
        public static (List<TransactionInstruction> updateListInstructions, List<TransactionInstruction> finalInstructions) UpdateStaleStakePool(
1567
            Models.StakePool stakePool,
1568
            ValidatorList validatorList,
1569
            PublicKey stakePoolAddress,
1570
            bool noMerge,
1571
            ulong currentEpoch)
1572
        {
1573
            // Derive the withdraw authority using the helper method.
1574
            var withdrawAuthority = FindWithdrawAuthorityProgramAddress(stakePoolAddress);
1✔
1575

1576
            // Build the update list instructions by processing validators in chunks.
1577
            var updateListInstructions = new List<TransactionInstruction>();
1✔
1578
            const int maxValidatorsToUpdate = MAX_VALIDATORS_TO_UPDATE; // Defined in this class.
1579

1580
            for (int i = 0; i < validatorList.Validators.Count; i += maxValidatorsToUpdate)
4✔
1581
            {
1582
                int chunkLen = Math.Min(maxValidatorsToUpdate, validatorList.Validators.Count - i);
1✔
1583
                // Call the stale update instruction helper for the current chunk.
1584
                var instruction = UpdateStaleValidatorListBalanceChunk(
1✔
1585
                    stakePoolAddress,
1✔
1586
                    withdrawAuthority,
1✔
1587
                    stakePool.ValidatorList,
1✔
1588
                    stakePool.ReserveStake,
1✔
1589
                    validatorList,
1✔
1590
                    chunkLen,
1✔
1591
                    i,
1✔
1592
                    noMerge,
1✔
1593
                    currentEpoch);
1✔
1594
                // Add the instruction only if it's non-null.
1595
                if (instruction != null)
1✔
1596
                {
1597
                    updateListInstructions.Add(instruction);
1✔
1598
                }
1599
            }
1600

1601
            // Final instructions: update stake pool balance and clean up removed validator entries.
1602
            var finalInstructions = new List<TransactionInstruction>
1✔
1603
            {
1✔
1604
                UpdateStakePoolBalance(
1✔
1605
                    stakePoolAddress,
1✔
1606
                    withdrawAuthority,
1✔
1607
                    stakePool.ValidatorList,
1✔
1608
                    stakePool.ReserveStake,
1✔
1609
                    stakePool.ManagerFeeAccount,
1✔
1610
                    stakePool.PoolMint,
1✔
1611
                    stakePool.TokenProgramId),
1✔
1612
                CleanupRemovedValidatorEntries(
1✔
1613
                    stakePoolAddress,
1✔
1614
                    stakePool.ValidatorList)
1✔
1615
            };
1✔
1616

1617
            return (updateListInstructions, finalInstructions);
1✔
1618
        }
1619

1620
        /// <summary>
1621
        /// Creates the DepositStake internal instructions. 
1622
        /// 
1623
        /// This method builds the account list and then prepends authorize instructions for deposit stake 
1624
        /// if a deposit authority is provided (or derives it if not), then appends the rest of the accounts required 
1625
        /// for deposit and encodes a DepositStake (or DepositStakeWithSlippage) instruction.
1626
        /// </summary>
1627
        /// <param name="stakePool">The stake pool account.</param>
1628
        /// <param name="validatorListStorage">The account for validator list storage.</param>
1629
        /// <param name="stakePoolDepositAuthority">Optional deposit authority; if null, it is derived.</param>
1630
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
1631
        /// <param name="depositStakeAddress">The deposit stake account address.</param>
1632
        /// <param name="depositStakeWithdrawAuthority">The withdraw authority for the deposit stake account.</param>
1633
        /// <param name="validatorStakeAccount">The validator stake account.</param>
1634
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
1635
        /// <param name="poolTokensTo">The destination account for pool tokens.</param>
1636
        /// <param name="managerFeeAccount">The manager fee pool token account.</param>
1637
        /// <param name="referrerPoolTokensAccount">The referrer’s pool token account.</param>
1638
        /// <param name="poolMint">The pool mint account.</param>
1639
        /// <param name="tokenProgramId">The token program Id.</param>
1640
        /// <param name="minimumPoolTokensOut">Optional minimum pool tokens desired; if provided, creates a slippage‐checking deposit.</param>
1641
        /// <returns>A list of transaction instructions.</returns>
1642
        public static List<TransactionInstruction> DepositStakeInternal(
1643
            PublicKey stakePool,
1644
            PublicKey validatorListStorage,
1645
            PublicKey? stakePoolDepositAuthority,
1646
            PublicKey stakePoolWithdrawAuthority,
1647
            PublicKey depositStakeAddress,
1648
            PublicKey depositStakeWithdrawAuthority,
1649
            PublicKey validatorStakeAccount,
1650
            PublicKey reserveStakeAccount,
1651
            PublicKey poolTokensTo,
1652
            PublicKey managerFeeAccount,
1653
            PublicKey referrerPoolTokensAccount,
1654
            PublicKey poolMint,
1655
            PublicKey tokenProgramId,
1656
            ulong? minimumPoolTokensOut)
1657
        {
1658
            var instructions = new List<TransactionInstruction>();
2✔
1659
            // Begin building accounts list with stake pool and validator list storage.
1660
            var accounts = new List<AccountMeta>
2✔
1661
    {
2✔
1662
        AccountMeta.Writable(stakePool, false),
2✔
1663
        AccountMeta.Writable(validatorListStorage, false)
2✔
1664
    };
2✔
1665

1666
            // Handle deposit authority.
1667
            if (stakePoolDepositAuthority != null)
2!
1668
            {
1669
                // If provided mark it as a signer.
NEW
1670
                accounts.Add(AccountMeta.ReadOnly(stakePoolDepositAuthority, true));
×
1671

1672
                // Add two authorize instructions to set the deposit stake's staker and withdrawer using the provided authority.
NEW
1673
                instructions.Add(StakeProgram.Authorize(
×
NEW
1674
                    depositStakeAddress,
×
NEW
1675
                    depositStakeWithdrawAuthority,
×
NEW
1676
                    stakePoolDepositAuthority,
×
NEW
1677
                    StakeAuthorize.Staker,
×
NEW
1678
                    null));
×
1679

NEW
1680
                instructions.Add(StakeProgram.Authorize(
×
NEW
1681
                    depositStakeAddress,
×
NEW
1682
                    depositStakeWithdrawAuthority,
×
NEW
1683
                    stakePoolDepositAuthority,
×
NEW
1684
                    StakeAuthorize.Withdrawer,
×
NEW
1685
                    null));
×
1686
            }
1687
            else
1688
            {
1689
                // Otherwise, derive the deposit authority for the stake pool.
1690
                var (derivedDepositAuthority, _) = FindDepositAuthorityProgramAddress(stakePool);
2✔
1691
                accounts.Add(AccountMeta.ReadOnly(derivedDepositAuthority, false));
2✔
1692

1693
                instructions.Add(StakeProgram.Authorize(
2✔
1694
                    depositStakeAddress,
2✔
1695
                    depositStakeWithdrawAuthority,
2✔
1696
                    derivedDepositAuthority,
2✔
1697
                    StakeAuthorize.Staker,
2✔
1698
                    null));
2✔
1699

1700
                instructions.Add(StakeProgram.Authorize(
2✔
1701
                    depositStakeAddress,
2✔
1702
                    depositStakeWithdrawAuthority,
2✔
1703
                    derivedDepositAuthority,
2✔
1704
                    StakeAuthorize.Withdrawer,
2✔
1705
                    null));
2✔
1706
            }
1707

1708
            // Append the remaining accounts.
1709
            accounts.AddRange(new List<AccountMeta>
2✔
1710
            {
2✔
1711
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
2✔
1712
                AccountMeta.Writable(depositStakeAddress, false),
2✔
1713
                AccountMeta.Writable(validatorStakeAccount, false),
2✔
1714
                AccountMeta.Writable(reserveStakeAccount, false),
2✔
1715
                AccountMeta.Writable(poolTokensTo, false),
2✔
1716
                AccountMeta.Writable(managerFeeAccount, false),
2✔
1717
                AccountMeta.Writable(referrerPoolTokensAccount, false),
2✔
1718
                AccountMeta.Writable(poolMint, false),
2✔
1719
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
2✔
1720
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
2✔
1721
                AccountMeta.ReadOnly(tokenProgramId, false),
2✔
1722
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false)
2✔
1723
            });
2✔
1724

1725
            // Depending on whether a minimum pool token output is required, encode the appropriate instruction.
1726
            TransactionInstruction depositInstruction;
1727
            if (minimumPoolTokensOut.HasValue)
2✔
1728
            {
1729
                depositInstruction = new TransactionInstruction
1✔
1730
                {
1✔
1731
                    ProgramId = StakePoolProgramIdKey,
1✔
1732
                    Keys = accounts,
1✔
1733
                    Data = StakePoolProgramData.EncodeDepositStakeWithSlippage(minimumPoolTokensOut.Value)
1✔
1734
                };
1✔
1735
            }
1736
            else
1737
            {
1738
                depositInstruction = new TransactionInstruction
1✔
1739
                {
1✔
1740
                    ProgramId = StakePoolProgramIdKey,
1✔
1741
                    Keys = accounts,
1✔
1742
                    Data = StakePoolProgramData.EncodeDepositStake()
1✔
1743
                };
1✔
1744
            }
1745

1746
            instructions.Add(depositInstruction);
2✔
1747
            return instructions;
2✔
1748
        }
1749

1750
        /// <summary>
1751
        /// Creates instructions required to deposit into a stake pool, given a stake account owned by the user.
1752
        /// </summary>
1753
        /// <param name="stakePool">The stake pool account.</param>
1754
        /// <param name="validatorListStorage">The account for validator list storage.</param>
1755
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
1756
        /// <param name="depositStakeAddress">The deposit stake account address.</param>
1757
        /// <param name="depositStakeWithdrawAuthority">The withdraw authority for the deposit stake account.</param>
1758
        /// <param name="validatorStakeAccount">The validator stake account.</param>
1759
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
1760
        /// <param name="poolTokensTo">The destination account for pool tokens.</param>
1761
        /// <param name="managerFeeAccount">The manager fee pool token account.</param>
1762
        /// <param name="referrerPoolTokensAccount">The referrer's pool token account.</param>
1763
        /// <param name="poolMint">The pool mint account.</param>
1764
        /// <param name="tokenProgramId">The token program Id.</param>
1765
        /// <returns>A list of transaction instructions.</returns>
1766
        public static List<TransactionInstruction> DepositStake(
1767
            PublicKey stakePool,
1768
            PublicKey validatorListStorage,
1769
            PublicKey stakePoolWithdrawAuthority,
1770
            PublicKey depositStakeAddress,
1771
            PublicKey depositStakeWithdrawAuthority,
1772
            PublicKey validatorStakeAccount,
1773
            PublicKey reserveStakeAccount,
1774
            PublicKey poolTokensTo,
1775
            PublicKey managerFeeAccount,
1776
            PublicKey referrerPoolTokensAccount,
1777
            PublicKey poolMint,
1778
            PublicKey tokenProgramId)
1779
        {
1780
            return DepositStakeInternal(
1✔
1781
                stakePool,
1✔
1782
                validatorListStorage,
1✔
1783
                null, // no deposit authority provided
1✔
1784
                stakePoolWithdrawAuthority,
1✔
1785
                depositStakeAddress,
1✔
1786
                depositStakeWithdrawAuthority,
1✔
1787
                validatorStakeAccount,
1✔
1788
                reserveStakeAccount,
1✔
1789
                poolTokensTo,
1✔
1790
                managerFeeAccount,
1✔
1791
                referrerPoolTokensAccount,
1✔
1792
                poolMint,
1✔
1793
                tokenProgramId,
1✔
1794
                null  // no minimum pool tokens out (no slippage check)
1✔
1795
            );
1✔
1796
        }
1797

1798
        /// <summary>
1799
        /// Creates instructions to deposit into a stake pool with slippage.
1800
        /// </summary>
1801
        /// <param name="stakePool">The stake pool account.</param>
1802
        /// <param name="validatorListStorage">The account for validator list storage.</param>
1803
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
1804
        /// <param name="depositStakeAddress">The deposit stake account address.</param>
1805
        /// <param name="depositStakeWithdrawAuthority">The withdraw authority for the deposit stake account.</param>
1806
        /// <param name="validatorStakeAccount">The validator stake account.</param>
1807
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
1808
        /// <param name="poolTokensTo">The destination account for pool tokens.</param>
1809
        /// <param name="managerFeeAccount">The manager fee pool token account.</param>
1810
        /// <param name="referrerPoolTokensAccount">The referrer's pool token account.</param>
1811
        /// <param name="poolMint">The pool mint account.</param>
1812
        /// <param name="tokenProgramId">The token program Id.</param>
1813
        /// <param name="minimumPoolTokensOut">The minimum pool tokens desired.</param>
1814
        /// <returns>A list of transaction instructions.</returns>
1815
        public static List<TransactionInstruction> DepositStakeWithSlippage(
1816
            PublicKey stakePool,
1817
            PublicKey validatorListStorage,
1818
            PublicKey stakePoolWithdrawAuthority,
1819
            PublicKey depositStakeAddress,
1820
            PublicKey depositStakeWithdrawAuthority,
1821
            PublicKey validatorStakeAccount,
1822
            PublicKey reserveStakeAccount,
1823
            PublicKey poolTokensTo,
1824
            PublicKey managerFeeAccount,
1825
            PublicKey referrerPoolTokensAccount,
1826
            PublicKey poolMint,
1827
            PublicKey tokenProgramId,
1828
            ulong minimumPoolTokensOut)
1829
        {
1830
            return DepositStakeInternal(
1✔
1831
                stakePool,
1✔
1832
                validatorListStorage,
1✔
1833
                null, // no deposit authority provided
1✔
1834
                stakePoolWithdrawAuthority,
1✔
1835
                depositStakeAddress,
1✔
1836
                depositStakeWithdrawAuthority,
1✔
1837
                validatorStakeAccount,
1✔
1838
                reserveStakeAccount,
1✔
1839
                poolTokensTo,
1✔
1840
                managerFeeAccount,
1✔
1841
                referrerPoolTokensAccount,
1✔
1842
                poolMint,
1✔
1843
                tokenProgramId,
1✔
1844
                minimumPoolTokensOut
1✔
1845
            );
1✔
1846
        }
1847

1848
        /// <summary>
1849
        /// Creates an instruction to deposit SOL directly into a stake pool with a slippage constraint,
1850
        /// requiring the deposit authority's signature (as needed for private pools).
1851
        /// </summary>
1852
        /// <param name="stakePool">The stake pool account.</param>
1853
        /// <param name="solDepositAuthority">The SOL deposit authority which must sign the instruction.</param>
1854
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
1855
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
1856
        /// <param name="lamportsFrom">The source account from which SOL lamports will be deducted.</param>
1857
        /// <param name="poolTokensTo">The destination pool tokens account.</param>
1858
        /// <param name="managerFeeAccount">The manager fee pool token account.</param>
1859
        /// <param name="referrerPoolTokensAccount">The referrer’s pool token account.</param>
1860
        /// <param name="poolMint">The pool mint account.</param>
1861
        /// <param name="tokenProgramId">The token program Id.</param>
1862
        /// <param name="lamportsIn">The amount of lamports to deposit.</param>
1863
        /// <param name="minimumPoolTokensOut">The minimum number of pool tokens expected (slippage constraint).</param>
1864
        /// <returns>A transaction instruction for the SOL deposit operation.</returns>
1865
        public static TransactionInstruction DepositSolWithAuthorityAndSlippage(
1866
            PublicKey stakePool,
1867
            PublicKey solDepositAuthority,
1868
            PublicKey stakePoolWithdrawAuthority,
1869
            PublicKey reserveStakeAccount,
1870
            PublicKey lamportsFrom,
1871
            PublicKey poolTokensTo,
1872
            PublicKey managerFeeAccount,
1873
            PublicKey referrerPoolTokensAccount,
1874
            PublicKey poolMint,
1875
            PublicKey tokenProgramId,
1876
            ulong lamportsIn,
1877
            ulong minimumPoolTokensOut)
1878
        {
NEW
1879
            return DepositSolInternal(
×
NEW
1880
                stakePool,
×
NEW
1881
                stakePoolWithdrawAuthority,
×
NEW
1882
                reserveStakeAccount,
×
NEW
1883
                lamportsFrom,
×
NEW
1884
                poolTokensTo,
×
NEW
1885
                managerFeeAccount,
×
NEW
1886
                referrerPoolTokensAccount,
×
NEW
1887
                poolMint,
×
NEW
1888
                tokenProgramId,
×
NEW
1889
                solDepositAuthority, // deposit authority provided
×
NEW
1890
                lamportsIn,
×
NEW
1891
                minimumPoolTokensOut
×
NEW
1892
            );
×
1893
        }
1894

1895
        /// <summary>
1896
        /// Creates instructions required to withdraw from a stake pool by splitting a stake account.
1897
        /// When a minimum lamports output is provided, a slippage check is enforced.
1898
        /// </summary>
1899
        /// <param name="stakePool">The stake pool account.</param>
1900
        /// <param name="validatorListStorage">The validator list storage account.</param>
1901
        /// <param name="stakePoolWithdraw">The stake pool withdraw authority.</param>
1902
        /// <param name="stakeToSplit">The stake account to split.</param>
1903
        /// <param name="stakeToReceive">The stake account to receive the split stake.</param>
1904
        /// <param name="userStakeAuthority">The user's stake authority.</param>
1905
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
1906
        /// <param name="userPoolTokenAccount">The user's pool token account.</param>
1907
        /// <param name="managerFeeAccount">The manager fee account.</param>
1908
        /// <param name="poolMint">The pool mint account.</param>
1909
        /// <param name="tokenProgramId">The token program Id.</param>
1910
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
1911
        /// <param name="minimumLamportsOut">Optional minimum lamports expected on withdrawal (slippage check).</param>
1912
        /// <returns>A transaction instruction for the stake withdrawal.</returns>
1913
        public static TransactionInstruction WithdrawStakeInternal(
1914
            PublicKey stakePool,
1915
            PublicKey validatorListStorage,
1916
            PublicKey stakePoolWithdraw,
1917
            PublicKey stakeToSplit,
1918
            PublicKey stakeToReceive,
1919
            PublicKey userStakeAuthority,
1920
            PublicKey userTransferAuthority,
1921
            PublicKey userPoolTokenAccount,
1922
            PublicKey managerFeeAccount,
1923
            PublicKey poolMint,
1924
            PublicKey tokenProgramId,
1925
            ulong poolTokensIn,
1926
            ulong? minimumLamportsOut)
1927
        {
1928
            var accounts = new List<AccountMeta>
2✔
1929
            {
2✔
1930
                AccountMeta.Writable(stakePool, false),
2✔
1931
                AccountMeta.Writable(validatorListStorage, false),
2✔
1932
                AccountMeta.ReadOnly(stakePoolWithdraw, false),
2✔
1933
                AccountMeta.Writable(stakeToSplit, false),
2✔
1934
                AccountMeta.Writable(stakeToReceive, false),
2✔
1935
                AccountMeta.ReadOnly(userStakeAuthority, false),
2✔
1936
                AccountMeta.ReadOnly(userTransferAuthority, true),
2✔
1937
                AccountMeta.Writable(userPoolTokenAccount, false),
2✔
1938
                AccountMeta.Writable(managerFeeAccount, false),
2✔
1939
                AccountMeta.Writable(poolMint, false),
2✔
1940
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
2✔
1941
                AccountMeta.ReadOnly(tokenProgramId, false),
2✔
1942
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false)
2✔
1943
            };
2✔
1944

1945
            byte[] data;
1946
            if (minimumLamportsOut.HasValue)
2✔
1947
            {
1948
                data = StakePoolProgramData.EncodeWithdrawStakeWithSlippage(poolTokensIn, minimumLamportsOut.Value);
1✔
1949
            }
1950
            else
1951
            {
1952
                data = StakePoolProgramData.EncodeWithdrawStake(poolTokensIn);
1✔
1953
            }
1954

1955
            return new TransactionInstruction
2✔
1956
            {
2✔
1957
                ProgramId = StakePoolProgramIdKey,
2✔
1958
                Keys = accounts,
2✔
1959
                Data = data
2✔
1960
            };
2✔
1961
        }
1962

1963
        /// <summary>
1964
        /// Creates a 'WithdrawStake' instruction.
1965
        /// </summary>
1966
        /// <param name="stakePool">The stake pool account.</param>
1967
        /// <param name="validatorListStorage">The validator list storage account.</param>
1968
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
1969
        /// <param name="stakeToSplit">The stake account to split.</param>
1970
        /// <param name="stakeToReceive">The stake account to receive the split stake.</param>
1971
        /// <param name="userStakeAuthority">The user's stake authority.</param>
1972
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
1973
        /// <param name="userPoolTokenAccount">The user's pool token account.</param>
1974
        /// <param name="managerFeeAccount">The manager fee account.</param>
1975
        /// <param name="poolMint">The pool mint account.</param>
1976
        /// <param name="tokenProgramId">The token program Id.</param>
1977
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
1978
        /// <returns>A transaction instruction for stake withdrawal.</returns>
1979
        public static TransactionInstruction WithdrawStake(
1980
            PublicKey stakePool,
1981
            PublicKey validatorListStorage,
1982
            PublicKey stakePoolWithdrawAuthority,
1983
            PublicKey stakeToSplit,
1984
            PublicKey stakeToReceive,
1985
            PublicKey userStakeAuthority,
1986
            PublicKey userTransferAuthority,
1987
            PublicKey userPoolTokenAccount,
1988
            PublicKey managerFeeAccount,
1989
            PublicKey poolMint,
1990
            PublicKey tokenProgramId,
1991
            ulong poolTokensIn)
1992
        {
1993
            return WithdrawStakeInternal(
1✔
1994
                stakePool,
1✔
1995
                validatorListStorage,
1✔
1996
                stakePoolWithdrawAuthority,
1✔
1997
                stakeToSplit,
1✔
1998
                stakeToReceive,
1✔
1999
                userStakeAuthority,
1✔
2000
                userTransferAuthority,
1✔
2001
                userPoolTokenAccount,
1✔
2002
                managerFeeAccount,
1✔
2003
                poolMint,
1✔
2004
                tokenProgramId,
1✔
2005
                poolTokensIn,
1✔
2006
                null // no minimum lamports out (no slippage check)
1✔
2007
            );
1✔
2008
        }
2009

2010
        /// <summary>
2011
        /// Creates a 'WithdrawStakeWithSlippage' instruction.
2012
        /// </summary>
2013
        /// <param name="stakePool">The stake pool account.</param>
2014
        /// <param name="validatorListStorage">The validator list storage account.</param>
2015
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
2016
        /// <param name="stakeToSplit">The stake account to split.</param>
2017
        /// <param name="stakeToReceive">The stake account to receive the split stake.</param>
2018
        /// <param name="userStakeAuthority">The user's stake authority.</param>
2019
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
2020
        /// <param name="userPoolTokenAccount">The user's pool token account.</param>
2021
        /// <param name="managerFeeAccount">The manager fee account.</param>
2022
        /// <param name="poolMint">The pool mint account.</param>
2023
        /// <param name="tokenProgramId">The token program Id.</param>
2024
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
2025
        /// <param name="minimumLamportsOut">The minimum lamports expected on withdrawal.</param>
2026
        /// <returns>A transaction instruction for stake withdrawal with slippage check.</returns>
2027
        public static TransactionInstruction WithdrawStakeWithSlippage(
2028
            PublicKey stakePool,
2029
            PublicKey validatorListStorage,
2030
            PublicKey stakePoolWithdrawAuthority,
2031
            PublicKey stakeToSplit,
2032
            PublicKey stakeToReceive,
2033
            PublicKey userStakeAuthority,
2034
            PublicKey userTransferAuthority,
2035
            PublicKey userPoolTokenAccount,
2036
            PublicKey managerFeeAccount,
2037
            PublicKey poolMint,
2038
            PublicKey tokenProgramId,
2039
            ulong poolTokensIn,
2040
            ulong minimumLamportsOut)
2041
        {
2042
            return WithdrawStakeInternal(
1✔
2043
                stakePool,
1✔
2044
                validatorListStorage,
1✔
2045
                stakePoolWithdrawAuthority,
1✔
2046
                stakeToSplit,
1✔
2047
                stakeToReceive,
1✔
2048
                userStakeAuthority,
1✔
2049
                userTransferAuthority,
1✔
2050
                userPoolTokenAccount,
1✔
2051
                managerFeeAccount,
1✔
2052
                poolMint,
1✔
2053
                tokenProgramId,
1✔
2054
                poolTokensIn,
1✔
2055
                minimumLamportsOut
1✔
2056
            );
1✔
2057
        }
2058

2059
        /// <summary>
2060
        /// Creates instructions required to withdraw SOL directly from a stake pool,
2061
        /// optionally enforcing a slippage constraint.
2062
        /// </summary>
2063
        /// <param name="stakePool">The stake pool account.</param>
2064
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
2065
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
2066
        /// <param name="poolTokensFrom">The account from which pool tokens will be deducted.</param>
2067
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
2068
        /// <param name="lamportsTo">The destination account for SOL lamports.</param>
2069
        /// <param name="managerFeeAccount">The manager fee account.</param>
2070
        /// <param name="poolMint">The pool mint account.</param>
2071
        /// <param name="tokenProgramId">The token program Id.</param>
2072
        /// <param name="solWithdrawAuthority">
2073
        /// Optional SOL withdraw authority; if provided, it must sign the instruction.
2074
        /// </param>
2075
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
2076
        /// <param name="minimumLamportsOut">
2077
        /// Optional minimum lamports expected on withdrawal (for slippage check).
2078
        /// </param>
2079
        /// <returns>A transaction instruction for SOL withdrawal.</returns>
2080
        public static TransactionInstruction WithdrawSolInternal(
2081
            PublicKey stakePool,
2082
            PublicKey stakePoolWithdrawAuthority,
2083
            PublicKey userTransferAuthority,
2084
            PublicKey poolTokensFrom,
2085
            PublicKey reserveStakeAccount,
2086
            PublicKey lamportsTo,
2087
            PublicKey managerFeeAccount,
2088
            PublicKey poolMint,
2089
            PublicKey tokenProgramId,
2090
            PublicKey? solWithdrawAuthority,
2091
            ulong poolTokensIn,
2092
            ulong? minimumLamportsOut)
2093
        {
2094
            var accounts = new List<AccountMeta>
4✔
2095
            {
4✔
2096
                AccountMeta.Writable(stakePool, false),
4✔
2097
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
4✔
2098
                AccountMeta.ReadOnly(userTransferAuthority, true),
4✔
2099
                AccountMeta.Writable(poolTokensFrom, false),
4✔
2100
                AccountMeta.Writable(reserveStakeAccount, false),
4✔
2101
                AccountMeta.Writable(lamportsTo, false),
4✔
2102
                AccountMeta.Writable(managerFeeAccount, false),
4✔
2103
                AccountMeta.Writable(poolMint, false),
4✔
2104
                AccountMeta.ReadOnly(SysVars.ClockKey, false),
4✔
2105
                AccountMeta.ReadOnly(SysVars.StakeHistoryKey, false),
4✔
2106
                // Assuming StakeProgram.ProgramIdKey returns a PublicKey for the stake program.
4✔
2107
                AccountMeta.ReadOnly(StakeProgram.ProgramIdKey, false),
4✔
2108
                AccountMeta.ReadOnly(tokenProgramId, false)
4✔
2109
            };
4✔
2110

2111
            if (solWithdrawAuthority != null)
4✔
2112
            {
2113
                accounts.Add(AccountMeta.ReadOnly(solWithdrawAuthority, true));
2✔
2114
            }
2115

2116
            byte[] data;
2117
            if (minimumLamportsOut.HasValue)
4✔
2118
            {
2119
                data = StakePoolProgramData.EncodeWithdrawSolWithSlippage(poolTokensIn, minimumLamportsOut.Value);
2✔
2120
            }
2121
            else
2122
            {
2123
                data = StakePoolProgramData.EncodeWithdrawSol(poolTokensIn);
2✔
2124
            }
2125

2126
            return new TransactionInstruction
4✔
2127
            {
4✔
2128
                ProgramId = StakePoolProgramIdKey,
4✔
2129
                Keys = accounts,
4✔
2130
                Data = data
4✔
2131
            };
4✔
2132
        }
2133

2134
        /// <summary>
2135
        /// Creates instruction required to withdraw SOL directly from a stake pool.
2136
        /// </summary>
2137
        /// <param name="stakePool">The stake pool account.</param>
2138
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
2139
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
2140
        /// <param name="poolTokensFrom">The account from which pool tokens will be deducted.</param>
2141
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
2142
        /// <param name="lamportsTo">The destination account for SOL lamports.</param>
2143
        /// <param name="managerFeeAccount">The manager fee account.</param>
2144
        /// <param name="poolMint">The pool mint account.</param>
2145
        /// <param name="tokenProgramId">The token program Id.</param>
2146
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
2147
        /// <returns>A transaction instruction for SOL withdrawal.</returns>
2148
        public static TransactionInstruction WithdrawSol(
2149
            PublicKey stakePool,
2150
            PublicKey stakePoolWithdrawAuthority,
2151
            PublicKey userTransferAuthority,
2152
            PublicKey poolTokensFrom,
2153
            PublicKey reserveStakeAccount,
2154
            PublicKey lamportsTo,
2155
            PublicKey managerFeeAccount,
2156
            PublicKey poolMint,
2157
            PublicKey tokenProgramId,
2158
            ulong poolTokensIn)
2159
        {
2160
            return WithdrawSolInternal(
1✔
2161
                stakePool,
1✔
2162
                stakePoolWithdrawAuthority,
1✔
2163
                userTransferAuthority,
1✔
2164
                poolTokensFrom,
1✔
2165
                reserveStakeAccount,
1✔
2166
                lamportsTo,
1✔
2167
                managerFeeAccount,
1✔
2168
                poolMint,
1✔
2169
                tokenProgramId,
1✔
2170
                null,         // SOL withdraw authority: not provided
1✔
2171
                poolTokensIn,
1✔
2172
                null          // minimum lamports out: not provided
1✔
2173
            );
1✔
2174
        }
2175

2176
        /// <summary>
2177
        /// Creates an instruction required to withdraw SOL directly from a stake pool with slippage constraints.
2178
        /// </summary>
2179
        /// <param name="stakePool">The stake pool account.</param>
2180
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
2181
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
2182
        /// <param name="poolTokensFrom">The account from which pool tokens will be deducted.</param>
2183
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
2184
        /// <param name="lamportsTo">The destination account for SOL lamports.</param>
2185
        /// <param name="managerFeeAccount">The manager fee account.</param>
2186
        /// <param name="poolMint">The pool mint account.</param>
2187
        /// <param name="tokenProgramId">The token program Id.</param>
2188
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
2189
        /// <param name="minimumLamportsOut">The minimum lamports expected on withdrawal (slippage constraint).</param>
2190
        /// <returns>A transaction instruction for stake withdrawal with slippage check.</returns>
2191
        public static TransactionInstruction WithdrawSolWithSlippage(
2192
            PublicKey stakePool,
2193
            PublicKey stakePoolWithdrawAuthority,
2194
            PublicKey userTransferAuthority,
2195
            PublicKey poolTokensFrom,
2196
            PublicKey reserveStakeAccount,
2197
            PublicKey lamportsTo,
2198
            PublicKey managerFeeAccount,
2199
            PublicKey poolMint,
2200
            PublicKey tokenProgramId,
2201
            ulong poolTokensIn,
2202
            ulong minimumLamportsOut)
2203
        {
2204
            return WithdrawSolInternal(
1✔
2205
                stakePool,
1✔
2206
                stakePoolWithdrawAuthority,
1✔
2207
                userTransferAuthority,
1✔
2208
                poolTokensFrom,
1✔
2209
                reserveStakeAccount,
1✔
2210
                lamportsTo,
1✔
2211
                managerFeeAccount,
1✔
2212
                poolMint,
1✔
2213
                tokenProgramId,
1✔
2214
                null,                   // SOL withdraw authority: not provided
1✔
2215
                poolTokensIn,
1✔
2216
                minimumLamportsOut
1✔
2217
            );
1✔
2218
        }
2219

2220
        /// <summary>
2221
        /// Creates an instruction required to withdraw SOL directly from a stake pool.
2222
        /// The difference with WithdrawSol() is that the SOL withdraw authority must sign this instruction.
2223
        /// </summary>
2224
        /// <param name="stakePool">The stake pool account.</param>
2225
        /// <param name="solWithdrawAuthority">The SOL withdraw authority (must sign).</param>
2226
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
2227
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
2228
        /// <param name="poolTokensFrom">The account from which pool tokens will be deducted.</param>
2229
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
2230
        /// <param name="lamportsTo">The destination account for SOL lamports.</param>
2231
        /// <param name="managerFeeAccount">The manager fee account.</param>
2232
        /// <param name="poolMint">The pool mint account.</param>
2233
        /// <param name="tokenProgramId">The token program Id.</param>
2234
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
2235
        /// <returns>A transaction instruction for SOL withdrawal with the required SOL withdraw authority signature.</returns>
2236
        public static TransactionInstruction WithdrawSolWithAuthority(
2237
            PublicKey stakePool,
2238
            PublicKey solWithdrawAuthority,
2239
            PublicKey stakePoolWithdrawAuthority,
2240
            PublicKey userTransferAuthority,
2241
            PublicKey poolTokensFrom,
2242
            PublicKey reserveStakeAccount,
2243
            PublicKey lamportsTo,
2244
            PublicKey managerFeeAccount,
2245
            PublicKey poolMint,
2246
            PublicKey tokenProgramId,
2247
            ulong poolTokensIn)
2248
        {
2249
            return WithdrawSolInternal(
1✔
2250
                stakePool,
1✔
2251
                stakePoolWithdrawAuthority,
1✔
2252
                userTransferAuthority,
1✔
2253
                poolTokensFrom,
1✔
2254
                reserveStakeAccount,
1✔
2255
                lamportsTo,
1✔
2256
                managerFeeAccount,
1✔
2257
                poolMint,
1✔
2258
                tokenProgramId,
1✔
2259
                solWithdrawAuthority,  // Provide the SOL withdraw authority which must sign
1✔
2260
                poolTokensIn,
1✔
2261
                null                   // No minimum lamports out (no slippage check)
1✔
2262
            );
1✔
2263
        }
2264

2265
        /// <summary>
2266
        /// Creates an instruction required to withdraw SOL directly from a stake pool with a slippage constraint.
2267
        /// The difference with WithdrawSol() is that the SOL withdraw authority must sign this instruction.
2268
        /// </summary>
2269
        /// <param name="stakePool">The stake pool account.</param>
2270
        /// <param name="solWithdrawAuthority">The SOL withdraw authority which must sign the instruction.</param>
2271
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
2272
        /// <param name="userTransferAuthority">The user's transfer authority (signer).</param>
2273
        /// <param name="poolTokensFrom">The account from which pool tokens will be deducted.</param>
2274
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
2275
        /// <param name="lamportsTo">The destination account for SOL lamports.</param>
2276
        /// <param name="managerFeeAccount">The manager fee account.</param>
2277
        /// <param name="poolMint">The pool mint account.</param>
2278
        /// <param name="tokenProgramId">The token program Id.</param>
2279
        /// <param name="poolTokensIn">The amount of pool tokens to redeem.</param>
2280
        /// <param name="minimumLamportsOut">The minimum lamports expected on withdrawal (slippage constraint).</param>
2281
        /// <returns>A transaction instruction for SOL withdrawal with the required SOL withdraw authority signature and slippage check.</returns>
2282
        public static TransactionInstruction WithdrawSolWithAuthorityAndSlippage(
2283
            PublicKey stakePool,
2284
            PublicKey solWithdrawAuthority,
2285
            PublicKey stakePoolWithdrawAuthority,
2286
            PublicKey userTransferAuthority,
2287
            PublicKey poolTokensFrom,
2288
            PublicKey reserveStakeAccount,
2289
            PublicKey lamportsTo,
2290
            PublicKey managerFeeAccount,
2291
            PublicKey poolMint,
2292
            PublicKey tokenProgramId,
2293
            ulong poolTokensIn,
2294
            ulong minimumLamportsOut)
2295
        {
2296
            return WithdrawSolInternal(
1✔
2297
                stakePool,
1✔
2298
                stakePoolWithdrawAuthority,
1✔
2299
                userTransferAuthority,
1✔
2300
                poolTokensFrom,
1✔
2301
                reserveStakeAccount,
1✔
2302
                lamportsTo,
1✔
2303
                managerFeeAccount,
1✔
2304
                poolMint,
1✔
2305
                tokenProgramId,
1✔
2306
                solWithdrawAuthority,       // SOL withdraw authority must sign
1✔
2307
                poolTokensIn,
1✔
2308
                minimumLamportsOut          // enforce slippage check
1✔
2309
            );
1✔
2310
        }
2311

2312
        /// <summary>
2313
        /// Creates a 'Set Manager' instruction.
2314
        /// </summary>
2315
        /// <param name="stakePool">The stake pool account.</param>
2316
        /// <param name="manager">The current manager (must sign).</param>
2317
        /// <param name="newManager">The new manager (must sign).</param>
2318
        /// <param name="newFeeReceiver">The new fee receiver account.</param>
2319
        /// <returns>A transaction instruction to set the manager.</returns>
2320
        public static TransactionInstruction SetManager(
2321
            PublicKey stakePool,
2322
            PublicKey manager,
2323
            PublicKey newManager,
2324
            PublicKey newFeeReceiver)
2325
        {
2326
            var accounts = new List<AccountMeta>
1✔
2327
            {
1✔
2328
                AccountMeta.Writable(stakePool, false),
1✔
2329
                AccountMeta.ReadOnly(manager, true),
1✔
2330
                AccountMeta.ReadOnly(newManager, true),
1✔
2331
                AccountMeta.ReadOnly(newFeeReceiver, false)
1✔
2332
            };
1✔
2333

2334
            return new TransactionInstruction
1✔
2335
            {
1✔
2336
                ProgramId = StakePoolProgramIdKey,
1✔
2337
                Keys = accounts,
1✔
2338
                Data = StakePoolProgramData.EncodeSetManager() // encode the SetManager variant
1✔
2339
            };
1✔
2340
        }
2341

2342
        /// <summary>
2343
        /// Creates a 'Set Fee' instruction.
2344
        /// </summary>
2345
        /// <param name="stakePool">The stake pool account.</param>
2346
        /// <param name="manager">The manager account (must sign).</param>
2347
        /// <param name="fee">The fee to be set.</param>
2348
        /// <returns>A transaction instruction to set the fee.</returns>
2349
        public static TransactionInstruction SetFee(
2350
            PublicKey stakePool,
2351
            PublicKey manager,
2352
            Fee fee)
2353
        {
2354
            var accounts = new List<AccountMeta>
1✔
2355
            {
1✔
2356
                AccountMeta.Writable(stakePool, false),
1✔
2357
                AccountMeta.ReadOnly(manager, true)
1✔
2358
            };
1✔
2359

2360
            return new TransactionInstruction
1✔
2361
            {
1✔
2362
                ProgramId = StakePoolProgramIdKey,
1✔
2363
                Keys = accounts,
1✔
2364
                Data = StakePoolProgramData.EncodeSetFee(fee)
1✔
2365
            };
1✔
2366
        }
2367

2368
        /// <summary>
2369
        /// Creates a 'Set Staker' instruction.
2370
        /// </summary>
2371
        /// <param name="stakePool">The stake pool account.</param>
2372
        /// <param name="setStakerAuthority">The current staker (must sign).</param>
2373
        /// <param name="newStaker">The new staker to be set.</param>
2374
        /// <returns>A transaction instruction to set the staker.</returns>
2375
        public static TransactionInstruction SetStaker(
2376
            PublicKey stakePool,
2377
            PublicKey setStakerAuthority,
2378
            PublicKey newStaker)
2379
        {
2380
            var accounts = new List<AccountMeta>
1✔
2381
            {
1✔
2382
                AccountMeta.Writable(stakePool, false),
1✔
2383
                AccountMeta.ReadOnly(setStakerAuthority, true),
1✔
2384
                AccountMeta.ReadOnly(newStaker, false)
1✔
2385
            };
1✔
2386

2387
            // Fix: Remove the public key from the data since it is provided in keys
2388
            return new TransactionInstruction
1✔
2389
            {
1✔
2390
                ProgramId = StakePoolProgramIdKey,
1✔
2391
                Keys = accounts,
1✔
2392
                Data = StakePoolProgramData.EncodeSetStaker() // now only encodes the discriminator
1✔
2393
            };
1✔
2394
        }
2395

2396
        /// <summary>
2397
        /// Creates a 'SetFundingAuthority' instruction.
2398
        /// </summary>
2399
        /// <param name="stakePool">The stake pool account.</param>
2400
        /// <param name="manager">The manager account (must sign).</param>
2401
        /// <param name="newSolDepositAuthority">
2402
        /// The new SOL deposit authority (optional). If provided, it is added as a read-only account.
2403
        /// </param>
2404
        /// <param name="fundingType">The funding type to be set.</param>
2405
        /// <returns>A transaction instruction to set the funding authority.</returns>
2406
        public static TransactionInstruction SetFundingAuthority(
2407
            PublicKey stakePool,
2408
            PublicKey manager,
2409
            PublicKey? newSolDepositAuthority,
2410
            FundingType fundingType)
2411
        {
2412
            var accounts = new List<AccountMeta>
1✔
2413
            {
1✔
2414
                AccountMeta.Writable(stakePool, false),
1✔
2415
                AccountMeta.ReadOnly(manager, true)
1✔
2416
            };
1✔
2417

2418
            if(newSolDepositAuthority != null)
1✔
2419
            {
2420
                accounts.Add(AccountMeta.ReadOnly(newSolDepositAuthority, false));
1✔
2421
            }
2422

2423
            return new TransactionInstruction
1✔
2424
            {
1✔
2425
                ProgramId = StakePoolProgramIdKey,
1✔
2426
                Keys = accounts,
1✔
2427
                Data = StakePoolProgramData.EncodeSetFundingAuthority(fundingType)
1✔
2428
            };
1✔
2429
        }
2430

2431
        /// <summary>
2432
        /// Creates an instruction to update metadata in the MPL token metadata program
2433
        /// account for the pool token.
2434
        /// </summary>
2435
        /// <param name="stakePool">The stake pool account.</param>
2436
        /// <param name="manager">The manager account (must sign).</param>
2437
        /// <param name="poolMint">The pool mint account.</param>
2438
        /// <param name="name">The new name for the pool token.</param>
2439
        /// <param name="symbol">The new symbol for the pool token.</param>
2440
        /// <param name="uri">The new URI for the pool token metadata.</param>
2441
        /// <returns>A transaction instruction for updating token metadata.</returns>
2442
        public static TransactionInstruction UpdateTokenMetadata(
2443
            PublicKey stakePool,
2444
            PublicKey manager,
2445
            PublicKey poolMint,
2446
            string name,
2447
            string symbol,
2448
            string uri)
2449
        {
2450
            // Derive the stake pool withdraw authority.
NEW
2451
            PublicKey stakePoolWithdrawAuthority = FindWithdrawAuthorityProgramAddress(stakePool);
×
2452
            // Derive the metadata account for the pool mint.
NEW
2453
            (PublicKey tokenMetadata, byte bump) = FindMetadataAccount(poolMint);
×
2454

NEW
2455
            var accounts = new List<AccountMeta>
×
NEW
2456
            {
×
NEW
2457
                AccountMeta.ReadOnly(stakePool, false),
×
NEW
2458
                AccountMeta.ReadOnly(manager, true),
×
NEW
2459
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
×
NEW
2460
                AccountMeta.Writable(tokenMetadata, false),
×
NEW
2461
                // InlineMplTokenMetadata.Id() returns the MPL token metadata program ID.
×
NEW
2462
                AccountMeta.ReadOnly(MplTokenMetadataProgramIdKey, false)
×
NEW
2463
            };
×
2464

NEW
2465
            byte[] data = StakePoolProgramData.EncodeUpdateTokenMetadata(name, symbol, uri);
×
NEW
2466
            return new TransactionInstruction
×
NEW
2467
            {
×
NEW
2468
                ProgramId = StakePoolProgramIdKey,
×
NEW
2469
                Keys = accounts,
×
NEW
2470
                Data = data
×
NEW
2471
            };
×
2472
        }
2473

2474
        /// <summary>
2475
        /// Creates an instruction to create metadata using the MPL token metadata
2476
        /// program for the pool token.
2477
        /// </summary>
2478
        /// <param name="stakePool">The stake pool account.</param>
2479
        /// <param name="manager">The manager account (must sign).</param>
2480
        /// <param name="poolMint">The pool mint account.</param>
2481
        /// <param name="payer">The account paying for the transaction (must sign).</param>
2482
        /// <param name="name">The name for the pool token.</param>
2483
        /// <param name="symbol">The symbol for the pool token.</param>
2484
        /// <param name="uri">The URI for the pool token metadata.</param>
2485
        /// <returns>A transaction instruction for creating token metadata.</returns>
2486
        public static TransactionInstruction CreateTokenMetadata(
2487
            PublicKey stakePool,
2488
            PublicKey manager,
2489
            PublicKey poolMint,
2490
            PublicKey payer,
2491
            string name,
2492
            string symbol,
2493
            string uri)
2494
        {
2495
            // Derive the stake pool withdraw authority.
NEW
2496
            PublicKey stakePoolWithdrawAuthority = FindWithdrawAuthorityProgramAddress(stakePool);
×
2497
            
2498
            // Derive the metadata account for the pool mint.
NEW
2499
            (PublicKey tokenMetadata, byte bump) = FindMetadataAccount(poolMint);
×
2500
            
2501
            // Build the accounts as required by the MPL Token Metadata program.
NEW
2502
            var accounts = new List<AccountMeta>
×
NEW
2503
            {
×
NEW
2504
                // The stake pool account (read-only)
×
NEW
2505
                AccountMeta.ReadOnly(stakePool, false),
×
NEW
2506
                // The manager must sign (read-only)
×
NEW
2507
                AccountMeta.ReadOnly(manager, true),
×
NEW
2508
                // The derived withdraw authority (read-only)
×
NEW
2509
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
×
NEW
2510
                // The pool mint (read-only)
×
NEW
2511
                AccountMeta.ReadOnly(poolMint, false),
×
NEW
2512
                // The payer (writable and signer)
×
NEW
2513
                AccountMeta.Writable(payer, true),
×
NEW
2514
                // The token metadata account (writable)
×
NEW
2515
                AccountMeta.Writable(tokenMetadata, false),
×
NEW
2516
                // The MPL Token Metadata program
×
NEW
2517
                AccountMeta.ReadOnly(MplTokenMetadataProgramIdKey, false),
×
NEW
2518
                // The system program
×
NEW
2519
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false)
×
NEW
2520
            };
×
2521

2522
            // Encode the instruction data for creating token metadata.
NEW
2523
            byte[] data = StakePoolProgramData.EncodeCreateTokenMetadata(name, symbol, uri);
×
2524

NEW
2525
            return new TransactionInstruction
×
NEW
2526
            {
×
NEW
2527
                ProgramId = StakePoolProgramIdKey,
×
NEW
2528
                Keys = accounts,
×
NEW
2529
                Data = data
×
NEW
2530
            };
×
2531
        }
2532

2533
        /// <summary>
2534
        /// Creates an instruction required to deposit SOL directly into a stake pool.
2535
        /// </summary>
2536
        /// <param name="stakePool">The stake pool account.</param>
2537
        /// <param name="stakePoolWithdrawAuthority">The stake pool withdraw authority.</param>
2538
        /// <param name="reserveStakeAccount">The reserve stake account.</param>
2539
        /// <param name="lamportsFrom">The source account for SOL lamports (must sign).</param>
2540
        /// <param name="poolTokensTo">The account to receive pool tokens.</param>
2541
        /// <param name="managerFeeAccount">The manager fee account.</param>
2542
        /// <param name="referrerPoolTokensAccount">The referrer’s pool token account.</param>
2543
        /// <param name="poolMint">The pool mint account.</param>
2544
        /// <param name="tokenProgramId">The token program account.</param>
2545
        /// <param name="solDepositAuthority">
2546
        /// Optional SOL deposit authority; if provided, it must sign.
2547
        /// </param>
2548
        /// <param name="lamportsIn">The amount of SOL lamports to deposit.</param>
2549
        /// <param name="minimumPoolTokensOut">
2550
        /// Optional minimum pool tokens expected (for slippage protection).
2551
        /// </param>
2552
        /// <returns>A transaction instruction for SOL deposit.</returns>
2553
        public static TransactionInstruction DepositSolInternal(
2554
            PublicKey stakePool,
2555
            PublicKey stakePoolWithdrawAuthority,
2556
            PublicKey reserveStakeAccount,
2557
            PublicKey lamportsFrom,
2558
            PublicKey poolTokensTo,
2559
            PublicKey managerFeeAccount,
2560
            PublicKey referrerPoolTokensAccount,
2561
            PublicKey poolMint,
2562
            PublicKey tokenProgramId,
2563
            PublicKey? solDepositAuthority,
2564
            ulong lamportsIn,
2565
            ulong? minimumPoolTokensOut)
2566
        {
NEW
2567
            var accounts = new List<AccountMeta>
×
NEW
2568
            {
×
NEW
2569
                AccountMeta.Writable(stakePool, false),
×
NEW
2570
                AccountMeta.ReadOnly(stakePoolWithdrawAuthority, false),
×
NEW
2571
                AccountMeta.Writable(reserveStakeAccount, false),
×
NEW
2572
                AccountMeta.Writable(lamportsFrom, true),
×
NEW
2573
                AccountMeta.Writable(poolTokensTo, false),
×
NEW
2574
                AccountMeta.Writable(managerFeeAccount, false),
×
NEW
2575
                AccountMeta.Writable(referrerPoolTokensAccount, false),
×
NEW
2576
                AccountMeta.Writable(poolMint, false),
×
NEW
2577
                AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
×
NEW
2578
                AccountMeta.ReadOnly(tokenProgramId, false)
×
NEW
2579
            };
×
2580

NEW
2581
            if (solDepositAuthority != null)
×
2582
            {
NEW
2583
                accounts.Add(AccountMeta.ReadOnly(solDepositAuthority, true));
×
2584
            }
2585

NEW
2586
            byte[] data = minimumPoolTokensOut.HasValue
×
NEW
2587
                ? StakePoolProgramData.EncodeDepositSolWithSlippage(lamportsIn, minimumPoolTokensOut.Value)
×
NEW
2588
                : StakePoolProgramData.EncodeDepositSol(lamportsIn);
×
2589

NEW
2590
            return new TransactionInstruction
×
NEW
2591
            {
×
NEW
2592
                ProgramId = StakePoolProgramIdKey.KeyBytes,
×
NEW
2593
                Keys = accounts,
×
NEW
2594
                Data = data
×
NEW
2595
            };
×
2596
        }
2597

2598
        /// <summary>
2599
        /// Finds the metadata account for a given mint using the MPL Token Metadata program.
2600
        /// </summary>
2601
        /// <param name="mint">The mint public key.</param>
2602
        /// <returns>A tuple containing the metadata account public key and the bump seed.</returns>
2603
        public static (PublicKey, byte) FindMetadataAccount(PublicKey mint)
2604
        {
2605
            // Seeds must be in the same order as in the Rust function:
2606
            // 1. The UTF8 bytes for "metadata"
2607
            // 2. The MPL Token Metadata Program ID bytes.
2608
            // 3. The mint's public key bytes.
NEW
2609
            var seeds = new List<byte[]>
×
NEW
2610
            {
×
NEW
2611
                Encoding.ASCII.GetBytes("metadata"),
×
NEW
2612
                MplTokenMetadataProgramIdKey.KeyBytes,
×
NEW
2613
                mint.KeyBytes
×
NEW
2614
            };
×
2615

NEW
2616
            if (!PublicKey.TryFindProgramAddress(seeds, MplTokenMetadataProgramIdKey, out PublicKey metadataAccount, out byte bump))
×
2617
            {
NEW
2618
                throw new InvalidProgramException("Unable to find metadata account for the provided mint.");
×
2619
            }
2620

NEW
2621
            return (metadataAccount, bump);
×
2622
        }
2623

2624
        #endregion
2625
    }
2626
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc