prepareWithdrawAccounts static method
Future<List<WithdrawAccount> >
prepareWithdrawAccounts({
- required SolanaRPC connection,
- required StakePoolAccount stakePool,
- required SolAddress stakePoolAddress,
- required BigInt amount,
- bool skipFee = false,
- Comparator<
ValidatorAccount> ? compareFn,
Implementation
static Future<List<WithdrawAccount>> prepareWithdrawAccounts({
required SolanaRPC connection,
required StakePoolAccount stakePool,
required SolAddress stakePoolAddress,
required BigInt amount,
bool skipFee = false,
Comparator<ValidatorAccount>? compareFn,
}) async {
final validatorList = await connection.request(
SolanaRPCGetStakePoolValidatorListAccount(
address: stakePool.validatorList.address));
if (validatorList == null || validatorList.validators.isEmpty) {
throw const SolanaPluginException(
"Validator list account does not found.");
}
final minBalanceForRentExemption = await connection.request(
SolanaRPCGetMinimumBalanceForRentExemption(
size: StakeProgramConst.stakeProgramSpace.toInt()));
final minBalance =
minBalanceForRentExemption + StakePoolProgramConst.minimumActiveStake;
final List<ValidatorAccount> accounts = [];
// Prepare accounts
for (final validator in validatorList.validators) {
if (validator.status != ValidatorStakeInfoStatus.active) {
continue;
}
final stakeAccountAddress = StakePoolProgramUtils.findStakeProgramAddress(
voteAccountAddress: validator.voteAccountAddress,
stakePoolAddress: stakePoolAddress,
);
if (validator.activeStakeLamports != BigInt.zero) {
final isPreferred =
stakePool.preferredWithdrawValidatorVoteAddress?.address ==
validator.voteAccountAddress.address;
accounts.add(ValidatorAccount(
type: isPreferred
? ValidatorAccountType.preferred
: ValidatorAccountType.active,
voteAddress: validator.voteAccountAddress,
stakeAddress: stakeAccountAddress.address,
lamports: validator.activeStakeLamports,
));
}
final transientStakeLamports =
validator.transientStakeLamports - minBalance;
if (transientStakeLamports > BigInt.zero) {
final transientStakeAccountAddress =
StakePoolProgramUtils.findTransientStakeProgramAddress(
voteAccountAddress: validator.voteAccountAddress,
stakePoolAddress: stakePoolAddress,
seed: validator.transientSeedSuffixStart,
);
accounts.add(ValidatorAccount(
type: ValidatorAccountType.transient,
voteAddress: validator.voteAccountAddress,
stakeAddress: transientStakeAccountAddress.address,
lamports: transientStakeLamports,
));
}
}
// Sort from highest to lowest balance
accounts.sort(compareFn ?? (a, b) => b.lamports.compareTo(a.lamports));
final reserveStake = await connection
.request(SolanaRPCGetAccountInfo(account: stakePool.reserveStake));
final reserveStakeBalance =
(reserveStake?.lamports ?? BigInt.zero) - minBalanceForRentExemption;
if (reserveStakeBalance > BigInt.zero) {
accounts.add(
ValidatorAccount(
type: ValidatorAccountType.reserve,
stakeAddress: stakePool.reserveStake,
lamports: reserveStakeBalance,
),
);
}
// Prepare the list of accounts to withdraw from
final withdrawFrom = <WithdrawAccount>[];
BigInt remainingAmount = amount;
final fee = stakePool.stakeWithdrawalFee;
final inverseFee = StakePoolFee(
numerator: fee.denominator - fee.numerator,
denominator: fee.denominator,
);
for (final type in ValidatorAccountType.values) {
final filteredAccounts = accounts.where((a) => a.type == type);
for (final i in filteredAccounts) {
if (i.lamports <= minBalance &&
type == ValidatorAccountType.transient) {
continue;
}
BigInt availableForWithdrawal =
calcPoolTokensForDeposit(stakePool, i.lamports);
if (!skipFee && inverseFee.numerator != BigInt.zero) {
availableForWithdrawal = BigRational(
availableForWithdrawal * inverseFee.denominator,
denominator: inverseFee.numerator,
).toBigInt();
}
final poolAmount = availableForWithdrawal < remainingAmount
? availableForWithdrawal
: remainingAmount;
if (poolAmount <= BigInt.zero) {
continue;
}
// Those accounts will be withdrawn completely with `claim` instruction
withdrawFrom.add(WithdrawAccount(
stakeAddress: i.stakeAddress,
voteAddress: i.voteAddress,
poolAmount: poolAmount));
remainingAmount -= poolAmount;
if (remainingAmount == BigInt.zero) {
break;
}
}
if (remainingAmount == BigInt.zero) {
break;
}
}
// Not enough stake to withdraw the specified amount
if (remainingAmount > BigInt.zero) {
throw const SolanaPluginException(
'No stake accounts found in this pool with enough balance to withdraw.');
}
return withdrawFrom;
}