diff --git a/index.js b/index.js index 09f8ec9..c0d5047 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -// SPHRc Liquidity Harvester Pro 2000 +// Ubiq Token Liquidity Harvester Pro 2000 // License: Public Domain /** @@ -16,8 +16,14 @@ * the terminal. */ -const account_number = 1; // Sparrow acct num, STARTS AT ZERO, CAN'T USE TREZOR -const loop_delay = 7200 * 1000; // 2 hours +// 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'; @@ -30,6 +36,12 @@ 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, @@ -40,6 +52,8 @@ const pool_addresses = [ 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'); @@ -48,6 +62,14 @@ 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 @@ -59,8 +81,10 @@ 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"); @@ -71,80 +95,97 @@ console.log("Starting loop"); 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(); - }) + 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} pools to harvest.`); - - console.log("Getting rewards from all pools.") - await Promise.all(reward_promises); - console.log("Done harvesting.") + 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 = 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 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 = await sphrc.methods.balanceOf(account).call(); - const sphrc_balance_pretty = new BigNumber(sphrc_balance).div('1e+18'); - console.log(`spherec balance: ${sphrc_balance_pretty}`); + const sphrc_balance = new BigNumber(await sphrc.methods.balanceOf(account).call()); + console.log(`spherec balance: ${sphrc_balance.div('1e+18')}`); - if (new BigNumber(sphr_balance).isZero() || new BigNumber(sphrc_balance).lt('1000000000000000000')) { + 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_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}`); + // 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')}`); + const needed_sphr = sphrc_balance.times(sphr_sphrc_ratio).integerValue(); + // console.log(`needed sphere: ${needed_sphr.div('1e+8')}`); - if (new BigNumber(needed_sphr).gt(sphr_balance)) { - console.log("Not enough sphr, skipping this step."); + 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 = 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')}`); + 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 = 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 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 = 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 }) ); + 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) { @@ -153,12 +194,12 @@ console.log("Starting loop"); const deadline = Math.floor(Date.now() / 1000) + deadline_seconds; - console.log('Adding liquidity to Shinobi.'); + console.log('Adding SPHR/SPHRc liquidity to Shinobi in exchange for SLPT tokens.'); await router.methods.addLiquidity( sphr_token_address, sphrc_token_address, - needed_sphr, - sphrc_balance, + usedSphr.toString(), + usedSphrc.toString(), sphr_min_desired, sphrc_min_desired, account, @@ -171,7 +212,7 @@ console.log("Starting loop"); const slpt_balance = await slpt.methods.balanceOf(account).call(); if ( new BigNumber(slpt_balance).isZero() ) { - console.log("No new SLPT to stake.") + console.log("No new SPHR/SPHRc SLPT to stake.") } else { console.log(`SLPT balance: ${new BigNumber(slpt_balance).div('1e+18')}`); @@ -188,6 +229,80 @@ console.log("Starting loop"); 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."); diff --git a/loop.sh b/loop.sh index ee09bae..bce07b7 100755 --- a/loop.sh +++ b/loop.sh @@ -1,6 +1,7 @@ #!/bin/bash +# defaults to account 0, two hour wait while : do - node index.js || continue + node index.js "${1:-0}" "${2:-2}" || continue sleep 5 done