UNPKG

@terminusbet/stake-vote-sdk

Version:

A simple SDK for interacting with terminusbet governance

447 lines 21.5 kB
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