UNPKG

@unloc-xyz/unloc-sdk

Version:
657 lines (561 loc) 18.9 kB
// @ts-ignore import * as anchor from '@project-serum/anchor'; import { TOKEN_PROGRAM_ID} from "@solana/spl-token"; const { BN, web3, Program, AnchorProvider } = anchor const { PublicKey, SystemProgram, Transaction } = web3 const utf8 = anchor.utils.bytes.utf8; const defaultAccounts = { tokenProgram: TOKEN_PROGRAM_ID, clock: anchor.web3.SYSVAR_CLOCK_PUBKEY, systemProgram: SystemProgram.programId, } import {IDL as idl, UnlocStaking} from '../types/unloc_staking' import { Connection, Keypair } from '@solana/web3.js'; import { createAssociatedTokenAccountIfNotExist2, sendTransaction } from '../utils'; import { STAKING_PID } from '../global-config'; let StakingProgram:anchor.Program<UnlocStaking> = null as unknown as anchor.Program<UnlocStaking> export function initStakingProgram( connection: Connection, wallet: any, programId: any = STAKING_PID ) { const provider = new anchor.AnchorProvider( connection, wallet, { commitment: 'confirmed', } ); // Generate the program client from IDL. const program = new (anchor as any).Program(idl, programId, provider) as anchor.Program<UnlocStaking>; StakingProgram = program } export interface ExtraRewardConfigs{ duration: any extraPercentage: any } export async function getStakingStateAddress(){ const [stateSigner, stateBump] = await anchor.web3.PublicKey.findProgramAddress( [utf8.encode('state')], StakingProgram.programId ); return stateSigner } async function getStakingExtraRewardAddress(){ const [stateSigner, stateBump] = await anchor.web3.PublicKey.findProgramAddress( [utf8.encode('extra')], StakingProgram.programId ); return stateSigner } async function getStakingPoolAddressFromMint(mint:string){ const [stateSigner, stateBump] = await anchor.web3.PublicKey.findProgramAddress( [new PublicKey(mint).toBuffer()], StakingProgram.programId ); return stateSigner } const ACC_PRECISION = new BN(100 * 1000 * 1000 * 1000); const FULL_100 = new BN (100 * 1000 * 1000 * 1000); export function estimateRewards( stateData:any, extraConfigData:any, poolData:any, userData:any, ){ const currentTimeStamp = Math.ceil(new Date().getTime() / 1000); const duration = new BN(Math.max(currentTimeStamp - poolData.lastRewardTime, 0)) const reward_per_share = stateData.tokenPerSecond.mul(duration).mul(ACC_PRECISION).div(poolData.amount); const acc_reward_per_share = poolData.accRewardPerShare.add(reward_per_share); let extraPercentage = new BN(0) extraConfigData.configs.forEach((item:any)=>{ if(item.duration.toString() === userData.lockDuration.toString()) { extraPercentage = item.extraPercentage return; } }) const pending_amount = userData.amount.mul( acc_reward_per_share).div(ACC_PRECISION).sub(userData.rewardDebt); const extra_amount = userData.extraReward.add(pending_amount.mul(extraPercentage).div(FULL_100)); const total_reward = userData.rewardAmount.add(pending_amount).add(extra_amount) return total_reward.toString() } export function estimateStakingRewardsPerSec( stateData:any, extraConfigData:any, poolData:any, userData:any, ){ const currentTimeStamp = Math.ceil(new Date().getTime() / 1000); const duration = new BN(Math.max(currentTimeStamp - poolData.lastRewardTime, 0)) const reward_per_share = stateData.tokenPerSecond.mul(duration).mul(ACC_PRECISION).div(poolData.amount); const acc_reward_per_share = poolData.accRewardPerShare.add(reward_per_share); let extraPercentage = new BN(0) extraConfigData.configs.forEach((item:any)=>{ if(item.duration.toString() === userData.lockDuration.toString()) { extraPercentage = item.extraPercentage return; } }) const pending_amount = userData.amount.mul( acc_reward_per_share).div(ACC_PRECISION).sub(userData.rewardDebt); const extra_amount = userData.extraReward.add(pending_amount.mul(extraPercentage).div(FULL_100)); const total_reward = userData.rewardAmount.add(pending_amount).add(extra_amount) return ((total_reward / 1000000000) / ((currentTimeStamp - poolData.lastRewardTime.toString()) * 1000)); } export async function createStakingState( connection:Connection, wallet: any, tokenPerSecond: number, earlyUnlocFee: number, profileLevels: anchor.BN[], rewardMint: string, ) { const transaction = new Transaction() const [stateSigner, stateBump] = await anchor.web3.PublicKey.findProgramAddress( [utf8.encode('state')], StakingProgram.programId ); const stateRewardVault = await createAssociatedTokenAccountIfNotExist2( null, stateSigner, wallet.publicKey, rewardMint, transaction ) const tx = await StakingProgram.rpc.createState(stateBump, new BN(tokenPerSecond),new BN(earlyUnlocFee), profileLevels, { accounts: { state: stateSigner, rewardMint: rewardMint, rewardVault: stateRewardVault, feeVault: stateRewardVault, authority: wallet.publicKey, ...defaultAccounts }, preInstructions: transaction.instructions }) return tx; } export async function getStakingState(){ const stateSigner = await getStakingStateAddress(); return await StakingProgram.account.stateAccount.fetch(stateSigner) } export async function getStakingExtraRewardConfigs(){ const [extraRewardSigner, extraRewardBump] = await anchor.web3.PublicKey.findProgramAddress( [utf8.encode('extra')], StakingProgram.programId ); const extraRewardConfigs = await StakingProgram.account.extraRewardsAccount.fetchNullable(extraRewardSigner) // assert.ok(extraRewardConfigs.configs.length === 3) // assert.ok(new BN(1).eq(extraRewardConfigs.configs[1].duration)) // assert.ok(getNumber(50).eq(extraRewardConfigs.configs[1].extraPercentage)) return extraRewardConfigs } export async function createStakingExtraReward( connection:Connection, wallet: any, ){ const transaction = new Transaction() const signers: Keypair[] = [] const [extraRewardSigner, extraRewardBump] = await anchor.web3.PublicKey.findProgramAddress( [utf8.encode('extra')], StakingProgram.programId ); console.log(extraRewardSigner.toString(), extraRewardBump) const tx = await StakingProgram.instruction.createExtraRewardConfigs(extraRewardBump, [ { duration: new BN(43200 * 60), extraPercentage: new BN(0) }, { duration: new BN(129600 * 60), extraPercentage: new BN(10 * 1000 * 1000 * 1000) }, { duration: new BN(259200 * 60), extraPercentage: new BN(30 * 1000 * 1000 * 1000) }, { duration: new BN(525600 * 60), extraPercentage: new BN(100 * 1000 * 1000 * 1000) } ], { accounts: { extraRewardAccount: extraRewardSigner, authority: wallet.publicKey, ...defaultAccounts }, }) transaction.add(tx) return await sendTransaction(connection, wallet, transaction, signers) } export async function setStakingExtraReward( connection:Connection, wallet: any, extraRewards: ExtraRewardConfigs[], ) { const transaction = new Transaction() const signers: Keypair[] = [] const [extraRewardSigner, extraRewardBump] = await anchor.web3.PublicKey.findProgramAddress( [utf8.encode('extra')], StakingProgram.programId ); transaction.add(StakingProgram.instruction.setExtraRewardConfigs(extraRewards, { accounts: { extraRewardAccount: extraRewardSigner, authority: wallet.publicKey, ...defaultAccounts }, })) return await sendTransaction(connection, wallet, transaction, signers) } export async function fundToStakingProgram( connection:Connection, wallet: any, poolSigner: string, masterRewardVault:string, amount:any ) { const transaction = new Transaction() const signers: Keypair[] = [] const stateSigner = await getStakingStateAddress() const state = await getStakingState() // await rewardMint.mintTo(stateRewardVault, wallet, [provider.wallet], getNumber(10000).toString()) const tx = StakingProgram.instruction.fundRewardToken(new BN(amount), { accounts: { state: stateSigner, rewardVault: state.rewardVault, userVault: masterRewardVault, authority: wallet.publicKey, ...defaultAccounts } }) transaction.add(tx) return await sendTransaction(connection, wallet, transaction, signers) } export async function getAllStakingPools(){ const pools = await StakingProgram.account.farmPoolAccount.all() return pools } export async function getFirstStakingPool(){ const pools = await StakingProgram.account.farmPoolAccount.all() return pools[0] } export async function createStakingPool( connection:Connection, wallet: any, rewardMint: string, pools: any[] ) { const transaction = new Transaction() const signers: Keypair[] = [] const [poolSigner, poolBump] = await anchor.web3.PublicKey.findProgramAddress( [new PublicKey(rewardMint).toBuffer()], StakingProgram.programId ); const stateSigner = await getStakingStateAddress() const poolVault = await createAssociatedTokenAccountIfNotExist2( null, poolSigner, wallet.publicKey, rewardMint, transaction) // let pools = await StakingProgram.account.farmPoolAccount.all() transaction.add( StakingProgram.instruction.createPool(poolBump, new BN('0'), new BN('0'), { accounts: { pool: poolSigner, state: stateSigner, mint: rewardMint, vault: poolVault, authority: wallet.publicKey, ...defaultAccounts }, remainingAccounts: pools.map(p => ({ pubkey: p.publicKey, isWritable: true, isSigner: false })) }) ) // let stateInfo = await program.account.stateAccount.fetch(stateSigner) // let poolInfo = await program.account.farmPoolAccount.fetch(poolSigner) // assert.ok(poolInfo.point.eq(stateInfo.totalPoint)) // assert.ok(poolInfo.point.eq(new BN('0'))) // assert.ok(poolInfo.amountMultipler.eq(new BN(0))) return await sendTransaction(connection, wallet, transaction, signers) } export async function closeStakingPool( connection:Connection, wallet: any, poolSigner:string, pools:any[] ){ const transaction = new Transaction() const signers: Keypair[] = [] const stateSigner = await getStakingStateAddress() StakingProgram.instruction.closePool({ accounts: { pool: new PublicKey(poolSigner), state: stateSigner, authority: wallet, ...defaultAccounts }, remainingAccounts: pools.map(p => ({ pubkey: p.publicKey, isWritable: true, isSigner: false })) }) return await sendTransaction(connection, wallet, transaction, signers) } export async function changeStakingPoolAmountMultipler( connection:Connection, wallet: any, poolSigner:string, ) { const transaction = new Transaction() const signers: Keypair[] = [] const stateSigner = await getStakingStateAddress() const tx = StakingProgram.instruction.changePoolAmountMultipler(new BN(1), { accounts: { pool: new PublicKey(poolSigner), state: stateSigner, authority: wallet.publicKey, ...defaultAccounts } }) transaction.add(tx); return await sendTransaction(connection, wallet, transaction, signers) } export async function changeStakingTokenPerSecond( connection:Connection, wallet: any, pools:any[], tokenPerSecond: number ){ const transaction = new Transaction() const signers: Keypair[] = [] const stateSigner = await getStakingStateAddress() const tx = StakingProgram.instruction.changeTokensPerSecond(new BN(tokenPerSecond), { accounts: { state: stateSigner, authority: wallet.publicKey, ...defaultAccounts }, remainingAccounts: pools.map(p => ({ pubkey: p.publicKey, isWritable: true, isSigner: false })) }) transaction.add(tx); return await sendTransaction(connection, wallet, transaction, signers) } export async function changeStakingPoolPoint( connection:Connection, wallet: any, poolSigner: string, ) { const transaction = new Transaction() const signers: Keypair[] = [] const stateSigner = await getStakingStateAddress() let pools = await getAllStakingPools() const ix = StakingProgram.instruction.changePoolPoint(new BN(1000), { accounts: { pool: new PublicKey(poolSigner), state: stateSigner, authority: wallet.publicKey, ...defaultAccounts }, remainingAccounts: pools.map((p : any) => ({ pubkey: p.publicKey, isWritable: true, isSigner: false })) }) transaction.add(ix); return await sendTransaction(connection, wallet, transaction, signers) } export async function getStakingPoolUserAccount( wallet: any, poolSigner: any, ){ const [poolUserAccount, bump1] = await PublicKey.findProgramAddress([ poolSigner.toBuffer(), wallet.publicKey.toBuffer() ], StakingProgram.programId) return await StakingProgram.account.farmPoolUserAccount.fetchNullable(poolUserAccount) } const ONE_YEAR_SECOND = 365 * 24 * 3600; export const TIERS_XCRP = [0, 200, 2000] export function calculateTiers(amount: number, lockDuration: number) { const rate = Number((lockDuration / ONE_YEAR_SECOND).toFixed(3)); const xCRP = rate * amount; let i = 0; for(; i < TIERS_XCRP.length; i ++) { if (TIERS_XCRP[i] > xCRP) { break; } } i --; return { xCRP: Number(xCRP.toFixed(2)), tiers: i, }; } export async function stake ( connection:Connection, wallet: any, poolSigner:string, rewardMint: string, poolVault: string, rewardUserVault: string, amount:any, lock = 0, signers: Keypair[] = [] ) { const transaction = new Transaction() const stateSigner = await getStakingStateAddress() const state = await getStakingState() const extraRewardSigner = await getStakingExtraRewardAddress() // const poolSigner = await getPoolAddressFromMint(rewardMint) const [userAccount, bump1] = await PublicKey.findProgramAddress([ new PublicKey(poolSigner).toBuffer(), wallet.publicKey.toBuffer() ], StakingProgram.programId) const userAccountData = await StakingProgram.account.farmPoolUserAccount.fetchNullable(userAccount) if(!userAccountData){ console.log("You are the new user to stake") transaction.add( StakingProgram.instruction.createUser(bump1, { accounts: { user: userAccount, state: stateSigner, pool: poolSigner, authority: wallet.publicKey, ...defaultAccounts } })); } try { const txHash = await StakingProgram.rpc.stake(new BN(amount), new BN(lock), { accounts: { mint: rewardMint, extraRewardAccount: extraRewardSigner, poolVault: poolVault, userVault: rewardUserVault, feeVault: state.feeVault, user: userAccount, state: stateSigner, pool: poolSigner, authority: wallet.publicKey, ...defaultAccounts }, preInstructions: transaction.instructions, signers }); return txHash } catch(e) { console.log(e); } return ''; } export async function createStakingUser ( connection:Connection, wallet: any, poolSigner:string, signers: Keypair[] = [] ) { const stateSigner = await getStakingStateAddress() const [userAccount, bump1] = await PublicKey.findProgramAddress([ new PublicKey(poolSigner).toBuffer(), wallet.publicKey.toBuffer() ], StakingProgram.programId) const userAccountData = await StakingProgram.account.farmPoolUserAccount.fetchNullable(userAccount) if(!userAccountData){ console.log("You are the new user to stake") try{ const tx = await StakingProgram.rpc.createUser(bump1, { accounts: { user: userAccount, state: stateSigner, pool: poolSigner, authority: wallet.publicKey, ...defaultAccounts }, signers }); return tx; } catch(e) { console.log(e); return null } } return null } export async function unstake ( connection:Connection, wallet: any, poolSigner:string, rewardMint: string, poolVault: string, rewardUserVault:string, amount: number, signers: Keypair[] = [] ) { const stateSigner = await getStakingStateAddress() const programState = await getStakingState() const extraRewardSigner = await getStakingExtraRewardAddress() // const poolSigner = await getPoolAddressFromMint(rewardMint) const [poolUserAccount, bump1] = await PublicKey.findProgramAddress([ new PublicKey(poolSigner).toBuffer(), wallet.publicKey.toBuffer() ], StakingProgram.programId) try { const txHash = await StakingProgram.rpc.unstake(new BN(amount), { accounts: { mint: rewardMint, extraRewardAccount: extraRewardSigner, poolVault: poolVault, userVault: rewardUserVault, feeVault: programState.feeVault, user: poolUserAccount, state: stateSigner, pool: poolSigner, authority: wallet.publicKey, ...defaultAccounts }, signers }) return txHash; } catch(e) { console.log(e); return null } } export async function harvest ( connection:Connection, wallet: any, poolSigner: string, rewardMint: string, rewardUserVault: string, stateRewardVault: string, signers: Keypair[] = [] ) { const stateSigner = await getStakingStateAddress() const extraRewardSigner = await getStakingExtraRewardAddress() // const poolSigner = await getPoolAddressFromMint(rewardMint) const [poolUserAccount, bump1] = await PublicKey.findProgramAddress([ new PublicKey(poolSigner).toBuffer(), wallet.publicKey.toBuffer() ], StakingProgram.programId) try { const txHash = await StakingProgram.rpc.harvest({ accounts: { mint: rewardMint, extraRewardAccount: extraRewardSigner, rewardVault: stateRewardVault, userVault: rewardUserVault, user: poolUserAccount, state: stateSigner, pool: poolSigner, authority: wallet.publicKey, ...defaultAccounts }, signers }); return txHash; } catch(e) { console.log(e); return null } }