UNPKG

@everstake/wallet-sdk-solana

Version:
875 lines (866 loc) 28.8 kB
// src/solana.ts import { Authorized, clusterApiUrl, ComputeBudgetProgram, Connection, Keypair, Lockup, PublicKey as PublicKey3, StakeProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; // ../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", GET_EPOCH_INFO_ERROR: "An error occurred while fetching epoch information" }; // src/constants/index.ts import { PublicKey } from "@solana/web3.js"; import { enums } from "superstruct"; var SOL_CHAIN = "solana"; var SOL_MIN_AMOUNT = 1e7; var SOL_MAINNET_VALIDATOR_ADDRESS = new PublicKey( "9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF" ); var SOL_DEVNET_VALIDATOR_ADDRESS = new PublicKey( "GkqYQysEGmuL6V2AJoNnWZUz2ZBGWhzQXsJiXm2CLKAN" ); var FILTER_DATA_SIZE = 200; var FILTER_OFFSET = 44; var SolNetwork = /* @__PURE__ */ ((SolNetwork2) => { SolNetwork2["Mainnet"] = "mainnet-beta"; SolNetwork2["Devnet"] = "devnet"; return SolNetwork2; })(SolNetwork || {}); var StakeState = { inactive: "inactive", activating: "activating", active: "active", deactivating: "deactivating", deactivated: "deactivated" }; var StakeAccountType = enums([ "uninitialized", "initialized", "delegated", "rewardsPool" ]); // src/solana.ts import BigNumber3 from "bignumber.js"; // src/types/stakeAccount.ts import { PublicKey as PublicKey2 } from "@solana/web3.js"; import { coerce, instance, nullable, number, string, type } from "superstruct"; import BigNumber from "bignumber.js"; var BigNumFromString = coerce( instance(BigNumber), string(), (value) => { if (typeof value === "string") return new BigNumber(value, 10); throw new Error("invalid big num"); } ); var PublicKeyFromString = coerce( instance(PublicKey2), string(), (value) => new PublicKey2(value) ); var StakeMeta = type({ rentExemptReserve: BigNumFromString, authorized: type({ staker: PublicKeyFromString, withdrawer: PublicKeyFromString }), lockup: type({ unixTimestamp: number(), epoch: number(), custodian: PublicKeyFromString }) }); var StakeAccountInfo = type({ meta: StakeMeta, stake: nullable( type({ delegation: type({ voter: PublicKeyFromString, stake: BigNumFromString, activationEpoch: BigNumFromString, deactivationEpoch: BigNumFromString, warmupCooldownRate: number() }), creditsObserved: number() }) ) }); var StakeAccount = type({ type: StakeAccountType, info: StakeAccountInfo }); // src/stakeAccount.ts import { create } from "superstruct"; import BigNumber2 from "bignumber.js"; var ParseStakeAccountError = class extends Error { }; var StakeAccount2 = class { account; constructor({ executable, owner, lamports, data, rentEpoch }) { if (!("parsed" in data)) { throw new ParseStakeAccountError( "Raw AccountInfo<Buffer>, data not parsed" ); } try { const parsedData = create(data.parsed, StakeAccount); this.account = { executable, owner, lamports, data: parsedData, rentEpoch }; } catch (e) { throw new ParseStakeAccountError(e.message); } } /** * Check if lockup is in force * @param currEpoch current epoch. * @param currUnixTimestamp current unix timetamp. * @returns a bool type result. */ isLockupInForce(currEpoch, currUnixTimestamp) { return this.account.data.info.meta.lockup.unixTimestamp > currUnixTimestamp || this.account.data.info.meta.lockup.epoch > currEpoch; } /** * Determins the current state of a stake account given the current epoch * @param currentEpoch * @returns `stakeAccount`'s stake state`string` */ stakeAccountState(currentEpoch) { const { type: type2, info: { stake } } = this.account.data; if (type2 !== "delegated" || stake === null) { return StakeState.inactive; } const currentEpochBN = new BigNumber2(currentEpoch); const activationEpoch = new BigNumber2(stake.delegation.activationEpoch); const deactivationEpoch = new BigNumber2(stake.delegation.deactivationEpoch); if (activationEpoch.gt(currentEpochBN)) { return StakeState.inactive; } if (activationEpoch.eq(currentEpochBN)) { if (deactivationEpoch.eq(activationEpoch)) return StakeState.inactive; return StakeState.activating; } if (deactivationEpoch.gt(currentEpochBN)) return StakeState.active; if (deactivationEpoch.eq(currentEpochBN)) return StakeState.deactivating; return StakeState.deactivated; } }; // src/solana.ts var Solana = class extends Blockchain { connection; validator; ERROR_MESSAGES = ERROR_MESSAGES; ORIGINAL_ERROR_MESSAGES = {}; constructor(network = "mainnet-beta" /* Mainnet */, rpc = null) { super(); if (rpc && !this.isValidURL(rpc)) { throw this.throwError("INVALID_RPC_ERROR"); } rpc = rpc || clusterApiUrl(network); try { this.connection = new Connection(rpc, "confirmed"); } catch (error) { throw this.handleError("CONNECTION_ERROR", error); } switch (network) { case "mainnet-beta" /* Mainnet */: this.validator = SOL_MAINNET_VALIDATOR_ADDRESS; break; case "devnet" /* Devnet */: this.validator = SOL_DEVNET_VALIDATOR_ADDRESS; break; default: throw this.throwError("UNSUPPORTED_NETWORK_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(address, lamports, source, lockup = Lockup.default) { if (lamports < SOL_MIN_AMOUNT) { this.throwError("MIN_AMOUNT_ERROR", SOL_MIN_AMOUNT.toString()); } try { const publicKey = new PublicKey3(address); const minimumRent = await this.connection.getMinimumBalanceForRentExemption( StakeProgram.space ); lockup = lockup || Lockup.default; const [createStakeAccountTx, stakeAccountPublicKey, externalSigners] = source === null ? await this.createAccountTx( publicKey, lamports + minimumRent, lockup ) : await this.createAccountWithSeedTx( publicKey, lamports + minimumRent, source, lockup ); const versionedTX = await this.prepareTransaction( createStakeAccountTx.instructions, publicKey, externalSigners ); return { result: { createStakeAccountVerTx: versionedTX, stakeAccount: stakeAccountPublicKey } }; } catch (error) { throw this.handleError("CREATE_ACCOUNT_ERROR", error); } } /** * Prepares a transaction with the given instructions and payer. * * @param instructions - An array of TransactionInstruction objects. * @param payer - The public key of the payer. * @param externalSigners - an array of external signers. * @returns A promise that resolves to a VersionedTransaction object. */ async prepareTransaction(instructions, payer, externalSigners) { const blockhash = await this.getBlockhash(); const messageV0 = new TransactionMessage({ payerKey: payer, recentBlockhash: blockhash, instructions }).compileToV0Message(); const tx = new VersionedTransaction(messageV0); if (externalSigners.length > 0) { tx.sign(externalSigners); } return tx; } /** * Retrieves the latest blockhash. * * @returns A promise that resolves to a string representing the blockhash. */ async getBlockhash() { const res = await this.connection.getLatestBlockhash({ commitment: "max" }); return res.blockhash; } /** * 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(address, lamports, stakeAccount) { if (lamports < SOL_MIN_AMOUNT) { this.throwError("MIN_AMOUNT_ERROR", SOL_MIN_AMOUNT.toString()); } try { const publicKey = new PublicKey3(address); const stakeAccountPublicKey = new PublicKey3(stakeAccount); const delegateTx = new Transaction().add( StakeProgram.delegate({ stakePubkey: stakeAccountPublicKey, authorizedPubkey: publicKey, votePubkey: this.validator }) ); const delegateVerTx = await this.prepareTransaction( delegateTx.instructions, publicKey, [] ); return { result: delegateVerTx }; } 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(address, stakeAccountPublicKey) { try { const publicKey = new PublicKey3(address); const stakeAccount = new PublicKey3(stakeAccountPublicKey); const deactivateTx = new Transaction().add( StakeProgram.deactivate({ stakePubkey: stakeAccount, authorizedPubkey: publicKey }) ); const deactivateVerTx = await this.prepareTransaction( deactivateTx.instructions, publicKey, [] ); return { result: deactivateVerTx }; } 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(address, stakeAccountPublicKey, stakeBalance) { try { const publicKey = new PublicKey3(address); const stakeAccount = new PublicKey3(stakeAccountPublicKey); const withdrawTx = new Transaction().add( StakeProgram.withdraw({ stakePubkey: stakeAccount, authorizedPubkey: publicKey, toPubkey: publicKey, lamports: stakeBalance }) ); const withdrawVerTx = await this.prepareTransaction( withdrawTx.instructions, publicKey, [] ); return { result: withdrawVerTx }; } 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(address) { try { const filters = [ { dataSize: FILTER_DATA_SIZE }, { memcmp: { offset: FILTER_OFFSET, bytes: address } } ]; const accounts = await this.connection.getParsedProgramAccounts( StakeProgram.programId, { filters } ); return { result: accounts }; } 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, lockup = Lockup.default) { try { const senderPublicKey = new PublicKey3(sender); const minimumRent = await this.connection.getMinimumBalanceForRentExemption( StakeProgram.space ); lockup = lockup || Lockup.default; const [createStakeAccountTx, stakeAccountPublicKey, externalSigners] = source === null ? await this.createAccountTx( senderPublicKey, lamports + minimumRent, lockup ) : await this.createAccountWithSeedTx( senderPublicKey, lamports + minimumRent, source, lockup ); const stakeTx = new Transaction().add( ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50 }), createStakeAccountTx, StakeProgram.delegate({ stakePubkey: stakeAccountPublicKey, authorizedPubkey: senderPublicKey, votePubkey: this.validator }) ); const stakeVerTx = await this.prepareTransaction( stakeTx.instructions, senderPublicKey, externalSigners ); return { result: stakeVerTx }; } 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(address, lamports, lockup) { const blockhash = await this.getBlockhash(); const stakeAccount = Keypair.generate(); const createStakeAccountTx = StakeProgram.createAccount({ authorized: new Authorized(address, address), fromPubkey: address, lamports, stakePubkey: stakeAccount.publicKey, lockup }); createStakeAccountTx.recentBlockhash = blockhash; createStakeAccountTx.sign(stakeAccount); return [createStakeAccountTx, stakeAccount.publicKey, [stakeAccount]]; } /** * 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, lockup) { const seed = this.formatSource(source); const stakeAccountPubkey = await PublicKey3.createWithSeed( authorityPublicKey, seed, StakeProgram.programId ); const createStakeAccountTx = new Transaction().add( StakeProgram.createAccountWithSeed({ authorized: new Authorized(authorityPublicKey, authorityPublicKey), fromPubkey: authorityPublicKey, basePubkey: authorityPublicKey, stakePubkey: stakeAccountPubkey, lockup, seed, lamports }) ); return [createStakeAccountTx, stakeAccountPubkey, []]; } /** unstake - unstake * @param {string} sender - account blockchain address (staker) * @param {number} lamports - lamport amount * @param {string} source - stake source * @returns {Promise<object>} Promise object with Versioned Tx */ async unstake(sender, lamports, source) { try { const delegations = await this.getDelegations(sender); const stakeAccounts = delegations.result.map((delegationAcc) => { return { pubkey: delegationAcc.pubkey, account: new StakeAccount2(delegationAcc.account) }; }); const epochInfo = await this.connection.getEpochInfo(); const tm = this.timestampInSec(); let totalActiveStake = new BigNumber3(0); const activeStakeAccounts = stakeAccounts.filter((acc) => { const isActive = !(acc.account.isLockupInForce(epochInfo.epoch, tm) || acc.account.stakeAccountState(epochInfo.epoch) !== StakeState.active); if (isActive && acc.account.account.data.info.stake) { totalActiveStake = totalActiveStake.plus( acc.account.account.data.info.stake.delegation.stake ); } return isActive; }); let lamportsBN = new BigNumber3(lamports); if (totalActiveStake.lt(lamportsBN)) throw this.throwError("NOT_ENOUGH_ACTIVE_STAKE_ERROR"); activeStakeAccounts.sort((a, b) => { const stakeA = a.account.account.data.info.stake?.delegation.stake; const stakeB = b.account.account.data.info.stake?.delegation.stake; if (!stakeA || !stakeB) return 0; return stakeA.minus(stakeB).toNumber(); }); const accountsToDeactivate = []; const accountsToSplit = []; let i = 0; while (lamportsBN.gt(new BigNumber3(0)) && i < activeStakeAccounts.length) { const acc = activeStakeAccounts[i]; if (acc === void 0 || acc.account.account.data.info.stake === null) { i++; continue; } const stakeAmount = acc.account.account.data.info.stake.delegation.stake; const isBelowThreshold = stakeAmount.lte(lamportsBN) || stakeAmount.minus(lamportsBN).lt(SOL_MIN_AMOUNT); if (isBelowThreshold) { accountsToDeactivate.push(acc); lamportsBN = lamportsBN.minus(stakeAmount); i++; continue; } accountsToSplit.push({ account: acc, lamports: lamportsBN.toNumber() }); break; } const senderPublicKey = new PublicKey3(sender); let instructions = [ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50 }) ]; const minimumRent = await this.connection.getMinimumBalanceForRentExemption( StakeProgram.space ); for (const acc of accountsToSplit) { const [tx, newStakeAccountPubkey] = await this.split( senderPublicKey, acc.lamports, acc.account.pubkey, source, minimumRent ); const deactivateTx = StakeProgram.deactivate({ stakePubkey: newStakeAccountPubkey, authorizedPubkey: senderPublicKey }); instructions.push(...tx.instructions, ...deactivateTx.instructions); } for (const acc of accountsToDeactivate) { const deactivateTx = StakeProgram.deactivate({ stakePubkey: acc.pubkey, authorizedPubkey: senderPublicKey }); instructions.push(...deactivateTx.instructions); } instructions = instructions.map((instruction) => { return new TransactionInstruction(instruction); }); const versionedTX = await this.prepareTransaction( instructions, senderPublicKey, [] ); return { result: versionedTX }; } 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 PublicKey3.createWithSeed( authorityPublicKey, seed, StakeProgram.programId ); const splitStakeAccountTx = new Transaction().add( StakeProgram.splitWithSeed( { stakePubkey: oldStakeAccountPubkey, authorizedPubkey: authorityPublicKey, splitStakePubkey: newStakeAccountPubkey, basePubkey: authorityPublicKey, seed, lamports }, rentExemptReserve ) ); return [splitStakeAccountTx, 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) { try { const delegations = await this.getDelegations(sender); const stakeAccounts = delegations.result.map((delegationAcc) => { return { pubkey: delegationAcc.pubkey, account: new StakeAccount2(delegationAcc.account) }; }); const epochInfo = await this.connection.getEpochInfo(); const tm = this.timestampInSec(); let totalClaimableStake = new BigNumber3(0); const deactivatedStakeAccounts = stakeAccounts.filter((acc) => { const { data } = acc.account.account; const { info } = data; const isDeactivated = !acc.account.isLockupInForce(epochInfo.epoch, tm) && acc.account.stakeAccountState(epochInfo.epoch) === StakeState.deactivated; if (info.stake && isDeactivated) { totalClaimableStake = totalClaimableStake.plus( info.stake.delegation.stake ); } return isDeactivated; }); if (deactivatedStakeAccounts.length === 0) throw this.throwError("NOTHING_TO_CLAIM_ERROR"); const senderPublicKey = new PublicKey3(sender); let instructions = [ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50 }) ]; for (const acc of deactivatedStakeAccounts) { const withdrawTx = StakeProgram.withdraw({ stakePubkey: acc.pubkey, authorizedPubkey: senderPublicKey, toPubkey: senderPublicKey, lamports: acc.account.account.lamports }); instructions.push(...withdrawTx.instructions); } instructions = instructions.map((instruction) => { return new TransactionInstruction(instruction); }); const versionedTX = await this.prepareTransaction( instructions, senderPublicKey, [] ); return { result: versionedTX }; } catch (error) { throw this.handleError("CLAIM_ERROR", error); } } async getEpochInfo() { try { const epochInfo = await this.connection.getEpochInfo(); return { result: epochInfo }; } catch (error) { throw this.handleError("GET_EPOCH_INFO_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. * */ async merge(authorityPublicKey, stakeAccount1, stakeAccount2) { const mergeStakeAccountTx = StakeProgram.merge({ stakePubkey: stakeAccount1, sourceStakePubKey: stakeAccount2, authorizedPubkey: authorityPublicKey }); return [mergeStakeAccountTx]; } /** * 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; } }; export { FILTER_DATA_SIZE, FILTER_OFFSET, ParseStakeAccountError, SOL_CHAIN, SOL_DEVNET_VALIDATOR_ADDRESS, SOL_MAINNET_VALIDATOR_ADDRESS, SOL_MIN_AMOUNT, SolNetwork, Solana, StakeAccount2 as StakeAccount, StakeAccountType, StakeState };