@kamino-finance/klend-sdk 7.1.1 → 7.1.3
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/dist/@codegen/unstaking_pool/accounts/PoolState.d.ts +53 -0
- package/dist/@codegen/unstaking_pool/accounts/PoolState.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/accounts/PoolState.js +167 -0
- package/dist/@codegen/unstaking_pool/accounts/PoolState.js.map +1 -0
- package/dist/@codegen/unstaking_pool/accounts/UnstakeTicket.d.ts +41 -0
- package/dist/@codegen/unstaking_pool/accounts/UnstakeTicket.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/accounts/UnstakeTicket.js +143 -0
- package/dist/@codegen/unstaking_pool/accounts/UnstakeTicket.js.map +1 -0
- package/dist/@codegen/unstaking_pool/accounts/index.d.ts +5 -0
- package/dist/@codegen/unstaking_pool/accounts/index.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/accounts/index.js +8 -0
- package/dist/@codegen/unstaking_pool/accounts/index.js.map +1 -0
- package/dist/@codegen/unstaking_pool/errors/anchor.d.ts +435 -0
- package/dist/@codegen/unstaking_pool/errors/anchor.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/errors/anchor.js +767 -0
- package/dist/@codegen/unstaking_pool/errors/anchor.js.map +1 -0
- package/dist/@codegen/unstaking_pool/errors/custom.d.ts +259 -0
- package/dist/@codegen/unstaking_pool/errors/custom.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/errors/custom.js +458 -0
- package/dist/@codegen/unstaking_pool/errors/custom.js.map +1 -0
- package/dist/@codegen/unstaking_pool/errors/index.d.ts +6 -0
- package/dist/@codegen/unstaking_pool/errors/index.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/errors/index.js +86 -0
- package/dist/@codegen/unstaking_pool/errors/index.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/burn.d.ts +21 -0
- package/dist/@codegen/unstaking_pool/instructions/burn.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/burn.js +67 -0
- package/dist/@codegen/unstaking_pool/instructions/burn.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/changeTicketAuthority.d.ts +8 -0
- package/dist/@codegen/unstaking_pool/instructions/changeTicketAuthority.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/changeTicketAuthority.js +16 -0
- package/dist/@codegen/unstaking_pool/instructions/changeTicketAuthority.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/collect.d.ts +16 -0
- package/dist/@codegen/unstaking_pool/instructions/collect.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/collect.js +24 -0
- package/dist/@codegen/unstaking_pool/instructions/collect.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/index.d.ts +17 -0
- package/dist/@codegen/unstaking_pool/instructions/index.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/index.js +20 -0
- package/dist/@codegen/unstaking_pool/instructions/index.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/initializePool.d.ts +14 -0
- package/dist/@codegen/unstaking_pool/instructions/initializePool.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/initializePool.js +22 -0
- package/dist/@codegen/unstaking_pool/instructions/initializePool.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/mint.d.ts +24 -0
- package/dist/@codegen/unstaking_pool/instructions/mint.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/mint.js +74 -0
- package/dist/@codegen/unstaking_pool/instructions/mint.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/splitTicket.d.ts +15 -0
- package/dist/@codegen/unstaking_pool/instructions/splitTicket.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/splitTicket.js +62 -0
- package/dist/@codegen/unstaking_pool/instructions/splitTicket.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/updateAdmin.d.ts +7 -0
- package/dist/@codegen/unstaking_pool/instructions/updateAdmin.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/updateAdmin.js +19 -0
- package/dist/@codegen/unstaking_pool/instructions/updateAdmin.js.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/updatePoolConfig.d.ts +13 -0
- package/dist/@codegen/unstaking_pool/instructions/updatePoolConfig.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/instructions/updatePoolConfig.js +60 -0
- package/dist/@codegen/unstaking_pool/instructions/updatePoolConfig.js.map +1 -0
- package/dist/@codegen/unstaking_pool/programId.d.ts +4 -0
- package/dist/@codegen/unstaking_pool/programId.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/programId.js +9 -0
- package/dist/@codegen/unstaking_pool/programId.js.map +1 -0
- package/dist/@codegen/unstaking_pool/types/PoolConfigField.d.ts +45 -0
- package/dist/@codegen/unstaking_pool/types/PoolConfigField.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/types/PoolConfigField.js +132 -0
- package/dist/@codegen/unstaking_pool/types/PoolConfigField.js.map +1 -0
- package/dist/@codegen/unstaking_pool/types/index.d.ts +5 -0
- package/dist/@codegen/unstaking_pool/types/index.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/types/index.js +39 -0
- package/dist/@codegen/unstaking_pool/types/index.js.map +1 -0
- package/dist/@codegen/unstaking_pool/utils/borshAddress.d.ts +4 -0
- package/dist/@codegen/unstaking_pool/utils/borshAddress.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/utils/borshAddress.js +30 -0
- package/dist/@codegen/unstaking_pool/utils/borshAddress.js.map +1 -0
- package/dist/@codegen/unstaking_pool/utils/index.d.ts +2 -0
- package/dist/@codegen/unstaking_pool/utils/index.d.ts.map +1 -0
- package/dist/@codegen/unstaking_pool/utils/index.js +18 -0
- package/dist/@codegen/unstaking_pool/utils/index.js.map +1 -0
- package/dist/classes/action.d.ts.map +1 -1
- package/dist/classes/action.js +5 -5
- package/dist/classes/action.js.map +1 -1
- package/dist/classes/index.d.ts +5 -0
- package/dist/classes/index.d.ts.map +1 -1
- package/dist/classes/index.js +5 -0
- package/dist/classes/index.js.map +1 -1
- package/dist/classes/manager.d.ts.map +1 -1
- package/dist/classes/manager.js +2 -3
- package/dist/classes/manager.js.map +1 -1
- package/dist/classes/stakePool.d.ts +8 -0
- package/dist/classes/stakePool.d.ts.map +1 -0
- package/dist/classes/stakePool.js +18 -0
- package/dist/classes/stakePool.js.map +1 -0
- package/dist/classes/standardStakePool.d.ts +76 -0
- package/dist/classes/standardStakePool.d.ts.map +1 -0
- package/dist/classes/standardStakePool.js +400 -0
- package/dist/classes/standardStakePool.js.map +1 -0
- package/dist/classes/unstakingPool.d.ts +115 -0
- package/dist/classes/unstakingPool.d.ts.map +1 -0
- package/dist/classes/unstakingPool.js +372 -0
- package/dist/classes/unstakingPool.js.map +1 -0
- package/dist/classes/unstakingPoolTypes.d.ts +10 -0
- package/dist/classes/unstakingPoolTypes.d.ts.map +1 -0
- package/dist/classes/unstakingPoolTypes.js +3 -0
- package/dist/classes/unstakingPoolTypes.js.map +1 -0
- package/dist/classes/vault.d.ts +0 -9
- package/dist/classes/vault.d.ts.map +1 -1
- package/dist/classes/vault.js +6 -41
- package/dist/classes/vault.js.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.js +0 -2
- package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
- package/dist/lending_operations/swap_collateral_operations.js +0 -1
- package/dist/lending_operations/swap_collateral_operations.js.map +1 -1
- package/dist/leverage/calcs.d.ts +2 -27
- package/dist/leverage/calcs.d.ts.map +1 -1
- package/dist/leverage/calcs.js +6 -136
- package/dist/leverage/calcs.js.map +1 -1
- package/dist/leverage/operations.d.ts +8 -9
- package/dist/leverage/operations.d.ts.map +1 -1
- package/dist/leverage/operations.js +72 -228
- package/dist/leverage/operations.js.map +1 -1
- package/dist/leverage/types.d.ts +0 -19
- package/dist/leverage/types.d.ts.map +1 -1
- package/dist/leverage/utils.d.ts +2 -19
- package/dist/leverage/utils.d.ts.map +1 -1
- package/dist/leverage/utils.js +0 -164
- package/dist/leverage/utils.js.map +1 -1
- package/dist/lib.d.ts +1 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +3 -1
- package/dist/lib.js.map +1 -1
- package/dist/utils/lookupTable.d.ts +9 -0
- package/dist/utils/lookupTable.d.ts.map +1 -1
- package/dist/utils/lookupTable.js +37 -0
- package/dist/utils/lookupTable.js.map +1 -1
- package/package.json +4 -2
- package/src/@codegen/unstaking_pool/accounts/PoolState.ts +188 -0
- package/src/@codegen/unstaking_pool/accounts/UnstakeTicket.ts +156 -0
- package/src/@codegen/unstaking_pool/accounts/index.ts +4 -0
- package/src/@codegen/unstaking_pool/errors/anchor.ts +773 -0
- package/src/@codegen/unstaking_pool/errors/custom.ts +477 -0
- package/src/@codegen/unstaking_pool/errors/index.ts +68 -0
- package/src/@codegen/unstaking_pool/instructions/burn.ts +70 -0
- package/src/@codegen/unstaking_pool/instructions/changeTicketAuthority.ts +37 -0
- package/src/@codegen/unstaking_pool/instructions/collect.ts +53 -0
- package/src/@codegen/unstaking_pool/instructions/index.ts +19 -0
- package/src/@codegen/unstaking_pool/instructions/initializePool.ts +49 -0
- package/src/@codegen/unstaking_pool/instructions/mint.ts +80 -0
- package/src/@codegen/unstaking_pool/instructions/splitTicket.ts +59 -0
- package/src/@codegen/unstaking_pool/instructions/updateAdmin.ts +39 -0
- package/src/@codegen/unstaking_pool/instructions/updatePoolConfig.ts +58 -0
- package/src/@codegen/unstaking_pool/programId.ts +9 -0
- package/src/@codegen/unstaking_pool/types/PoolConfigField.ts +121 -0
- package/src/@codegen/unstaking_pool/types/index.ts +12 -0
- package/src/@codegen/unstaking_pool/utils/borshAddress.ts +43 -0
- package/src/@codegen/unstaking_pool/utils/index.ts +1 -0
- package/src/classes/action.ts +10 -5
- package/src/classes/index.ts +5 -0
- package/src/classes/manager.ts +3 -4
- package/src/classes/stakePool.ts +21 -0
- package/src/classes/standardStakePool.ts +487 -0
- package/src/classes/unstakingPool.ts +504 -0
- package/src/classes/unstakingPoolTypes.ts +12 -0
- package/src/classes/vault.ts +43 -56
- package/src/idl/unstaking_pool.json +662 -0
- package/src/lending_operations/repay_with_collateral_operations.ts +0 -2
- package/src/lending_operations/swap_collateral_operations.ts +0 -1
- package/src/leverage/calcs.ts +2 -201
- package/src/leverage/operations.ts +45 -377
- package/src/leverage/types.ts +0 -20
- package/src/leverage/utils.ts +3 -320
- package/src/lib.ts +1 -0
- package/src/utils/lookupTable.ts +48 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import {
|
|
2
|
+
STAKE_POOL_PROGRAM_ID as STAKE_POOL_PROGRAM_ID_LEGACY,
|
|
3
|
+
ValidatorList,
|
|
4
|
+
ValidatorListLayout,
|
|
5
|
+
} from '@solana/spl-stake-pool';
|
|
6
|
+
import { StakePool, StakePoolLayout } from '@solana/spl-stake-pool';
|
|
7
|
+
import { CLOCK_PROGRAM_ID, STAKE_POOL_SIZE, STAKE_PROGRAM_ID } from './unstakingPool';
|
|
8
|
+
import * as borsh from '@coral-xyz/borsh'; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
9
|
+
import BN from 'bn.js';
|
|
10
|
+
import assert from 'assert';
|
|
11
|
+
import {
|
|
12
|
+
Address,
|
|
13
|
+
Base58EncodedBytes,
|
|
14
|
+
fetchEncodedAccount,
|
|
15
|
+
fetchEncodedAccounts,
|
|
16
|
+
generateKeyPairSigner,
|
|
17
|
+
GetAccountInfoApi,
|
|
18
|
+
getAddressEncoder,
|
|
19
|
+
GetBalanceApi,
|
|
20
|
+
GetMultipleAccountsApi,
|
|
21
|
+
GetProgramAccountsApi,
|
|
22
|
+
getProgramDerivedAddress,
|
|
23
|
+
AccountMeta,
|
|
24
|
+
AccountSignerMeta,
|
|
25
|
+
KeyPairSigner,
|
|
26
|
+
Rpc,
|
|
27
|
+
} from '@solana/kit';
|
|
28
|
+
import { fromLegacyPublicKey } from '@solana/compat';
|
|
29
|
+
import { getProgramAccounts } from '../utils';
|
|
30
|
+
import { getSolBalanceInLamports } from '@kamino-finance/farms-sdk/dist/utils';
|
|
31
|
+
import { borshAddress } from '../@codegen/unstaking_pool/utils';
|
|
32
|
+
|
|
33
|
+
export const TRANSIENT_STAKE_SEED_PREFIX = Buffer.from('transient');
|
|
34
|
+
export const STAKE_ACCOUNT_RENT_EXEMPTION: BN = new BN(2_282_880);
|
|
35
|
+
export const STAKE_POOL_PROGRAM_ID: Address = fromLegacyPublicKey(STAKE_POOL_PROGRAM_ID_LEGACY);
|
|
36
|
+
const MINIMUM_ACTIVE_STAKE: BN = new BN(1_000_000);
|
|
37
|
+
// This represents the minimum each validator stake account must have and cannot be withdrawn
|
|
38
|
+
const TRANSIENT_STAKE_ACCOUNT_RENT_EXEMPTION: BN = STAKE_ACCOUNT_RENT_EXEMPTION.add(MINIMUM_ACTIVE_STAKE);
|
|
39
|
+
|
|
40
|
+
export async function getStandardPoolState(rpc: Rpc<GetAccountInfoApi>, address: Address): Promise<StakePool> {
|
|
41
|
+
const accountInfo = await fetchEncodedAccount(rpc, address);
|
|
42
|
+
if (!accountInfo.exists) {
|
|
43
|
+
throw new Error(`Cannot fetch standard stake pool account ${address.toString()}`);
|
|
44
|
+
}
|
|
45
|
+
return StakePoolLayout.decode(Buffer.from(accountInfo.data));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function getValidatorList(rpc: Rpc<GetAccountInfoApi>, address: Address): Promise<ValidatorList> {
|
|
49
|
+
const accountInfo = await fetchEncodedAccount(rpc, address);
|
|
50
|
+
if (!accountInfo.exists) {
|
|
51
|
+
throw new Error(`Cannot fetch standard stake pool account ${address.toString()}`);
|
|
52
|
+
}
|
|
53
|
+
return ValidatorListLayout.decode(Buffer.from(accountInfo.data));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function maybeGetStakedPoolByMint(
|
|
57
|
+
rpc: Rpc<GetProgramAccountsApi>,
|
|
58
|
+
mint: Address
|
|
59
|
+
): Promise<[StakePool, Address] | undefined> {
|
|
60
|
+
const results = await getProgramAccounts(rpc, STAKE_POOL_PROGRAM_ID, STAKE_POOL_SIZE, [
|
|
61
|
+
{ memcmp: { offset: 162n, bytes: mint.toString() as Base58EncodedBytes, encoding: 'base58' } },
|
|
62
|
+
]);
|
|
63
|
+
// There should be only 1 stake pool for mint
|
|
64
|
+
if (results.length === 0) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
if (results.length === 1) {
|
|
68
|
+
return [StakePoolLayout.decode(results[0].data), results[0].address];
|
|
69
|
+
}
|
|
70
|
+
// This should not happen
|
|
71
|
+
throw new Error(`Got ${results.length} stake pools for mint ${mint.toString()} and not sure which one is correct.`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function getStandardPoolMintRemainingAccounts(
|
|
75
|
+
rpc: Rpc<GetAccountInfoApi & GetMultipleAccountsApi & GetBalanceApi>,
|
|
76
|
+
stakedSolPool: StakePool,
|
|
77
|
+
stakedSolPoolPk: Address,
|
|
78
|
+
stakedSolToDeposit: BN
|
|
79
|
+
): Promise<[Array<AccountMeta | AccountSignerMeta>, Array<KeyPairSigner>]> {
|
|
80
|
+
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(STAKE_POOL_PROGRAM_ID, stakedSolPoolPk);
|
|
81
|
+
const remainingAccounts: Array<AccountMeta | AccountSignerMeta> = [
|
|
82
|
+
{ address: stakedSolPoolPk, role: 1 },
|
|
83
|
+
{ address: fromLegacyPublicKey(stakedSolPool.validatorList), role: 1 },
|
|
84
|
+
{ address: withdrawAuthority, role: 1 },
|
|
85
|
+
{ address: fromLegacyPublicKey(stakedSolPool.managerFeeAccount), role: 1 },
|
|
86
|
+
{ address: CLOCK_PROGRAM_ID, role: 0 },
|
|
87
|
+
{ address: STAKE_PROGRAM_ID, role: 0 },
|
|
88
|
+
{ address: STAKE_POOL_PROGRAM_ID, role: 0 },
|
|
89
|
+
];
|
|
90
|
+
const withdrawCandidates = await getWithdrawCandidates(rpc, stakedSolPool, stakedSolPoolPk, stakedSolToDeposit);
|
|
91
|
+
// Each withdraw candidate should also create a new keypair for the stake account
|
|
92
|
+
const withdrawCandidatesTo: KeyPairSigner[] = [];
|
|
93
|
+
for (const withdrawCandidateFrom of withdrawCandidates) {
|
|
94
|
+
remainingAccounts.push({ address: withdrawCandidateFrom, role: 1 });
|
|
95
|
+
const withdrawCandidateTo = await generateKeyPairSigner();
|
|
96
|
+
remainingAccounts.push({ address: withdrawCandidateTo.address, signer: withdrawCandidateTo, role: 3 });
|
|
97
|
+
withdrawCandidatesTo.push(withdrawCandidateTo);
|
|
98
|
+
}
|
|
99
|
+
return [remainingAccounts, withdrawCandidatesTo];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function getAllWithdrawCandidatesSorted(
|
|
103
|
+
rpc: Rpc<GetAccountInfoApi & GetMultipleAccountsApi & GetBalanceApi>,
|
|
104
|
+
stakedSolPool: StakePool,
|
|
105
|
+
stakedSolPoolPk: Address
|
|
106
|
+
): Promise<Array<{ isPreferred: boolean; balance: BN; pk: Address }>> {
|
|
107
|
+
const reserveStake = fromLegacyPublicKey(stakedSolPool.reserveStake);
|
|
108
|
+
const activeValidators: { isPreferred: boolean; balance: BN; pk: Address }[] = [];
|
|
109
|
+
const transientValidators: { isPreferred: boolean; balance: BN; pk: Address }[] = [];
|
|
110
|
+
const validatorList = await getValidatorList(rpc, fromLegacyPublicKey(stakedSolPool.validatorList));
|
|
111
|
+
const accountsToFetch: Address[] = [];
|
|
112
|
+
// Add all accounts to be fetched to an array so that we can use getMultipleAccounts
|
|
113
|
+
for (const validator of validatorList.validators) {
|
|
114
|
+
const stakeAccount = await findStakeProgramAddress(
|
|
115
|
+
STAKE_POOL_PROGRAM_ID,
|
|
116
|
+
fromLegacyPublicKey(validator.voteAccountAddress),
|
|
117
|
+
stakedSolPoolPk,
|
|
118
|
+
validator.transientSeedSuffixStart.toNumber()
|
|
119
|
+
);
|
|
120
|
+
const transientAccount = await findTransientStakeProgramAddress(
|
|
121
|
+
STAKE_POOL_PROGRAM_ID,
|
|
122
|
+
fromLegacyPublicKey(validator.voteAccountAddress),
|
|
123
|
+
stakedSolPoolPk,
|
|
124
|
+
validator.transientSeedSuffixEnd
|
|
125
|
+
);
|
|
126
|
+
accountsToFetch.push(stakeAccount);
|
|
127
|
+
accountsToFetch.push(transientAccount);
|
|
128
|
+
}
|
|
129
|
+
let accountsBalances: Array<BN> = [];
|
|
130
|
+
// TODO: if this is still too slow we can also start all getMultipleAccounts in parallel and do Promise.all
|
|
131
|
+
for (let i = 0; i < accountsToFetch.length; i += 100) {
|
|
132
|
+
const accountInfos = await fetchEncodedAccounts(rpc, accountsToFetch.slice(i, i + 100));
|
|
133
|
+
accountsBalances = accountsBalances.concat(
|
|
134
|
+
accountInfos.map((accountInfo) => (accountInfo.exists ? new BN(accountInfo.lamports.toString()) : new BN(0)))
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
assert(accountsBalances.length === accountsToFetch.length);
|
|
138
|
+
let i = 0;
|
|
139
|
+
for (const validator of validatorList.validators) {
|
|
140
|
+
const isPreferred = stakedSolPool.preferredWithdrawValidatorVoteAddress
|
|
141
|
+
? validator.voteAccountAddress.equals(stakedSolPool.preferredWithdrawValidatorVoteAddress)
|
|
142
|
+
: false;
|
|
143
|
+
const stakeAccount = accountsToFetch[i];
|
|
144
|
+
const stakeAccountBalance = accountsBalances[i].sub(TRANSIENT_STAKE_ACCOUNT_RENT_EXEMPTION);
|
|
145
|
+
if (stakeAccountBalance.gt(new BN(0))) {
|
|
146
|
+
activeValidators.push({ isPreferred, balance: stakeAccountBalance, pk: stakeAccount });
|
|
147
|
+
}
|
|
148
|
+
const transientAccount = accountsToFetch[i + 1];
|
|
149
|
+
const transientAccountBalance = accountsBalances[i + 1].sub(TRANSIENT_STAKE_ACCOUNT_RENT_EXEMPTION);
|
|
150
|
+
if (transientAccountBalance.gt(new BN(0))) {
|
|
151
|
+
transientValidators.push({ isPreferred, balance: transientAccountBalance, pk: transientAccount });
|
|
152
|
+
}
|
|
153
|
+
i += 2;
|
|
154
|
+
}
|
|
155
|
+
// Sorting descending based on balance, but preferred validators should always be used first
|
|
156
|
+
const byPreferrenceAndBalance = (
|
|
157
|
+
a: { isPreferred: boolean; balance: BN; pk: Address },
|
|
158
|
+
b: { isPreferred: boolean; balance: BN; pk: Address }
|
|
159
|
+
) => {
|
|
160
|
+
// First, sort by isPreferred (preferred validators come first)
|
|
161
|
+
if (a.isPreferred !== b.isPreferred) {
|
|
162
|
+
return a.isPreferred ? -1 : 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If both have the same preference status, sort by balance (descending)
|
|
166
|
+
return b.balance.cmp(a.balance);
|
|
167
|
+
};
|
|
168
|
+
activeValidators.sort(byPreferrenceAndBalance);
|
|
169
|
+
transientValidators.sort(byPreferrenceAndBalance);
|
|
170
|
+
const allCandidates = activeValidators.concat(transientValidators);
|
|
171
|
+
|
|
172
|
+
// Add reserve stake account at the end as that should be used only if no validators have enough stake
|
|
173
|
+
const reserveStakeBalance = new BN(await getSolBalanceInLamports(rpc, reserveStake)).sub(
|
|
174
|
+
STAKE_ACCOUNT_RENT_EXEMPTION
|
|
175
|
+
);
|
|
176
|
+
if (reserveStakeBalance.gt(new BN(0))) {
|
|
177
|
+
allCandidates.push({
|
|
178
|
+
isPreferred: false,
|
|
179
|
+
balance: reserveStakeBalance,
|
|
180
|
+
pk: reserveStake,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return allCandidates;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function getWithdrawCandidates(
|
|
187
|
+
rpc: Rpc<GetAccountInfoApi & GetMultipleAccountsApi & GetBalanceApi>,
|
|
188
|
+
stakedSolPool: StakePool,
|
|
189
|
+
stakedSolPoolPk: Address,
|
|
190
|
+
stakedSolToDeposit: BN
|
|
191
|
+
): Promise<Array<Address>> {
|
|
192
|
+
const allCandidates = await getAllWithdrawCandidatesSorted(rpc, stakedSolPool, stakedSolPoolPk);
|
|
193
|
+
|
|
194
|
+
let stakedSolRemaining = stakedSolToDeposit;
|
|
195
|
+
let solToWithdraw = new BN(0);
|
|
196
|
+
const withdrawCandidates: Array<Address> = [];
|
|
197
|
+
|
|
198
|
+
const reserveStake = fromLegacyPublicKey(stakedSolPool.reserveStake);
|
|
199
|
+
// Try to withdraw all of the SOL from validators' active/transient accounts
|
|
200
|
+
for (const candidate of allCandidates) {
|
|
201
|
+
if (stakedSolRemaining.isZero()) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// See how much the validator balance is worth in staked sol
|
|
206
|
+
// but limit to amount of needed stake sol
|
|
207
|
+
let stakedSolAmountToWithdraw = BN.min(
|
|
208
|
+
stakedSolRemaining,
|
|
209
|
+
solToStakePoolTokensWithInverseFee(stakedSolPool, new BN(candidate.balance))
|
|
210
|
+
);
|
|
211
|
+
// Convert it back to staked sol so we get the real amount
|
|
212
|
+
let actualSolAmount = calcLamportsWithdrawAmount(stakedSolPool, stakedSolAmountToWithdraw);
|
|
213
|
+
|
|
214
|
+
const remainingSolAmount = calcLamportsWithdrawAmount(
|
|
215
|
+
stakedSolPool,
|
|
216
|
+
stakedSolRemaining.sub(stakedSolAmountToWithdraw)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// If the current validator uses up all of the remaining staked sol except some minimum that we need
|
|
220
|
+
// in order to split_stake, then leave at least the minimum required to be consumed by another validator
|
|
221
|
+
if (!remainingSolAmount.isZero() && remainingSolAmount < new BN(MINIMUM_ACTIVE_STAKE)) {
|
|
222
|
+
stakedSolAmountToWithdraw = stakedSolAmountToWithdraw.sub(
|
|
223
|
+
solToStakePoolTokensWithInverseFee(stakedSolPool, new BN(MINIMUM_ACTIVE_STAKE))
|
|
224
|
+
);
|
|
225
|
+
actualSolAmount = calcLamportsWithdrawAmount(stakedSolPool, stakedSolAmountToWithdraw);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (actualSolAmount < new BN(MINIMUM_ACTIVE_STAKE) && candidate.pk != reserveStake) {
|
|
229
|
+
// Skip if the amount to withdraw is less than the minimum required for a valid stake
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Update stake_pool so simulation stays true to what happens on chain
|
|
234
|
+
stakedSolRemaining = stakedSolRemaining.sub(stakedSolAmountToWithdraw);
|
|
235
|
+
solToWithdraw = solToWithdraw.add(actualSolAmount);
|
|
236
|
+
stakedSolPool.totalLamports = stakedSolPool.totalLamports.sub(actualSolAmount);
|
|
237
|
+
stakedSolPool.poolTokenSupply = stakedSolPool.poolTokenSupply.sub(
|
|
238
|
+
stakePoolTokensMinusFee(stakedSolPool, stakedSolAmountToWithdraw)
|
|
239
|
+
);
|
|
240
|
+
withdrawCandidates.push(candidate.pk);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return withdrawCandidates;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function calcPoolTokensStakeWithdrawalFee(stakedSolPool: StakePool, stakedSolAmountToWithdraw: BN): BN {
|
|
247
|
+
const denominator = stakedSolPool.stakeWithdrawalFee.denominator;
|
|
248
|
+
if (denominator.isZero()) {
|
|
249
|
+
return new BN(0);
|
|
250
|
+
}
|
|
251
|
+
const numerator = stakedSolAmountToWithdraw.mul(stakedSolPool.stakeWithdrawalFee.numerator);
|
|
252
|
+
const poolTokens = numerator.add(denominator).sub(new BN(1)).div(denominator);
|
|
253
|
+
return poolTokens;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function stakePoolTokensMinusFee(stakedSolPool: StakePool, stakedSolAmountToWithdraw: BN): BN {
|
|
257
|
+
const stakedSolFee = calcPoolTokensStakeWithdrawalFee(stakedSolPool, stakedSolAmountToWithdraw);
|
|
258
|
+
return stakedSolAmountToWithdraw.sub(stakedSolFee);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function solToStakePoolTokensWithInverseFee(stakedSolPool: StakePool, sol: BN): BN {
|
|
262
|
+
let poolTokens = calcPoolTokensForDeposit(stakedSolPool, sol);
|
|
263
|
+
if (!stakedSolPool.stakeWithdrawalFee.numerator.isZero()) {
|
|
264
|
+
const numerator = poolTokens.mul(stakedSolPool.stakeWithdrawalFee.denominator);
|
|
265
|
+
const denominator = stakedSolPool.stakeWithdrawalFee.denominator.sub(stakedSolPool.stakeWithdrawalFee.numerator);
|
|
266
|
+
if (denominator.isZero()) {
|
|
267
|
+
// If the pool has 100% fee for some reason just fail it, we cannot compute the inverse
|
|
268
|
+
throw new Error('Pool fee cannot be 100%');
|
|
269
|
+
}
|
|
270
|
+
poolTokens = numerator.div(denominator);
|
|
271
|
+
}
|
|
272
|
+
return poolTokens;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Below functions/types are not exported from spl-stake-pool
|
|
276
|
+
|
|
277
|
+
const addressEncoder = getAddressEncoder();
|
|
278
|
+
/**
|
|
279
|
+
* Generates the withdraw authority program address for the stake pool
|
|
280
|
+
*/
|
|
281
|
+
export async function findWithdrawAuthorityProgramAddress(programId: Address, stakePoolAddress: Address) {
|
|
282
|
+
const [publicKey] = await getProgramDerivedAddress({
|
|
283
|
+
seeds: [addressEncoder.encode(stakePoolAddress), Buffer.from('withdraw')],
|
|
284
|
+
programAddress: programId,
|
|
285
|
+
});
|
|
286
|
+
return publicKey;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function findStakeProgramAddress(
|
|
290
|
+
programId: Address,
|
|
291
|
+
voteAccountAddress: Address,
|
|
292
|
+
stakedSolPoolPk: Address,
|
|
293
|
+
seed: number
|
|
294
|
+
): Promise<Address> {
|
|
295
|
+
const [publicKey] = await getProgramDerivedAddress({
|
|
296
|
+
seeds: [
|
|
297
|
+
addressEncoder.encode(voteAccountAddress),
|
|
298
|
+
addressEncoder.encode(stakedSolPoolPk),
|
|
299
|
+
seed ? new BN(seed).toArrayLike(Buffer, 'le', 4) : Buffer.alloc(0),
|
|
300
|
+
],
|
|
301
|
+
programAddress: programId,
|
|
302
|
+
});
|
|
303
|
+
return publicKey;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function findTransientStakeProgramAddress(
|
|
307
|
+
programId: Address,
|
|
308
|
+
voteAccountAddress: Address,
|
|
309
|
+
stakePoolAddress: Address,
|
|
310
|
+
seed: BN
|
|
311
|
+
) {
|
|
312
|
+
const [publicKey] = await getProgramDerivedAddress({
|
|
313
|
+
seeds: [
|
|
314
|
+
TRANSIENT_STAKE_SEED_PREFIX,
|
|
315
|
+
addressEncoder.encode(voteAccountAddress),
|
|
316
|
+
addressEncoder.encode(stakePoolAddress),
|
|
317
|
+
seed.toArrayLike(Buffer, 'le', 8),
|
|
318
|
+
],
|
|
319
|
+
programAddress: programId,
|
|
320
|
+
});
|
|
321
|
+
return publicKey;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function calcPoolTokensForDeposit(stakePool: StakePool, stakeLamports: BN): BN {
|
|
325
|
+
if (stakePool.poolTokenSupply.isZero() || stakePool.totalLamports.isZero()) {
|
|
326
|
+
return stakeLamports;
|
|
327
|
+
}
|
|
328
|
+
const numerator = stakeLamports.mul(stakePool.poolTokenSupply);
|
|
329
|
+
return numerator.div(stakePool.totalLamports);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function calcLamportsWithdrawAmount(stakePool: StakePool, poolTokens: BN): BN {
|
|
333
|
+
const numerator = poolTokens.mul(stakePool.totalLamports);
|
|
334
|
+
const denominator = stakePool.poolTokenSupply;
|
|
335
|
+
if (numerator.lt(denominator)) {
|
|
336
|
+
return new BN(0);
|
|
337
|
+
}
|
|
338
|
+
return numerator.div(denominator);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export class StakeMeta {
|
|
342
|
+
readonly rentExemptReserve: BN;
|
|
343
|
+
readonly authorizedStaker: Address;
|
|
344
|
+
readonly authorizedWithdrawer: Address;
|
|
345
|
+
readonly lockupUnixTimestamp: BN;
|
|
346
|
+
readonly lockupEpoch: BN;
|
|
347
|
+
readonly lockupCustodian: Address;
|
|
348
|
+
|
|
349
|
+
static layout(property?: string) {
|
|
350
|
+
return borsh.struct<StakeMeta>(
|
|
351
|
+
[
|
|
352
|
+
borsh.u64('rentExemptReserve'),
|
|
353
|
+
borshAddress('authorizedStaker'),
|
|
354
|
+
borshAddress('authorizedWithdrawer'),
|
|
355
|
+
borsh.i64('lockupUnixTimestamp'),
|
|
356
|
+
borsh.u64('lockupEpoch'),
|
|
357
|
+
borshAddress('lockupCustodian'),
|
|
358
|
+
],
|
|
359
|
+
property
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
constructor(fields: {
|
|
364
|
+
rentExemptReserve: BN;
|
|
365
|
+
authorizedStaker: Address;
|
|
366
|
+
authorizedWithdrawer: Address;
|
|
367
|
+
lockupUnixTimestamp: BN;
|
|
368
|
+
lockupEpoch: BN;
|
|
369
|
+
lockupCustodian: Address;
|
|
370
|
+
}) {
|
|
371
|
+
this.rentExemptReserve = fields.rentExemptReserve;
|
|
372
|
+
this.authorizedStaker = fields.authorizedStaker;
|
|
373
|
+
this.authorizedWithdrawer = fields.authorizedWithdrawer;
|
|
374
|
+
this.lockupUnixTimestamp = fields.lockupUnixTimestamp;
|
|
375
|
+
this.lockupEpoch = fields.lockupEpoch;
|
|
376
|
+
this.lockupCustodian = fields.lockupCustodian;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
static decode(data: Buffer): StakeMeta {
|
|
380
|
+
const dec = StakeMeta.layout().decode(data);
|
|
381
|
+
|
|
382
|
+
return new StakeMeta({
|
|
383
|
+
rentExemptReserve: dec.rentExemptReserve,
|
|
384
|
+
authorizedStaker: dec.authorizedStaker,
|
|
385
|
+
authorizedWithdrawer: dec.authorizedWithdrawer,
|
|
386
|
+
lockupUnixTimestamp: dec.lockupUnixTimestamp,
|
|
387
|
+
lockupEpoch: dec.lockupEpoch,
|
|
388
|
+
lockupCustodian: dec.lockupCustodian,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export class StakeInfo {
|
|
394
|
+
readonly delegationVoter: Address;
|
|
395
|
+
readonly delegationStake: BN;
|
|
396
|
+
readonly delegationActivationEpoch: BN;
|
|
397
|
+
readonly delegationDeactivationEpoch: BN;
|
|
398
|
+
readonly delegationWarmupCooldownRate: number;
|
|
399
|
+
readonly creditsObserved: BN;
|
|
400
|
+
|
|
401
|
+
static layout(property?: string) {
|
|
402
|
+
return borsh.struct<StakeInfo>(
|
|
403
|
+
[
|
|
404
|
+
borshAddress('delegationVoter'),
|
|
405
|
+
borsh.u64('delegationStake'),
|
|
406
|
+
borsh.u64('delegationActivationEpoch'),
|
|
407
|
+
borsh.u64('delegationDeactivationEpoch'),
|
|
408
|
+
borsh.f64('delegationWarmupCooldownRate'),
|
|
409
|
+
borsh.u64('creditsObserved'),
|
|
410
|
+
],
|
|
411
|
+
property
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
constructor(fields: {
|
|
416
|
+
delegationVoter: Address;
|
|
417
|
+
delegationStake: BN;
|
|
418
|
+
delegationActivationEpoch: BN;
|
|
419
|
+
delegationDeactivationEpoch: BN;
|
|
420
|
+
delegationWarmupCooldownRate: number;
|
|
421
|
+
creditsObserved: BN;
|
|
422
|
+
}) {
|
|
423
|
+
this.delegationVoter = fields.delegationVoter;
|
|
424
|
+
this.delegationStake = fields.delegationStake;
|
|
425
|
+
this.delegationActivationEpoch = fields.delegationActivationEpoch;
|
|
426
|
+
this.delegationDeactivationEpoch = fields.delegationDeactivationEpoch;
|
|
427
|
+
this.delegationWarmupCooldownRate = fields.delegationWarmupCooldownRate;
|
|
428
|
+
this.creditsObserved = fields.creditsObserved;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
static decode(data: Buffer): StakeInfo {
|
|
432
|
+
const dec = StakeInfo.layout().decode(data);
|
|
433
|
+
|
|
434
|
+
return new StakeInfo({
|
|
435
|
+
delegationVoter: dec.delegationVoter,
|
|
436
|
+
delegationStake: dec.delegationStake,
|
|
437
|
+
delegationActivationEpoch: dec.delegationActivationEpoch,
|
|
438
|
+
delegationDeactivationEpoch: dec.delegationDeactivationEpoch,
|
|
439
|
+
delegationWarmupCooldownRate: dec.delegationWarmupCooldownRate,
|
|
440
|
+
creditsObserved: dec.creditsObserved,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export class SmallStakeAccountInfo {
|
|
446
|
+
readonly meta: StakeMeta;
|
|
447
|
+
readonly stake: StakeInfo;
|
|
448
|
+
|
|
449
|
+
static layout(property?: string) {
|
|
450
|
+
return borsh.struct<SmallStakeAccountInfo>([StakeMeta.layout('meta'), StakeInfo.layout('stake')], property);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
constructor(fields: { meta: StakeMeta; stake: StakeInfo }) {
|
|
454
|
+
this.meta = fields.meta;
|
|
455
|
+
this.stake = fields.stake;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
static decode(data: Buffer): SmallStakeAccountInfo {
|
|
459
|
+
const dec = SmallStakeAccountInfo.layout().decode(data);
|
|
460
|
+
|
|
461
|
+
return new SmallStakeAccountInfo({
|
|
462
|
+
meta: dec.meta,
|
|
463
|
+
stake: dec.stake,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export class StakeAccount {
|
|
469
|
+
readonly type: number;
|
|
470
|
+
readonly info: SmallStakeAccountInfo;
|
|
471
|
+
|
|
472
|
+
static readonly layout = borsh.struct<StakeAccount>([borsh.u32('type'), SmallStakeAccountInfo.layout('info')]);
|
|
473
|
+
|
|
474
|
+
constructor(fields: { type: number; info: SmallStakeAccountInfo }) {
|
|
475
|
+
this.type = fields.type;
|
|
476
|
+
this.info = fields.info;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
static decode(data: Buffer): StakeAccount {
|
|
480
|
+
const dec = StakeAccount.layout.decode(data);
|
|
481
|
+
|
|
482
|
+
return new StakeAccount({
|
|
483
|
+
type: dec.type,
|
|
484
|
+
info: dec.info,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|