PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/@solana/spl-stake-pool/src
Просмотр файла: index.ts
import {
AccountInfo,
Connection,
Keypair,
PublicKey,
Signer,
StakeAuthorizationLayout,
StakeProgram,
SystemProgram,
TransactionInstruction,
} from '@solana/web3.js';
import {
createApproveInstruction,
createAssociatedTokenAccountIdempotentInstruction,
getAccount,
getAssociatedTokenAddressSync,
} from '@solana/spl-token';
import {
ValidatorAccount,
arrayChunk,
calcLamportsWithdrawAmount,
findStakeProgramAddress,
findTransientStakeProgramAddress,
findWithdrawAuthorityProgramAddress,
getValidatorListAccount,
newStakeAccount,
prepareWithdrawAccounts,
lamportsToSol,
solToLamports,
findEphemeralStakeProgramAddress,
findMetadataAddress,
} from './utils';
import { StakePoolInstruction } from './instructions';
import {
StakeAccount,
StakePool,
StakePoolLayout,
ValidatorList,
ValidatorListLayout,
ValidatorStakeInfo,
} from './layouts';
import { MAX_VALIDATORS_TO_UPDATE, MINIMUM_ACTIVE_STAKE, STAKE_POOL_PROGRAM_ID } from './constants';
import { create } from 'superstruct';
import BN from 'bn.js';
export type { StakePool, AccountType, ValidatorList, ValidatorStakeInfo } from './layouts';
export { STAKE_POOL_PROGRAM_ID } from './constants';
export * from './instructions';
export { StakePoolLayout, ValidatorListLayout, ValidatorStakeInfoLayout } from './layouts';
export interface ValidatorListAccount {
pubkey: PublicKey;
account: AccountInfo<ValidatorList>;
}
export interface StakePoolAccount {
pubkey: PublicKey;
account: AccountInfo<StakePool>;
}
export interface WithdrawAccount {
stakeAddress: PublicKey;
voteAddress?: PublicKey;
poolAmount: BN;
}
/**
* Wrapper class for a stake pool.
* Each stake pool has a stake pool account and a validator list account.
*/
export interface StakePoolAccounts {
stakePool: StakePoolAccount | undefined;
validatorList: ValidatorListAccount | undefined;
}
/**
* Retrieves and deserializes a StakePool account using a web3js connection and the stake pool address.
* @param connection: An active web3js connection.
* @param stakePoolAddress: The public key (address) of the stake pool account.
*/
export async function getStakePoolAccount(
connection: Connection,
stakePoolAddress: PublicKey,
): Promise<StakePoolAccount> {
const account = await connection.getAccountInfo(stakePoolAddress);
if (!account) {
throw new Error('Invalid stake pool account');
}
return {
pubkey: stakePoolAddress,
account: {
data: StakePoolLayout.decode(account.data),
executable: account.executable,
lamports: account.lamports,
owner: account.owner,
},
};
}
/**
* Retrieves and deserializes a Stake account using a web3js connection and the stake address.
* @param connection: An active web3js connection.
* @param stakeAccount: The public key (address) of the stake account.
*/
export async function getStakeAccount(
connection: Connection,
stakeAccount: PublicKey,
): Promise<StakeAccount> {
const result = (await connection.getParsedAccountInfo(stakeAccount)).value;
if (!result || !('parsed' in result.data)) {
throw new Error('Invalid stake account');
}
const program = result.data.program;
if (program != 'stake') {
throw new Error('Not a stake account');
}
const parsed = create(result.data.parsed, StakeAccount);
return parsed;
}
/**
* Retrieves all StakePool and ValidatorList accounts that are running a particular StakePool program.
* @param connection: An active web3js connection.
* @param stakePoolProgramAddress: The public key (address) of the StakePool program.
*/
export async function getStakePoolAccounts(
connection: Connection,
stakePoolProgramAddress: PublicKey,
): Promise<(StakePoolAccount | ValidatorListAccount | undefined)[] | undefined> {
const response = await connection.getProgramAccounts(stakePoolProgramAddress);
return response
.map((a) => {
try {
if (a.account.data.readUInt8() === 1) {
const data = StakePoolLayout.decode(a.account.data);
return {
pubkey: a.pubkey,
account: {
data,
executable: a.account.executable,
lamports: a.account.lamports,
owner: a.account.owner,
},
};
} else if (a.account.data.readUInt8() === 2) {
const data = ValidatorListLayout.decode(a.account.data);
return {
pubkey: a.pubkey,
account: {
data,
executable: a.account.executable,
lamports: a.account.lamports,
owner: a.account.owner,
},
};
} else {
console.error(
`Could not decode. StakePoolAccount Enum is ${a.account.data.readUInt8()}, expected 1 or 2!`,
);
return undefined;
}
} catch (error) {
console.error('Could not decode account. Error:', error);
return undefined;
}
})
.filter((a) => a !== undefined);
}
/**
* Creates instructions required to deposit stake to stake pool.
*/
export async function depositStake(
connection: Connection,
stakePoolAddress: PublicKey,
authorizedPubkey: PublicKey,
validatorVote: PublicKey,
depositStake: PublicKey,
poolTokenReceiverAccount?: PublicKey,
) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const validatorStake = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorVote,
stakePoolAddress,
);
const instructions: TransactionInstruction[] = [];
const signers: Signer[] = [];
const poolMint = stakePool.account.data.poolMint;
// Create token account if not specified
if (!poolTokenReceiverAccount) {
const associatedAddress = getAssociatedTokenAddressSync(poolMint, authorizedPubkey);
instructions.push(
createAssociatedTokenAccountIdempotentInstruction(
authorizedPubkey,
associatedAddress,
authorizedPubkey,
poolMint,
),
);
poolTokenReceiverAccount = associatedAddress;
}
instructions.push(
...StakeProgram.authorize({
stakePubkey: depositStake,
authorizedPubkey,
newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
stakeAuthorizationType: StakeAuthorizationLayout.Staker,
}).instructions,
);
instructions.push(
...StakeProgram.authorize({
stakePubkey: depositStake,
authorizedPubkey,
newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
}).instructions,
);
instructions.push(
StakePoolInstruction.depositStake({
stakePool: stakePoolAddress,
validatorList: stakePool.account.data.validatorList,
depositAuthority: stakePool.account.data.stakeDepositAuthority,
reserveStake: stakePool.account.data.reserveStake,
managerFeeAccount: stakePool.account.data.managerFeeAccount,
referralPoolAccount: poolTokenReceiverAccount,
destinationPoolAccount: poolTokenReceiverAccount,
withdrawAuthority,
depositStake,
validatorStake,
poolMint,
}),
);
return {
instructions,
signers,
};
}
/**
* Creates instructions required to deposit sol to stake pool.
*/
export async function depositSol(
connection: Connection,
stakePoolAddress: PublicKey,
from: PublicKey,
lamports: number,
destinationTokenAccount?: PublicKey,
referrerTokenAccount?: PublicKey,
depositAuthority?: PublicKey,
) {
const fromBalance = await connection.getBalance(from, 'confirmed');
if (fromBalance < lamports) {
throw new Error(
`Not enough SOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(
fromBalance,
)} SOL.`,
);
}
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
const stakePool = stakePoolAccount.account.data;
// Ephemeral SOL account just to do the transfer
const userSolTransfer = new Keypair();
const signers: Signer[] = [userSolTransfer];
const instructions: TransactionInstruction[] = [];
// Create the ephemeral SOL account
instructions.push(
SystemProgram.transfer({
fromPubkey: from,
toPubkey: userSolTransfer.publicKey,
lamports,
}),
);
// Create token account if not specified
if (!destinationTokenAccount) {
const associatedAddress = getAssociatedTokenAddressSync(stakePool.poolMint, from);
instructions.push(
createAssociatedTokenAccountIdempotentInstruction(
from,
associatedAddress,
from,
stakePool.poolMint,
),
);
destinationTokenAccount = associatedAddress;
}
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
instructions.push(
StakePoolInstruction.depositSol({
stakePool: stakePoolAddress,
reserveStake: stakePool.reserveStake,
fundingAccount: userSolTransfer.publicKey,
destinationPoolAccount: destinationTokenAccount,
managerFeeAccount: stakePool.managerFeeAccount,
referralPoolAccount: referrerTokenAccount ?? destinationTokenAccount,
poolMint: stakePool.poolMint,
lamports,
withdrawAuthority,
depositAuthority,
}),
);
return {
instructions,
signers,
};
}
/**
* Creates instructions required to withdraw stake from a stake pool.
*/
export async function withdrawStake(
connection: Connection,
stakePoolAddress: PublicKey,
tokenOwner: PublicKey,
amount: number,
useReserve = false,
voteAccountAddress?: PublicKey,
stakeReceiver?: PublicKey,
poolTokenAccount?: PublicKey,
validatorComparator?: (_a: ValidatorAccount, _b: ValidatorAccount) => number,
) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const poolAmount = new BN(solToLamports(amount));
if (!poolTokenAccount) {
poolTokenAccount = getAssociatedTokenAddressSync(stakePool.account.data.poolMint, tokenOwner);
}
const tokenAccount = await getAccount(connection, poolTokenAccount);
// Check withdrawFrom balance
if (tokenAccount.amount < poolAmount.toNumber()) {
throw new Error(
`Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
);
}
const stakeAccountRentExemption = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space,
);
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
let stakeReceiverAccount = null;
if (stakeReceiver) {
stakeReceiverAccount = await getStakeAccount(connection, stakeReceiver);
}
const withdrawAccounts: WithdrawAccount[] = [];
if (useReserve) {
withdrawAccounts.push({
stakeAddress: stakePool.account.data.reserveStake,
voteAddress: undefined,
poolAmount,
});
} else if (stakeReceiverAccount && stakeReceiverAccount?.type == 'delegated') {
const voteAccount = stakeReceiverAccount.info?.stake?.delegation.voter;
if (!voteAccount) throw new Error(`Invalid stake receiver ${stakeReceiver} delegation`);
const validatorListAccount = await connection.getAccountInfo(
stakePool.account.data.validatorList,
);
const validatorList = ValidatorListLayout.decode(validatorListAccount?.data) as ValidatorList;
const isValidVoter = validatorList.validators.find((val) =>
val.voteAccountAddress.equals(voteAccount),
);
if (voteAccountAddress && voteAccountAddress !== voteAccount) {
throw new Error(`Provided withdrawal vote account ${voteAccountAddress} does not match delegation on stake receiver account ${voteAccount},
remove this flag or provide a different stake account delegated to ${voteAccountAddress}`);
}
if (isValidVoter) {
const stakeAccountAddress = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
voteAccount,
stakePoolAddress,
);
const stakeAccount = await connection.getAccountInfo(stakeAccountAddress);
if (!stakeAccount) {
throw new Error(`Preferred withdraw valdator's stake account is invalid`);
}
const availableForWithdrawal = calcLamportsWithdrawAmount(
stakePool.account.data,
new BN(stakeAccount.lamports - MINIMUM_ACTIVE_STAKE - stakeAccountRentExemption),
);
if (availableForWithdrawal.lt(poolAmount)) {
throw new Error(
`Not enough lamports available for withdrawal from ${stakeAccountAddress},
${poolAmount} asked, ${availableForWithdrawal} available.`,
);
}
withdrawAccounts.push({
stakeAddress: stakeAccountAddress,
voteAddress: voteAccount,
poolAmount,
});
} else {
throw new Error(
`Provided stake account is delegated to a vote account ${voteAccount} which does not exist in the stake pool`,
);
}
} else if (voteAccountAddress) {
const stakeAccountAddress = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
voteAccountAddress,
stakePoolAddress,
);
const stakeAccount = await connection.getAccountInfo(stakeAccountAddress);
if (!stakeAccount) {
throw new Error('Invalid Stake Account');
}
const availableLamports = new BN(
stakeAccount.lamports - MINIMUM_ACTIVE_STAKE - stakeAccountRentExemption,
);
if (availableLamports.lt(new BN(0))) {
throw new Error('Invalid Stake Account');
}
const availableForWithdrawal = calcLamportsWithdrawAmount(
stakePool.account.data,
availableLamports,
);
if (availableForWithdrawal.lt(poolAmount)) {
// noinspection ExceptionCaughtLocallyJS
throw new Error(
`Not enough lamports available for withdrawal from ${stakeAccountAddress},
${poolAmount} asked, ${availableForWithdrawal} available.`,
);
}
withdrawAccounts.push({
stakeAddress: stakeAccountAddress,
voteAddress: voteAccountAddress,
poolAmount,
});
} else {
// Get the list of accounts to withdraw from
withdrawAccounts.push(
...(await prepareWithdrawAccounts(
connection,
stakePool.account.data,
stakePoolAddress,
poolAmount,
validatorComparator,
poolTokenAccount.equals(stakePool.account.data.managerFeeAccount),
)),
);
}
// Construct transaction to withdraw from withdrawAccounts account list
const instructions: TransactionInstruction[] = [];
const userTransferAuthority = Keypair.generate();
const signers: Signer[] = [userTransferAuthority];
instructions.push(
createApproveInstruction(
poolTokenAccount,
userTransferAuthority.publicKey,
tokenOwner,
poolAmount.toNumber(),
),
);
let totalRentFreeBalances = 0;
// Max 5 accounts to prevent an error: "Transaction too large"
const maxWithdrawAccounts = 5;
let i = 0;
// Go through prepared accounts and withdraw/claim them
for (const withdrawAccount of withdrawAccounts) {
if (i > maxWithdrawAccounts) {
break;
}
// Convert pool tokens amount to lamports
const solWithdrawAmount = calcLamportsWithdrawAmount(
stakePool.account.data,
withdrawAccount.poolAmount,
);
let infoMsg = `Withdrawing ◎${solWithdrawAmount},
from stake account ${withdrawAccount.stakeAddress?.toBase58()}`;
if (withdrawAccount.voteAddress) {
infoMsg = `${infoMsg}, delegated to ${withdrawAccount.voteAddress?.toBase58()}`;
}
console.info(infoMsg);
let stakeToReceive;
if (!stakeReceiver || (stakeReceiverAccount && stakeReceiverAccount.type === 'delegated')) {
const stakeKeypair = newStakeAccount(tokenOwner, instructions, stakeAccountRentExemption);
signers.push(stakeKeypair);
totalRentFreeBalances += stakeAccountRentExemption;
stakeToReceive = stakeKeypair.publicKey;
} else {
stakeToReceive = stakeReceiver;
}
instructions.push(
StakePoolInstruction.withdrawStake({
stakePool: stakePoolAddress,
validatorList: stakePool.account.data.validatorList,
validatorStake: withdrawAccount.stakeAddress,
destinationStake: stakeToReceive,
destinationStakeAuthority: tokenOwner,
sourceTransferAuthority: userTransferAuthority.publicKey,
sourcePoolAccount: poolTokenAccount,
managerFeeAccount: stakePool.account.data.managerFeeAccount,
poolMint: stakePool.account.data.poolMint,
poolTokens: withdrawAccount.poolAmount.toNumber(),
withdrawAuthority,
}),
);
i++;
}
if (stakeReceiver && stakeReceiverAccount && stakeReceiverAccount.type === 'delegated') {
signers.forEach((newStakeKeypair) => {
instructions.concat(
StakeProgram.merge({
stakePubkey: stakeReceiver,
sourceStakePubKey: newStakeKeypair.publicKey,
authorizedPubkey: tokenOwner,
}).instructions,
);
});
}
return {
instructions,
signers,
stakeReceiver,
totalRentFreeBalances,
};
}
/**
* Creates instructions required to withdraw SOL directly from a stake pool.
*/
export async function withdrawSol(
connection: Connection,
stakePoolAddress: PublicKey,
tokenOwner: PublicKey,
solReceiver: PublicKey,
amount: number,
solWithdrawAuthority?: PublicKey,
) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const poolAmount = solToLamports(amount);
const poolTokenAccount = getAssociatedTokenAddressSync(
stakePool.account.data.poolMint,
tokenOwner,
);
const tokenAccount = await getAccount(connection, poolTokenAccount);
// Check withdrawFrom balance
if (tokenAccount.amount < poolAmount) {
throw new Error(
`Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
);
}
// Construct transaction to withdraw from withdrawAccounts account list
const instructions: TransactionInstruction[] = [];
const userTransferAuthority = Keypair.generate();
const signers: Signer[] = [userTransferAuthority];
instructions.push(
createApproveInstruction(
poolTokenAccount,
userTransferAuthority.publicKey,
tokenOwner,
poolAmount,
),
);
const poolWithdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
if (solWithdrawAuthority) {
const expectedSolWithdrawAuthority = stakePool.account.data.solWithdrawAuthority;
if (!expectedSolWithdrawAuthority) {
throw new Error('SOL withdraw authority specified in arguments but stake pool has none');
}
if (solWithdrawAuthority.toBase58() != expectedSolWithdrawAuthority.toBase58()) {
throw new Error(
`Invalid deposit withdraw specified, expected ${expectedSolWithdrawAuthority.toBase58()}, received ${solWithdrawAuthority.toBase58()}`,
);
}
}
const withdrawTransaction = StakePoolInstruction.withdrawSol({
stakePool: stakePoolAddress,
withdrawAuthority: poolWithdrawAuthority,
reserveStake: stakePool.account.data.reserveStake,
sourcePoolAccount: poolTokenAccount,
sourceTransferAuthority: userTransferAuthority.publicKey,
destinationSystemAccount: solReceiver,
managerFeeAccount: stakePool.account.data.managerFeeAccount,
poolMint: stakePool.account.data.poolMint,
poolTokens: poolAmount,
solWithdrawAuthority,
});
instructions.push(withdrawTransaction);
return {
instructions,
signers,
};
}
export async function addValidatorToPool(
connection: Connection,
stakePoolAddress: PublicKey,
validatorVote: PublicKey,
seed?: number,
) {
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
const stakePool = stakePoolAccount.account.data;
const { reserveStake, staker, validatorList } = stakePool;
const validatorListAccount = await getValidatorListAccount(connection, validatorList);
const validatorInfo = validatorListAccount.account.data.validators.find(
(v) => v.voteAccountAddress.toBase58() == validatorVote.toBase58(),
);
if (validatorInfo) {
throw new Error('Vote account is already in validator list');
}
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const validatorStake = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorVote,
stakePoolAddress,
seed,
);
const instructions: TransactionInstruction[] = [
StakePoolInstruction.addValidatorToPool({
stakePool: stakePoolAddress,
staker: staker,
reserveStake: reserveStake,
withdrawAuthority: withdrawAuthority,
validatorList: validatorList,
validatorStake: validatorStake,
validatorVote: validatorVote,
}),
];
return {
instructions,
};
}
export async function removeValidatorFromPool(
connection: Connection,
stakePoolAddress: PublicKey,
validatorVote: PublicKey,
seed?: number,
) {
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
const stakePool = stakePoolAccount.account.data;
const { staker, validatorList } = stakePool;
const validatorListAccount = await getValidatorListAccount(connection, validatorList);
const validatorInfo = validatorListAccount.account.data.validators.find(
(v) => v.voteAccountAddress.toBase58() == validatorVote.toBase58(),
);
if (!validatorInfo) {
throw new Error('Vote account is not already in validator list');
}
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const validatorStake = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorVote,
stakePoolAddress,
seed,
);
const transientStakeSeed = validatorInfo.transientSeedSuffixStart;
const transientStake = await findTransientStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorInfo.voteAccountAddress,
stakePoolAddress,
transientStakeSeed,
);
const instructions: TransactionInstruction[] = [
StakePoolInstruction.removeValidatorFromPool({
stakePool: stakePoolAddress,
staker: staker,
withdrawAuthority,
validatorList,
validatorStake,
transientStake,
}),
];
return {
instructions,
};
}
/**
* Creates instructions required to increase validator stake.
*/
export async function increaseValidatorStake(
connection: Connection,
stakePoolAddress: PublicKey,
validatorVote: PublicKey,
lamports: number,
ephemeralStakeSeed?: number,
) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const validatorList = await getValidatorListAccount(
connection,
stakePool.account.data.validatorList,
);
const validatorInfo = validatorList.account.data.validators.find(
(v) => v.voteAccountAddress.toBase58() == validatorVote.toBase58(),
);
if (!validatorInfo) {
throw new Error('Vote account not found in validator list');
}
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
// Bump transient seed suffix by one to avoid reuse when not using the increaseAdditionalStake instruction
const transientStakeSeed =
ephemeralStakeSeed == undefined
? validatorInfo.transientSeedSuffixStart.addn(1)
: validatorInfo.transientSeedSuffixStart;
const transientStake = await findTransientStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorInfo.voteAccountAddress,
stakePoolAddress,
transientStakeSeed,
);
const validatorStake = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorInfo.voteAccountAddress,
stakePoolAddress,
);
const instructions: TransactionInstruction[] = [];
if (ephemeralStakeSeed != undefined) {
const ephemeralStake = await findEphemeralStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
new BN(ephemeralStakeSeed),
);
instructions.push(
StakePoolInstruction.increaseAdditionalValidatorStake({
stakePool: stakePoolAddress,
staker: stakePool.account.data.staker,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
transientStakeSeed: transientStakeSeed.toNumber(),
withdrawAuthority,
transientStake,
validatorStake,
validatorVote,
lamports,
ephemeralStake,
ephemeralStakeSeed,
}),
);
} else {
instructions.push(
StakePoolInstruction.increaseValidatorStake({
stakePool: stakePoolAddress,
staker: stakePool.account.data.staker,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
transientStakeSeed: transientStakeSeed.toNumber(),
withdrawAuthority,
transientStake,
validatorStake,
validatorVote,
lamports,
}),
);
}
return {
instructions,
};
}
/**
* Creates instructions required to decrease validator stake.
*/
export async function decreaseValidatorStake(
connection: Connection,
stakePoolAddress: PublicKey,
validatorVote: PublicKey,
lamports: number,
ephemeralStakeSeed?: number,
) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const validatorList = await getValidatorListAccount(
connection,
stakePool.account.data.validatorList,
);
const validatorInfo = validatorList.account.data.validators.find(
(v) => v.voteAccountAddress.toBase58() == validatorVote.toBase58(),
);
if (!validatorInfo) {
throw new Error('Vote account not found in validator list');
}
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const validatorStake = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorInfo.voteAccountAddress,
stakePoolAddress,
);
// Bump transient seed suffix by one to avoid reuse when not using the decreaseAdditionalStake instruction
const transientStakeSeed =
ephemeralStakeSeed == undefined
? validatorInfo.transientSeedSuffixStart.addn(1)
: validatorInfo.transientSeedSuffixStart;
const transientStake = await findTransientStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validatorInfo.voteAccountAddress,
stakePoolAddress,
transientStakeSeed,
);
const instructions: TransactionInstruction[] = [];
if (ephemeralStakeSeed != undefined) {
const ephemeralStake = await findEphemeralStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
new BN(ephemeralStakeSeed),
);
instructions.push(
StakePoolInstruction.decreaseAdditionalValidatorStake({
stakePool: stakePoolAddress,
staker: stakePool.account.data.staker,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
transientStakeSeed: transientStakeSeed.toNumber(),
withdrawAuthority,
validatorStake,
transientStake,
lamports,
ephemeralStake,
ephemeralStakeSeed,
}),
);
} else {
instructions.push(
StakePoolInstruction.decreaseValidatorStakeWithReserve({
stakePool: stakePoolAddress,
staker: stakePool.account.data.staker,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
transientStakeSeed: transientStakeSeed.toNumber(),
withdrawAuthority,
validatorStake,
transientStake,
lamports,
}),
);
}
return {
instructions,
};
}
/**
* Creates instructions required to completely update a stake pool after epoch change.
*/
export async function updateStakePool(
connection: Connection,
stakePool: StakePoolAccount,
noMerge = false,
) {
const stakePoolAddress = stakePool.pubkey;
const validatorList = await getValidatorListAccount(
connection,
stakePool.account.data.validatorList,
);
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const updateListInstructions: TransactionInstruction[] = [];
const instructions: TransactionInstruction[] = [];
let startIndex = 0;
const validatorChunks: Array<ValidatorStakeInfo[]> = arrayChunk(
validatorList.account.data.validators,
MAX_VALIDATORS_TO_UPDATE,
);
for (const validatorChunk of validatorChunks) {
const validatorAndTransientStakePairs: PublicKey[] = [];
for (const validator of validatorChunk) {
const validatorStake = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validator.voteAccountAddress,
stakePoolAddress,
);
validatorAndTransientStakePairs.push(validatorStake);
const transientStake = await findTransientStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validator.voteAccountAddress,
stakePoolAddress,
validator.transientSeedSuffixStart,
);
validatorAndTransientStakePairs.push(transientStake);
}
updateListInstructions.push(
StakePoolInstruction.updateValidatorListBalance({
stakePool: stakePoolAddress,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
validatorAndTransientStakePairs,
withdrawAuthority,
startIndex,
noMerge,
}),
);
startIndex += MAX_VALIDATORS_TO_UPDATE;
}
instructions.push(
StakePoolInstruction.updateStakePoolBalance({
stakePool: stakePoolAddress,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
managerFeeAccount: stakePool.account.data.managerFeeAccount,
poolMint: stakePool.account.data.poolMint,
withdrawAuthority,
}),
);
instructions.push(
StakePoolInstruction.cleanupRemovedValidatorEntries({
stakePool: stakePoolAddress,
validatorList: stakePool.account.data.validatorList,
}),
);
return {
updateListInstructions,
finalInstructions: instructions,
};
}
/**
* Retrieves detailed information about the StakePool.
*/
export async function stakePoolInfo(connection: Connection, stakePoolAddress: PublicKey) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const reserveAccountStakeAddress = stakePool.account.data.reserveStake;
const totalLamports = stakePool.account.data.totalLamports;
const lastUpdateEpoch = stakePool.account.data.lastUpdateEpoch;
const validatorList = await getValidatorListAccount(
connection,
stakePool.account.data.validatorList,
);
const maxNumberOfValidators = validatorList.account.data.maxValidators;
const currentNumberOfValidators = validatorList.account.data.validators.length;
const epochInfo = await connection.getEpochInfo();
const reserveStake = await connection.getAccountInfo(reserveAccountStakeAddress);
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const minimumReserveStakeBalance = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space,
);
const stakeAccounts = await Promise.all(
validatorList.account.data.validators.map(async (validator) => {
const stakeAccountAddress = await findStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validator.voteAccountAddress,
stakePoolAddress,
);
const transientStakeAccountAddress = await findTransientStakeProgramAddress(
STAKE_POOL_PROGRAM_ID,
validator.voteAccountAddress,
stakePoolAddress,
validator.transientSeedSuffixStart,
);
const updateRequired = !validator.lastUpdateEpoch.eqn(epochInfo.epoch);
return {
voteAccountAddress: validator.voteAccountAddress.toBase58(),
stakeAccountAddress: stakeAccountAddress.toBase58(),
validatorActiveStakeLamports: validator.activeStakeLamports.toString(),
validatorLastUpdateEpoch: validator.lastUpdateEpoch.toString(),
validatorLamports: validator.activeStakeLamports
.add(validator.transientStakeLamports)
.toString(),
validatorTransientStakeAccountAddress: transientStakeAccountAddress.toBase58(),
validatorTransientStakeLamports: validator.transientStakeLamports.toString(),
updateRequired,
};
}),
);
const totalPoolTokens = lamportsToSol(stakePool.account.data.poolTokenSupply);
const updateRequired = !lastUpdateEpoch.eqn(epochInfo.epoch);
return {
address: stakePoolAddress.toBase58(),
poolWithdrawAuthority: withdrawAuthority.toBase58(),
manager: stakePool.account.data.manager.toBase58(),
staker: stakePool.account.data.staker.toBase58(),
stakeDepositAuthority: stakePool.account.data.stakeDepositAuthority.toBase58(),
stakeWithdrawBumpSeed: stakePool.account.data.stakeWithdrawBumpSeed,
maxValidators: maxNumberOfValidators,
validatorList: validatorList.account.data.validators.map((validator) => {
return {
activeStakeLamports: validator.activeStakeLamports.toString(),
transientStakeLamports: validator.transientStakeLamports.toString(),
lastUpdateEpoch: validator.lastUpdateEpoch.toString(),
transientSeedSuffixStart: validator.transientSeedSuffixStart.toString(),
transientSeedSuffixEnd: validator.transientSeedSuffixEnd.toString(),
status: validator.status.toString(),
voteAccountAddress: validator.voteAccountAddress.toString(),
};
}), // CliStakePoolValidator
validatorListStorageAccount: stakePool.account.data.validatorList.toBase58(),
reserveStake: stakePool.account.data.reserveStake.toBase58(),
poolMint: stakePool.account.data.poolMint.toBase58(),
managerFeeAccount: stakePool.account.data.managerFeeAccount.toBase58(),
tokenProgramId: stakePool.account.data.tokenProgramId.toBase58(),
totalLamports: stakePool.account.data.totalLamports.toString(),
poolTokenSupply: stakePool.account.data.poolTokenSupply.toString(),
lastUpdateEpoch: stakePool.account.data.lastUpdateEpoch.toString(),
lockup: stakePool.account.data.lockup, // pub lockup: CliStakePoolLockup
epochFee: stakePool.account.data.epochFee,
nextEpochFee: stakePool.account.data.nextEpochFee,
preferredDepositValidatorVoteAddress:
stakePool.account.data.preferredDepositValidatorVoteAddress,
preferredWithdrawValidatorVoteAddress:
stakePool.account.data.preferredWithdrawValidatorVoteAddress,
stakeDepositFee: stakePool.account.data.stakeDepositFee,
stakeWithdrawalFee: stakePool.account.data.stakeWithdrawalFee,
// CliStakePool the same
nextStakeWithdrawalFee: stakePool.account.data.nextStakeWithdrawalFee,
stakeReferralFee: stakePool.account.data.stakeReferralFee,
solDepositAuthority: stakePool.account.data.solDepositAuthority?.toBase58(),
solDepositFee: stakePool.account.data.solDepositFee,
solReferralFee: stakePool.account.data.solReferralFee,
solWithdrawAuthority: stakePool.account.data.solWithdrawAuthority?.toBase58(),
solWithdrawalFee: stakePool.account.data.solWithdrawalFee,
nextSolWithdrawalFee: stakePool.account.data.nextSolWithdrawalFee,
lastEpochPoolTokenSupply: stakePool.account.data.lastEpochPoolTokenSupply.toString(),
lastEpochTotalLamports: stakePool.account.data.lastEpochTotalLamports.toString(),
details: {
reserveStakeLamports: reserveStake?.lamports,
reserveAccountStakeAddress: reserveAccountStakeAddress.toBase58(),
minimumReserveStakeBalance,
stakeAccounts,
totalLamports,
totalPoolTokens,
currentNumberOfValidators,
maxNumberOfValidators,
updateRequired,
}, // CliStakePoolDetails
};
}
/**
* Creates instructions required to create pool token metadata.
*/
export async function createPoolTokenMetadata(
connection: Connection,
stakePoolAddress: PublicKey,
payer: PublicKey,
name: string,
symbol: string,
uri: string,
) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint);
const manager = stakePool.account.data.manager;
const instructions: TransactionInstruction[] = [];
instructions.push(
StakePoolInstruction.createTokenMetadata({
stakePool: stakePoolAddress,
poolMint: stakePool.account.data.poolMint,
payer,
manager,
tokenMetadata,
withdrawAuthority,
name,
symbol,
uri,
}),
);
return {
instructions,
};
}
/**
* Creates instructions required to update pool token metadata.
*/
export async function updatePoolTokenMetadata(
connection: Connection,
stakePoolAddress: PublicKey,
name: string,
symbol: string,
uri: string,
) {
const stakePool = await getStakePoolAccount(connection, stakePoolAddress);
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
STAKE_POOL_PROGRAM_ID,
stakePoolAddress,
);
const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint);
const instructions: TransactionInstruction[] = [];
instructions.push(
StakePoolInstruction.updateTokenMetadata({
stakePool: stakePoolAddress,
manager: stakePool.account.data.manager,
tokenMetadata,
withdrawAuthority,
name,
symbol,
uri,
}),
);
return {
instructions,
};
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!