@ignitionfi/spl-stake-pool 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +176 -0
- package/dist/codecs.d.ts +15 -0
- package/dist/constants.d.ts +12 -0
- package/dist/index.browser.cjs.js +2570 -0
- package/dist/index.browser.cjs.js.map +1 -0
- package/dist/index.browser.esm.js +2524 -0
- package/dist/index.browser.esm.js.map +1 -0
- package/dist/index.cjs.js +2570 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +195 -0
- package/dist/index.esm.js +2524 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.iife.js +23755 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.iife.min.js +19 -0
- package/dist/index.iife.min.js.map +1 -0
- package/dist/instructions.d.ts +329 -0
- package/dist/layouts.d.ts +318 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/instruction.d.ts +21 -0
- package/dist/utils/math.d.ts +3 -0
- package/dist/utils/program-address.d.ts +26 -0
- package/dist/utils/stake.d.ts +29 -0
- package/package.json +90 -0
- package/src/codecs.ts +159 -0
- package/src/constants.ts +29 -0
- package/src/index.ts +1522 -0
- package/src/instructions.ts +1293 -0
- package/src/layouts.ts +248 -0
- package/src/types/buffer-layout.d.ts +29 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/instruction.ts +46 -0
- package/src/utils/math.ts +29 -0
- package/src/utils/program-address.ts +103 -0
- package/src/utils/stake.ts +230 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1522 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createApproveInstruction,
|
|
3
|
+
createAssociatedTokenAccountIdempotentInstruction,
|
|
4
|
+
getAccount,
|
|
5
|
+
getAssociatedTokenAddressSync,
|
|
6
|
+
NATIVE_MINT,
|
|
7
|
+
} from '@solana/spl-token'
|
|
8
|
+
import {
|
|
9
|
+
AccountInfo,
|
|
10
|
+
Connection,
|
|
11
|
+
Keypair,
|
|
12
|
+
PublicKey,
|
|
13
|
+
Signer,
|
|
14
|
+
StakeAuthorizationLayout,
|
|
15
|
+
StakeProgram,
|
|
16
|
+
SystemProgram,
|
|
17
|
+
TransactionInstruction,
|
|
18
|
+
} from '@solana/web3.js'
|
|
19
|
+
import BN from 'bn.js'
|
|
20
|
+
import { create } from 'superstruct'
|
|
21
|
+
import {
|
|
22
|
+
DEVNET_STAKE_POOL_PROGRAM_ID,
|
|
23
|
+
MAX_VALIDATORS_TO_UPDATE,
|
|
24
|
+
MINIMUM_ACTIVE_STAKE,
|
|
25
|
+
STAKE_POOL_PROGRAM_ID,
|
|
26
|
+
} from './constants'
|
|
27
|
+
import { StakePoolInstruction } from './instructions'
|
|
28
|
+
import {
|
|
29
|
+
StakeAccount,
|
|
30
|
+
StakePool,
|
|
31
|
+
StakePoolLayout,
|
|
32
|
+
ValidatorList,
|
|
33
|
+
ValidatorListLayout,
|
|
34
|
+
ValidatorStakeInfo,
|
|
35
|
+
} from './layouts'
|
|
36
|
+
import {
|
|
37
|
+
arrayChunk,
|
|
38
|
+
calcLamportsWithdrawAmount,
|
|
39
|
+
findEphemeralStakeProgramAddress,
|
|
40
|
+
findMetadataAddress,
|
|
41
|
+
findStakeProgramAddress,
|
|
42
|
+
findTransientStakeProgramAddress,
|
|
43
|
+
findWithdrawAuthorityProgramAddress,
|
|
44
|
+
findWsolTransientProgramAddress,
|
|
45
|
+
getValidatorListAccount,
|
|
46
|
+
lamportsToSol,
|
|
47
|
+
newStakeAccount,
|
|
48
|
+
prepareWithdrawAccounts,
|
|
49
|
+
solToLamports,
|
|
50
|
+
ValidatorAccount,
|
|
51
|
+
} from './utils'
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
DEVNET_STAKE_POOL_PROGRAM_ID,
|
|
55
|
+
STAKE_POOL_PROGRAM_ID,
|
|
56
|
+
} from './constants'
|
|
57
|
+
export * from './instructions'
|
|
58
|
+
export type {
|
|
59
|
+
AccountType,
|
|
60
|
+
StakePool,
|
|
61
|
+
ValidatorList,
|
|
62
|
+
ValidatorStakeInfo,
|
|
63
|
+
} from './layouts'
|
|
64
|
+
export {
|
|
65
|
+
StakePoolLayout,
|
|
66
|
+
ValidatorListLayout,
|
|
67
|
+
ValidatorStakeInfoLayout,
|
|
68
|
+
} from './layouts'
|
|
69
|
+
|
|
70
|
+
export interface ValidatorListAccount {
|
|
71
|
+
pubkey: PublicKey
|
|
72
|
+
account: AccountInfo<ValidatorList>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface StakePoolAccount {
|
|
76
|
+
pubkey: PublicKey
|
|
77
|
+
account: AccountInfo<StakePool>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface WithdrawAccount {
|
|
81
|
+
stakeAddress: PublicKey
|
|
82
|
+
voteAddress?: PublicKey
|
|
83
|
+
poolAmount: BN
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Wrapper class for a stake pool.
|
|
88
|
+
* Each stake pool has a stake pool account and a validator list account.
|
|
89
|
+
*/
|
|
90
|
+
export interface StakePoolAccounts {
|
|
91
|
+
stakePool: StakePoolAccount | undefined
|
|
92
|
+
validatorList: ValidatorListAccount | undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getStakePoolProgramId(rpcEndpoint: string): PublicKey {
|
|
96
|
+
if (rpcEndpoint.includes('devnet')) {
|
|
97
|
+
return DEVNET_STAKE_POOL_PROGRAM_ID
|
|
98
|
+
} else {
|
|
99
|
+
return STAKE_POOL_PROGRAM_ID
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Retrieves and deserializes a StakePool account using a web3js connection and the stake pool address.
|
|
105
|
+
* @param connection An active web3js connection.
|
|
106
|
+
* @param stakePoolAddress The public key (address) of the stake pool account.
|
|
107
|
+
*/
|
|
108
|
+
export async function getStakePoolAccount(
|
|
109
|
+
connection: Connection,
|
|
110
|
+
stakePoolAddress: PublicKey,
|
|
111
|
+
): Promise<StakePoolAccount> {
|
|
112
|
+
const account = await connection.getAccountInfo(stakePoolAddress)
|
|
113
|
+
|
|
114
|
+
if (!account) {
|
|
115
|
+
throw new Error('Invalid stake pool account')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
pubkey: stakePoolAddress,
|
|
120
|
+
account: {
|
|
121
|
+
data: StakePoolLayout.decode(account.data),
|
|
122
|
+
executable: account.executable,
|
|
123
|
+
lamports: account.lamports,
|
|
124
|
+
owner: account.owner,
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Retrieves and deserializes a Stake account using a web3js connection and the stake address.
|
|
131
|
+
* @param connection An active web3js connection.
|
|
132
|
+
* @param stakeAccount The public key (address) of the stake account.
|
|
133
|
+
*/
|
|
134
|
+
export async function getStakeAccount(
|
|
135
|
+
connection: Connection,
|
|
136
|
+
stakeAccount: PublicKey,
|
|
137
|
+
): Promise<StakeAccount> {
|
|
138
|
+
const result = (await connection.getParsedAccountInfo(stakeAccount)).value
|
|
139
|
+
if (!result || !('parsed' in result.data)) {
|
|
140
|
+
throw new Error('Invalid stake account')
|
|
141
|
+
}
|
|
142
|
+
const program = result.data.program
|
|
143
|
+
if (program !== 'stake') {
|
|
144
|
+
throw new Error('Not a stake account')
|
|
145
|
+
}
|
|
146
|
+
return create(result.data.parsed, StakeAccount)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Retrieves all StakePool and ValidatorList accounts that are running a particular StakePool program.
|
|
151
|
+
* @param connection An active web3js connection.
|
|
152
|
+
* @param stakePoolProgramAddress The public key (address) of the StakePool program.
|
|
153
|
+
*/
|
|
154
|
+
export async function getStakePoolAccounts(
|
|
155
|
+
connection: Connection,
|
|
156
|
+
stakePoolProgramAddress: PublicKey,
|
|
157
|
+
): Promise<
|
|
158
|
+
(StakePoolAccount | ValidatorListAccount | undefined)[] | undefined
|
|
159
|
+
> {
|
|
160
|
+
const response = await connection.getProgramAccounts(stakePoolProgramAddress)
|
|
161
|
+
|
|
162
|
+
return response
|
|
163
|
+
.map((a) => {
|
|
164
|
+
try {
|
|
165
|
+
if (a.account.data.readUInt8() === 1) {
|
|
166
|
+
const data = StakePoolLayout.decode(a.account.data)
|
|
167
|
+
return {
|
|
168
|
+
pubkey: a.pubkey,
|
|
169
|
+
account: {
|
|
170
|
+
data,
|
|
171
|
+
executable: a.account.executable,
|
|
172
|
+
lamports: a.account.lamports,
|
|
173
|
+
owner: a.account.owner,
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
} else if (a.account.data.readUInt8() === 2) {
|
|
177
|
+
const data = ValidatorListLayout.decode(a.account.data)
|
|
178
|
+
return {
|
|
179
|
+
pubkey: a.pubkey,
|
|
180
|
+
account: {
|
|
181
|
+
data,
|
|
182
|
+
executable: a.account.executable,
|
|
183
|
+
lamports: a.account.lamports,
|
|
184
|
+
owner: a.account.owner,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
console.error(
|
|
189
|
+
`Could not decode. StakePoolAccount Enum is ${a.account.data.readUInt8()}, expected 1 or 2!`,
|
|
190
|
+
)
|
|
191
|
+
return undefined
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Could not decode account. Error:', error)
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
.filter(a => a !== undefined)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Creates instructions required to deposit stake to stake pool.
|
|
203
|
+
*/
|
|
204
|
+
export async function depositStake(
|
|
205
|
+
connection: Connection,
|
|
206
|
+
stakePoolAddress: PublicKey,
|
|
207
|
+
authorizedPubkey: PublicKey,
|
|
208
|
+
validatorVote: PublicKey,
|
|
209
|
+
depositStake: PublicKey,
|
|
210
|
+
poolTokenReceiverAccount?: PublicKey,
|
|
211
|
+
) {
|
|
212
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
213
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
214
|
+
|
|
215
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
216
|
+
stakePoolProgramId,
|
|
217
|
+
stakePoolAddress,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
const validatorStake = await findStakeProgramAddress(
|
|
221
|
+
stakePoolProgramId,
|
|
222
|
+
validatorVote,
|
|
223
|
+
stakePoolAddress,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const instructions: TransactionInstruction[] = []
|
|
227
|
+
const signers: Signer[] = []
|
|
228
|
+
|
|
229
|
+
const poolMint = stakePool.account.data.poolMint
|
|
230
|
+
|
|
231
|
+
// Create token account if not specified
|
|
232
|
+
if (!poolTokenReceiverAccount) {
|
|
233
|
+
const associatedAddress = getAssociatedTokenAddressSync(
|
|
234
|
+
poolMint,
|
|
235
|
+
authorizedPubkey,
|
|
236
|
+
)
|
|
237
|
+
instructions.push(
|
|
238
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
239
|
+
authorizedPubkey,
|
|
240
|
+
associatedAddress,
|
|
241
|
+
authorizedPubkey,
|
|
242
|
+
poolMint,
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
poolTokenReceiverAccount = associatedAddress
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
instructions.push(
|
|
249
|
+
...StakeProgram.authorize({
|
|
250
|
+
stakePubkey: depositStake,
|
|
251
|
+
authorizedPubkey,
|
|
252
|
+
newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
|
|
253
|
+
stakeAuthorizationType: StakeAuthorizationLayout.Staker,
|
|
254
|
+
}).instructions,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
instructions.push(
|
|
258
|
+
...StakeProgram.authorize({
|
|
259
|
+
stakePubkey: depositStake,
|
|
260
|
+
authorizedPubkey,
|
|
261
|
+
newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
|
|
262
|
+
stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
|
|
263
|
+
}).instructions,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
instructions.push(
|
|
267
|
+
StakePoolInstruction.depositStake({
|
|
268
|
+
programId: stakePoolProgramId,
|
|
269
|
+
stakePool: stakePoolAddress,
|
|
270
|
+
validatorList: stakePool.account.data.validatorList,
|
|
271
|
+
depositAuthority: stakePool.account.data.stakeDepositAuthority,
|
|
272
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
273
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
274
|
+
referralPoolAccount: poolTokenReceiverAccount,
|
|
275
|
+
destinationPoolAccount: poolTokenReceiverAccount,
|
|
276
|
+
withdrawAuthority,
|
|
277
|
+
depositStake,
|
|
278
|
+
validatorStake,
|
|
279
|
+
poolMint,
|
|
280
|
+
}),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
instructions,
|
|
285
|
+
signers,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Creates instructions required to deposit sol to stake pool.
|
|
291
|
+
*/
|
|
292
|
+
export async function depositWsolWithSession(
|
|
293
|
+
connection: Connection,
|
|
294
|
+
stakePoolAddress: PublicKey,
|
|
295
|
+
signerOrSession: PublicKey,
|
|
296
|
+
userPubkey: PublicKey,
|
|
297
|
+
lamports: number,
|
|
298
|
+
destinationTokenAccount?: PublicKey,
|
|
299
|
+
referrerTokenAccount?: PublicKey,
|
|
300
|
+
depositAuthority?: PublicKey,
|
|
301
|
+
payer?: PublicKey,
|
|
302
|
+
) {
|
|
303
|
+
const wsolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, userPubkey)
|
|
304
|
+
|
|
305
|
+
const tokenAccountInfo = await connection.getTokenAccountBalance(
|
|
306
|
+
wsolTokenAccount,
|
|
307
|
+
'confirmed',
|
|
308
|
+
)
|
|
309
|
+
const wsolBalance = tokenAccountInfo
|
|
310
|
+
? parseInt(tokenAccountInfo.value.amount)
|
|
311
|
+
: 0
|
|
312
|
+
|
|
313
|
+
if (wsolBalance < lamports) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(
|
|
316
|
+
wsolBalance,
|
|
317
|
+
)} WSOL.`,
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress)
|
|
322
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
323
|
+
const stakePool = stakePoolAccount.account.data
|
|
324
|
+
|
|
325
|
+
// stakePool.tokenProgramId
|
|
326
|
+
|
|
327
|
+
const instructions: TransactionInstruction[] = []
|
|
328
|
+
|
|
329
|
+
// Create token account if not specified
|
|
330
|
+
if (!destinationTokenAccount) {
|
|
331
|
+
const associatedAddress = getAssociatedTokenAddressSync(
|
|
332
|
+
stakePool.poolMint,
|
|
333
|
+
userPubkey,
|
|
334
|
+
)
|
|
335
|
+
instructions.push(
|
|
336
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
337
|
+
payer ?? signerOrSession,
|
|
338
|
+
associatedAddress,
|
|
339
|
+
userPubkey,
|
|
340
|
+
stakePool.poolMint,
|
|
341
|
+
),
|
|
342
|
+
)
|
|
343
|
+
destinationTokenAccount = associatedAddress
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
347
|
+
stakePoolProgramId,
|
|
348
|
+
stakePoolAddress,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
const [programSigner] = PublicKey.findProgramAddressSync(
|
|
352
|
+
[Buffer.from('fogo_session_program_signer')],
|
|
353
|
+
stakePoolProgramId,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
const wsolTransientAccount = findWsolTransientProgramAddress(
|
|
357
|
+
stakePoolProgramId,
|
|
358
|
+
userPubkey,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
instructions.push(
|
|
362
|
+
StakePoolInstruction.depositWsolWithSession({
|
|
363
|
+
programId: stakePoolProgramId,
|
|
364
|
+
stakePool: stakePoolAddress,
|
|
365
|
+
reserveStake: stakePool.reserveStake,
|
|
366
|
+
fundingAccount: signerOrSession,
|
|
367
|
+
destinationPoolAccount: destinationTokenAccount,
|
|
368
|
+
managerFeeAccount: stakePool.managerFeeAccount,
|
|
369
|
+
referralPoolAccount: referrerTokenAccount ?? destinationTokenAccount,
|
|
370
|
+
poolMint: stakePool.poolMint,
|
|
371
|
+
lamports,
|
|
372
|
+
withdrawAuthority,
|
|
373
|
+
depositAuthority,
|
|
374
|
+
|
|
375
|
+
wsolMint: NATIVE_MINT,
|
|
376
|
+
wsolTokenAccount,
|
|
377
|
+
wsolTransientAccount,
|
|
378
|
+
tokenProgramId: stakePool.tokenProgramId,
|
|
379
|
+
programSigner,
|
|
380
|
+
payer,
|
|
381
|
+
}),
|
|
382
|
+
)
|
|
383
|
+
return {
|
|
384
|
+
instructions,
|
|
385
|
+
signers: [],
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Creates instructions required to deposit sol to stake pool.
|
|
391
|
+
*/
|
|
392
|
+
export async function depositSol(
|
|
393
|
+
connection: Connection,
|
|
394
|
+
stakePoolAddress: PublicKey,
|
|
395
|
+
from: PublicKey,
|
|
396
|
+
lamports: number,
|
|
397
|
+
destinationTokenAccount?: PublicKey,
|
|
398
|
+
referrerTokenAccount?: PublicKey,
|
|
399
|
+
depositAuthority?: PublicKey,
|
|
400
|
+
) {
|
|
401
|
+
const fromBalance = await connection.getBalance(from, 'confirmed')
|
|
402
|
+
if (fromBalance < lamports) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Not enough SOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(
|
|
405
|
+
fromBalance,
|
|
406
|
+
)} SOL.`,
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const stakePoolAccount = await getStakePoolAccount(
|
|
411
|
+
connection,
|
|
412
|
+
stakePoolAddress,
|
|
413
|
+
)
|
|
414
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
415
|
+
const stakePool = stakePoolAccount.account.data
|
|
416
|
+
|
|
417
|
+
// Ephemeral SOL account just to do the transfer
|
|
418
|
+
const userSolTransfer = new Keypair()
|
|
419
|
+
const signers: Signer[] = [userSolTransfer]
|
|
420
|
+
const instructions: TransactionInstruction[] = []
|
|
421
|
+
|
|
422
|
+
// Create the ephemeral SOL account
|
|
423
|
+
instructions.push(
|
|
424
|
+
SystemProgram.transfer({
|
|
425
|
+
fromPubkey: from,
|
|
426
|
+
toPubkey: userSolTransfer.publicKey,
|
|
427
|
+
lamports,
|
|
428
|
+
}),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
// Create token account if not specified
|
|
432
|
+
if (!destinationTokenAccount) {
|
|
433
|
+
const associatedAddress = getAssociatedTokenAddressSync(
|
|
434
|
+
stakePool.poolMint,
|
|
435
|
+
from,
|
|
436
|
+
)
|
|
437
|
+
instructions.push(
|
|
438
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
439
|
+
from,
|
|
440
|
+
associatedAddress,
|
|
441
|
+
from,
|
|
442
|
+
stakePool.poolMint,
|
|
443
|
+
),
|
|
444
|
+
)
|
|
445
|
+
destinationTokenAccount = associatedAddress
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
449
|
+
stakePoolProgramId,
|
|
450
|
+
stakePoolAddress,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
instructions.push(
|
|
454
|
+
StakePoolInstruction.depositSol({
|
|
455
|
+
programId: stakePoolProgramId,
|
|
456
|
+
stakePool: stakePoolAddress,
|
|
457
|
+
reserveStake: stakePool.reserveStake,
|
|
458
|
+
fundingAccount: userSolTransfer.publicKey,
|
|
459
|
+
destinationPoolAccount: destinationTokenAccount,
|
|
460
|
+
managerFeeAccount: stakePool.managerFeeAccount,
|
|
461
|
+
referralPoolAccount: referrerTokenAccount ?? destinationTokenAccount,
|
|
462
|
+
poolMint: stakePool.poolMint,
|
|
463
|
+
lamports,
|
|
464
|
+
withdrawAuthority,
|
|
465
|
+
depositAuthority,
|
|
466
|
+
}),
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
instructions,
|
|
471
|
+
signers,
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Creates instructions required to withdraw stake from a stake pool.
|
|
477
|
+
*/
|
|
478
|
+
export async function withdrawStake(
|
|
479
|
+
connection: Connection,
|
|
480
|
+
stakePoolAddress: PublicKey,
|
|
481
|
+
tokenOwner: PublicKey,
|
|
482
|
+
amount: number,
|
|
483
|
+
useReserve = false,
|
|
484
|
+
voteAccountAddress?: PublicKey,
|
|
485
|
+
stakeReceiver?: PublicKey,
|
|
486
|
+
poolTokenAccount?: PublicKey,
|
|
487
|
+
validatorComparator?: (_a: ValidatorAccount, _b: ValidatorAccount) => number,
|
|
488
|
+
) {
|
|
489
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
490
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
491
|
+
const poolAmount = new BN(solToLamports(amount))
|
|
492
|
+
|
|
493
|
+
if (!poolTokenAccount) {
|
|
494
|
+
poolTokenAccount = getAssociatedTokenAddressSync(
|
|
495
|
+
stakePool.account.data.poolMint,
|
|
496
|
+
tokenOwner,
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const tokenAccount = await getAccount(connection, poolTokenAccount)
|
|
501
|
+
|
|
502
|
+
// Check withdrawFrom balance
|
|
503
|
+
if (tokenAccount.amount < poolAmount.toNumber()) {
|
|
504
|
+
throw new Error(
|
|
505
|
+
`Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
|
|
506
|
+
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const stakeAccountRentExemption
|
|
511
|
+
= await connection.getMinimumBalanceForRentExemption(StakeProgram.space)
|
|
512
|
+
|
|
513
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
514
|
+
stakePoolProgramId,
|
|
515
|
+
stakePoolAddress,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
let stakeReceiverAccount = null
|
|
519
|
+
if (stakeReceiver) {
|
|
520
|
+
stakeReceiverAccount = await getStakeAccount(connection, stakeReceiver)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const withdrawAccounts: WithdrawAccount[] = []
|
|
524
|
+
|
|
525
|
+
if (useReserve) {
|
|
526
|
+
withdrawAccounts.push({
|
|
527
|
+
stakeAddress: stakePool.account.data.reserveStake,
|
|
528
|
+
voteAddress: undefined,
|
|
529
|
+
poolAmount,
|
|
530
|
+
})
|
|
531
|
+
} else if (
|
|
532
|
+
stakeReceiverAccount
|
|
533
|
+
&& stakeReceiverAccount?.type === 'delegated'
|
|
534
|
+
) {
|
|
535
|
+
const voteAccount = stakeReceiverAccount.info?.stake?.delegation.voter
|
|
536
|
+
if (!voteAccount) {
|
|
537
|
+
throw new Error(`Invalid stake receiver ${stakeReceiver} delegation`)
|
|
538
|
+
}
|
|
539
|
+
const validatorListAccount = await connection.getAccountInfo(
|
|
540
|
+
stakePool.account.data.validatorList,
|
|
541
|
+
)
|
|
542
|
+
const validatorList = ValidatorListLayout.decode(
|
|
543
|
+
validatorListAccount?.data,
|
|
544
|
+
) as ValidatorList
|
|
545
|
+
const isValidVoter = validatorList.validators.find(val =>
|
|
546
|
+
val.voteAccountAddress.equals(voteAccount),
|
|
547
|
+
)
|
|
548
|
+
if (voteAccountAddress && voteAccountAddress !== voteAccount) {
|
|
549
|
+
throw new Error(`Provided withdrawal vote account ${voteAccountAddress} does not match delegation on stake receiver account ${voteAccount},
|
|
550
|
+
remove this flag or provide a different stake account delegated to ${voteAccountAddress}`)
|
|
551
|
+
}
|
|
552
|
+
if (isValidVoter) {
|
|
553
|
+
const stakeAccountAddress = await findStakeProgramAddress(
|
|
554
|
+
stakePoolProgramId,
|
|
555
|
+
voteAccount,
|
|
556
|
+
stakePoolAddress,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
const stakeAccount = await connection.getAccountInfo(stakeAccountAddress)
|
|
560
|
+
if (!stakeAccount) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`Preferred withdraw valdator's stake account is invalid`,
|
|
563
|
+
)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const availableForWithdrawal = calcLamportsWithdrawAmount(
|
|
567
|
+
stakePool.account.data,
|
|
568
|
+
new BN(
|
|
569
|
+
stakeAccount.lamports
|
|
570
|
+
- MINIMUM_ACTIVE_STAKE
|
|
571
|
+
- stakeAccountRentExemption,
|
|
572
|
+
),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
if (availableForWithdrawal.lt(poolAmount)) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
`Not enough lamports available for withdrawal from ${stakeAccountAddress},
|
|
578
|
+
${poolAmount} asked, ${availableForWithdrawal} available.`,
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
withdrawAccounts.push({
|
|
582
|
+
stakeAddress: stakeAccountAddress,
|
|
583
|
+
voteAddress: voteAccount,
|
|
584
|
+
poolAmount,
|
|
585
|
+
})
|
|
586
|
+
} else {
|
|
587
|
+
throw new Error(
|
|
588
|
+
`Provided stake account is delegated to a vote account ${voteAccount} which does not exist in the stake pool`,
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
} else if (voteAccountAddress) {
|
|
592
|
+
const stakeAccountAddress = await findStakeProgramAddress(
|
|
593
|
+
stakePoolProgramId,
|
|
594
|
+
voteAccountAddress,
|
|
595
|
+
stakePoolAddress,
|
|
596
|
+
)
|
|
597
|
+
const stakeAccount = await connection.getAccountInfo(stakeAccountAddress)
|
|
598
|
+
if (!stakeAccount) {
|
|
599
|
+
throw new Error('Invalid Stake Account')
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const availableLamports = new BN(
|
|
603
|
+
stakeAccount.lamports - MINIMUM_ACTIVE_STAKE - stakeAccountRentExemption,
|
|
604
|
+
)
|
|
605
|
+
if (availableLamports.lt(new BN(0))) {
|
|
606
|
+
throw new Error('Invalid Stake Account')
|
|
607
|
+
}
|
|
608
|
+
const availableForWithdrawal = calcLamportsWithdrawAmount(
|
|
609
|
+
stakePool.account.data,
|
|
610
|
+
availableLamports,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
if (availableForWithdrawal.lt(poolAmount)) {
|
|
614
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
615
|
+
throw new Error(
|
|
616
|
+
`Not enough lamports available for withdrawal from ${stakeAccountAddress},
|
|
617
|
+
${poolAmount} asked, ${availableForWithdrawal} available.`,
|
|
618
|
+
)
|
|
619
|
+
}
|
|
620
|
+
withdrawAccounts.push({
|
|
621
|
+
stakeAddress: stakeAccountAddress,
|
|
622
|
+
voteAddress: voteAccountAddress,
|
|
623
|
+
poolAmount,
|
|
624
|
+
})
|
|
625
|
+
} else {
|
|
626
|
+
// Get the list of accounts to withdraw from
|
|
627
|
+
withdrawAccounts.push(
|
|
628
|
+
...(await prepareWithdrawAccounts(
|
|
629
|
+
connection,
|
|
630
|
+
stakePool.account.data,
|
|
631
|
+
stakePoolAddress,
|
|
632
|
+
poolAmount,
|
|
633
|
+
validatorComparator,
|
|
634
|
+
poolTokenAccount.equals(stakePool.account.data.managerFeeAccount),
|
|
635
|
+
)),
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Construct transaction to withdraw from withdrawAccounts account list
|
|
640
|
+
const instructions: TransactionInstruction[] = []
|
|
641
|
+
const userTransferAuthority = Keypair.generate()
|
|
642
|
+
|
|
643
|
+
const signers: Signer[] = [userTransferAuthority]
|
|
644
|
+
|
|
645
|
+
instructions.push(
|
|
646
|
+
createApproveInstruction(
|
|
647
|
+
poolTokenAccount,
|
|
648
|
+
userTransferAuthority.publicKey,
|
|
649
|
+
tokenOwner,
|
|
650
|
+
poolAmount.toNumber(),
|
|
651
|
+
),
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
let totalRentFreeBalances = 0
|
|
655
|
+
|
|
656
|
+
// Max 5 accounts to prevent an error: "Transaction too large"
|
|
657
|
+
const maxWithdrawAccounts = 5
|
|
658
|
+
let i = 0
|
|
659
|
+
|
|
660
|
+
// Go through prepared accounts and withdraw/claim them
|
|
661
|
+
for (const withdrawAccount of withdrawAccounts) {
|
|
662
|
+
if (i > maxWithdrawAccounts) {
|
|
663
|
+
break
|
|
664
|
+
}
|
|
665
|
+
// Convert pool tokens amount to lamports
|
|
666
|
+
const solWithdrawAmount = calcLamportsWithdrawAmount(
|
|
667
|
+
stakePool.account.data,
|
|
668
|
+
withdrawAccount.poolAmount,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
let infoMsg = `Withdrawing ◎${solWithdrawAmount},
|
|
672
|
+
from stake account ${withdrawAccount.stakeAddress?.toBase58()}`
|
|
673
|
+
|
|
674
|
+
if (withdrawAccount.voteAddress) {
|
|
675
|
+
infoMsg = `${infoMsg}, delegated to ${withdrawAccount.voteAddress?.toBase58()}`
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
console.info(infoMsg)
|
|
679
|
+
let stakeToReceive
|
|
680
|
+
|
|
681
|
+
if (
|
|
682
|
+
!stakeReceiver
|
|
683
|
+
|| (stakeReceiverAccount && stakeReceiverAccount.type === 'delegated')
|
|
684
|
+
) {
|
|
685
|
+
const stakeKeypair = newStakeAccount(
|
|
686
|
+
tokenOwner,
|
|
687
|
+
instructions,
|
|
688
|
+
stakeAccountRentExemption,
|
|
689
|
+
)
|
|
690
|
+
signers.push(stakeKeypair)
|
|
691
|
+
totalRentFreeBalances += stakeAccountRentExemption
|
|
692
|
+
stakeToReceive = stakeKeypair.publicKey
|
|
693
|
+
} else {
|
|
694
|
+
stakeToReceive = stakeReceiver
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
instructions.push(
|
|
698
|
+
StakePoolInstruction.withdrawStake({
|
|
699
|
+
programId: stakePoolProgramId,
|
|
700
|
+
stakePool: stakePoolAddress,
|
|
701
|
+
validatorList: stakePool.account.data.validatorList,
|
|
702
|
+
validatorStake: withdrawAccount.stakeAddress,
|
|
703
|
+
destinationStake: stakeToReceive,
|
|
704
|
+
destinationStakeAuthority: tokenOwner,
|
|
705
|
+
sourceTransferAuthority: userTransferAuthority.publicKey,
|
|
706
|
+
sourcePoolAccount: poolTokenAccount,
|
|
707
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
708
|
+
poolMint: stakePool.account.data.poolMint,
|
|
709
|
+
poolTokens: withdrawAccount.poolAmount.toNumber(),
|
|
710
|
+
withdrawAuthority,
|
|
711
|
+
}),
|
|
712
|
+
)
|
|
713
|
+
i++
|
|
714
|
+
}
|
|
715
|
+
if (
|
|
716
|
+
stakeReceiver
|
|
717
|
+
&& stakeReceiverAccount
|
|
718
|
+
&& stakeReceiverAccount.type === 'delegated'
|
|
719
|
+
) {
|
|
720
|
+
signers.forEach((newStakeKeypair) => {
|
|
721
|
+
instructions.concat(
|
|
722
|
+
StakeProgram.merge({
|
|
723
|
+
stakePubkey: stakeReceiver,
|
|
724
|
+
sourceStakePubKey: newStakeKeypair.publicKey,
|
|
725
|
+
authorizedPubkey: tokenOwner,
|
|
726
|
+
}).instructions,
|
|
727
|
+
)
|
|
728
|
+
})
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
instructions,
|
|
733
|
+
signers,
|
|
734
|
+
stakeReceiver,
|
|
735
|
+
totalRentFreeBalances,
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Creates instructions required to withdraw SOL directly from a stake pool.
|
|
741
|
+
*/
|
|
742
|
+
export async function withdrawSol(
|
|
743
|
+
connection: Connection,
|
|
744
|
+
stakePoolAddress: PublicKey,
|
|
745
|
+
tokenOwner: PublicKey,
|
|
746
|
+
solReceiver: PublicKey,
|
|
747
|
+
amount: number,
|
|
748
|
+
solWithdrawAuthority?: PublicKey,
|
|
749
|
+
) {
|
|
750
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
751
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
752
|
+
const poolAmount = solToLamports(amount)
|
|
753
|
+
|
|
754
|
+
const poolTokenAccount = getAssociatedTokenAddressSync(
|
|
755
|
+
stakePool.account.data.poolMint,
|
|
756
|
+
tokenOwner,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
const tokenAccount = await getAccount(connection, poolTokenAccount)
|
|
760
|
+
|
|
761
|
+
// Check withdrawFrom balance
|
|
762
|
+
if (tokenAccount.amount < poolAmount) {
|
|
763
|
+
throw new Error(
|
|
764
|
+
`Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
|
|
765
|
+
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
|
|
766
|
+
)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Construct transaction to withdraw from withdrawAccounts account list
|
|
770
|
+
const instructions: TransactionInstruction[] = []
|
|
771
|
+
const userTransferAuthority = Keypair.generate()
|
|
772
|
+
const signers: Signer[] = [userTransferAuthority]
|
|
773
|
+
|
|
774
|
+
instructions.push(
|
|
775
|
+
createApproveInstruction(
|
|
776
|
+
poolTokenAccount,
|
|
777
|
+
userTransferAuthority.publicKey,
|
|
778
|
+
tokenOwner,
|
|
779
|
+
poolAmount,
|
|
780
|
+
),
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
const poolWithdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
784
|
+
stakePoolProgramId,
|
|
785
|
+
stakePoolAddress,
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
if (solWithdrawAuthority) {
|
|
789
|
+
const expectedSolWithdrawAuthority
|
|
790
|
+
= stakePool.account.data.solWithdrawAuthority
|
|
791
|
+
if (!expectedSolWithdrawAuthority) {
|
|
792
|
+
throw new Error(
|
|
793
|
+
'SOL withdraw authority specified in arguments but stake pool has none',
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
if (
|
|
797
|
+
solWithdrawAuthority.toBase58() !== expectedSolWithdrawAuthority.toBase58()
|
|
798
|
+
) {
|
|
799
|
+
throw new Error(
|
|
800
|
+
`Invalid deposit withdraw specified, expected ${expectedSolWithdrawAuthority.toBase58()}, received ${solWithdrawAuthority.toBase58()}`,
|
|
801
|
+
)
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const withdrawTransaction = StakePoolInstruction.withdrawSol({
|
|
806
|
+
programId: stakePoolProgramId,
|
|
807
|
+
stakePool: stakePoolAddress,
|
|
808
|
+
withdrawAuthority: poolWithdrawAuthority,
|
|
809
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
810
|
+
sourcePoolAccount: poolTokenAccount,
|
|
811
|
+
sourceTransferAuthority: userTransferAuthority.publicKey,
|
|
812
|
+
destinationSystemAccount: solReceiver,
|
|
813
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
814
|
+
poolMint: stakePool.account.data.poolMint,
|
|
815
|
+
poolTokens: poolAmount,
|
|
816
|
+
solWithdrawAuthority,
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
instructions.push(withdrawTransaction)
|
|
820
|
+
|
|
821
|
+
return {
|
|
822
|
+
instructions,
|
|
823
|
+
signers,
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Creates instructions required to withdraw wSOL from a stake pool.
|
|
829
|
+
*/
|
|
830
|
+
export async function withdrawWsolWithSession(
|
|
831
|
+
connection: Connection,
|
|
832
|
+
stakePoolAddress: PublicKey,
|
|
833
|
+
signerOrSession: PublicKey,
|
|
834
|
+
userPubkey: PublicKey,
|
|
835
|
+
amount: number,
|
|
836
|
+
solWithdrawAuthority?: PublicKey,
|
|
837
|
+
) {
|
|
838
|
+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress)
|
|
839
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
840
|
+
const stakePool = stakePoolAccount.account.data
|
|
841
|
+
const poolTokens = solToLamports(amount)
|
|
842
|
+
|
|
843
|
+
const poolTokenAccount = getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey)
|
|
844
|
+
const tokenAccount = await getAccount(connection, poolTokenAccount)
|
|
845
|
+
|
|
846
|
+
if (tokenAccount.amount < poolTokens) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`Not enough token balance to withdraw ${amount} pool tokens.
|
|
849
|
+
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
|
|
850
|
+
)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const userWsolAccount = getAssociatedTokenAddressSync(NATIVE_MINT, userPubkey)
|
|
854
|
+
|
|
855
|
+
const instructions: TransactionInstruction[] = []
|
|
856
|
+
const signers: Signer[] = []
|
|
857
|
+
|
|
858
|
+
instructions.push(
|
|
859
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
860
|
+
signerOrSession,
|
|
861
|
+
userWsolAccount,
|
|
862
|
+
userPubkey,
|
|
863
|
+
NATIVE_MINT,
|
|
864
|
+
),
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
const [programSigner] = PublicKey.findProgramAddressSync(
|
|
868
|
+
[Buffer.from('fogo_session_program_signer')],
|
|
869
|
+
stakePoolProgramId,
|
|
870
|
+
)
|
|
871
|
+
|
|
872
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
873
|
+
stakePoolProgramId,
|
|
874
|
+
stakePoolAddress,
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
instructions.push(
|
|
878
|
+
StakePoolInstruction.withdrawWsolWithSession({
|
|
879
|
+
programId: stakePoolProgramId,
|
|
880
|
+
stakePool: stakePoolAddress,
|
|
881
|
+
withdrawAuthority,
|
|
882
|
+
userTransferAuthority: signerOrSession,
|
|
883
|
+
poolTokensFrom: poolTokenAccount,
|
|
884
|
+
reserveStake: stakePool.reserveStake,
|
|
885
|
+
userWsolAccount,
|
|
886
|
+
managerFeeAccount: stakePool.managerFeeAccount,
|
|
887
|
+
poolMint: stakePool.poolMint,
|
|
888
|
+
tokenProgramId: stakePool.tokenProgramId,
|
|
889
|
+
solWithdrawAuthority,
|
|
890
|
+
wsolMint: NATIVE_MINT,
|
|
891
|
+
programSigner,
|
|
892
|
+
poolTokens,
|
|
893
|
+
}),
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
instructions,
|
|
898
|
+
signers,
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
export async function addValidatorToPool(
|
|
903
|
+
connection: Connection,
|
|
904
|
+
stakePoolAddress: PublicKey,
|
|
905
|
+
validatorVote: PublicKey,
|
|
906
|
+
seed?: number,
|
|
907
|
+
) {
|
|
908
|
+
const stakePoolAccount = await getStakePoolAccount(
|
|
909
|
+
connection,
|
|
910
|
+
stakePoolAddress,
|
|
911
|
+
)
|
|
912
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
913
|
+
const stakePool = stakePoolAccount.account.data
|
|
914
|
+
const { reserveStake, staker, validatorList } = stakePool
|
|
915
|
+
|
|
916
|
+
const validatorListAccount = await getValidatorListAccount(
|
|
917
|
+
connection,
|
|
918
|
+
validatorList,
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
const validatorInfo = validatorListAccount.account.data.validators.find(
|
|
922
|
+
v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
if (validatorInfo) {
|
|
926
|
+
throw new Error('Vote account is already in validator list')
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
930
|
+
stakePoolProgramId,
|
|
931
|
+
stakePoolAddress,
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
const validatorStake = await findStakeProgramAddress(
|
|
935
|
+
stakePoolProgramId,
|
|
936
|
+
validatorVote,
|
|
937
|
+
stakePoolAddress,
|
|
938
|
+
seed,
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
const instructions: TransactionInstruction[] = [
|
|
942
|
+
StakePoolInstruction.addValidatorToPool({
|
|
943
|
+
programId: stakePoolProgramId,
|
|
944
|
+
stakePool: stakePoolAddress,
|
|
945
|
+
staker,
|
|
946
|
+
reserveStake,
|
|
947
|
+
withdrawAuthority,
|
|
948
|
+
validatorList,
|
|
949
|
+
validatorStake,
|
|
950
|
+
validatorVote,
|
|
951
|
+
}),
|
|
952
|
+
]
|
|
953
|
+
|
|
954
|
+
return {
|
|
955
|
+
instructions,
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
export async function removeValidatorFromPool(
|
|
960
|
+
connection: Connection,
|
|
961
|
+
stakePoolAddress: PublicKey,
|
|
962
|
+
validatorVote: PublicKey,
|
|
963
|
+
seed?: number,
|
|
964
|
+
) {
|
|
965
|
+
const stakePoolAccount = await getStakePoolAccount(
|
|
966
|
+
connection,
|
|
967
|
+
stakePoolAddress,
|
|
968
|
+
)
|
|
969
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
970
|
+
const stakePool = stakePoolAccount.account.data
|
|
971
|
+
const { staker, validatorList } = stakePool
|
|
972
|
+
|
|
973
|
+
const validatorListAccount = await getValidatorListAccount(
|
|
974
|
+
connection,
|
|
975
|
+
validatorList,
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
const validatorInfo = validatorListAccount.account.data.validators.find(
|
|
979
|
+
v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
if (!validatorInfo) {
|
|
983
|
+
throw new Error('Vote account is not already in validator list')
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
987
|
+
stakePoolProgramId,
|
|
988
|
+
stakePoolAddress,
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
const validatorStake = await findStakeProgramAddress(
|
|
992
|
+
stakePoolProgramId,
|
|
993
|
+
validatorVote,
|
|
994
|
+
stakePoolAddress,
|
|
995
|
+
seed,
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
const transientStakeSeed = validatorInfo.transientSeedSuffixStart
|
|
999
|
+
|
|
1000
|
+
const transientStake = await findTransientStakeProgramAddress(
|
|
1001
|
+
stakePoolProgramId,
|
|
1002
|
+
validatorInfo.voteAccountAddress,
|
|
1003
|
+
stakePoolAddress,
|
|
1004
|
+
transientStakeSeed,
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
const instructions: TransactionInstruction[] = [
|
|
1008
|
+
StakePoolInstruction.removeValidatorFromPool({
|
|
1009
|
+
programId: stakePoolProgramId,
|
|
1010
|
+
stakePool: stakePoolAddress,
|
|
1011
|
+
staker,
|
|
1012
|
+
withdrawAuthority,
|
|
1013
|
+
validatorList,
|
|
1014
|
+
validatorStake,
|
|
1015
|
+
transientStake,
|
|
1016
|
+
}),
|
|
1017
|
+
]
|
|
1018
|
+
|
|
1019
|
+
return {
|
|
1020
|
+
instructions,
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Creates instructions required to increase validator stake.
|
|
1026
|
+
*/
|
|
1027
|
+
export async function increaseValidatorStake(
|
|
1028
|
+
connection: Connection,
|
|
1029
|
+
stakePoolAddress: PublicKey,
|
|
1030
|
+
validatorVote: PublicKey,
|
|
1031
|
+
lamports: number,
|
|
1032
|
+
ephemeralStakeSeed?: number,
|
|
1033
|
+
) {
|
|
1034
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
1035
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
1036
|
+
|
|
1037
|
+
const validatorList = await getValidatorListAccount(
|
|
1038
|
+
connection,
|
|
1039
|
+
stakePool.account.data.validatorList,
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
const validatorInfo = validatorList.account.data.validators.find(
|
|
1043
|
+
v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
if (!validatorInfo) {
|
|
1047
|
+
throw new Error('Vote account not found in validator list')
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
1051
|
+
stakePoolProgramId,
|
|
1052
|
+
stakePoolAddress,
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
// Bump transient seed suffix by one to avoid reuse when not using the increaseAdditionalStake instruction
|
|
1056
|
+
const transientStakeSeed
|
|
1057
|
+
= ephemeralStakeSeed === undefined
|
|
1058
|
+
? validatorInfo.transientSeedSuffixStart.addn(1)
|
|
1059
|
+
: validatorInfo.transientSeedSuffixStart
|
|
1060
|
+
|
|
1061
|
+
const transientStake = await findTransientStakeProgramAddress(
|
|
1062
|
+
stakePoolProgramId,
|
|
1063
|
+
validatorInfo.voteAccountAddress,
|
|
1064
|
+
stakePoolAddress,
|
|
1065
|
+
transientStakeSeed,
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
const validatorStake = await findStakeProgramAddress(
|
|
1069
|
+
stakePoolProgramId,
|
|
1070
|
+
validatorInfo.voteAccountAddress,
|
|
1071
|
+
stakePoolAddress,
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
const instructions: TransactionInstruction[] = []
|
|
1075
|
+
|
|
1076
|
+
if (ephemeralStakeSeed !== undefined) {
|
|
1077
|
+
const ephemeralStake = await findEphemeralStakeProgramAddress(
|
|
1078
|
+
stakePoolProgramId,
|
|
1079
|
+
stakePoolAddress,
|
|
1080
|
+
new BN(ephemeralStakeSeed),
|
|
1081
|
+
)
|
|
1082
|
+
instructions.push(
|
|
1083
|
+
StakePoolInstruction.increaseAdditionalValidatorStake({
|
|
1084
|
+
programId: stakePoolProgramId,
|
|
1085
|
+
stakePool: stakePoolAddress,
|
|
1086
|
+
staker: stakePool.account.data.staker,
|
|
1087
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1088
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
1089
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
1090
|
+
withdrawAuthority,
|
|
1091
|
+
transientStake,
|
|
1092
|
+
validatorStake,
|
|
1093
|
+
validatorVote,
|
|
1094
|
+
lamports,
|
|
1095
|
+
ephemeralStake,
|
|
1096
|
+
ephemeralStakeSeed,
|
|
1097
|
+
}),
|
|
1098
|
+
)
|
|
1099
|
+
} else {
|
|
1100
|
+
instructions.push(
|
|
1101
|
+
StakePoolInstruction.increaseValidatorStake({
|
|
1102
|
+
programId: stakePoolProgramId,
|
|
1103
|
+
stakePool: stakePoolAddress,
|
|
1104
|
+
staker: stakePool.account.data.staker,
|
|
1105
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1106
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
1107
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
1108
|
+
withdrawAuthority,
|
|
1109
|
+
transientStake,
|
|
1110
|
+
validatorStake,
|
|
1111
|
+
validatorVote,
|
|
1112
|
+
lamports,
|
|
1113
|
+
}),
|
|
1114
|
+
)
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
return {
|
|
1118
|
+
instructions,
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Creates instructions required to decrease validator stake.
|
|
1124
|
+
*/
|
|
1125
|
+
export async function decreaseValidatorStake(
|
|
1126
|
+
connection: Connection,
|
|
1127
|
+
stakePoolAddress: PublicKey,
|
|
1128
|
+
validatorVote: PublicKey,
|
|
1129
|
+
lamports: number,
|
|
1130
|
+
ephemeralStakeSeed?: number,
|
|
1131
|
+
) {
|
|
1132
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
1133
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
1134
|
+
const validatorList = await getValidatorListAccount(
|
|
1135
|
+
connection,
|
|
1136
|
+
stakePool.account.data.validatorList,
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
const validatorInfo = validatorList.account.data.validators.find(
|
|
1140
|
+
v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
if (!validatorInfo) {
|
|
1144
|
+
throw new Error('Vote account not found in validator list')
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
1148
|
+
stakePoolProgramId,
|
|
1149
|
+
stakePoolAddress,
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
const validatorStake = await findStakeProgramAddress(
|
|
1153
|
+
stakePoolProgramId,
|
|
1154
|
+
validatorInfo.voteAccountAddress,
|
|
1155
|
+
stakePoolAddress,
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
// Bump transient seed suffix by one to avoid reuse when not using the decreaseAdditionalStake instruction
|
|
1159
|
+
const transientStakeSeed
|
|
1160
|
+
= ephemeralStakeSeed === undefined
|
|
1161
|
+
? validatorInfo.transientSeedSuffixStart.addn(1)
|
|
1162
|
+
: validatorInfo.transientSeedSuffixStart
|
|
1163
|
+
|
|
1164
|
+
const transientStake = await findTransientStakeProgramAddress(
|
|
1165
|
+
stakePoolProgramId,
|
|
1166
|
+
validatorInfo.voteAccountAddress,
|
|
1167
|
+
stakePoolAddress,
|
|
1168
|
+
transientStakeSeed,
|
|
1169
|
+
)
|
|
1170
|
+
|
|
1171
|
+
const instructions: TransactionInstruction[] = []
|
|
1172
|
+
|
|
1173
|
+
if (ephemeralStakeSeed !== undefined) {
|
|
1174
|
+
const ephemeralStake = await findEphemeralStakeProgramAddress(
|
|
1175
|
+
stakePoolProgramId,
|
|
1176
|
+
stakePoolAddress,
|
|
1177
|
+
new BN(ephemeralStakeSeed),
|
|
1178
|
+
)
|
|
1179
|
+
instructions.push(
|
|
1180
|
+
StakePoolInstruction.decreaseAdditionalValidatorStake({
|
|
1181
|
+
programId: stakePoolProgramId,
|
|
1182
|
+
stakePool: stakePoolAddress,
|
|
1183
|
+
staker: stakePool.account.data.staker,
|
|
1184
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1185
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
1186
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
1187
|
+
withdrawAuthority,
|
|
1188
|
+
validatorStake,
|
|
1189
|
+
transientStake,
|
|
1190
|
+
lamports,
|
|
1191
|
+
ephemeralStake,
|
|
1192
|
+
ephemeralStakeSeed,
|
|
1193
|
+
}),
|
|
1194
|
+
)
|
|
1195
|
+
} else {
|
|
1196
|
+
instructions.push(
|
|
1197
|
+
StakePoolInstruction.decreaseValidatorStakeWithReserve({
|
|
1198
|
+
programId: stakePoolProgramId,
|
|
1199
|
+
stakePool: stakePoolAddress,
|
|
1200
|
+
staker: stakePool.account.data.staker,
|
|
1201
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1202
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
1203
|
+
transientStakeSeed: transientStakeSeed.toNumber(),
|
|
1204
|
+
withdrawAuthority,
|
|
1205
|
+
validatorStake,
|
|
1206
|
+
transientStake,
|
|
1207
|
+
lamports,
|
|
1208
|
+
}),
|
|
1209
|
+
)
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
return {
|
|
1213
|
+
instructions,
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Creates instructions required to completely update a stake pool after epoch change.
|
|
1219
|
+
*/
|
|
1220
|
+
export async function updateStakePool(
|
|
1221
|
+
connection: Connection,
|
|
1222
|
+
stakePool: StakePoolAccount,
|
|
1223
|
+
noMerge = false,
|
|
1224
|
+
) {
|
|
1225
|
+
const stakePoolAddress = stakePool.pubkey
|
|
1226
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
1227
|
+
|
|
1228
|
+
const validatorList = await getValidatorListAccount(
|
|
1229
|
+
connection,
|
|
1230
|
+
stakePool.account.data.validatorList,
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
1234
|
+
stakePoolProgramId,
|
|
1235
|
+
stakePoolAddress,
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
const updateListInstructions: TransactionInstruction[] = []
|
|
1239
|
+
const instructions: TransactionInstruction[] = []
|
|
1240
|
+
|
|
1241
|
+
let startIndex = 0
|
|
1242
|
+
const validatorChunks: Array<ValidatorStakeInfo[]> = arrayChunk(
|
|
1243
|
+
validatorList.account.data.validators,
|
|
1244
|
+
MAX_VALIDATORS_TO_UPDATE,
|
|
1245
|
+
)
|
|
1246
|
+
|
|
1247
|
+
for (const validatorChunk of validatorChunks) {
|
|
1248
|
+
const validatorAndTransientStakePairs: PublicKey[] = []
|
|
1249
|
+
|
|
1250
|
+
for (const validator of validatorChunk) {
|
|
1251
|
+
const validatorStake = await findStakeProgramAddress(
|
|
1252
|
+
stakePoolProgramId,
|
|
1253
|
+
validator.voteAccountAddress,
|
|
1254
|
+
stakePoolAddress,
|
|
1255
|
+
)
|
|
1256
|
+
validatorAndTransientStakePairs.push(validatorStake)
|
|
1257
|
+
|
|
1258
|
+
const transientStake = await findTransientStakeProgramAddress(
|
|
1259
|
+
stakePoolProgramId,
|
|
1260
|
+
validator.voteAccountAddress,
|
|
1261
|
+
stakePoolAddress,
|
|
1262
|
+
validator.transientSeedSuffixStart,
|
|
1263
|
+
)
|
|
1264
|
+
validatorAndTransientStakePairs.push(transientStake)
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
updateListInstructions.push(
|
|
1268
|
+
StakePoolInstruction.updateValidatorListBalance({
|
|
1269
|
+
programId: stakePoolProgramId,
|
|
1270
|
+
stakePool: stakePoolAddress,
|
|
1271
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1272
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
1273
|
+
validatorAndTransientStakePairs,
|
|
1274
|
+
withdrawAuthority,
|
|
1275
|
+
startIndex,
|
|
1276
|
+
noMerge,
|
|
1277
|
+
}),
|
|
1278
|
+
)
|
|
1279
|
+
startIndex += MAX_VALIDATORS_TO_UPDATE
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
instructions.push(
|
|
1283
|
+
StakePoolInstruction.updateStakePoolBalance({
|
|
1284
|
+
programId: stakePoolProgramId,
|
|
1285
|
+
stakePool: stakePoolAddress,
|
|
1286
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1287
|
+
reserveStake: stakePool.account.data.reserveStake,
|
|
1288
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount,
|
|
1289
|
+
poolMint: stakePool.account.data.poolMint,
|
|
1290
|
+
withdrawAuthority,
|
|
1291
|
+
}),
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
instructions.push(
|
|
1295
|
+
StakePoolInstruction.cleanupRemovedValidatorEntries({
|
|
1296
|
+
programId: stakePoolProgramId,
|
|
1297
|
+
stakePool: stakePoolAddress,
|
|
1298
|
+
validatorList: stakePool.account.data.validatorList,
|
|
1299
|
+
}),
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
return {
|
|
1303
|
+
updateListInstructions,
|
|
1304
|
+
finalInstructions: instructions,
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Retrieves detailed information about the StakePool.
|
|
1310
|
+
*/
|
|
1311
|
+
export async function stakePoolInfo(
|
|
1312
|
+
connection: Connection,
|
|
1313
|
+
stakePoolAddress: PublicKey,
|
|
1314
|
+
) {
|
|
1315
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
1316
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
1317
|
+
const reserveAccountStakeAddress = stakePool.account.data.reserveStake
|
|
1318
|
+
const totalLamports = stakePool.account.data.totalLamports
|
|
1319
|
+
const lastUpdateEpoch = stakePool.account.data.lastUpdateEpoch
|
|
1320
|
+
|
|
1321
|
+
const validatorList = await getValidatorListAccount(
|
|
1322
|
+
connection,
|
|
1323
|
+
stakePool.account.data.validatorList,
|
|
1324
|
+
)
|
|
1325
|
+
|
|
1326
|
+
const maxNumberOfValidators = validatorList.account.data.maxValidators
|
|
1327
|
+
const currentNumberOfValidators
|
|
1328
|
+
= validatorList.account.data.validators.length
|
|
1329
|
+
|
|
1330
|
+
const epochInfo = await connection.getEpochInfo()
|
|
1331
|
+
const reserveStake = await connection.getAccountInfo(
|
|
1332
|
+
reserveAccountStakeAddress,
|
|
1333
|
+
)
|
|
1334
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
1335
|
+
stakePoolProgramId,
|
|
1336
|
+
stakePoolAddress,
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
const minimumReserveStakeBalance
|
|
1340
|
+
= await connection.getMinimumBalanceForRentExemption(StakeProgram.space)
|
|
1341
|
+
|
|
1342
|
+
const stakeAccounts = await Promise.all(
|
|
1343
|
+
validatorList.account.data.validators.map(async (validator) => {
|
|
1344
|
+
const stakeAccountAddress = await findStakeProgramAddress(
|
|
1345
|
+
stakePoolProgramId,
|
|
1346
|
+
validator.voteAccountAddress,
|
|
1347
|
+
stakePoolAddress,
|
|
1348
|
+
)
|
|
1349
|
+
const transientStakeAccountAddress
|
|
1350
|
+
= await findTransientStakeProgramAddress(
|
|
1351
|
+
stakePoolProgramId,
|
|
1352
|
+
validator.voteAccountAddress,
|
|
1353
|
+
stakePoolAddress,
|
|
1354
|
+
validator.transientSeedSuffixStart,
|
|
1355
|
+
)
|
|
1356
|
+
const updateRequired = !validator.lastUpdateEpoch.eqn(epochInfo.epoch)
|
|
1357
|
+
return {
|
|
1358
|
+
voteAccountAddress: validator.voteAccountAddress.toBase58(),
|
|
1359
|
+
stakeAccountAddress: stakeAccountAddress.toBase58(),
|
|
1360
|
+
validatorActiveStakeLamports: validator.activeStakeLamports.toString(),
|
|
1361
|
+
validatorLastUpdateEpoch: validator.lastUpdateEpoch.toString(),
|
|
1362
|
+
validatorLamports: validator.activeStakeLamports
|
|
1363
|
+
.add(validator.transientStakeLamports)
|
|
1364
|
+
.toString(),
|
|
1365
|
+
validatorTransientStakeAccountAddress:
|
|
1366
|
+
transientStakeAccountAddress.toBase58(),
|
|
1367
|
+
validatorTransientStakeLamports:
|
|
1368
|
+
validator.transientStakeLamports.toString(),
|
|
1369
|
+
updateRequired,
|
|
1370
|
+
}
|
|
1371
|
+
}),
|
|
1372
|
+
)
|
|
1373
|
+
|
|
1374
|
+
const totalPoolTokens = lamportsToSol(stakePool.account.data.poolTokenSupply)
|
|
1375
|
+
const updateRequired = !lastUpdateEpoch.eqn(epochInfo.epoch)
|
|
1376
|
+
|
|
1377
|
+
return {
|
|
1378
|
+
address: stakePoolAddress.toBase58(),
|
|
1379
|
+
poolWithdrawAuthority: withdrawAuthority.toBase58(),
|
|
1380
|
+
manager: stakePool.account.data.manager.toBase58(),
|
|
1381
|
+
staker: stakePool.account.data.staker.toBase58(),
|
|
1382
|
+
stakeDepositAuthority:
|
|
1383
|
+
stakePool.account.data.stakeDepositAuthority.toBase58(),
|
|
1384
|
+
stakeWithdrawBumpSeed: stakePool.account.data.stakeWithdrawBumpSeed,
|
|
1385
|
+
maxValidators: maxNumberOfValidators,
|
|
1386
|
+
validatorList: validatorList.account.data.validators.map((validator) => {
|
|
1387
|
+
return {
|
|
1388
|
+
activeStakeLamports: validator.activeStakeLamports.toString(),
|
|
1389
|
+
transientStakeLamports: validator.transientStakeLamports.toString(),
|
|
1390
|
+
lastUpdateEpoch: validator.lastUpdateEpoch.toString(),
|
|
1391
|
+
transientSeedSuffixStart: validator.transientSeedSuffixStart.toString(),
|
|
1392
|
+
transientSeedSuffixEnd: validator.transientSeedSuffixEnd.toString(),
|
|
1393
|
+
status: validator.status.toString(),
|
|
1394
|
+
voteAccountAddress: validator.voteAccountAddress.toString(),
|
|
1395
|
+
}
|
|
1396
|
+
}), // CliStakePoolValidator
|
|
1397
|
+
validatorListStorageAccount:
|
|
1398
|
+
stakePool.account.data.validatorList.toBase58(),
|
|
1399
|
+
reserveStake: stakePool.account.data.reserveStake.toBase58(),
|
|
1400
|
+
poolMint: stakePool.account.data.poolMint.toBase58(),
|
|
1401
|
+
managerFeeAccount: stakePool.account.data.managerFeeAccount.toBase58(),
|
|
1402
|
+
tokenProgramId: stakePool.account.data.tokenProgramId.toBase58(),
|
|
1403
|
+
totalLamports: stakePool.account.data.totalLamports.toString(),
|
|
1404
|
+
poolTokenSupply: stakePool.account.data.poolTokenSupply.toString(),
|
|
1405
|
+
lastUpdateEpoch: stakePool.account.data.lastUpdateEpoch.toString(),
|
|
1406
|
+
lockup: stakePool.account.data.lockup, // pub lockup: CliStakePoolLockup
|
|
1407
|
+
epochFee: stakePool.account.data.epochFee,
|
|
1408
|
+
nextEpochFee: stakePool.account.data.nextEpochFee,
|
|
1409
|
+
preferredDepositValidatorVoteAddress:
|
|
1410
|
+
stakePool.account.data.preferredDepositValidatorVoteAddress,
|
|
1411
|
+
preferredWithdrawValidatorVoteAddress:
|
|
1412
|
+
stakePool.account.data.preferredWithdrawValidatorVoteAddress,
|
|
1413
|
+
stakeDepositFee: stakePool.account.data.stakeDepositFee,
|
|
1414
|
+
stakeWithdrawalFee: stakePool.account.data.stakeWithdrawalFee,
|
|
1415
|
+
// CliStakePool the same
|
|
1416
|
+
nextStakeWithdrawalFee: stakePool.account.data.nextStakeWithdrawalFee,
|
|
1417
|
+
stakeReferralFee: stakePool.account.data.stakeReferralFee,
|
|
1418
|
+
solDepositAuthority: stakePool.account.data.solDepositAuthority?.toBase58(),
|
|
1419
|
+
solDepositFee: stakePool.account.data.solDepositFee,
|
|
1420
|
+
solReferralFee: stakePool.account.data.solReferralFee,
|
|
1421
|
+
solWithdrawAuthority:
|
|
1422
|
+
stakePool.account.data.solWithdrawAuthority?.toBase58(),
|
|
1423
|
+
solWithdrawalFee: stakePool.account.data.solWithdrawalFee,
|
|
1424
|
+
nextSolWithdrawalFee: stakePool.account.data.nextSolWithdrawalFee,
|
|
1425
|
+
lastEpochPoolTokenSupply:
|
|
1426
|
+
stakePool.account.data.lastEpochPoolTokenSupply.toString(),
|
|
1427
|
+
lastEpochTotalLamports:
|
|
1428
|
+
stakePool.account.data.lastEpochTotalLamports.toString(),
|
|
1429
|
+
details: {
|
|
1430
|
+
reserveStakeLamports: reserveStake?.lamports,
|
|
1431
|
+
reserveAccountStakeAddress: reserveAccountStakeAddress.toBase58(),
|
|
1432
|
+
minimumReserveStakeBalance,
|
|
1433
|
+
stakeAccounts,
|
|
1434
|
+
totalLamports,
|
|
1435
|
+
totalPoolTokens,
|
|
1436
|
+
currentNumberOfValidators,
|
|
1437
|
+
maxNumberOfValidators,
|
|
1438
|
+
updateRequired,
|
|
1439
|
+
}, // CliStakePoolDetails
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/**
|
|
1444
|
+
* Creates instructions required to create pool token metadata.
|
|
1445
|
+
*/
|
|
1446
|
+
export async function createPoolTokenMetadata(
|
|
1447
|
+
connection: Connection,
|
|
1448
|
+
stakePoolAddress: PublicKey,
|
|
1449
|
+
payer: PublicKey,
|
|
1450
|
+
name: string,
|
|
1451
|
+
symbol: string,
|
|
1452
|
+
uri: string,
|
|
1453
|
+
) {
|
|
1454
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
1455
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
1456
|
+
|
|
1457
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
1458
|
+
stakePoolProgramId,
|
|
1459
|
+
stakePoolAddress,
|
|
1460
|
+
)
|
|
1461
|
+
const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint)
|
|
1462
|
+
const manager = stakePool.account.data.manager
|
|
1463
|
+
|
|
1464
|
+
const instructions: TransactionInstruction[] = []
|
|
1465
|
+
instructions.push(
|
|
1466
|
+
StakePoolInstruction.createTokenMetadata({
|
|
1467
|
+
programId: stakePoolProgramId,
|
|
1468
|
+
stakePool: stakePoolAddress,
|
|
1469
|
+
poolMint: stakePool.account.data.poolMint,
|
|
1470
|
+
payer,
|
|
1471
|
+
manager,
|
|
1472
|
+
tokenMetadata,
|
|
1473
|
+
withdrawAuthority,
|
|
1474
|
+
name,
|
|
1475
|
+
symbol,
|
|
1476
|
+
uri,
|
|
1477
|
+
}),
|
|
1478
|
+
)
|
|
1479
|
+
|
|
1480
|
+
return {
|
|
1481
|
+
instructions,
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Creates instructions required to update pool token metadata.
|
|
1487
|
+
*/
|
|
1488
|
+
export async function updatePoolTokenMetadata(
|
|
1489
|
+
connection: Connection,
|
|
1490
|
+
stakePoolAddress: PublicKey,
|
|
1491
|
+
name: string,
|
|
1492
|
+
symbol: string,
|
|
1493
|
+
uri: string,
|
|
1494
|
+
) {
|
|
1495
|
+
const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
|
|
1496
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
1497
|
+
|
|
1498
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
|
|
1499
|
+
stakePoolProgramId,
|
|
1500
|
+
stakePoolAddress,
|
|
1501
|
+
)
|
|
1502
|
+
|
|
1503
|
+
const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint)
|
|
1504
|
+
|
|
1505
|
+
const instructions: TransactionInstruction[] = []
|
|
1506
|
+
instructions.push(
|
|
1507
|
+
StakePoolInstruction.updateTokenMetadata({
|
|
1508
|
+
programId: stakePoolProgramId,
|
|
1509
|
+
stakePool: stakePoolAddress,
|
|
1510
|
+
manager: stakePool.account.data.manager,
|
|
1511
|
+
tokenMetadata,
|
|
1512
|
+
withdrawAuthority,
|
|
1513
|
+
name,
|
|
1514
|
+
symbol,
|
|
1515
|
+
uri,
|
|
1516
|
+
}),
|
|
1517
|
+
)
|
|
1518
|
+
|
|
1519
|
+
return {
|
|
1520
|
+
instructions,
|
|
1521
|
+
}
|
|
1522
|
+
}
|