@everstake/wallet-sdk-solana
Version:
Solana - Everstake Wallet SDK
902 lines (894 loc) • 33.7 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
ADDRESS_DEFAULT: () => ADDRESS_DEFAULT,
Blockchain: () => Blockchain,
CHAIN: () => CHAIN,
DEVNET_VALIDATOR_ADDRESS: () => DEVNET_VALIDATOR_ADDRESS,
FILTER_DATA_SIZE: () => FILTER_DATA_SIZE,
FILTER_OFFSET: () => FILTER_OFFSET,
MAINNET_VALIDATOR_ADDRESS: () => MAINNET_VALIDATOR_ADDRESS,
MAX_CLAIM_ACCOUNTS: () => MAX_CLAIM_ACCOUNTS,
MAX_DEACTIVATE_ACCOUNTS: () => MAX_DEACTIVATE_ACCOUNTS,
MAX_DEACTIVATE_ACCOUNTS_WITH_SPLIT: () => MAX_DEACTIVATE_ACCOUNTS_WITH_SPLIT,
MIN_AMOUNT: () => MIN_AMOUNT,
Network: () => Network,
STAKE_ACCOUNT_V2_SIZE: () => STAKE_ACCOUNT_V2_SIZE,
STAKE_CONFIG_ACCOUNT: () => STAKE_CONFIG_ACCOUNT,
STAKE_HISTORY_ACCOUNT: () => STAKE_HISTORY_ACCOUNT,
Solana: () => Solana,
StakeState: () => StakeState,
WalletSDKError: () => WalletSDKError,
isLockupInForce: () => isLockupInForce,
isStake: () => isStake,
stakeAccountState: () => stakeAccountState
});
module.exports = __toCommonJS(index_exports);
// src/solana.ts
var import_kit2 = require("@solana/kit");
var import_system = require("@solana-program/system");
var import_compute_budget = require("@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
var import_kit = require("@solana/kit");
var CHAIN = "solana";
var MIN_AMOUNT = 1e7;
var MAINNET_VALIDATOR_ADDRESS = (0, import_kit.address)(
"9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF"
);
var DEVNET_VALIDATOR_ADDRESS = (0, import_kit.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 = (0, import_kit.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
var import_stake = require("@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 || (0, import_kit2.mainnet)("https://api.mainnet-beta.solana.com");
this.validator = MAINNET_VALIDATOR_ADDRESS;
break;
case "devnet" /* Devnet */:
rpcConfig.rpc = rpcConfig.rpc || (0, import_kit2.devnet)("https://api.devnet.solana.com");
this.validator = DEVNET_VALIDATOR_ADDRESS;
break;
default:
throw this.throwError("UNSUPPORTED_NETWORK_ERROR");
}
try {
const transport = (0, import_kit2.createDefaultRpcTransport)({
url: rpcConfig.rpc,
headers: {
"User-Agent": rpcConfig.userAgent || ""
}
});
this.connection = (0, import_kit2.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(
(0, import_kit2.address)(sender),
BigInt(amountInLamports) + minimumRent
// lockup,
)
) : await this.createAccountWithSeedTx(
(0, import_kit2.address)(sender),
BigInt(amountInLamports) + minimumRent,
source
// lockup,
);
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = (0, import_kit2.appendTransactionMessageInstruction)(
createAccountInstruction,
transactionMessage
);
transactionMessage = (0, import_kit2.appendTransactionMessageInstruction)(
initializeInstruction,
transactionMessage
);
const signedTransactionMessage = source === null ? await (0, import_kit2.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 = (0, import_stake.getDelegateStakeInstruction)({
stake: (0, import_kit2.address)(stakeAccount),
vote: this.validator,
stakeHistory: STAKE_HISTORY_ACCOUNT,
unused: STAKE_CONFIG_ACCOUNT,
stakeAuthority: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(sender))
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = (0, import_kit2.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 = (0, import_stake.getDeactivateInstruction)({
stake: (0, import_kit2.address)(stakeAccountPublicKey),
stakeAuthority: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(sender))
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = (0, import_kit2.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 = (0, import_stake.getWithdrawInstruction)({
stake: stakeAccountPublicKey,
recipient: sender,
stakeHistory: STAKE_HISTORY_ACCOUNT,
withdrawAuthority: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(sender)),
args: stakeBalance
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = (0, import_kit2.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(import_stake.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 = (0, import_kit2.parseBase64RpcAccount)(account.pubkey, account.account);
return (0, import_stake.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(
(0, import_kit2.address)(sender),
BigInt(lamports) + minimumRent
// lockup,
)
) : await this.createAccountWithSeedTx(
(0, import_kit2.address)(sender),
BigInt(lamports) + minimumRent,
source
// lockup,
);
const delegateInstruction = (0, import_stake.getDelegateStakeInstruction)({
stake: stakeAccountPublicKey,
vote: this.validator,
stakeHistory: STAKE_HISTORY_ACCOUNT,
unused: STAKE_CONFIG_ACCOUNT,
stakeAuthority: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(sender))
});
let transactionMessage = await this.baseTx(sender, params);
transactionMessage = (0, import_kit2.appendTransactionMessageInstruction)(
createStakeAccountInstruction,
transactionMessage
);
transactionMessage = (0, import_kit2.appendTransactionMessageInstruction)(
initializeStakeAccountInstruction,
transactionMessage
);
transactionMessage = (0, import_kit2.appendTransactionMessageInstruction)(
delegateInstruction,
transactionMessage
);
const signedTransactionMessage = source === null ? await (0, import_kit2.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 (0, import_kit2.generateKeyPair)();
const signer = await (0, import_kit2.createSignerFromKeyPair)(stakeAccountKeyPair);
const createAccountInstruction = (0, import_system.getCreateAccountInstruction)({
payer: (0, import_kit2.createNoopSigner)(authorityPublicKey),
newAccount: signer,
lamports,
// TODO get from package
space: STAKE_ACCOUNT_V2_SIZE,
programAddress: import_stake.STAKE_PROGRAM_ADDRESS
});
const initializeInstruction = (0, import_stake.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 (0, import_kit2.createAddressWithSeed)({
baseAddress: authorityPublicKey,
programAddress: import_stake.STAKE_PROGRAM_ADDRESS,
seed
});
const createAccountInstruction = (0, import_system.getCreateAccountWithSeedInstruction)({
payer: (0, import_kit2.createNoopSigner)(authorityPublicKey),
newAccount: stakeAccountPubkey,
baseAccount: (0, import_kit2.createNoopSigner)(authorityPublicKey),
base: (0, import_kit2.address)(authorityPublicKey),
seed,
amount: lamports,
// TODO get from package
space: STAKE_ACCOUNT_V2_SIZE,
programAddress: import_stake.STAKE_PROGRAM_ADDRESS
});
const initializeInstruction = (0, import_stake.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 = (0, import_kit2.address)(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 = (0, import_kit2.appendTransactionMessageInstruction)(
splitInstruction,
transactionMessage
)
);
const deactivateInstruction = (0, import_stake.getDeactivateInstruction)({
stake: newStakeAccountPubkey,
stakeAuthority: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(sender))
});
transactionMessage = (0, import_kit2.appendTransactionMessageInstruction)(
deactivateInstruction,
transactionMessage
);
}
accountsToDeactivate.forEach((acc) => {
const deactivateInstruction = (0, import_stake.getDeactivateInstruction)({
stake: acc.address,
stakeAuthority: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(sender))
});
transactionMessage = (0, import_kit2.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 (0, import_kit2.createAddressWithSeed)({
baseAddress: authorityPublicKey,
programAddress: import_stake.STAKE_PROGRAM_ADDRESS,
seed
});
const instructions = [];
const allocateWithSeedInstruction = (0, import_system.getAllocateWithSeedInstruction)({
newAccount: newStakeAccountPubkey,
baseAccount: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(authorityPublicKey)),
base: authorityPublicKey,
seed,
//TODO get from library if possible
space: STAKE_ACCOUNT_V2_SIZE,
programAddress: import_stake.STAKE_PROGRAM_ADDRESS
});
instructions.push(allocateWithSeedInstruction);
if (rentExemptReserve && rentExemptReserve > 0) {
const rentTransferInstruction = (0, import_system.getTransferSolInstruction)({
source: (0, import_kit2.createNoopSigner)(authorityPublicKey),
destination: newStakeAccountPubkey,
amount: rentExemptReserve
});
instructions.push(rentTransferInstruction);
}
const splitInstruction = (0, import_stake.getSplitInstruction)({
stake: oldStakeAccountPubkey,
splitStake: newStakeAccountPubkey,
stakeAuthority: (0, import_kit2.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 = (0, import_stake.getWithdrawInstruction)({
stake: acc.address,
recipient: (0, import_kit2.address)(sender),
stakeHistory: STAKE_HISTORY_ACCOUNT,
withdrawAuthority: (0, import_kit2.createNoopSigner)((0, import_kit2.address)(sender)),
args: acc.lamports
});
transactionMessage = (0, import_kit2.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 = (0, import_kit2.pipe)(
(0, import_kit2.createTransactionMessage)({ version: 0 }),
(tx) => (0, import_kit2.setTransactionMessageFeePayer)((0, import_kit2.address)(sender), tx),
(tx) => (0, import_kit2.setTransactionMessageLifetimeUsingBlockhash)(finalLatestBlockhash, tx)
);
if (params?.computeUnitLimit !== void 0 && params?.computeUnitLimit > 0) {
const unitLimitInstruction = (0, import_compute_budget.getSetComputeUnitLimitInstruction)({
/** Transaction compute unit limit used for prioritization fees. */
units: params?.computeUnitLimit
});
transactionMessage = (0, import_kit2.prependTransactionMessageInstruction)(
unitLimitInstruction,
transactionMessage
);
}
if (params?.\u0441omputeUnitPrice !== void 0 && params?.\u0441omputeUnitPrice > 0) {
const unitPriceInstruction = (0, import_compute_budget.getSetComputeUnitPriceInstruction)({
/** Transaction compute unit price used for prioritization fees. */
microLamports: params?.\u0441omputeUnitPrice
});
transactionMessage = (0, import_kit2.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";
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
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
});