// Ubiq Token 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. */ // Sparrow acct num, STARTS AT ZERO, CAN'T USE TREZOR/LEDGER. // Defaults to second account ("1"), or pass a number as the first parameter // on the command line. const account_number = process.argv.length >= 3 ? parseInt(process.argv[2]) : 1; // Time between runs in milliseconds, or pass a number of hours as second // argument on command line. const loop_delay = ( process.argv.length >= 4 ? (parseFloat(process.argv[3]) * 3600) : 7200 ) * 1000; 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 tengrans_token_address = '0x0826180A4c981d5095Cb5c48BB2A098A44cf6f73'; const tengrans_ubiq_slpt_token_address = '0x6321C294f34C2CDAF61012Ac4f3588a527F4D990'; const tengrans_pool_address = '0x9969A0123c7e7553dac5390221e321C05630d102'; const wubq_token_address = '0x1FA6A37c64804C0D797bA6bC1955E50068FbF362'; 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 one_eth = '1000000000000000000'; 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; if (mnemonic) { console.log("Mnemonic found."); } else { console.error("No mnemonic found, remember to do ' export MNEMONIC=\"your seed phrase\"' before running."); process.exit(1); } 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 pair_abi = require('./IUniswapV2Pair.json').abi; const router_abi = require('./IUniswapV2Router02.json').abi; const factory_abi = require('./IUniswapV2Factory.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 = 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); pool.methods.earned(account).call().then(earned => { if (earned === '0') { console.log(`No rewards for ${pool_address}`); resolve(); } else { 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} SPHR pools to harvest.`); console.log("Getting rewards from all SPHR pools.") if (reward_promises.length) await Promise.all(reward_promises); console.log("Done harvesting SPHRc.") const sphr = new Contract(erc20_abi, sphr_token_address); const sphr_balance = new BigNumber(await sphr.methods.balanceOf(account).call()); console.log(`sphere balance: ${sphr_balance.div('1e+8')}`); const sphrc = new Contract(erc20_abi, sphrc_token_address); const sphrc_balance = new BigNumber(await sphrc.methods.balanceOf(account).call()); console.log(`spherec balance: ${sphrc_balance.div('1e+18')}`); if (sphr_balance.isZero() || sphrc_balance.lt(one_eth)) { 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 = 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 = sphrc_balance.times(sphr_sphrc_ratio).integerValue(); // console.log(`needed sphere: ${needed_sphr.div('1e+8')}`); if (sphrc_balance.isZero() || sphr_balance.isZero()) { console.log("Not enough SPHR/SPHRc, skipping this step."); } else { let usedSphr; let usedSphrc; const use_full_sphr_balance = needed_sphr.lte(sphr_balance); if (use_full_sphr_balance) { usedSphr = needed_sphr; usedSphrc = sphrc_balance; } else { usedSphr = sphr_balance; usedSphrc = sphr_balance.div(sphr_sphrc_ratio).integerValue(); } console.log(`Will use ${usedSphr.div('1e+8')} SPHR, ${usedSphrc.div('1e+18')} SPHRc.`); // account for slippage const sphr_min_desired = usedSphr.times(slippage_pct).integerValue().toString(); const sphrc_min_desired = usedSphrc.times(slippage_pct).integerValue().toString(); // make sure both tokens are approved for the amounts const approval_promises = []; const sphr_allowance = new BigNumber(await sphr.methods.allowance(account, router_address).call()); if (sphr_allowance.lt(usedSphr)) { console.log("SPHR allowance isn't big enough, approving now."); approval_promises.push( sphr.methods.approve(router_address, usedSphr.toString()).send({ from: account }) ); } const sphrc_allowance = new BigNumber(await sphrc.methods.allowance(account, router_address).call()); if (sphrc_allowance.lt(usedSphrc)) { console.log("SPHRc allowance isn't big enough, approving now."); approval_promises.push( sphrc.methods.approve(router_address, usedSphrc.toString()).send({ from: account }) ); } if (approval_promises.length) { await Promise.all(approval_promises); } const deadline = Math.floor(Date.now() / 1000) + deadline_seconds; console.log('Adding SPHR/SPHRc liquidity to Shinobi in exchange for SLPT tokens.'); await router.methods.addLiquidity( sphr_token_address, sphrc_token_address, usedSphr.toString(), usedSphrc.toString(), 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 SPHR/SPHRc 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 }); } const tengrans = new Contract(erc20_abi, tengrans_token_address); const tengrans_pool = new Contract(pool_abi, tengrans_pool_address); console.log("Harvesting 10grans rewards"); await tengrans_pool.methods.getReward().send({ from: account }); const tengrans_balance = new BigNumber(await tengrans.methods.balanceOf(account).call()); const ubiq_balance = new BigNumber(await web3.eth.getBalance(account)); console.log(`10grans balance: ${tengrans_balance.div('1e+18')}`); if (tengrans_balance.lt('1000000000000000')) { console.log("Not enough 10grans to stake, skipping."); } else if (ubiq_balance.lt(one_eth)) { console.log("Not enough ubiq to stake, skipping.") } else { const pair_address = await factory.methods.getPair(tengrans_token_address, wubq_token_address).call(); const pair = new Contract(pair_abi, pair_address); const reserves = await pair.methods.getReserves().call(); console.log(`reserve0: ${reserves.reserve0}, reserve1: ${reserves.reserve1}`); const grans_ubiq_ratio = new BigNumber(reserves.reserve1).div(reserves.reserve0); console.log(`10grans/ubiq reserves ratio: ${grans_ubiq_ratio}`); const needed_ubiq = new BigNumber(tengrans_balance).times(grans_ubiq_ratio).integerValue(); console.log(`needed ubiq: ${needed_ubiq.div('1e+18')}`); if (ubiq_balance.lt(needed_ubiq)) { console.log("Not enough ubiq to stake 10grans, skipping.") } else { // account for slippage const grans_min_desired = tengrans_balance.times(slippage_pct).integerValue().toString(); const ubiq_min_desired = needed_ubiq.times(slippage_pct).integerValue().toString() const grans_allowance = new BigNumber(await tengrans.methods.allowance(account, router_address).call()); console.log(`10grans allowance: ${grans_allowance.div('1e+18')}`); if (grans_allowance.lt(tengrans_balance)) { console.log("10grans allowance for Shinobi isn't big enough, approving now."); await tengrans.methods.approve(router_address, tengrans_balance.toString()).send({from: account}); } const deadline = Math.floor(Date.now() / 1000) + deadline_seconds; console.log("Adding liquidity to Shinobi."); await router.methods.addLiquidityETH( tengrans_token_address, tengrans_balance.toString(), grans_min_desired, ubiq_min_desired, account, deadline ).send({ from: account, value: needed_ubiq.toString() }); } } const grans_slpt = new Contract(erc20_abi, tengrans_ubiq_slpt_token_address); const grans_slpt_balance = new BigNumber(await grans_slpt.methods.balanceOf(account).call()); console.log(`SLPT balance: ${grans_slpt_balance.div('1e+18')}`); if (grans_slpt_balance.isZero()) { console.log("No 10grans/Ubiq SLPT to stake."); } else { const grans_slpt_allowance = new BigNumber(await grans_slpt.methods.allowance(account, tengrans_pool_address).call()); if (grans_slpt_allowance.lt(grans_slpt_balance)) { console.log("10grans staking pool allowance isn't big enough, approving now."); await grans_slpt.methods.approve(tengrans_pool_address, grans_slpt_balance.toString()).send({ from: account }); } console.log("Staking SLPT tokens."); await tengrans_pool.methods.stake(grans_slpt_balance.toString()).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); });