@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/src/layouts.ts ADDED
@@ -0,0 +1,248 @@
1
+ import { PublicKey } from '@solana/web3.js'
2
+ import BN from 'bn.js'
3
+ import { Layout as LayoutCls, struct, u8, u32 } from 'buffer-layout'
4
+ import {
5
+ coerce,
6
+ enums,
7
+ Infer,
8
+ instance,
9
+ nullable,
10
+ number,
11
+ optional,
12
+ string,
13
+ type,
14
+ } from 'superstruct'
15
+ import { Layout, option, publicKey, u64, vec } from './codecs'
16
+
17
+ export interface Fee {
18
+ denominator: BN
19
+ numerator: BN
20
+ }
21
+
22
+ const feeFields = [u64('denominator'), u64('numerator')]
23
+
24
+ export enum AccountType {
25
+ Uninitialized,
26
+ StakePool,
27
+ ValidatorList,
28
+ }
29
+
30
+ export const BigNumFromString = coerce(instance(BN), string(), (value) => {
31
+ if (typeof value === 'string') {
32
+ return new BN(value, 10)
33
+ }
34
+ throw new Error('invalid big num')
35
+ })
36
+
37
+ export const PublicKeyFromString = coerce(
38
+ instance(PublicKey),
39
+ string(),
40
+ value => new PublicKey(value),
41
+ )
42
+
43
+ export class FutureEpochLayout<T> extends LayoutCls<T | null> {
44
+ layout: Layout<T>
45
+ discriminator: Layout<number>
46
+
47
+ constructor(layout: Layout<T>, property?: string) {
48
+ super(-1, property)
49
+ this.layout = layout
50
+ this.discriminator = u8()
51
+ }
52
+
53
+ encode(src: T | null, b: Buffer, offset = 0): number {
54
+ if (src === null || src === undefined) {
55
+ return this.discriminator.encode(0, b, offset)
56
+ }
57
+ // This isn't right, but we don't typically encode outside of tests
58
+ this.discriminator.encode(2, b, offset)
59
+ return this.layout.encode(src, b, offset + 1) + 1
60
+ }
61
+
62
+ decode(b: Buffer, offset = 0): T | null {
63
+ const discriminator = this.discriminator.decode(b, offset)
64
+ if (discriminator === 0) {
65
+ return null
66
+ } else if (discriminator === 1 || discriminator === 2) {
67
+ return this.layout.decode(b, offset + 1)
68
+ }
69
+ throw new Error(`Invalid future epoch ${this.property}`)
70
+ }
71
+
72
+ getSpan(b: Buffer, offset = 0): number {
73
+ const discriminator = this.discriminator.decode(b, offset)
74
+ if (discriminator === 0) {
75
+ return 1
76
+ } else if (discriminator === 1 || discriminator === 2) {
77
+ return this.layout.getSpan(b, offset + 1) + 1
78
+ }
79
+ throw new Error(`Invalid future epoch ${this.property}`)
80
+ }
81
+ }
82
+
83
+ export function futureEpoch<T>(layout: Layout<T>, property?: string): LayoutCls<T | null> {
84
+ return new FutureEpochLayout<T>(layout, property)
85
+ }
86
+
87
+ export type StakeAccountType = Infer<typeof StakeAccountType>
88
+ export const StakeAccountType = enums(['uninitialized', 'initialized', 'delegated', 'rewardsPool'])
89
+
90
+ export type StakeMeta = Infer<typeof StakeMeta>
91
+ export const StakeMeta = type({
92
+ rentExemptReserve: BigNumFromString,
93
+ authorized: type({
94
+ staker: PublicKeyFromString,
95
+ withdrawer: PublicKeyFromString,
96
+ }),
97
+ lockup: type({
98
+ unixTimestamp: number(),
99
+ epoch: number(),
100
+ custodian: PublicKeyFromString,
101
+ }),
102
+ })
103
+
104
+ export type StakeAccountInfo = Infer<typeof StakeAccountInfo>
105
+ export const StakeAccountInfo = type({
106
+ meta: StakeMeta,
107
+ stake: nullable(
108
+ type({
109
+ delegation: type({
110
+ voter: PublicKeyFromString,
111
+ stake: BigNumFromString,
112
+ activationEpoch: BigNumFromString,
113
+ deactivationEpoch: BigNumFromString,
114
+ warmupCooldownRate: number(),
115
+ }),
116
+ creditsObserved: number(),
117
+ }),
118
+ ),
119
+ })
120
+
121
+ export type StakeAccount = Infer<typeof StakeAccount>
122
+ export const StakeAccount = type({
123
+ type: StakeAccountType,
124
+ info: optional(StakeAccountInfo),
125
+ })
126
+ export interface Lockup {
127
+ unixTimestamp: BN
128
+ epoch: BN
129
+ custodian: PublicKey
130
+ }
131
+
132
+ export interface StakePool {
133
+ accountType: AccountType
134
+ manager: PublicKey
135
+ staker: PublicKey
136
+ stakeDepositAuthority: PublicKey
137
+ stakeWithdrawBumpSeed: number
138
+ validatorList: PublicKey
139
+ reserveStake: PublicKey
140
+ poolMint: PublicKey
141
+ managerFeeAccount: PublicKey
142
+ tokenProgramId: PublicKey
143
+ totalLamports: BN
144
+ poolTokenSupply: BN
145
+ lastUpdateEpoch: BN
146
+ lockup: Lockup
147
+ epochFee: Fee
148
+ nextEpochFee?: Fee | undefined
149
+ preferredDepositValidatorVoteAddress?: PublicKey | undefined
150
+ preferredWithdrawValidatorVoteAddress?: PublicKey | undefined
151
+ stakeDepositFee: Fee
152
+ stakeWithdrawalFee: Fee
153
+ nextStakeWithdrawalFee?: Fee | undefined
154
+ stakeReferralFee: number
155
+ solDepositAuthority?: PublicKey | undefined
156
+ solDepositFee: Fee
157
+ solReferralFee: number
158
+ solWithdrawAuthority?: PublicKey | undefined
159
+ solWithdrawalFee: Fee
160
+ nextSolWithdrawalFee?: Fee | undefined
161
+ lastEpochPoolTokenSupply: BN
162
+ lastEpochTotalLamports: BN
163
+ }
164
+
165
+ export const StakePoolLayout = struct<StakePool>([
166
+ u8('accountType'),
167
+ publicKey('manager'),
168
+ publicKey('staker'),
169
+ publicKey('stakeDepositAuthority'),
170
+ u8('stakeWithdrawBumpSeed'),
171
+ publicKey('validatorList'),
172
+ publicKey('reserveStake'),
173
+ publicKey('poolMint'),
174
+ publicKey('managerFeeAccount'),
175
+ publicKey('tokenProgramId'),
176
+ u64('totalLamports'),
177
+ u64('poolTokenSupply'),
178
+ u64('lastUpdateEpoch'),
179
+ struct([u64('unixTimestamp'), u64('epoch'), publicKey('custodian')], 'lockup'),
180
+ struct(feeFields, 'epochFee'),
181
+ futureEpoch(struct(feeFields), 'nextEpochFee'),
182
+ option(publicKey(), 'preferredDepositValidatorVoteAddress'),
183
+ option(publicKey(), 'preferredWithdrawValidatorVoteAddress'),
184
+ struct(feeFields, 'stakeDepositFee'),
185
+ struct(feeFields, 'stakeWithdrawalFee'),
186
+ futureEpoch(struct(feeFields), 'nextStakeWithdrawalFee'),
187
+ u8('stakeReferralFee'),
188
+ option(publicKey(), 'solDepositAuthority'),
189
+ struct(feeFields, 'solDepositFee'),
190
+ u8('solReferralFee'),
191
+ option(publicKey(), 'solWithdrawAuthority'),
192
+ struct(feeFields, 'solWithdrawalFee'),
193
+ futureEpoch(struct(feeFields), 'nextSolWithdrawalFee'),
194
+ u64('lastEpochPoolTokenSupply'),
195
+ u64('lastEpochTotalLamports'),
196
+ ])
197
+
198
+ export enum ValidatorStakeInfoStatus {
199
+ Active,
200
+ DeactivatingTransient,
201
+ ReadyForRemoval,
202
+ }
203
+
204
+ export interface ValidatorStakeInfo {
205
+ status: ValidatorStakeInfoStatus
206
+ voteAccountAddress: PublicKey
207
+ activeStakeLamports: BN
208
+ transientStakeLamports: BN
209
+ transientSeedSuffixStart: BN
210
+ transientSeedSuffixEnd: BN
211
+ lastUpdateEpoch: BN
212
+ }
213
+
214
+ export const ValidatorStakeInfoLayout = struct<ValidatorStakeInfo>([
215
+ /// Amount of active stake delegated to this validator
216
+ /// Note that if `last_update_epoch` does not match the current epoch then
217
+ /// this field may not be accurate
218
+ u64('activeStakeLamports'),
219
+ /// Amount of transient stake delegated to this validator
220
+ /// Note that if `last_update_epoch` does not match the current epoch then
221
+ /// this field may not be accurate
222
+ u64('transientStakeLamports'),
223
+ /// Last epoch the active and transient stake lamports fields were updated
224
+ u64('lastUpdateEpoch'),
225
+ /// Start of the validator transient account seed suffixes
226
+ u64('transientSeedSuffixStart'),
227
+ /// End of the validator transient account seed suffixes
228
+ u64('transientSeedSuffixEnd'),
229
+ /// Status of the validator stake account
230
+ u8('status'),
231
+ /// Validator vote account address
232
+ publicKey('voteAccountAddress'),
233
+ ])
234
+
235
+ export interface ValidatorList {
236
+ /// Account type, must be ValidatorList currently
237
+ accountType: number
238
+ /// Maximum allowable number of validators
239
+ maxValidators: number
240
+ /// List of stake info for each validator in the pool
241
+ validators: ValidatorStakeInfo[]
242
+ }
243
+
244
+ export const ValidatorListLayout = struct<ValidatorList>([
245
+ u8('accountType'),
246
+ u32('maxValidators'),
247
+ vec(ValidatorStakeInfoLayout, 'validators'),
248
+ ])
@@ -0,0 +1,29 @@
1
+ declare module 'buffer-layout' {
2
+ export class Layout<T = any> {
3
+ span: number
4
+ property?: string
5
+ constructor(span: number, property?: string)
6
+ decode(b: Buffer | undefined, offset?: number): T
7
+ encode(src: T, b: Buffer, offset?: number): number
8
+ getSpan(b: Buffer, offset?: number): number
9
+ replicate(name: string): this
10
+ }
11
+ export function struct<T>(
12
+ fields: Layout<any>[],
13
+ property?: string,
14
+ decodePrefixes?: boolean,
15
+ ): Layout<T>
16
+ export function seq<T>(
17
+ elementLayout: Layout<T>,
18
+ count: number | Layout<number>,
19
+ property?: string,
20
+ ): Layout<T[]>
21
+ export function offset<T>(layout: Layout<T>, offset?: number, property?: string): Layout<T>
22
+ export function blob(length: number | Layout<number>, property?: string): Layout<Buffer>
23
+ export function s32(property?: string): Layout<number>
24
+ export function u32(property?: string): Layout<number>
25
+ export function s16(property?: string): Layout<number>
26
+ export function u16(property?: string): Layout<number>
27
+ export function s8(property?: string): Layout<number>
28
+ export function u8(property?: string): Layout<number>
29
+ }
@@ -0,0 +1,12 @@
1
+ export * from './instruction'
2
+ export * from './math'
3
+ export * from './program-address'
4
+ export * from './stake'
5
+
6
+ export function arrayChunk(array: any[], size: number): any[] {
7
+ const result = []
8
+ for (let i = 0; i < array.length; i += size) {
9
+ result.push(array.slice(i, i + size))
10
+ }
11
+ return result
12
+ }
@@ -0,0 +1,46 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import * as BufferLayout from '@solana/buffer-layout'
3
+
4
+ /**
5
+ * @internal
6
+ */
7
+ export type InstructionType = {
8
+ /** The Instruction index (from solana upstream program) */
9
+ index: number
10
+ /** The BufferLayout to use to build data */
11
+ layout: BufferLayout.Layout<any>
12
+ }
13
+
14
+ /**
15
+ * Populate a buffer of instruction data using an InstructionType
16
+ * @internal
17
+ */
18
+ export function encodeData(type: InstructionType, fields?: any): Buffer {
19
+ const allocLength = type.layout.span
20
+ const data = Buffer.alloc(allocLength)
21
+ const layoutFields = Object.assign({ instruction: type.index }, fields)
22
+ type.layout.encode(layoutFields, data)
23
+
24
+ return data
25
+ }
26
+
27
+ /**
28
+ * Decode instruction data buffer using an InstructionType
29
+ * @internal
30
+ */
31
+ export function decodeData(type: InstructionType, buffer: Buffer): any {
32
+ let data
33
+ try {
34
+ data = type.layout.decode(buffer)
35
+ } catch (err) {
36
+ throw new Error(`invalid instruction; ${err}`)
37
+ }
38
+
39
+ if (data.instruction !== type.index) {
40
+ throw new Error(
41
+ `invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`,
42
+ )
43
+ }
44
+
45
+ return data
46
+ }
@@ -0,0 +1,29 @@
1
+ import { LAMPORTS_PER_SOL } from '@solana/web3.js'
2
+ import BN from 'bn.js'
3
+
4
+ export function solToLamports(amount: number): number {
5
+ if (isNaN(amount)) {
6
+ return Number(0)
7
+ }
8
+ return Number(amount * LAMPORTS_PER_SOL)
9
+ }
10
+
11
+ export function lamportsToSol(lamports: number | BN | bigint): number {
12
+ if (typeof lamports === 'number') {
13
+ return Math.abs(lamports) / LAMPORTS_PER_SOL
14
+ }
15
+ if (typeof lamports === 'bigint') {
16
+ return Math.abs(Number(lamports)) / LAMPORTS_PER_SOL
17
+ }
18
+
19
+ let signMultiplier = 1
20
+ if (lamports.isNeg()) {
21
+ signMultiplier = -1
22
+ }
23
+
24
+ const absLamports = lamports.abs()
25
+ const lamportsString = absLamports.toString(10).padStart(10, '0')
26
+ const splitIndex = lamportsString.length - 9
27
+ const solString = `${lamportsString.slice(0, splitIndex)}.${lamportsString.slice(splitIndex)}`
28
+ return signMultiplier * Number.parseFloat(solString)
29
+ }
@@ -0,0 +1,125 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { PublicKey } from '@solana/web3.js'
3
+ import BN from 'bn.js'
4
+ import {
5
+ EPHEMERAL_STAKE_SEED_PREFIX,
6
+ METADATA_PROGRAM_ID,
7
+ TRANSIENT_STAKE_SEED_PREFIX,
8
+ USER_STAKE_SEED_PREFIX,
9
+ } from '../constants'
10
+
11
+ /**
12
+ * Generates the wSOL transient program address for the stake pool
13
+ */
14
+ export function findWsolTransientProgramAddress(
15
+ programId: PublicKey,
16
+ userPubkey: PublicKey,
17
+ ) {
18
+ const [publicKey] = PublicKey.findProgramAddressSync(
19
+ [Buffer.from('transient_wsol'), userPubkey.toBuffer()],
20
+ programId,
21
+ )
22
+ return publicKey
23
+ }
24
+
25
+ /**
26
+ * Generates the withdraw authority program address for the stake pool
27
+ */
28
+ export async function findWithdrawAuthorityProgramAddress(
29
+ programId: PublicKey,
30
+ stakePoolAddress: PublicKey,
31
+ ) {
32
+ const [publicKey] = PublicKey.findProgramAddressSync(
33
+ [stakePoolAddress.toBuffer(), Buffer.from('withdraw')],
34
+ programId,
35
+ )
36
+ return publicKey
37
+ }
38
+
39
+ /**
40
+ * Generates the stake program address for a validator's vote account
41
+ */
42
+ export async function findStakeProgramAddress(
43
+ programId: PublicKey,
44
+ voteAccountAddress: PublicKey,
45
+ stakePoolAddress: PublicKey,
46
+ seed?: number,
47
+ ) {
48
+ const [publicKey] = PublicKey.findProgramAddressSync(
49
+ [
50
+ voteAccountAddress.toBuffer(),
51
+ stakePoolAddress.toBuffer(),
52
+ seed ? new BN(seed).toArrayLike(Buffer, 'le', 4) : Buffer.alloc(0),
53
+ ],
54
+ programId,
55
+ )
56
+ return publicKey
57
+ }
58
+
59
+ /**
60
+ * Generates the stake program address for a validator's vote account
61
+ */
62
+ export async function findTransientStakeProgramAddress(
63
+ programId: PublicKey,
64
+ voteAccountAddress: PublicKey,
65
+ stakePoolAddress: PublicKey,
66
+ seed: BN,
67
+ ) {
68
+ const [publicKey] = PublicKey.findProgramAddressSync(
69
+ [
70
+ TRANSIENT_STAKE_SEED_PREFIX,
71
+ voteAccountAddress.toBuffer(),
72
+ stakePoolAddress.toBuffer(),
73
+ seed.toArrayLike(Buffer, 'le', 8),
74
+ ],
75
+ programId,
76
+ )
77
+ return publicKey
78
+ }
79
+
80
+ /**
81
+ * Generates the ephemeral program address for stake pool redelegation
82
+ */
83
+ export async function findEphemeralStakeProgramAddress(
84
+ programId: PublicKey,
85
+ stakePoolAddress: PublicKey,
86
+ seed: BN,
87
+ ) {
88
+ const [publicKey] = PublicKey.findProgramAddressSync(
89
+ [EPHEMERAL_STAKE_SEED_PREFIX, stakePoolAddress.toBuffer(), seed.toArrayLike(Buffer, 'le', 8)],
90
+ programId,
91
+ )
92
+ return publicKey
93
+ }
94
+
95
+ /**
96
+ * Generates the metadata program address for the stake pool
97
+ */
98
+ export function findMetadataAddress(stakePoolMintAddress: PublicKey) {
99
+ const [publicKey] = PublicKey.findProgramAddressSync(
100
+ [Buffer.from('metadata'), METADATA_PROGRAM_ID.toBuffer(), stakePoolMintAddress.toBuffer()],
101
+ METADATA_PROGRAM_ID,
102
+ )
103
+ return publicKey
104
+ }
105
+
106
+ /**
107
+ * Generates the user stake account PDA for session-based withdrawals.
108
+ * The PDA is derived from the user's wallet and a unique seed.
109
+ */
110
+ export function findUserStakeProgramAddress(
111
+ programId: PublicKey,
112
+ userWallet: PublicKey,
113
+ seed: BN | number,
114
+ ) {
115
+ const seedBN = typeof seed === 'number' ? new BN(seed) : seed
116
+ const [publicKey] = PublicKey.findProgramAddressSync(
117
+ [
118
+ USER_STAKE_SEED_PREFIX,
119
+ userWallet.toBuffer(),
120
+ seedBN.toArrayLike(Buffer, 'le', 8),
121
+ ],
122
+ programId,
123
+ )
124
+ return publicKey
125
+ }