// SPHRc Liquidity Harvester Pro 2000 // License: Public Domain /** * INSTALL: * first install node.js and npm (they are usually together.) * navigate in terminal to the directory you unpacked this in. * type: npm install * * RUN: * navigate in terminal to the directory you unpacked this in. * type, putting a space before "export": * export MNEMONIC="your seed phrase goes here" * type: ./loop.sh * leave the terminal open. It will run forever. If you need to end it, exit * the terminal. */ const account_number = 1; // Sparrow acct num, STARTS AT ZERO, CAN'T USE TREZOR const loop_delay = 7200 * 1000; // 2 hours const router_address = '0xf3ce4655a44146c8eefbf45651f6479f9d67a77a'; const sphr_token_address = '0x20e3dD746DdF519B23Ffbbb6Da7a5d33eA6349D6'; const sphrc_token_address = '0x402C2C3aceeb52b8654A0631012ceD49cbc9bDc4'; const slpt_token_address = '0x596430dF1662B39d481265752A0c694f72C99AD3'; const grans_sphr_pool_address = '0xcc22E95B1926F48acF17852582F14B22AB4cF0b9'; const ubiq_sphr_pool_address = '0x5130bc430Ed7cD252a18B724fBa40cd1078F8C75'; const ricks_sphr_pool_address = '0xB934A32F065a9db4B2Df565b901276c88dc31F3E'; const sphrc_sphr_pool_address = '0x93CdC7AF7fa7CfA9E603Cf4406acD81171A7Cfaa'; const pool_addresses = [ grans_sphr_pool_address, ubiq_sphr_pool_address, ricks_sphr_pool_address, sphrc_sphr_pool_address ]; const deadline_seconds = 600; slippage_pct = .995; const HDWalletProvider = require('@truffle/hdwallet-provider'); const Web3 = require("web3"); const Contract = require('web3-eth-contract'); const BigNumber = require('bignumber.js'); BigNumber.set({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_FLOOR }) const mnemonic = process.env.MNEMONIC; const provider = new HDWalletProvider({ mnemonic: { phrase: mnemonic }, providerOrUrl: "https://rpc.octano.dev" }); Contract.setProvider(provider); let web3 = new Web3(provider); const erc20_abi = require('./IERC20.json').abi; const router_abi = require('./IUniswapV2Router02.json').abi; const router = new Contract(router_abi, router_address); console.log("Starting loop"); (async () => { const account = (await web3.eth.getAccounts())[account_number]; console.log(`account: ${account}`); const factory_address = await router.methods.factory().call(); console.log(`factory address: ${factory_address}`); const factory_abi = require('./IUniswapV2Factory.json').abi; const factory = new Contract(factory_abi, factory_address); const pool_abi = require('./ShinobiPool.json').abi; while (true) { const reward_promises = []; for (const pool_address of pool_addresses) { reward_promises.push( new Promise(resolve => { const pool = new Contract(pool_abi, pool_address); console.log(`Calling harvest for ${pool_address}`); pool.methods.getReward().send({ from: account }).then(() => { console.log(`Harvested rewards for ${pool_address}`); resolve(); }) }) ); } console.log(`There are ${reward_promises.length} pools to harvest.`); console.log("Getting rewards from all pools.") await Promise.all(reward_promises); console.log("Done harvesting.") const sphr = new Contract(erc20_abi, sphr_token_address); const sphr_balance = await sphr.methods.balanceOf(account).call(); const sphr_balance_pretty = new BigNumber(sphr_balance).div('1e+8'); console.log(`sphere balance: ${sphr_balance_pretty}`); const sphrc = new Contract(erc20_abi, sphrc_token_address); const sphrc_balance = await sphrc.methods.balanceOf(account).call(); const sphrc_balance_pretty = new BigNumber(sphrc_balance).div('1e+18'); console.log(`spherec balance: ${sphrc_balance_pretty}`); if (new BigNumber(sphr_balance).isZero() || new BigNumber(sphrc_balance).lt('1000000000000000000')) { console.log("sphere or spherec balance too low, skipping this step.") } else { const pair_address = await factory.methods.getPair(sphr_token_address, sphrc_token_address).call(); const pair_abi = require('./IUniswapV2Pair.json').abi; const pair = new Contract(pair_abi, pair_address); const reserves = await pair.methods.getReserves().call(); console.log(`reserve0: ${reserves.reserve0}, reserve1: ${reserves.reserve1}`); const sphr_sphrc_ratio = new BigNumber(reserves.reserve0).div(reserves.reserve1); console.log(`ratio of sphr to sphrc: ${sphr_sphrc_ratio}`); const needed_sphr = new BigNumber(sphrc_balance).times(sphr_sphrc_ratio).integerValue().toString(); console.log(`needed sphere: ${new BigNumber(needed_sphr).div('1e+8')}`); if (new BigNumber(needed_sphr).gt(sphr_balance)) { console.log("Not enough sphr, skipping this step."); } else { // account for slippage const sphr_min_desired = new BigNumber(needed_sphr).times(slippage_pct).integerValue().toString(); const sphrc_min_desired = new BigNumber(sphrc_balance).times(slippage_pct).integerValue().toString() console.log(`desired: ${new BigNumber(sphr_min_desired).div('1e+8')} ${new BigNumber(sphrc_min_desired).div('1e+18')}`); // make sure both tokens are approved for the amounts const approval_promises = []; const sphr_allowance = await sphr.methods.allowance(account, router_address).call(); if (new BigNumber(sphr_allowance).lt(needed_sphr)) { console.log("Sphere allowance isn't big enough, approving now."); approval_promises.push( sphr.methods.approve(router_address, needed_sphr).send({ from: account }) ); } const sphrc_allowance = await sphrc.methods.allowance(account, router_address).call() if (new BigNumber(sphrc_allowance).lt(sphrc_balance)) { console.log("Sphere Cubed allowance isn't big enough, approving now."); approval_promises.push( sphrc.methods.approve(router_address, sphrc_balance).send({ from: account }) ); } if (approval_promises.length) { await Promise.all(approval_promises); } const deadline = Math.floor(Date.now() / 1000) + deadline_seconds; console.log('Adding liquidity to Shinobi.'); await router.methods.addLiquidity( sphr_token_address, sphrc_token_address, needed_sphr, sphrc_balance, sphr_min_desired, sphrc_min_desired, account, deadline ).send({ from: account }); } } const slpt = new Contract(erc20_abi, slpt_token_address); const slpt_balance = await slpt.methods.balanceOf(account).call(); if ( new BigNumber(slpt_balance).isZero() ) { console.log("No new SLPT to stake.") } else { console.log(`SLPT balance: ${new BigNumber(slpt_balance).div('1e+18')}`); const sphr_sphrc_pool = new Contract(pool_abi, sphrc_sphr_pool_address); const slpt_allowance = await slpt.methods.allowance(account, sphrc_sphr_pool_address).call(); if (new BigNumber(slpt_allowance).lt(slpt_balance)) { console.log("SLPT Allowance for yield farm is not enough, approving now."); await slpt.methods.approve(sphrc_sphr_pool_address, slpt_balance).send({ from: account }); } console.log("Staking SLPT tokens."); await sphr_sphrc_pool.methods.stake(slpt_balance).send({ from: account }); } console.log("Waiting for a while..."); await new Promise(resolve => setTimeout(resolve, loop_delay)); console.log("Done waiting, resuming."); } })().catch(e => { console.error('oh shit', e); try { provider.engine.stop(); } catch(e) {} process.exit(1); });