@ignitionfi/fogo-stake-pool 1.0.0
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 +13 -0
- package/dist/index.browser.cjs.js +2898 -0
- package/dist/index.browser.cjs.js.map +1 -0
- package/dist/index.browser.esm.js +2843 -0
- package/dist/index.browser.esm.js.map +1 -0
- package/dist/index.cjs.js +2898 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.esm.js +2843 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.iife.js +24083 -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 +381 -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 +31 -0
- package/dist/utils/stake.d.ts +29 -0
- package/package.json +90 -0
- package/src/codecs.ts +159 -0
- package/src/constants.ts +30 -0
- package/src/index.ts +1823 -0
- package/src/instructions.ts +1439 -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 +125 -0
- package/src/utils/stake.ts +233 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Connection,
|
|
3
|
+
Keypair,
|
|
4
|
+
PublicKey,
|
|
5
|
+
StakeProgram,
|
|
6
|
+
SystemProgram,
|
|
7
|
+
TransactionInstruction,
|
|
8
|
+
} from '@solana/web3.js'
|
|
9
|
+
import BN from 'bn.js'
|
|
10
|
+
import { MINIMUM_ACTIVE_STAKE } from '../constants'
|
|
11
|
+
|
|
12
|
+
import { getStakePoolProgramId, WithdrawAccount } from '../index'
|
|
13
|
+
import {
|
|
14
|
+
Fee,
|
|
15
|
+
StakePool,
|
|
16
|
+
ValidatorList,
|
|
17
|
+
ValidatorListLayout,
|
|
18
|
+
ValidatorStakeInfoStatus,
|
|
19
|
+
} from '../layouts'
|
|
20
|
+
import { lamportsToSol } from './math'
|
|
21
|
+
import { findStakeProgramAddress, findTransientStakeProgramAddress } from './program-address'
|
|
22
|
+
|
|
23
|
+
export async function getValidatorListAccount(connection: Connection, pubkey: PublicKey) {
|
|
24
|
+
const account = await connection.getAccountInfo(pubkey)
|
|
25
|
+
if (!account) {
|
|
26
|
+
throw new Error('Invalid validator list account')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
pubkey,
|
|
31
|
+
account: {
|
|
32
|
+
data: ValidatorListLayout.decode(account?.data) as ValidatorList,
|
|
33
|
+
executable: account.executable,
|
|
34
|
+
lamports: account.lamports,
|
|
35
|
+
owner: account.owner,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ValidatorAccount {
|
|
41
|
+
type: 'preferred' | 'active' | 'transient' | 'reserve'
|
|
42
|
+
voteAddress?: PublicKey | undefined
|
|
43
|
+
stakeAddress: PublicKey
|
|
44
|
+
lamports: BN
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function prepareWithdrawAccounts(
|
|
48
|
+
connection: Connection,
|
|
49
|
+
stakePool: StakePool,
|
|
50
|
+
stakePoolAddress: PublicKey,
|
|
51
|
+
amount: BN,
|
|
52
|
+
compareFn?: (a: ValidatorAccount, b: ValidatorAccount) => number,
|
|
53
|
+
skipFee?: boolean,
|
|
54
|
+
): Promise<WithdrawAccount[]> {
|
|
55
|
+
const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
|
|
56
|
+
const validatorListAcc = await connection.getAccountInfo(stakePool.validatorList)
|
|
57
|
+
const validatorList = ValidatorListLayout.decode(validatorListAcc?.data) as ValidatorList
|
|
58
|
+
|
|
59
|
+
if (!validatorList?.validators || validatorList?.validators.length === 0) {
|
|
60
|
+
throw new Error('No accounts found')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const minBalanceForRentExemption = await connection.getMinimumBalanceForRentExemption(
|
|
64
|
+
StakeProgram.space,
|
|
65
|
+
)
|
|
66
|
+
const minBalance = new BN(minBalanceForRentExemption + MINIMUM_ACTIVE_STAKE)
|
|
67
|
+
|
|
68
|
+
let accounts = [] as Array<{
|
|
69
|
+
type: 'preferred' | 'active' | 'transient' | 'reserve'
|
|
70
|
+
voteAddress?: PublicKey | undefined
|
|
71
|
+
stakeAddress: PublicKey
|
|
72
|
+
lamports: BN
|
|
73
|
+
}>
|
|
74
|
+
|
|
75
|
+
// Prepare accounts
|
|
76
|
+
for (const validator of validatorList.validators) {
|
|
77
|
+
if (validator.status !== ValidatorStakeInfoStatus.Active) {
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const stakeAccountAddress = await findStakeProgramAddress(
|
|
82
|
+
stakePoolProgramId,
|
|
83
|
+
validator.voteAccountAddress,
|
|
84
|
+
stakePoolAddress,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
// For active stake accounts, subtract the minimum balance that must remain
|
|
88
|
+
// to allow for merges and maintain rent exemption
|
|
89
|
+
const availableActiveLamports = validator.activeStakeLamports.sub(minBalance)
|
|
90
|
+
if (availableActiveLamports.gt(new BN(0))) {
|
|
91
|
+
const isPreferred = stakePool?.preferredWithdrawValidatorVoteAddress?.equals(
|
|
92
|
+
validator.voteAccountAddress,
|
|
93
|
+
)
|
|
94
|
+
accounts.push({
|
|
95
|
+
type: isPreferred ? 'preferred' : 'active',
|
|
96
|
+
voteAddress: validator.voteAccountAddress,
|
|
97
|
+
stakeAddress: stakeAccountAddress,
|
|
98
|
+
lamports: availableActiveLamports,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const transientStakeLamports = validator.transientStakeLamports.sub(minBalance)
|
|
103
|
+
if (transientStakeLamports.gt(new BN(0))) {
|
|
104
|
+
const transientStakeAccountAddress = await findTransientStakeProgramAddress(
|
|
105
|
+
stakePoolProgramId,
|
|
106
|
+
validator.voteAccountAddress,
|
|
107
|
+
stakePoolAddress,
|
|
108
|
+
validator.transientSeedSuffixStart,
|
|
109
|
+
)
|
|
110
|
+
accounts.push({
|
|
111
|
+
type: 'transient',
|
|
112
|
+
voteAddress: validator.voteAccountAddress,
|
|
113
|
+
stakeAddress: transientStakeAccountAddress,
|
|
114
|
+
lamports: transientStakeLamports,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sort from highest to lowest balance
|
|
120
|
+
accounts = accounts.sort(compareFn || ((a, b) => b.lamports.sub(a.lamports).toNumber()))
|
|
121
|
+
|
|
122
|
+
const reserveStake = await connection.getAccountInfo(stakePool.reserveStake)
|
|
123
|
+
const reserveStakeBalance = new BN((reserveStake?.lamports ?? 0) - minBalanceForRentExemption)
|
|
124
|
+
if (reserveStakeBalance.gt(new BN(0))) {
|
|
125
|
+
accounts.push({
|
|
126
|
+
type: 'reserve',
|
|
127
|
+
stakeAddress: stakePool.reserveStake,
|
|
128
|
+
lamports: reserveStakeBalance,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Prepare the list of accounts to withdraw from
|
|
133
|
+
const withdrawFrom: WithdrawAccount[] = []
|
|
134
|
+
let remainingAmount = new BN(amount)
|
|
135
|
+
|
|
136
|
+
const fee = stakePool.stakeWithdrawalFee
|
|
137
|
+
const inverseFee: Fee = {
|
|
138
|
+
numerator: fee.denominator.sub(fee.numerator),
|
|
139
|
+
denominator: fee.denominator,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const type of ['preferred', 'active', 'transient', 'reserve']) {
|
|
143
|
+
const filteredAccounts = accounts.filter(a => a.type === type)
|
|
144
|
+
|
|
145
|
+
for (const { stakeAddress, voteAddress, lamports } of filteredAccounts) {
|
|
146
|
+
if (lamports.lte(minBalance) && type === 'transient') {
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let availableForWithdrawal = calcPoolTokensForDeposit(stakePool, lamports)
|
|
151
|
+
|
|
152
|
+
if (!skipFee && !inverseFee.numerator.isZero()) {
|
|
153
|
+
availableForWithdrawal = availableForWithdrawal
|
|
154
|
+
.mul(inverseFee.denominator)
|
|
155
|
+
.div(inverseFee.numerator)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const poolAmount = BN.min(availableForWithdrawal, remainingAmount)
|
|
159
|
+
if (poolAmount.lte(new BN(0))) {
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Those accounts will be withdrawn completely with `claim` instruction
|
|
164
|
+
withdrawFrom.push({ stakeAddress, voteAddress, poolAmount })
|
|
165
|
+
remainingAmount = remainingAmount.sub(poolAmount)
|
|
166
|
+
|
|
167
|
+
if (remainingAmount.isZero()) {
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (remainingAmount.isZero()) {
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Not enough stake to withdraw the specified amount
|
|
178
|
+
if (remainingAmount.gt(new BN(0))) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`No stake accounts found in this pool with enough balance to withdraw ${lamportsToSol(
|
|
181
|
+
amount,
|
|
182
|
+
)} pool tokens.`,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return withdrawFrom
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Calculate the pool tokens that should be minted for a deposit of `stakeLamports`
|
|
191
|
+
*/
|
|
192
|
+
export function calcPoolTokensForDeposit(stakePool: StakePool, stakeLamports: BN): BN {
|
|
193
|
+
if (stakePool.poolTokenSupply.isZero() || stakePool.totalLamports.isZero()) {
|
|
194
|
+
return stakeLamports
|
|
195
|
+
}
|
|
196
|
+
const numerator = stakeLamports.mul(stakePool.poolTokenSupply)
|
|
197
|
+
return numerator.div(stakePool.totalLamports)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Calculate lamports amount on withdrawal
|
|
202
|
+
*/
|
|
203
|
+
export function calcLamportsWithdrawAmount(stakePool: StakePool, poolTokens: BN): BN {
|
|
204
|
+
const numerator = poolTokens.mul(stakePool.totalLamports)
|
|
205
|
+
const denominator = stakePool.poolTokenSupply
|
|
206
|
+
if (numerator.lt(denominator)) {
|
|
207
|
+
return new BN(0)
|
|
208
|
+
}
|
|
209
|
+
return numerator.div(denominator)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function newStakeAccount(
|
|
213
|
+
feePayer: PublicKey,
|
|
214
|
+
instructions: TransactionInstruction[],
|
|
215
|
+
lamports: number,
|
|
216
|
+
): Keypair {
|
|
217
|
+
// Account for tokens not specified, creating one
|
|
218
|
+
const stakeReceiverKeypair = Keypair.generate()
|
|
219
|
+
console.log(`Creating account to receive stake ${stakeReceiverKeypair.publicKey}`)
|
|
220
|
+
|
|
221
|
+
instructions.push(
|
|
222
|
+
// Creating new account
|
|
223
|
+
SystemProgram.createAccount({
|
|
224
|
+
fromPubkey: feePayer,
|
|
225
|
+
newAccountPubkey: stakeReceiverKeypair.publicKey,
|
|
226
|
+
lamports,
|
|
227
|
+
space: StakeProgram.space,
|
|
228
|
+
programId: StakeProgram.programId,
|
|
229
|
+
}),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return stakeReceiverKeypair
|
|
233
|
+
}
|