@everstake/wallet-sdk-solana
Version:
Solana - Everstake Wallet SDK
889 lines (883 loc) • 31.1 kB
JavaScript
// src/solana.ts
import {
createAddressWithSeed,
createDefaultRpcTransport,
createSolanaRpcFromTransport,
mainnet,
devnet,
address as address2,
createNoopSigner,
pipe,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
generateKeyPair,
createSignerFromKeyPair,
partiallySignTransactionMessageWithSigners,
parseBase64RpcAccount,
prependTransactionMessageInstruction
} from "@solana/kit";
import {
getCreateAccountWithSeedInstruction,
getCreateAccountInstruction,
getTransferSolInstruction,
getAllocateWithSeedInstruction
} from "@solana-program/system";
import {
getSetComputeUnitLimitInstruction,
getSetComputeUnitPriceInstruction
} from "@solana-program/compute-budget";
// ../utils/constants/errors.ts
var COMMON_ERROR_MESSAGES = {
UNKNOWN_ERROR: "An unknown error occurred",
TOKEN_ERROR: "Please create or use correct token"
};
// ../utils/index.ts
var WalletSDKError = class extends Error {
constructor(message, code, originalError) {
super(message);
this.code = code;
this.originalError = originalError;
Object.setPrototypeOf(this, new.target.prototype);
}
};
var Blockchain = class {
/**
* Handles errors that occur within the Ethereum class.
*
* @param {keyof typeof ERROR_MESSAGES} code - The error code associated with the error.
* @param {Error | WalletSDKError | unknown} originalError - The original error that was thrown.
*
* If the original error is an instance of WalletSDKError, it is thrown as is.
* If the original error is an instance of the built-in Error class, a new WalletSDKError is thrown with the original error as the cause.
* If the original error is not an instance of WalletSDKError or Error, a new WalletSDKError is thrown with a generic message and code.
*/
handleError(code, originalError) {
const message = this.ERROR_MESSAGES[code];
if (originalError instanceof WalletSDKError || !message || !code) {
throw originalError;
}
if (originalError instanceof Error) {
const newMessage = Object.entries(this.ORIGINAL_ERROR_MESSAGES).find(
([originalMessage]) => originalError.message.includes(originalMessage)
)?.[1];
const errorMessage = newMessage || this.ERROR_MESSAGES[code] || COMMON_ERROR_MESSAGES["UNKNOWN_ERROR"];
throw new WalletSDKError(errorMessage, String(code), originalError);
}
throw new WalletSDKError(
COMMON_ERROR_MESSAGES["UNKNOWN_ERROR"],
"UNKNOWN_ERROR"
);
}
/**
* Throws a WalletSDKError with a specified error code and message.
*
* @param {keyof typeof ERROR_MESSAGES} code - The error code associated with the error.
* @param {...string[]} values - The values to be inserted into the error message.
*
* The method retrieves the error message template associated with the provided code from the ERROR_MESSAGES object.
* It then replaces placeholders in the message template with provided values and throws a WalletSDKError with the final message and the provided code.
*/
throwError(code, ...values) {
let message = this.ERROR_MESSAGES[code];
values.forEach((value, index) => {
message = message?.replace(`{${index}}`, value);
});
if (!message) {
throw new WalletSDKError(
COMMON_ERROR_MESSAGES["UNKNOWN_ERROR"],
"UNKNOWN_ERROR"
);
}
throw new WalletSDKError(message, String(code));
}
/**
* Check if the URL is valid
*
* @param {string} url - URL
* @returns a bool type result.
*
*/
isValidURL(url) {
let urlClass;
try {
urlClass = new URL(url);
} catch (_) {
return false;
}
return urlClass.protocol === "http:" || urlClass.protocol === "https:";
}
};
// src/constants/errors.ts
var ERROR_MESSAGES = {
CONNECTION_ERROR: "An error occurred while connecting to the network",
MIN_AMOUNT_ERROR: "Min Amount {0}",
CREATE_ACCOUNT_ERROR: "An error occurred while creating the account",
DELEGATE_ERROR: "An error occurred while delegating the stake",
DEACTIVATE_ERROR: "An error occurred while deactivating the stake",
WITHDRAW_ERROR: "An error occurred while withdrawing the stake",
GET_DELEGATIONS_ERROR: "An error occurred while fetching the delegations",
STAKE_ERROR: "An error occurred while staking",
INVALID_RPC_ERROR: "Invalid RPC URL",
UNSUPPORTED_NETWORK_ERROR: "Unsupported Network",
CLAIM_ERROR: "An error occurred while claim SOL",
UNSTAKE_ERROR: "An error occurred while unstaking the stake",
NOTHING_TO_CLAIM_ERROR: "Nothing to claim while claiming",
NOT_ENOUGH_ACTIVE_STAKE_ERROR: "Active stake less than requested"
};
// src/constants/index.ts
import { address } from "@solana/kit";
var CHAIN = "solana";
var MIN_AMOUNT = 1e7;
var MAINNET_VALIDATOR_ADDRESS = address(
"9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF"
);
var DEVNET_VALIDATOR_ADDRESS = address(
"GkqYQysEGmuL6V2AJoNnWZUz2ZBGWhzQXsJiXm2CLKAN"
);
var FILTER_DATA_SIZE = 200n;
var FILTER_OFFSET = 44n;
var Network = /* @__PURE__ */ ((Network2) => {
Network2["Mainnet"] = "mainnet-beta";
Network2["Devnet"] = "devnet";
return Network2;
})(Network || {});
var StakeState = {
Inactive: "inactive",
Activating: "activating",
Active: "active",
Deactivating: "deactivating",
Deactivated: "deactivated"
};
var STAKE_ACCOUNT_V2_SIZE = 200;
var ADDRESS_DEFAULT = address("11111111111111111111111111111111");
var STAKE_HISTORY_ACCOUNT = "SysvarStakeHistory1111111111111111111111111";
var STAKE_CONFIG_ACCOUNT = "StakeConfig11111111111111111111111111111111";
var MAX_CLAIM_ACCOUNTS = 16;
var MAX_DEACTIVATE_ACCOUNTS = 22;
var MAX_DEACTIVATE_ACCOUNTS_WITH_SPLIT = 16;
// src/solana.ts
import {
getWithdrawInstruction,
getDelegateStakeInstruction,
getDeactivateInstruction,
getInitializeInstruction,
getSplitInstruction,
STAKE_PROGRAM_ADDRESS,
decodeStakeStateAccount
} from "@solana-program/stake";
var Solana = class extends Blockchain {
connection;
validator;
ERROR_MESSAGES = ERROR_MESSAGES;
ORIGINAL_ERROR_MESSAGES = {};
constructor(network = "mainnet-beta" /* Mainnet */, rpcConfig = {}) {
super();
if (rpcConfig.rpc && !this.isValidURL(rpcConfig.rpc)) {
throw this.throwError("INVALID_RPC_ERROR");
}
switch (network) {
case "mainnet-beta" /* Mainnet */:
rpcConfig.rpc = rpcConfig.rpc || mainnet("https://api.mainnet-beta.solana.com");
this.validator = MAINNET_VALIDATOR_ADDRESS;
break;
case "devnet" /* Devnet */:
rpcConfig.rpc = rpcConfig.rpc || devnet("https://api.devnet.solana.com");
this.validator = DEVNET_VALIDATOR_ADDRESS;
break;
default:
throw this.throwError("UNSUPPORTED_NETWORK_ERROR");
}
try {
const transport = createDefaultRpcTransport({
url: rpcConfig.rpc,
headers: {
"User-Agent": rpcConfig.userAgent || ""
}
});
this.connection = createSolanaRpcFromTransport(transport);
} catch (error) {
throw this.handleError("CONNECTION_ERROR", error);
}
}
/**
* Creates a new stake account.
*
* @param address - The public key of the account as PublicKey.
* @param lamports - The amount to stake in lamports.
* @param source - stake source
* @param lockup - stake account lockup
*
* @throws Throws an error if the lamports is less than the minimum amount.
* @throws Throws an error if there's an issue creating the stake account.
*
* @returns Returns a promise that resolves with the versioned transaction of the stake account creation and the public key of the stake account.
*
*/
async createAccount(sender, amountInLamports, source, params) {
if (amountInLamports < MIN_AMOUNT) {
this.throwError("MIN_AMOUNT_ERROR", MIN_AMOUNT.toString());
}
try {
const minimumRent = await this.connection.getMinimumBalanceForRentExemption(
//TODO get from account when it's would be available
BigInt(STAKE_ACCOUNT_V2_SIZE)
).send();
const [
createAccountInstruction,
initializeInstruction,
stakeAccountPubkey
] = source === null ? (
// TODO fix create account sign
await this.createAccountTx(
address2(sender),
BigInt(amountInLamports) + minimumRent
// lockup,
)
) : await this.createAccountWithSeedTx(
address2(sender),
BigInt(amountInLamports) + minimumRent,
source
// lockup,
);
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = appendTransactionMessageInstruction(
createAccountInstruction,
transactionMessage
);
transactionMessage = appendTransactionMessageInstruction(
initializeInstruction,
transactionMessage
);
const signedTransactionMessage = source === null ? await partiallySignTransactionMessageWithSigners(transactionMessage) : transactionMessage;
return {
result: {
transaction: signedTransactionMessage,
stakeAccount: stakeAccountPubkey
}
};
} catch (error) {
throw this.handleError("CREATE_ACCOUNT_ERROR", error);
}
}
/**
* Delegates a specified amount from a stake account to a validator.
*
* @param address - The public key of the account.
* @param lamports - The amount in lamports to be delegated.
* @param stakeAccount - The public key of the stake account.
*
* @throws Throws an error if the amount is less than the minimum amount, or if there's an issue during the delegation process.
*
* @returns Returns a promise that resolves with the delegation transaction.
*
*/
async delegate(sender, lamports, stakeAccount, params) {
if (lamports < MIN_AMOUNT) {
this.throwError("MIN_AMOUNT_ERROR", MIN_AMOUNT.toString());
}
try {
const delegateInstruction = getDelegateStakeInstruction({
stake: address2(stakeAccount),
vote: this.validator,
stakeHistory: STAKE_HISTORY_ACCOUNT,
unused: STAKE_CONFIG_ACCOUNT,
stakeAuthority: createNoopSigner(address2(sender))
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = appendTransactionMessageInstruction(
delegateInstruction,
transactionMessage
);
return { result: transactionMessage };
} catch (error) {
throw this.handleError("DELEGATE_ERROR", error);
}
}
/**
* Deactivates a stake account.
*
* @param address - The public key of the account.
* @param stakeAccountPublicKey - The public key of the stake account.
* @throws Throws an error if there's an issue during the deactivation process.
* @returns Returns a promise that resolves with the deactivation transaction.
*
*/
async deactivate(sender, stakeAccountPublicKey, params) {
try {
const deactivateInstruction = getDeactivateInstruction({
stake: address2(stakeAccountPublicKey),
stakeAuthority: createNoopSigner(address2(sender))
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = appendTransactionMessageInstruction(
deactivateInstruction,
transactionMessage
);
return { result: transactionMessage };
} catch (error) {
throw this.handleError("DEACTIVATE_ERROR", error);
}
}
/**
* Withdraws a specified amount from a stake account.
*
* @param address - The public key of the account.
* @param stakeAccountPublicKey - The public key of the stake account.
* @param stakeBalance - The amount in lamports to be withdrawn from the stake account.
*
* @throws Throws an error if there's an issue during the withdrawal process.
*
* @returns Returns a promise that resolves with the withdrawal transaction.
*
*/
async withdraw(sender, stakeAccountPublicKey, stakeBalance, params) {
try {
const withdrawInstruction = getWithdrawInstruction({
stake: stakeAccountPublicKey,
recipient: sender,
stakeHistory: STAKE_HISTORY_ACCOUNT,
withdrawAuthority: createNoopSigner(address2(sender)),
args: stakeBalance
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = appendTransactionMessageInstruction(
withdrawInstruction,
transactionMessage
);
return { result: transactionMessage };
} catch (error) {
throw this.handleError("WITHDRAW_ERROR", error);
}
}
/**
* Fetches the delegations of a given account.
*
* @param address - The public key of the account.
*
* @throws Throws an error if there's an issue fetching the delegations.
*
* @returns Returns a promise that resolves with the delegations of the account.
*
*/
async getDelegations(address3) {
try {
const accounts = await this.connection.getProgramAccounts(STAKE_PROGRAM_ADDRESS, {
encoding: "base64",
filters: [
{
dataSize: FILTER_DATA_SIZE
// Token account size
},
{
memcmp: {
bytes: address3,
encoding: "base58",
offset: FILTER_OFFSET
}
}
]
}).send();
const acs = accounts.map((account) => {
const acc = parseBase64RpcAccount(account.pubkey, account.account);
return decodeStakeStateAccount(acc);
});
return { result: acs };
} catch (error) {
throw this.handleError("GET_DELEGATIONS_ERROR", error);
}
}
/**
* Stakes a certain amount of lamports.
*
* @param sender - The public key of the sender.
* @param lamports - The number of lamports to stake.
* @param source - stake source
* @param lockup - stake account lockup
* @returns A promise that resolves to a VersionedTransaction object.
*/
async stake(sender, lamports, source, params) {
try {
const minimumRent = await this.connection.getMinimumBalanceForRentExemption(
//TODO get from account when would be added
BigInt(STAKE_ACCOUNT_V2_SIZE)
).send();
const [
createStakeAccountInstruction,
initializeStakeAccountInstruction,
stakeAccountPublicKey
] = source === null ? (
// TODO fix create account sign
await this.createAccountTx(
address2(sender),
BigInt(lamports) + minimumRent
// lockup,
)
) : await this.createAccountWithSeedTx(
address2(sender),
BigInt(lamports) + minimumRent,
source
// lockup,
);
const delegateInstruction = getDelegateStakeInstruction({
stake: stakeAccountPublicKey,
vote: this.validator,
stakeHistory: STAKE_HISTORY_ACCOUNT,
unused: STAKE_CONFIG_ACCOUNT,
stakeAuthority: createNoopSigner(address2(sender))
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = appendTransactionMessageInstruction(
createStakeAccountInstruction,
transactionMessage
);
transactionMessage = appendTransactionMessageInstruction(
initializeStakeAccountInstruction,
transactionMessage
);
transactionMessage = appendTransactionMessageInstruction(
delegateInstruction,
transactionMessage
);
const signedTransactionMessage = source === null ? await partiallySignTransactionMessageWithSigners(transactionMessage) : transactionMessage;
return {
result: {
stakeTx: signedTransactionMessage,
stakeAccount: stakeAccountPublicKey
}
};
} catch (error) {
throw this.handleError("STAKE_ERROR", error);
}
}
/**
* Create account Tx, public key and array of keypair.
*
* @param address - The public key of the account.
* @param lamports - The number of lamports to stake.
* @param lockup - The stake account lockup
*
* @throws Throws an error if there's an issue creating an account.
*
* @returns Returns a promise that resolves with the Transaction, PublicKey and array of Keypair.
*
*/
async createAccountTx(authorityPublicKey, lamports) {
const stakeAccountKeyPair = await generateKeyPair();
const signer = await createSignerFromKeyPair(stakeAccountKeyPair);
const createAccountInstruction = getCreateAccountInstruction({
payer: createNoopSigner(authorityPublicKey),
newAccount: signer,
lamports,
// TODO get from package
space: STAKE_ACCOUNT_V2_SIZE,
programAddress: STAKE_PROGRAM_ADDRESS
});
const initializeInstruction = getInitializeInstruction(
/** Uninitialized stake account */
{
stake: signer.address,
arg0: {
staker: authorityPublicKey,
withdrawer: authorityPublicKey
},
arg1: {
//TODO use default
unixTimestamp: 0,
epoch: 0,
custodian: ADDRESS_DEFAULT
}
}
);
return [createAccountInstruction, initializeInstruction, signer.address];
}
/**
* Create account Tx, public key and array of keypair using seed.
*
* @param authorityPublicKey - The public key of the account.
* @param lamports - The number of lamports to stake.
* @param source - The stake source
* @param lockup - The stake account lockup
*
* @throws Throws an error if there's an issue creating an account.
*
* @returns Returns a promise that resolves with the Transaction, PublicKey and array of Keypair.
*
*/
async createAccountWithSeedTx(authorityPublicKey, lamports, source) {
const seed = this.formatSource(source || "");
const stakeAccountPubkey = await createAddressWithSeed({
baseAddress: authorityPublicKey,
programAddress: STAKE_PROGRAM_ADDRESS,
seed
});
const createAccountInstruction = getCreateAccountWithSeedInstruction({
payer: createNoopSigner(authorityPublicKey),
newAccount: stakeAccountPubkey,
baseAccount: createNoopSigner(authorityPublicKey),
base: address2(authorityPublicKey),
seed,
amount: lamports,
// TODO get from package
space: STAKE_ACCOUNT_V2_SIZE,
programAddress: STAKE_PROGRAM_ADDRESS
});
const initializeInstruction = getInitializeInstruction(
/** Uninitialized stake account */
{
stake: stakeAccountPubkey,
arg0: {
staker: authorityPublicKey,
withdrawer: authorityPublicKey
},
arg1: {
//TODO implement Lockup
unixTimestamp: 0,
epoch: 0,
custodian: ADDRESS_DEFAULT
}
}
);
return [
createAccountInstruction,
initializeInstruction,
stakeAccountPubkey
];
}
/** unstake - unstake
* @param {string} sender - account blockchain address (staker)
* @param {bigint} lamports - lamport amount
* @param {string} source - stake source
* @returns {Promise<object>} Promise object with Versioned Tx
*/
async unstake(sender, lamports, source, params) {
try {
const stakeAccounts = (await this.getDelegations(sender)).result;
const epoch = params?.epoch || (await this.connection.getEpochInfo().send()).epoch;
const tm = this.timestampInSec();
let unstakeAmount = lamports;
let totalActiveStake = 0n;
const activeStakeAccounts = stakeAccounts.filter((acc) => {
if (acc.data.state.__kind !== "Stake") {
return false;
}
const isActive = !(isLockupInForce(acc.data, epoch, BigInt(tm)) || stakeAccountState(acc.data, epoch) !== StakeState.Active);
if (isActive) {
totalActiveStake = totalActiveStake + acc.data.state.fields[1].delegation.stake;
}
return isActive;
});
if (totalActiveStake < lamports)
throw this.throwError("NOT_ENOUGH_ACTIVE_STAKE_ERROR");
activeStakeAccounts.sort((a, b) => {
const stakeA = isStake(a.data.state) ? a.data.state.fields[1].delegation.stake : 0n;
const stakeB = isStake(b.data.state) ? b.data.state.fields[1].delegation.stake : 0n;
if (activeStakeAccounts.length < MAX_DEACTIVATE_ACCOUNTS_WITH_SPLIT) {
return Number(stakeA - stakeB);
}
return Number(stakeB - stakeA);
});
const accountsToDeactivate = [];
const accountsToSplit = [];
let i = 0;
while (lamports > 0n && i < activeStakeAccounts.length) {
const acc = activeStakeAccounts[i];
if (acc === void 0 || !isStake(acc.data.state)) {
i++;
continue;
}
const stakeAmount = acc.data.state.fields[1].delegation.stake;
const isBelowThreshold = stakeAmount <= lamports || stakeAmount - lamports < MIN_AMOUNT;
if (isBelowThreshold) {
accountsToDeactivate.push(acc);
lamports = lamports - stakeAmount;
i++;
if (accountsToDeactivate.length === MAX_DEACTIVATE_ACCOUNTS) {
unstakeAmount -= lamports;
break;
}
continue;
}
if (accountsToDeactivate.length > MAX_DEACTIVATE_ACCOUNTS_WITH_SPLIT) {
unstakeAmount -= lamports;
break;
}
accountsToSplit.push([acc, lamports]);
break;
}
const senderPublicKey = address2(sender);
let transactionMessage = await this.baseTx(sender, params);
const minimumRent = accountsToSplit.length > 0 ? await this.connection.getMinimumBalanceForRentExemption(
//TODO get from account when it's would be available
BigInt(STAKE_ACCOUNT_V2_SIZE)
).send() : 0n;
for (const acc of accountsToSplit) {
const [splitInstructions, newStakeAccountPubkey] = await this.split(
senderPublicKey,
acc[1],
acc[0].address,
source,
// Need additional value for rent
minimumRent
);
splitInstructions.forEach(
(splitInstruction) => transactionMessage = appendTransactionMessageInstruction(
splitInstruction,
transactionMessage
)
);
const deactivateInstruction = getDeactivateInstruction({
stake: newStakeAccountPubkey,
stakeAuthority: createNoopSigner(address2(sender))
});
transactionMessage = appendTransactionMessageInstruction(
deactivateInstruction,
transactionMessage
);
}
accountsToDeactivate.forEach((acc) => {
const deactivateInstruction = getDeactivateInstruction({
stake: acc.address,
stakeAuthority: createNoopSigner(address2(sender))
});
transactionMessage = appendTransactionMessageInstruction(
deactivateInstruction,
transactionMessage
);
});
if (transactionMessage.instructions.length === 0) {
this.handleError("UNSTAKE_ERROR", "zero instructions");
}
return {
result: { unstakeTx: transactionMessage, unstakeAmount }
};
} catch (error) {
throw this.handleError("UNSTAKE_ERROR", error);
}
}
/**
* Split existing account to create a new one
*
* @param authorityPublicKey - The public key of the account.
* @param lamports - The number of lamports to stake.
* @param oldStakeAccountPubkey -The public key of the old account.
* @param source - The stake source
*
* @throws Throws an error if there's an issue splitting an account.
*
* @returns Returns a promise that resolves with the Transaction, PublicKey and array of Keypair.
*
*/
async split(authorityPublicKey, lamports, oldStakeAccountPubkey, source, rentExemptReserve) {
const seed = this.formatSource(source);
const newStakeAccountPubkey = await createAddressWithSeed({
baseAddress: authorityPublicKey,
programAddress: STAKE_PROGRAM_ADDRESS,
seed
});
const instructions = [];
const allocateWithSeedInstruction = getAllocateWithSeedInstruction({
newAccount: newStakeAccountPubkey,
baseAccount: createNoopSigner(address2(authorityPublicKey)),
base: authorityPublicKey,
seed,
//TODO get from library if possible
space: STAKE_ACCOUNT_V2_SIZE,
programAddress: STAKE_PROGRAM_ADDRESS
});
instructions.push(allocateWithSeedInstruction);
if (rentExemptReserve && rentExemptReserve > 0) {
const rentTransferInstruction = getTransferSolInstruction({
source: createNoopSigner(authorityPublicKey),
destination: newStakeAccountPubkey,
amount: rentExemptReserve
});
instructions.push(rentTransferInstruction);
}
const splitInstruction = getSplitInstruction({
stake: oldStakeAccountPubkey,
splitStake: newStakeAccountPubkey,
stakeAuthority: createNoopSigner(authorityPublicKey),
args: lamports
});
instructions.push(splitInstruction);
return [instructions, newStakeAccountPubkey];
}
/**
* Claim makes withdrawal from all sender's deactivated accounts.
*
* @param sender - The sender solana address.
*
* @throws Throws an error if there's an issue while claiming a stake.
*
* @returns Returns a promise that resolves with a Versioned Transaction.
*
*/
async claim(sender, params) {
try {
const delegations = await this.getDelegations(sender);
const epoch = params?.epoch || (await this.connection.getEpochInfo().send()).epoch;
const tm = this.timestampInSec();
const deactivatedStakeAccounts = delegations.result.filter((acc) => {
return !isLockupInForce(acc.data, epoch, BigInt(tm)) && stakeAccountState(acc.data, epoch) === StakeState.Deactivated;
});
if (deactivatedStakeAccounts.length === 0)
throw this.throwError("NOTHING_TO_CLAIM_ERROR");
let transactionMessage = await this.baseTx(sender, params);
let totalClaimableStake = 0n;
let accountsForClaim = 0;
for (const acc of deactivatedStakeAccounts) {
const withdrawInstruction = getWithdrawInstruction({
stake: acc.address,
recipient: address2(sender),
stakeHistory: STAKE_HISTORY_ACCOUNT,
withdrawAuthority: createNoopSigner(address2(sender)),
args: acc.lamports
});
transactionMessage = appendTransactionMessageInstruction(
withdrawInstruction,
transactionMessage
);
totalClaimableStake += acc.lamports;
accountsForClaim++;
if (accountsForClaim === MAX_CLAIM_ACCOUNTS) {
break;
}
}
return {
result: {
claimTx: transactionMessage,
totalClaimAmount: totalClaimableStake
}
};
} catch (error) {
throw this.handleError("CLAIM_ERROR", error);
}
}
/**
* Merge two accounts into a new one
*
* @param authorityPublicKey - The public key of the account.
* @param stakeAccount1 - The public key of the first account.
* @param stakeAccount2 - The public key of the second account.
*
* @throws Throws an error if there's an issue while merging an account.
*
* @returns Returns a promise that resolves with the Transaction, PublicKey and array of Keypair.
*
*/
// private async merge(
// authorityPublicKey: PublicKey,
// stakeAccount1: PublicKey,
// stakeAccount2: PublicKey,
// ) {
// const mergeStakeAccountTx = StakeProgram.merge({
// stakePubkey: stakeAccount1,
// sourceStakePubKey: stakeAccount2,
// authorizedPubkey: authorityPublicKey,
// });
// return [mergeStakeAccountTx];
// }
async baseTx(sender, params) {
const finalLatestBlockhash = params?.finalLatestBlockhash || (await this.connection.getLatestBlockhash().send()).value;
let transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayer(address2(sender), tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(finalLatestBlockhash, tx)
);
if (params?.computeUnitLimit !== void 0 && params?.computeUnitLimit > 0) {
const unitLimitInstruction = getSetComputeUnitLimitInstruction({
/** Transaction compute unit limit used for prioritization fees. */
units: params?.computeUnitLimit
});
transactionMessage = prependTransactionMessageInstruction(
unitLimitInstruction,
transactionMessage
);
}
if (params?.\u0441omputeUnitPrice !== void 0 && params?.\u0441omputeUnitPrice > 0) {
const unitPriceInstruction = getSetComputeUnitPriceInstruction({
/** Transaction compute unit price used for prioritization fees. */
microLamports: params?.\u0441omputeUnitPrice
});
transactionMessage = prependTransactionMessageInstruction(
unitPriceInstruction,
transactionMessage
);
}
return transactionMessage;
}
/**
* Generate a unique source for crating an account.
*
* @param source - source ID.
*
* @returns Returns a unique source for an account.
*
*/
formatSource(source) {
const timestamp = (/* @__PURE__ */ new Date()).getTime();
source = `everstake ${source}:${timestamp}`;
return source;
}
/**
* Generate timestamp in seconds.
*
* @returns Returns a timestamp in seconds.
*
*/
timestampInSec() {
return Date.now() / 1e3 | 0;
}
};
function stakeAccountState(account, currentEpoch) {
if (account.state.__kind !== "Stake") {
return StakeState.Inactive;
}
const activationEpoch = account.state.fields[1].delegation.activationEpoch;
const deactivationEpoch = account.state.fields[1].delegation.deactivationEpoch;
if (activationEpoch > currentEpoch) {
return StakeState.Inactive;
}
if (activationEpoch === currentEpoch) {
if (deactivationEpoch === activationEpoch) return StakeState.Inactive;
return StakeState.Activating;
}
if (deactivationEpoch > currentEpoch) return StakeState.Active;
if (deactivationEpoch === currentEpoch) return StakeState.Deactivating;
return StakeState.Deactivated;
}
function isLockupInForce(account, currEpoch, currUnixTimestamp) {
if (account.state.__kind !== "Stake" && account.state.__kind !== "Initialized") {
return false;
}
const { unixTimestamp, epoch } = account.state.fields[0].lockup;
return unixTimestamp > currUnixTimestamp || epoch > currEpoch;
}
function isStake(state) {
return state.__kind === "Stake";
}
export {
ADDRESS_DEFAULT,
Blockchain,
CHAIN,
DEVNET_VALIDATOR_ADDRESS,
FILTER_DATA_SIZE,
FILTER_OFFSET,
MAINNET_VALIDATOR_ADDRESS,
MAX_CLAIM_ACCOUNTS,
MAX_DEACTIVATE_ACCOUNTS,
MAX_DEACTIVATE_ACCOUNTS_WITH_SPLIT,
MIN_AMOUNT,
Network,
STAKE_ACCOUNT_V2_SIZE,
STAKE_CONFIG_ACCOUNT,
STAKE_HISTORY_ACCOUNT,
Solana,
StakeState,
WalletSDKError,
isLockupInForce,
isStake,
stakeAccountState
};