import {Injectable} from '@angular/core'; import {Contract} from 'web3-eth-contract'; import {Subject} from 'rxjs'; import {Web3Service} from '../../../integrations/services/web3/web3.service'; import {UtilsService} from './utils.service'; import {BLOCKS_PER_DAY, DIVIDER_FOR_BALANCE, PID, STAKING_CONTRACT_ADDRESS_MAIN_NET, TOKEN_DECIMALS, WEB3, XFT_APY_CONST} from '../../../integrations/dictionaries/meta-mask.dictionary'; import {ONSEN_CONTRACT_ADDRESS_MAIN_NET} from '../../../integrations/dictionaries/meta-mask.dictionary'; import sushiData from '@sushiswap/sushi-data'; import {AngularWeb3RecentTransactionsService} from 'angular-web3-components'; @Injectable({ providedIn: 'root' }) export class StakeSlpService { private slpBalance: string; public slpBalanceChange = new Subject(); private staked: string; public stakedChange = new Subject(); private slpAllowance: string; public slpAllowanceChange = new Subject(); private rewardsXFT: number; public rewardsXFTChange = new Subject(); private rewardsSushi: number; public rewardsSushiChange = new Subject(); private apySushi: any; public apySushiChange = new Subject(); private apyXFT: any; public apyXFTChange = new Subject(); constructor( private web3Service: Web3Service, private utilsService: UtilsService, private recentTransactionsService: AngularWeb3RecentTransactionsService, ) {} public getSLPBalance(accountAddress: string): void { if (this.slpBalance) { this.slpBalanceChange.next(this.slpBalance); } else { this.fetchSLPBalance(accountAddress); } } public fetchSLPBalance(accountAddress: string): void { this.web3Service.getSlpContract().then((contract: Contract) => { contract.methods.balanceOf(accountAddress).call({from: accountAddress}).then((res: string) => { this.slpBalance = res; this.slpBalanceChange.next(this.slpBalance); }); }); } public getStaked(accountAddress: string): void { if (this.staked) { this.stakedChange.next(this.staked); } else { this.fetchStaked(accountAddress); } } public fetchStaked(accountAddress: string): void { this.web3Service.getStakingContract().then((contract: Contract) => { contract.methods.balanceOf(accountAddress).call({from: accountAddress}).then((res: string) => { this.staked = res; this.stakedChange.next(this.staked); }); }); } public getSLPAllowance(accountAddress: string): void { if (this.slpAllowance) { this.slpAllowanceChange.next(this.slpAllowance); } else { this.fetchSLPAllowance(accountAddress); } } public fetchSLPAllowance(accountAddress: string): void { this.web3Service.getSlpContract().then((contract: Contract) => { contract.methods.allowance(accountAddress, STAKING_CONTRACT_ADDRESS_MAIN_NET).call({from: accountAddress}).then((res: string) => { this.slpAllowance = res; this.slpAllowanceChange.next(this.slpAllowance); }); }); } public approveNewAllowance(amount: string, accountAddress: string): Promise { return this.web3Service.getSlpContract().then((contract: Contract) => { return contract.methods.approve( STAKING_CONTRACT_ADDRESS_MAIN_NET, amount ).send({from: accountAddress}, (err, hash) => { if (!err) { this.recentTransactionsService.saveTransaction('Approve new allowance for stake SLP', hash); } }); }); } public stake(amount: string, accountAddress: string): Promise { return new Promise((resolve, reject) => { this.web3Service.getStakingContract().then((contract: Contract) => { contract.methods.stake(amount).send({from: accountAddress}, (err, hash) => { if (!err) { this.recentTransactionsService.saveTransaction('Stake SLP', hash); resolve(hash); } else { reject(err); } }); }); }); } public unStake(amount: string, accountAddress: string): Promise { return new Promise((resolve, reject) => { this.web3Service.getStakingContract().then((contract: Contract) => { contract.methods.withdraw(amount).send({from: accountAddress}, (err, hash) => { if (!err) { this.recentTransactionsService.saveTransaction('Withdraw SLP', hash); resolve(hash); } else { reject(err); } }); }); }); } public claimReward(accountAddress: string): Promise { return new Promise((resolve, reject) => { return this.web3Service.getStakingContract().then((contract: Contract) => { contract.methods.getReward().send({from: accountAddress}, (err, hash) => { if (!err) { this.recentTransactionsService.saveTransaction('Get reward', hash); resolve(hash); } else { reject(err); } }); }); }); } public getRewardsXFT(accountAddress: string): void { if (this.rewardsXFT) { this.rewardsXFTChange.next(this.rewardsXFT); } else { this.fetchRewardsXFT(accountAddress); } } public fetchRewardsXFT(accountAddress: string): void { this.web3Service.getStakingContract().then((contract: Contract) => { contract.methods.earnedXFT(accountAddress).call({from: accountAddress}).then((res: string) => { this.rewardsXFT = this.utilsService.parseAmount(res, DIVIDER_FOR_BALANCE); this.rewardsXFT = this.rewardsXFT / 100; this.rewardsXFTChange.next(this.rewardsXFT); }); }); } public getRewardsSushi(accountAddress: string): void { if (this.rewardsSushi) { this.rewardsSushiChange.next(this.rewardsSushi); } else { this.fetchRewardsSushi(accountAddress); } } public fetchRewardsSushi(accountAddress: string): void { this.web3Service.getStakingContract().then((contract: Contract) => { contract.methods.earnedSushi(accountAddress).call({from: accountAddress}).then((res: string) => { this.rewardsSushi = this.utilsService.parseAmount(res, DIVIDER_FOR_BALANCE); this.rewardsSushi = this.rewardsSushi / 100; this.rewardsSushiChange.next(this.rewardsSushi); }); }); } public getAPYSushi(accountAddress: string): void { if (this.apySushi) { this.apySushiChange.next(this.apySushi); } else { this.fetchAPYSushi(accountAddress).then(); } } public async fetchAPYSushi(accountAddress: string) { const info = await sushiData.sushi.info(); const masterchefInfo = await sushiData.masterchef.info(); const derivedETH = info.derivedETH * Math.pow(10, 18); this.web3Service.getSlpContract().then((slpContract: Contract) => { slpContract.methods.totalSupply().call({from: accountAddress}).then((totalSupplyResult: any) => { const totalSupply = totalSupplyResult; this.web3Service.getOnsenContract().then((onsenContract: Contract) => { onsenContract.methods.poolInfo(PID).call({from: accountAddress}).then((rewardPerBlockResult: any) => { const allocPoint = rewardPerBlockResult['allocPoint']; slpContract.methods.balanceOf(ONSEN_CONTRACT_ADDRESS_MAIN_NET).call({from: accountAddress}).then((slpBalanceResult: string) => { const slpBalance = slpBalanceResult; slpContract.methods.getReserves().call({from: accountAddress}).then((reservesResult: any) => { const totalValueETH = reservesResult['_reserve1']; onsenContract.methods.sushiPerBlock().call({from: accountAddress}).then((sushiPerBlockResult: any) => { const sushiPerBlock = sushiPerBlockResult; this.apySushi = this.calcSushiAPY( derivedETH, sushiPerBlock, allocPoint, masterchefInfo.totalAllocPoint, totalValueETH, slpBalance, totalSupply ); this.apySushi = this.apySushi * 100; this.apySushi = this.utilsService.parseAmount(this.apySushi.toString(), 16) / 100 ; this.apySushiChange.next(this.apySushi); }); }); }); }); }); }); }); } public getAPYXft(accountAddress: string): void { if (this.apyXFT) { this.apyXFTChange.next(this.apyXFT); } else { this.fetchAPYXft(accountAddress); } } public fetchAPYXft(accountAddress: string) { this.web3Service.getStakingContract().then((stakingContract: Contract) => { stakingContract.methods.rewardRate().call({from: accountAddress}).then((rewardRateResult: any) => { const rewardRate = rewardRateResult; stakingContract.methods.totalStaked().call({from: accountAddress}).then((totalStakedResult: any) => { const totalStaked = totalStakedResult; this.web3Service.getSlpContract().then((slpContract: Contract) => { slpContract.methods.totalSupply().call({from: accountAddress}).then((totalSupplyResult: any) => { const totalSupply = totalSupplyResult; slpContract.methods.getReserves().call({from: accountAddress}).then((reserveResult: any) => { const reserve = reserveResult._reserve0; this.apyXFT = ((rewardRate * XFT_APY_CONST) / ((totalStaked / totalSupply ) * 2 * reserve)) * 100; this.apyXFT = this.apyXFT.toFixed(2); this.apyXFTChange.next(this.apyXFT); }); }); }); }); }); }); } private calcSushiAPY = (derivedETH, sushiPerBlock, allocPoint, totalAllocPoint, totalValueETH, slpBalance, totalSupply) => { return ( (derivedETH * BLOCKS_PER_DAY * sushiPerBlock * 3 * 365 * (allocPoint / totalAllocPoint) ) / (totalValueETH * 2 * (slpBalance / totalSupply))); } }