test-npm-den
Advanced tools
Comparing version 1.0.16 to 1.0.17
@@ -8,2 +8,3 @@ import { ESolConfig, ClusterType } from './config'; | ||
unDelegateSol(userAddress: PublicKey, lamports: number, solWithdrawAuthority?: PublicKey): Promise<Transaction>; | ||
withdrawSol(userAddress: PublicKey, lamports: number, stakeReceiver?: PublicKey, poolTokenAccount?: PublicKey): Promise<Transaction>; | ||
} |
140
lib/eSol.js
import { ESolConfig } from './config'; | ||
import { PublicKey, Transaction, Keypair, SystemProgram, StakeProgram, } from '@solana/web3.js'; | ||
import { lamportsToSol, solToLamports } from './utils'; | ||
import { getStakePoolAccount, addAssociatedTokenAccount, findWithdrawAuthorityProgramAddress, getTokenAccount, } from './service/service'; | ||
import { getStakePoolAccount, addAssociatedTokenAccount, findWithdrawAuthorityProgramAddress, getTokenAccount, prepareWithdrawAccounts, calcLamportsWithdrawAmount, newStakeAccount, } from './service/service'; | ||
import { StakePoolProgram } from './service/stakepool-program'; | ||
@@ -254,2 +254,140 @@ import { DAO_STATE_LAYOUT, COMMUNITY_TOKEN_LAYOUT } from './service/layouts'; | ||
} | ||
async withdrawSol(userAddress, lamports, stakeReceiver, poolTokenAccount) { | ||
var _a, _b; | ||
const CONNECTION = this.config.connection; | ||
const stakePoolAddress = this.config.eSOLStakePoolAddress; | ||
const stakePool = await getStakePoolAccount(CONNECTION, stakePoolAddress); | ||
const poolAmount = solToLamports(lamports); | ||
// dao part | ||
const daoStateDtoInfo = await PublicKey.findProgramAddress([Buffer.from(this.config.seedPrefixDaoState), stakePoolAddress.toBuffer(), StakePoolProgram.programId.toBuffer()], StakePoolProgram.programId); | ||
const daoStateDtoPubkey = daoStateDtoInfo[0]; | ||
const daoStateDtoInfoAccount = await CONNECTION.getAccountInfo(daoStateDtoPubkey); | ||
if (!daoStateDtoInfoAccount) { | ||
throw Error("Didn't find dao state account"); | ||
} | ||
const daoState = DAO_STATE_LAYOUT.decode(daoStateDtoInfoAccount.data); | ||
const isDaoEnabled = daoState.isEnabled; | ||
if (!isDaoEnabled) { | ||
throw Error('Dao is not enable'); // it should never happened!!! | ||
} | ||
if (!poolTokenAccount) { | ||
poolTokenAccount = await Token.getAssociatedTokenAddress(ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, stakePool.account.data.poolMint, userAddress); | ||
} | ||
const tokenAccount = await getTokenAccount(CONNECTION, poolTokenAccount, stakePool.account.data.poolMint); | ||
if (!tokenAccount) { | ||
throw new Error('Invalid token account'); | ||
} | ||
// Check withdrawFrom balance | ||
if (tokenAccount.amount.toNumber() < poolAmount) { | ||
throw new Error(`Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens. | ||
Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount.toNumber())} pool tokens.`); | ||
} | ||
const withdrawAuthority = await findWithdrawAuthorityProgramAddress(StakePoolProgram.programId, stakePoolAddress); | ||
// Construct transaction to withdraw from withdrawAccounts account list | ||
const instructions = []; | ||
const userTransferAuthority = Keypair.generate(); | ||
const signers = [userTransferAuthority]; | ||
// dao | ||
const communityTokenStakingRewardsInfo = await PublicKey.findProgramAddress([ | ||
Buffer.from(this.config.seedPrefixCommunityTokenStakingRewards), | ||
stakePoolAddress.toBuffer(), | ||
userAddress.toBuffer(), | ||
StakePoolProgram.programId.toBuffer(), | ||
], StakePoolProgram.programId); | ||
const communityTokenStakingRewardsPubkey = communityTokenStakingRewardsInfo[0]; | ||
const communityTokenStakingRewardsAccount = await CONNECTION.getAccountInfo(communityTokenStakingRewardsPubkey); | ||
// WE SHOULD CHECK NEXT PART IF USER WITHDRAW !!NOT!! ALL ESOL | ||
// We can be sure that this account already exists, as it is created when you deposit. | ||
// But there are some number of users who made a deposit before updating the code with DAO strategy, | ||
// so here we create an account especially for them. | ||
// { | ||
const communityTokenDtoInfo = await PublicKey.findProgramAddress([ | ||
Buffer.from(this.config.seedPrefixCommunityToken), | ||
stakePoolAddress.toBuffer(), | ||
StakePoolProgram.programId.toBuffer(), | ||
], StakePoolProgram.programId); | ||
const communityTokenPubkey = communityTokenDtoInfo[0]; | ||
const communityTokenAccount = await CONNECTION.getAccountInfo(communityTokenPubkey); | ||
if (!communityTokenAccount) { | ||
throw Error('Community token is not exist'); // if isDaoEnabled -> error should NOT happened | ||
} | ||
const communityTokenInfo = COMMUNITY_TOKEN_LAYOUT.decode(communityTokenAccount.data); | ||
// check associatedTokenAccount for RENT 165 BYTES FOR RENTs | ||
const daoCommunityTokenReceiverAccount = await addAssociatedTokenAccount(CONNECTION, userAddress, communityTokenInfo.tokenMint, instructions); | ||
// } | ||
// communityTokenStakingRewardsCounter | ||
const communityTokenStakingRewardsCounterDtoInfo = await PublicKey.findProgramAddress([ | ||
Buffer.from(this.config.seedPrefixCommunityTokenStakingRewardsCounter), | ||
stakePoolAddress.toBuffer(), | ||
StakePoolProgram.programId.toBuffer(), | ||
], StakePoolProgram.programId); | ||
const communityStakingRewardsCounterPubkey = communityTokenStakingRewardsCounterDtoInfo[0]; | ||
const communityTokenStakingRewardsCounterAccount = await CONNECTION.getAccountInfo(communityStakingRewardsCounterPubkey); | ||
if (!communityTokenStakingRewardsCounterAccount) { | ||
throw Error('Community token staking reward counter is not exist'); // if isDaoEnabled -> error should NOT happened | ||
} | ||
if (!communityTokenStakingRewardsAccount) { | ||
// create CommunityTokenStakingRewards | ||
instructions.push(StakePoolProgram.createCommunityTokenStakingRewards({ | ||
stakePoolPubkey: stakePoolAddress, | ||
ownerWallet: userAddress, | ||
communityTokenStakingRewardsDTO: communityTokenStakingRewardsPubkey, | ||
communityTokenStakingRewardsCounterDTO: communityStakingRewardsCounterPubkey, | ||
})); | ||
} | ||
instructions.push(Token.createApproveInstruction(TOKEN_PROGRAM_ID, poolTokenAccount, userTransferAuthority.publicKey, userAddress, [], poolAmount)); | ||
const withdrawAccount = await prepareWithdrawAccounts(CONNECTION, stakePool.account.data, stakePoolAddress, poolAmount); | ||
if (!withdrawAccount) { | ||
throw Error(`Not available at the moment. Please try again later. Sorry for the inconvenience.`); | ||
} | ||
const availableSol = lamportsToSol(withdrawAccount.poolAmount); | ||
if (withdrawAccount.poolAmount < poolAmount) { | ||
throw Error(`Currently, you can undelegate only ${availableSol} SOL within one transaction due to delayed unstake limitations. Please unstake the desired amount in few transactions. Note that you will be able to track your unstaked SOL in the “Wallet” tab as a summary of transactions!.`); | ||
} | ||
const solWithdrawAmount = Math.ceil(calcLamportsWithdrawAmount(stakePool.account.data, withdrawAccount.poolAmount)); | ||
let infoMsg = `Withdrawing ◎${solWithdrawAmount}, | ||
from stake account ${(_a = withdrawAccount.stakeAddress) === null || _a === void 0 ? void 0 : _a.toBase58()}`; | ||
if (withdrawAccount.voteAddress) { | ||
infoMsg = `${infoMsg}, delegated to ${(_b = withdrawAccount.voteAddress) === null || _b === void 0 ? void 0 : _b.toBase58()}`; | ||
} | ||
let stakeToReceive; | ||
let numberOfStakeAccounts = 1; | ||
let totalRentFreeBalances = 0; | ||
function incrementStakeAccount() { | ||
numberOfStakeAccounts++; | ||
} | ||
const stakeReceiverAccountBalance = await CONNECTION.getMinimumBalanceForRentExemption(StakeProgram.space); | ||
const stakeAccountPubkey = await newStakeAccount(CONNECTION, userAddress, instructions, stakeReceiverAccountBalance, numberOfStakeAccounts, incrementStakeAccount); | ||
totalRentFreeBalances += stakeReceiverAccountBalance; | ||
stakeToReceive = stakeAccountPubkey; | ||
stakeReceiver = stakeAccountPubkey; | ||
instructions.push(StakePoolProgram.withdrawStakeWithDao({ | ||
daoCommunityTokenReceiverAccount, | ||
communityTokenStakingRewards: communityTokenStakingRewardsPubkey, | ||
ownerWallet: userAddress, | ||
communityTokenPubkey, | ||
stakePool: stakePoolAddress, | ||
validatorList: stakePool.account.data.validatorList, | ||
validatorStake: withdrawAccount.stakeAddress, | ||
destinationStake: stakeToReceive, | ||
destinationStakeAuthority: userAddress, | ||
sourceTransferAuthority: userTransferAuthority.publicKey, | ||
sourcePoolAccount: poolTokenAccount, | ||
managerFeeAccount: stakePool.account.data.managerFeeAccount, | ||
poolMint: stakePool.account.data.poolMint, | ||
poolTokens: withdrawAccount.poolAmount, | ||
withdrawAuthority, | ||
})); | ||
const deactivateTransaction = StakeProgram.deactivate({ | ||
stakePubkey: stakeToReceive, | ||
authorizedPubkey: userAddress, | ||
}); | ||
instructions.push(...deactivateTransaction.instructions); | ||
const transaction = new Transaction(); | ||
instructions.forEach((instruction) => transaction.add(instruction)); | ||
transaction.feePayer = userAddress; | ||
transaction.recentBlockhash = (await CONNECTION.getRecentBlockhash()).blockhash; | ||
transaction.sign(...signers); | ||
return transaction; | ||
} | ||
} |
@@ -7,2 +7,3 @@ import { PublicKey, Connection, AccountInfo, TransactionInstruction } from '@solana/web3.js'; | ||
} | ||
declare function calcLamportsWithdrawAmount(stakePool: StakePool, poolTokens: number): number; | ||
declare function getStakePoolAccount(connection: Connection, stakePoolPubKey: PublicKey): Promise<StakePoolAccount>; | ||
@@ -12,2 +13,4 @@ declare function addAssociatedTokenAccount(connection: Connection, owner: PublicKey, mint: PublicKey, instructions: TransactionInstruction[]): Promise<PublicKey>; | ||
declare function getTokenAccount(connection: Connection, tokenAccountAddress: PublicKey, expectedTokenMint: PublicKey): Promise<any>; | ||
export { getStakePoolAccount, addAssociatedTokenAccount, findWithdrawAuthorityProgramAddress, getTokenAccount }; | ||
declare function prepareWithdrawAccounts(connection: Connection, stakePool: StakePool, stakePoolAddress: PublicKey, amount: number): Promise<any>; | ||
declare function newStakeAccount(CONNECTION: any, feePayer: PublicKey, instructions: TransactionInstruction[], lamports: number, numberOfStakeAccounts: number, incrementStakeAccount: any): Promise<PublicKey>; | ||
export { getStakePoolAccount, addAssociatedTokenAccount, findWithdrawAuthorityProgramAddress, getTokenAccount, prepareWithdrawAccounts, calcLamportsWithdrawAmount, newStakeAccount, }; |
@@ -1,6 +0,36 @@ | ||
import { PublicKey } from '@solana/web3.js'; | ||
import { PublicKey, SystemProgram, StakeProgram, } from '@solana/web3.js'; | ||
import { Token, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'; | ||
import { STAKE_POOL_LAYOUT, ACCOUNT_LAYOUT } from '../layouts/index'; | ||
import { STAKE_POOL_LAYOUT, ACCOUNT_LAYOUT, VALIDATOR_LIST_LAYOUT, ValidatorStakeInfoStatus, } from '../layouts/index'; | ||
import { findTransientStakeProgramAddress } from './program-address'; | ||
import { StakePoolProgram } from './stakepool-program'; | ||
import BN from 'bn.js'; | ||
const FAILED_TO_FIND_ACCOUNT = 'Failed to find account'; | ||
const INVALID_ACCOUNT_OWNER = 'Invalid account owner'; | ||
async function findStakeProgramAddress(programId, voteAccountAddress, stakePoolAddress) { | ||
const [publicKey] = await PublicKey.findProgramAddress([voteAccountAddress.toBuffer(), stakePoolAddress.toBuffer()], programId); | ||
return publicKey; | ||
} | ||
function calcLamportsWithdrawAmount(stakePool, poolTokens) { | ||
const numerator = new BN(poolTokens).mul(stakePool.totalLamports); | ||
const denominator = stakePool.poolTokenSupply; | ||
if (numerator.lt(denominator)) { | ||
return 0; | ||
} | ||
return divideBnToNumber(numerator, denominator); | ||
} | ||
function calcPoolTokensForDeposit(stakePool, stakeLamports) { | ||
if (stakePool.poolTokenSupply.isZero() || stakePool.totalLamports.isZero()) { | ||
return stakeLamports; | ||
} | ||
return divideBnToNumber(new BN(stakeLamports).mul(stakePool.poolTokenSupply), stakePool.totalLamports); | ||
} | ||
function divideBnToNumber(numerator, denominator) { | ||
if (denominator.isZero()) { | ||
return 0; | ||
} | ||
const quotient = numerator.div(denominator).toNumber(); | ||
const rem = numerator.umod(denominator); | ||
const gcd = rem.gcd(denominator); | ||
return quotient + rem.div(gcd).toNumber() / denominator.div(gcd).toNumber(); | ||
} | ||
async function getStakePoolAccount(connection, stakePoolPubKey) { | ||
@@ -83,2 +113,119 @@ const account = await connection.getAccountInfo(stakePoolPubKey); | ||
} | ||
export { getStakePoolAccount, addAssociatedTokenAccount, findWithdrawAuthorityProgramAddress, getTokenAccount }; | ||
async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount) { | ||
var _a; | ||
const validatorListAcc = await connection.getAccountInfo(stakePool.validatorList); | ||
const validatorList = VALIDATOR_LIST_LAYOUT.decode(validatorListAcc.data); | ||
if (!(validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators) || (validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators.length) === 0) { | ||
throw new Error('No accounts found'); | ||
} | ||
let accounts = []; | ||
// Prepare accounts | ||
for (const validator of validatorList.validators) { | ||
if (validator.status !== ValidatorStakeInfoStatus.Active) { | ||
continue; | ||
} | ||
const stakeAccountAddress = await findStakeProgramAddress(StakePoolProgram.programId, validator.voteAccountAddress, stakePoolAddress); | ||
if (!validator.activeStakeLamports.isZero()) { | ||
const isPreferred = stakePool.preferredWithdrawValidatorVoteAddress && | ||
stakePool.preferredWithdrawValidatorVoteAddress.toBase58() === validator.voteAccountAddress.toBase58(); | ||
accounts.push({ | ||
type: isPreferred ? 'preferred' : 'active', | ||
voteAddress: validator.voteAccountAddress, | ||
stakeAddress: stakeAccountAddress, | ||
lamports: validator.activeStakeLamports.toNumber(), | ||
}); | ||
} | ||
const transientStakeAccountAddress = await findTransientStakeProgramAddress(StakePoolProgram.programId, validator.voteAccountAddress, stakePoolAddress, validator.transientSeedSuffixStart); | ||
if (!((_a = validator.transientStakeLamports) === null || _a === void 0 ? void 0 : _a.isZero())) { | ||
accounts.push({ | ||
type: 'transient', | ||
voteAddress: validator.voteAccountAddress, | ||
stakeAddress: transientStakeAccountAddress, | ||
lamports: validator.transientStakeLamports.toNumber(), | ||
}); | ||
} | ||
} | ||
// Sort from highest to lowest balance | ||
accounts = accounts.sort((a, b) => b.lamports - a.lamports); | ||
// const reserveStake = await connection.getAccountInfo(stakePool.reserveStake); | ||
// if (reserveStake && reserveStake.lamports > 0) { | ||
// accounts.push({ | ||
// type: "reserve", | ||
// stakeAddress: stakePool.reserveStake, | ||
// lamports: reserveStake?.lamports, | ||
// }); | ||
// } | ||
// Prepare the list of accounts to withdraw from | ||
// const withdrawFrom: WithdrawAccount[] = []; | ||
let withdrawFrom; | ||
const remainingAmount = amount; | ||
// for (const type of ["preferred", "active", "transient", "reserve"]) { | ||
for (const type of ['active']) { | ||
const filteredAccounts = accounts.filter((a) => a.type === type); | ||
for (const { stakeAddress, voteAddress, lamports } of filteredAccounts) { | ||
let availableForWithdrawal = Math.floor(calcPoolTokensForDeposit(stakePool, lamports)); | ||
if (!stakePool.stakeWithdrawalFee.denominator.isZero()) { | ||
availableForWithdrawal = divideBnToNumber(new BN(availableForWithdrawal).mul(stakePool.stakeWithdrawalFee.denominator), stakePool.stakeWithdrawalFee.denominator.sub(stakePool.stakeWithdrawalFee.numerator)); | ||
} | ||
const poolAmount = Math.min(availableForWithdrawal, remainingAmount); | ||
if (poolAmount <= 0) { | ||
continue; | ||
} | ||
// Those accounts will be withdrawn completely with `claim` instruction | ||
withdrawFrom = { stakeAddress, voteAddress, poolAmount }; | ||
// new | ||
break; | ||
// remainingAmount -= poolAmount; | ||
// if (remainingAmount == 0) { | ||
// break; | ||
// } | ||
} | ||
if (remainingAmount === 0) { | ||
break; | ||
} | ||
} | ||
// Not enough stake to withdraw the specified amount | ||
// if (remainingAmount > 0) { | ||
// throw new Error( | ||
// `No stake accounts found in this pool with enough balance to withdraw ${lamportsToSol( | ||
// amount, | ||
// )} pool tokens.`, | ||
// ); | ||
// } | ||
return withdrawFrom; | ||
} | ||
async function newStakeAccount(CONNECTION, feePayer, instructions, lamports, numberOfStakeAccounts, incrementStakeAccount) { | ||
// Account for tokens not specified, creating one | ||
const programId = StakePoolProgram.programId.toString(); | ||
let counter = numberOfStakeAccounts; | ||
let stakeReceiverPubkey; | ||
let seed; | ||
while (counter < 12) { | ||
seed = `${feePayer.toString().slice(0, 4)}${programId.slice(0, 4)}everstake${counter}`; | ||
stakeReceiverPubkey = await PublicKey.createWithSeed(feePayer, seed, StakeProgram.programId); | ||
const stakeAccount = await CONNECTION.getAccountInfo(stakeReceiverPubkey); | ||
if (stakeAccount) { | ||
incrementStakeAccount(); | ||
counter++; | ||
} | ||
else { | ||
break; | ||
} | ||
} | ||
incrementStakeAccount(); | ||
if (counter === 12) { | ||
throw Error('This transaction cannot be processed due to the withdrawal accounts limit. Try to use the “Instant unstake” or wait for a new epoch to undelegate. You can also withdraw already deactivated SOL if you have any.'); | ||
} | ||
// Creating new account | ||
instructions.push(SystemProgram.createAccountWithSeed({ | ||
fromPubkey: feePayer, | ||
newAccountPubkey: stakeReceiverPubkey, | ||
basePubkey: feePayer, | ||
seed, | ||
lamports, | ||
space: StakeProgram.space, | ||
programId: StakeProgram.programId, | ||
})); | ||
return stakeReceiverPubkey; | ||
} | ||
export { getStakePoolAccount, addAssociatedTokenAccount, findWithdrawAuthorityProgramAddress, getTokenAccount, prepareWithdrawAccounts, calcLamportsWithdrawAmount, newStakeAccount, }; |
{ | ||
"name": "test-npm-den", | ||
"version": "1.0.16", | ||
"version": "1.0.17", | ||
"description": "Test", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
158546
42
3393