@terminusbet/stake-vote-sdk
Version:
A simple SDK for interacting with terminusbet governance
447 lines • 21.5 kB
JavaScript
import { BN, Program } from "@coral-xyz/anchor";
import { PublicKey, Transaction } from "@solana/web3.js";
import { createAssociatedTokenAccountInstruction, getAssociatedTokenAddressSync } from "@solana/spl-token";
import { DEFAULT_COMMITMENT, DEFAULT_FINALITY, sendTx, simulateMethodCall } from "./util";
import { ACTIVE_BALLOT_BOX_SEED, APP_CONFIG_SEED, DEFAULT_DEMICAL, STAKE_REWARD_SEED, STAKE_STATE_SEED, USER_ACTIVE_BALLOT_BOX_SEED, USER_REWARD_SEED, USER_VOTE_STATE_SEED, VOTE_STATE_SEED } from "./constants";
import { UserStakeVoteState, UserVoteState, StakeState, StakePoolState, UserRewardState, VoteRewardState, BallotBotState, UserActiveBallotBoxState, ActiveBallotBoxState } from "./state";
import { IDL } from "./idl";
export class StakeVoteSDK {
program;
constructor(provider) {
this.program = new Program(IDL, provider);
}
async stake(user, stakePoolState, amount, priorityFees, commitment = DEFAULT_COMMITMENT, finality = DEFAULT_FINALITY) {
let tx = await this.getStakeTransactions(user.publicKey, stakePoolState, amount);
return await sendTx(this.program.provider.connection, tx, user.publicKey, [user], priorityFees, [], commitment, finality);
}
async stakeAdd(user, stakePoolState, amount, addType, priorityFees, commitment = DEFAULT_COMMITMENT, finality = DEFAULT_FINALITY) {
let tx = await this.getStakeAddTransactions(user.publicKey, stakePoolState, amount, addType);
return await sendTx(this.program.provider.connection, tx, user.publicKey, [user], priorityFees, [], commitment, finality);
}
async stakeWithdraw(user, stakePoolState, amount, priorityFees, commitment = DEFAULT_COMMITMENT, finality = DEFAULT_FINALITY) {
let tx = await this.getStakeWithdrawTransactions(user.publicKey, stakePoolState, amount);
return await sendTx(this.program.provider.connection, tx, user.publicKey, [user], priorityFees, [], commitment, finality);
}
async vote(user, ballotBoxState, amount, slippage, priorityFees, commitment = DEFAULT_COMMITMENT, finality = DEFAULT_FINALITY) {
let tx = await this.getAutoVoteTransactions(user.publicKey, ballotBoxState, amount, slippage);
return await sendTx(this.program.provider.connection, tx, user.publicKey, [user], priorityFees, [], commitment, finality);
}
async voteWhole(user, ballotBoxState, priorityFees, commitment = DEFAULT_COMMITMENT, finality = DEFAULT_FINALITY) {
let tx = await this.getAutoWholeVoteTransactions(user.publicKey, ballotBoxState);
return await sendTx(this.program.provider.connection, tx, user.publicKey, [user], priorityFees, [], commitment, finality);
}
async rewardReceive(user, rewardUseBallotBox, priorityFees, commitment = DEFAULT_COMMITMENT, finality = DEFAULT_FINALITY) {
let tx = await this.getRewardReceiveTransactions(user.publicKey, rewardUseBallotBox);
return await sendTx(this.program.provider.connection, tx, user.publicKey, [user], priorityFees, [], commitment, finality);
}
async lockedAmount(user, stakePoolState) {
let lockedAmount = new BN(0);
const userActiveBallotBoxState = await this.getUserActiveBallotBoxStateAccount(stakePoolState, user);
if (!userActiveBallotBoxState || userActiveBallotBoxState.ballotLocks.length === 0) {
return lockedAmount;
}
const inProgressBallotBoxs = await this.getActiveBallotBoxStateAccount();
if (!inProgressBallotBoxs || inProgressBallotBoxs.ballotBots.length === 0) {
return lockedAmount;
}
for (const ballotLock of userActiveBallotBoxState.ballotLocks) {
if (inProgressBallotBoxs.ballotBots.find((item) => item.equals(ballotLock.ballotBot))) {
lockedAmount = lockedAmount.add(ballotLock.stakeLock);
}
}
return lockedAmount;
}
async getStakeTransactions(user, stakePoolState, amount) {
const stakeStateAccount = await this.getStakeStateAccount();
if (!stakeStateAccount) {
throw new Error("stakeStateAccount not found");
}
const voucherMint = stakeStateAccount.voucherMint;
let transaction = new Transaction();
let ata = getAssociatedTokenAddressSync(voucherMint, user, true);
const ataAccount = await this.program.provider.connection.getAccountInfo(ata);
if (!ataAccount) {
transaction.add(createAssociatedTokenAccountInstruction(user, ata, user, voucherMint));
}
const userActiveBallotBoxState = await this.getUserActiveBallotBoxStateAccount(stakePoolState, user);
if (!userActiveBallotBoxState) {
transaction.add(await this.program.methods
.userActiveBallotBoxInit()
.accounts({
user,
stakePoolState,
})
.transaction());
}
transaction.add(await this.program.methods
.stake({ amount })
.accounts({
user,
stakePoolState,
voucherMint,
platformMint: stakeStateAccount.platformMint,
})
.transaction());
return transaction;
}
async getStakeAddTransactions(user, stakePoolState, amount, addType) {
const stakeStateAccount = await this.getStakeStateAccount();
if (!stakeStateAccount) {
throw new Error("stakeStateAccount not found");
}
const voucherMint = stakeStateAccount.voucherMint;
let transaction = new Transaction();
let ata = getAssociatedTokenAddressSync(voucherMint, user, true);
const ataAccount = await this.program.provider.connection.getAccountInfo(ata);
if (!ataAccount) {
transaction.add(createAssociatedTokenAccountInstruction(user, ata, user, voucherMint));
}
transaction.add(await this.program.methods
.stakeAdd({ amount, addType })
.accounts({
user,
stakePoolState,
voucherMint,
platformMint: stakeStateAccount.platformMint,
})
.transaction());
return transaction;
}
async getStakeWithdrawTransactions(user, stakePoolState, amount) {
const stakeStateAccount = await this.getStakeStateAccount();
if (!stakeStateAccount) {
throw new Error("stakeStateAccount not found");
}
const voucherMint = stakeStateAccount.voucherMint;
let transaction = new Transaction();
transaction.add(await this.program.methods
.stakeWithdraw(amount)
.accounts({
user,
stakePoolState,
voucherMint,
platformMint: stakeStateAccount.platformMint,
})
.transaction());
return transaction;
}
async getVoteTransactions(user, ballotBoxState, stakePoolState, amount, minAmount, isWhole) {
let transaction = new Transaction();
transaction.add(await this.program.methods
.vote({
amount,
minAmount,
isWhole
})
.accounts({
user,
stakePoolState,
ballotBoxState,
})
.transaction());
return transaction;
}
async getAutoVoteTransactions(user, ballotBoxState, amount, slippage = 500n) {
let transaction = new Transaction();
let { userVouchers } = await this.getUserVouchers(user);
let realVoteAmount = BigInt(Math.min(Number(userVouchers), Number(amount)));
const pools = await this.getStakePoolStates();
if (pools.length === 0) {
throw new Error("No pools found");
}
const userVotePools = [];
for (let i = 0; i < pools.length; i++) {
const userStakeVote = await this.getUserVouchersByPool(pools[i].publicKey, user);
userStakeVote.voteLeft > 0n && userStakeVote.userStakeVoteState && userVotePools.push({
publicKey: pools[i].publicKey,
voteLeft: userStakeVote.voteLeft,
timeEndAt: userStakeVote.userStakeVoteState.timeEndAt,
});
}
// Sort the pool list in ascending order by period timeEndAt
userVotePools.sort((a, b) => Number(a.timeEndAt) - Number(b.timeEndAt));
for (let i = 0; i < userVotePools.length; i++) {
if (realVoteAmount > 0n && userVotePools[i].voteLeft <= realVoteAmount) {
realVoteAmount -= userVotePools[i].voteLeft;
transaction.add(await this.getVoteTransactions(user, ballotBoxState, userVotePools[i].publicKey, new BN(0), new BN(0), true));
}
else if (realVoteAmount > 0n && userVotePools[i].voteLeft > realVoteAmount) {
let minAmountBn = realVoteAmount - (realVoteAmount * slippage) / BigInt(10000);
let minAmount = new BN(minAmountBn.toString());
transaction.add(await this.getVoteTransactions(user, ballotBoxState, userVotePools[i].publicKey, new BN(realVoteAmount.toString()), minAmount, false));
break;
}
}
return transaction;
}
async getAutoWholeVoteTransactions(user, ballotBoxState) {
let transaction = new Transaction();
const pools = await this.getStakePoolStates();
if (pools.length === 0) {
throw new Error("No pools found");
}
const userVotePools = [];
for (let i = 0; i < pools.length; i++) {
const userStakeVote = await this.getUserVouchersByPool(pools[i].publicKey, user);
userStakeVote.voteLeft > 0n && userStakeVote.userStakeVoteState && userVotePools.push({
publicKey: pools[i].publicKey,
voteLeft: userStakeVote.voteLeft,
timeEndAt: userStakeVote.userStakeVoteState.timeEndAt,
});
}
// Sort the pool list in ascending order by period timeEndAt
userVotePools.sort((a, b) => Number(a.timeEndAt) - Number(b.timeEndAt));
for (let i = 0; i < userVotePools.length; i++) {
transaction.add(await this.getVoteTransactions(user, ballotBoxState, userVotePools[i].publicKey, new BN(0), new BN(0), true));
}
return transaction;
}
async getRewardReceiveTransactions(user, rewardUseBallotBox) {
let transaction = new Transaction();
let voteRewardState = await this.getVoteRewardStateAccount(rewardUseBallotBox);
if (!voteRewardState) {
throw new Error(`${rewardUseBallotBox.toBase58()} is not a reward ballot box`);
}
if (!voteRewardState.isStart) {
throw new Error(`${rewardUseBallotBox.toBase58()} is not open for rewards yet`);
}
transaction.add(await this.program.methods
.rewardReceive()
.accounts({
user,
mint: voteRewardState.rewardMint,
ballotBox: rewardUseBallotBox,
})
.transaction());
return transaction;
}
async getBatchRewardReceiveTransactions(user, rewardUseBallotBoxs) {
let transaction = new Transaction();
for (const box of rewardUseBallotBoxs) {
transaction.add(await this.getRewardReceiveTransactions(user, box));
}
return transaction;
}
async getStakePoolStates() {
return await this.program.account.stakePoolState.all();
}
async getUserVouchersByPool(stakePoolState, user) {
const userStakeVoteState = await this.getUserStakeVoteStateAccount(stakePoolState, user);
if (!userStakeVoteState) {
return { voteLeft: 0n, userStakeVoteState };
}
const currentTs = BigInt(Math.floor(Date.now() / 1000));
const timeLeft = userStakeVoteState.periodUsed + currentTs;
if (userStakeVoteState.timeStartAt > timeLeft || timeLeft > userStakeVoteState.timeEndAt) {
return { voteLeft: 0n, userStakeVoteState };
}
const peroidLeft = userStakeVoteState.timeEndAt - timeLeft;
const voteLeft = userStakeVoteState.votePerTime * peroidLeft / BigInt(Math.pow(10, DEFAULT_DEMICAL));
return { voteLeft, userStakeVoteState };
}
async getUserVouchers(user) {
const stakePools = await this.getStakePoolStates();
let userVouchers = 0n;
for (let i = 0; i < stakePools.length; i++) {
const stakePool = stakePools[i].publicKey;
const userStakeVote = await this.getUserVouchersByPool(stakePool, user);
userVouchers += userStakeVote.voteLeft;
}
return { userVouchers, decimal: DEFAULT_DEMICAL };
}
async getUserRewards(ballotBoxState, user) {
const ballotBotStateAccount = await this.getBallotBotStateAccount(ballotBoxState);
if (!ballotBotStateAccount) {
throw new Error("invalid ballot box");
}
const voteRewardStateAccount = await this.getVoteRewardStateAccount(ballotBoxState);
if (!voteRewardStateAccount) {
throw new Error("invalid reward ballot box");
}
if (!voteRewardStateAccount.isStart) {
throw new Error("this ballot box is not open for rewards yet");
}
const userVoteStateAccount = await this.getUserVoteStateAccount(ballotBoxState, user);
if (!userVoteStateAccount) {
return { userRewards: 0n, rewardMint: voteRewardStateAccount.rewardMint };
}
let userRewards = userVoteStateAccount.voteCount * voteRewardStateAccount.amount / ballotBotStateAccount.totaVote;
const userRewardStateAccount = await this.getUserRewardStateAccount(ballotBoxState, user);
if (userRewardStateAccount) {
userRewards -= userRewardStateAccount.amount;
}
return { userRewards, rewardMint: voteRewardStateAccount.rewardMint };
}
async simulateAddStakeAmount(payer, user, stakePoolState, amount) {
const transaction = await this.program.methods
.simulateAddStakeAmount(amount)
.accounts({
payer,
user,
stakePoolState
})
.transaction();
return simulateMethodCall(this.program.provider.connection, payer, transaction);
}
async simulateGetCurrentVoteByAmount(payer, user, stakePoolState, amount, minAmount) {
const transaction = await this.program.methods
.simulateGetCurrentVoteByAmount(amount, minAmount)
.accounts({
payer,
user,
stakePoolState
})
.transaction();
return simulateMethodCall(this.program.provider.connection, payer, transaction);
}
async simulateAddStakeTime(payer, user, stakePoolState, peroid) {
const transaction = await this.program.methods
.simulateAddStakeTime(peroid)
.accounts({
payer,
user,
stakePoolState
})
.transaction();
return simulateMethodCall(this.program.provider.connection, payer, transaction);
}
async simulateStakeRelease(payer, user, stakePoolState) {
const transaction = await this.program.methods
.simulateStakeRelease()
.accounts({
payer,
user,
stakePoolState
})
.transaction();
return simulateMethodCall(this.program.provider.connection, payer, transaction);
}
async getStakePoolStateAccount(stakePoolState) {
const stakePoolStateAccount = await this.program.provider.connection.getAccountInfo(stakePoolState);
if (!stakePoolStateAccount) {
return null;
}
return StakePoolState.fromBuffer(stakePoolStateAccount.data);
}
async getUserVoteStateAccount(ballotBoxState, user, commitment = DEFAULT_COMMITMENT) {
const userVoteState = this.getUserVoteStatePDA(ballotBoxState, user);
const userVoteStateAccount = await this.program.provider.connection.getAccountInfo(userVoteState, commitment);
if (!userVoteStateAccount) {
return null;
}
return UserVoteState.fromBuffer(userVoteStateAccount.data);
}
async getUserStakeVoteStateAccount(stakePoolState, user, commitment = DEFAULT_COMMITMENT) {
const userStakeVoteState = this.getUserStakeVoteStatePDA(stakePoolState, user);
const userStakeVoteStateAccount = await this.program.provider.connection.getAccountInfo(userStakeVoteState, commitment);
if (!userStakeVoteStateAccount) {
return null;
}
return UserStakeVoteState.fromBuffer(userStakeVoteStateAccount.data);
}
async getStakeStateAccount(commitment = DEFAULT_COMMITMENT) {
const stakeState = this.getStakeStatePDA();
const stakeStateAccount = await this.program.provider.connection.getAccountInfo(stakeState, commitment);
if (!stakeStateAccount) {
return null;
}
return StakeState.fromBuffer(stakeStateAccount.data);
}
async getUserRewardStateAccount(ballotBoxState, user, commitment = DEFAULT_COMMITMENT) {
const userRewordState = this.getUserRewardStatePDA(ballotBoxState, user);
const userRewordStateAccount = await this.program.provider.connection.getAccountInfo(userRewordState, commitment);
if (!userRewordStateAccount) {
return null;
}
return UserRewardState.fromBuffer(userRewordStateAccount.data);
}
async getVoteRewardStateAccount(ballotBoxState, commitment = DEFAULT_COMMITMENT) {
const voteRewardState = this.getVoteRewardStatePDA(ballotBoxState);
const voteRewardStateAccount = await this.program.provider.connection.getAccountInfo(voteRewardState, commitment);
if (!voteRewardStateAccount) {
return null;
}
return VoteRewardState.fromBuffer(voteRewardStateAccount.data);
}
async getBallotBotStateAccount(ballotBot, commitment = DEFAULT_COMMITMENT) {
const ballotBotStateAccount = await this.program.provider.connection.getAccountInfo(ballotBot, commitment);
if (!ballotBotStateAccount) {
return null;
}
return BallotBotState.fromBuffer(ballotBotStateAccount.data);
}
async getUserActiveBallotBoxStateAccount(stakePoolState, user, commitment = DEFAULT_COMMITMENT) {
const userActiveBallotBoxState = this.getUserActiveBallotBoxStatePDA(stakePoolState, user);
const userActiveBallotBoxStateAccount = await this.program.provider.connection.getAccountInfo(userActiveBallotBoxState, commitment);
if (!userActiveBallotBoxStateAccount) {
return null;
}
return UserActiveBallotBoxState.fromBuffer(userActiveBallotBoxStateAccount.data);
}
async getActiveBallotBoxStateAccount(commitment = DEFAULT_COMMITMENT) {
const activeBallotBoxState = this.getActiveBallotBoxStatePDA();
const activeBallotBoxStateAccount = await this.program.provider.connection.getAccountInfo(activeBallotBoxState, commitment);
if (!activeBallotBoxStateAccount) {
return null;
}
return ActiveBallotBoxState.fromBuffer(activeBallotBoxStateAccount.data);
}
getConfigStatePDA() {
const [configState] = PublicKey.findProgramAddressSync([
Buffer.from(APP_CONFIG_SEED)
], this.program.programId);
return configState;
}
getStakeStatePDA() {
const [stakeState] = PublicKey.findProgramAddressSync([
Buffer.from(STAKE_STATE_SEED)
], this.program.programId);
return stakeState;
}
getUserStakeVoteStatePDA(stakePoolState, user) {
const [userStakeVoteState] = PublicKey.findProgramAddressSync([
Buffer.from(USER_VOTE_STATE_SEED),
stakePoolState.toBuffer(),
user.toBuffer(),
], this.program.programId);
return userStakeVoteState;
}
getUserVoteStatePDA(ballotBoxState, user) {
const [userVoteState] = PublicKey.findProgramAddressSync([
Buffer.from(VOTE_STATE_SEED),
ballotBoxState.toBuffer(),
user.toBuffer(),
], this.program.programId);
return userVoteState;
}
getVoteRewardStatePDA(ballotBoxState) {
const [voteRewardState] = PublicKey.findProgramAddressSync([
Buffer.from(STAKE_REWARD_SEED),
ballotBoxState.toBuffer(),
], this.program.programId);
return voteRewardState;
}
getUserRewardStatePDA(ballotBoxState, user) {
const [userRewardState] = PublicKey.findProgramAddressSync([
Buffer.from(USER_REWARD_SEED),
ballotBoxState.toBuffer(),
user.toBuffer(),
], this.program.programId);
return userRewardState;
}
getUserActiveBallotBoxStatePDA(stakePoolState, user) {
const [userActiveBallotBoxState] = PublicKey.findProgramAddressSync([
Buffer.from(USER_ACTIVE_BALLOT_BOX_SEED),
stakePoolState.toBuffer(),
user.toBuffer(),
], this.program.programId);
return userActiveBallotBoxState;
}
getActiveBallotBoxStatePDA() {
const [activeBallotBoxState] = PublicKey.findProgramAddressSync([
Buffer.from(ACTIVE_BALLOT_BOX_SEED)
], this.program.programId);
return activeBallotBoxState;
}
}
//# sourceMappingURL=stakeVote.js.map