Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
INFURA_PROJECT_ID=
ACCOUNT=
PRIVATE_KEY=
CHECK_EACH_X_MINUTES=1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
node_modules/
.build/
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
require("dotenv").config();

import {
Fetcher,
Percent,
Route,
TokenAmount,
Trade,
TradeType,
WETH,
} from "@uniswap/sdk";
import { ethers } from "ethers";
import { uniswapJSONInterface } from "./constants/uniswap-json-interface";
const axios = require('axios')
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider(`https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`));

const {
cEthAbi,
comptrollerAbi,
priceFeedAbi,
cErcAbi,
erc20Abi,
} = require('../contracts.json');


web3.eth.accounts.wallet.add('0x' + process.env.PRIVATE_KEY);
const myWalletAddress = web3.eth.accounts.wallet[0].address;

const cEthAddress = '0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5';
const cEthContract = new web3.eth.Contract(cEthAbi, cEthAddress);

const comptrollerAddress = '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b';
const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddress);

const priceFeedAddress = '0x922018674c12a7f0d394ebeef9b58f186cde13c1';
const priceFeed = new web3.eth.Contract(priceFeedAbi, priceFeedAddress);

const underlyingAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F';

const cTokenAddress = '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643';
const cToken = new web3.eth.Contract(cErcAbi, cTokenAddress);
const assetName = 'DAI';
const underlyingDecimals = 18;


ensureEnvironmentIsReasonablyConfigured();

const gasPriceLimitForInvestmentRound: number = 165000000000 // could be flexibilized
let amountOfDAIToBeBorrowedInThisRound: number = 0 // initialization
let freeValueInETH: number = 0 // initialization


setInterval(async () => {
if (await isAnInvestmentRoundReasonable(gasPriceLimitForInvestmentRound)) {
console.log("starting an investmentround.");
await borrowDAIFromCompound(amountOfDAIToBeBorrowedInThisRound);
await swapDAIToETH();
await depositETHToCompound(freeValueInETH * 0.7); // times 0.7 ensuring there stays ETH for gas in wallet
} else {
console.log("At the moment it does not make sense to trigger another investment round.");
}
}, 1000 * 60 * Number(process.env.CHECK_EACH_X_MINUTES));


function ensureEnvironmentIsReasonablyConfigured(): void {

if (process.env.ACCOUNT === undefined || process.env.ACCOUNT.length < 10) {
throw new Error(`Please copy the .env.example file to .env and add your data for the wallet you want to optimize.`);
} else {
console.log(`optimizing crypto investments for wallet: ${process.env.ACCOUNT} on a regular basis`);
}
};


// I would love to publish a deno.land module containing the following functions :)
async function isAnInvestmentRoundReasonable(gasPriceLimitForInvestmentRound: number): Promise<boolean> {

const gasPrice = await web3.eth.getGasPrice()

const apiResult = (await axios.get(`https://api.compound.finance/api/v2/account?addresses[]=${process.env.ACCOUNT}`)).data
const totalCollateralValueInETH = apiResult.accounts[0].total_collateral_value_in_eth.value
const totalBorrowValueInETH = apiResult.accounts[0].total_borrow_value_in_eth.value
const healthFactor = apiResult.accounts[0].health.value

freeValueInETH = (totalCollateralValueInETH * 0.8) - totalBorrowValueInETH

if (gasPrice > gasPriceLimitForInvestmentRound) {
console.log(`The gas Price ${gasPrice} seems too high as your limit is set to ${gasPriceLimitForInvestmentRound}.`)

return false
}

if (healthFactor < 2) {
console.log(`The health factor of ${healthFactor} is below your limit of 2.`)

return false
}

console.log(`The gas price of ${gasPrice} is fine as your limit is set to ${gasPriceLimitForInvestmentRound}.`)
console.log(`The health factor of ${healthFactor} also allows for an additional investment round.`)

amountOfDAIToBeBorrowedInThisRound = await getAmountOfDAIWhichCanBeBorrowed()
console.log(`We could borrow ${amountOfDAIToBeBorrowedInThisRound}.`)

return true
};

async function getAmountOfDAIWhichCanBeBorrowed(): Promise<number> {

const priceFeedAddress = '0x922018674c12a7f0d394ebeef9b58f186cde13c1';
const priceFeed = new web3.eth.Contract(priceFeedAbi, priceFeedAddress);
let priceInUsd = (await priceFeed.methods.price('ETH').call()) / 1000000;

console.log(`According to the price feed address: The price for Ether is about ${priceInUsd} USD.`)

return freeValueInETH * priceInUsd * 0.9 // the "* 0.9" just ensures we handle the investment carefully
}

async function borrowDAIFromCompound(amountOfDAIToBeBorrowed: number) {

const fromMyWallet = {
from: myWalletAddress,
gasLimit: web3.utils.toHex(500000),
gasPrice: await web3.eth.getGasPrice()
};

let markets = [cEthAddress];
await comptroller.methods.enterMarkets(markets).send(fromMyWallet);

console.log('Calculating your liquid assets in the protocol...');
let { 1: liquidity } = await comptroller.methods.getAccountLiquidity(myWalletAddress).call();
liquidity = liquidity / 1e18;

console.log('Fetching cETH collateral factor...');
let { 1: collateralFactor } = await comptroller.methods.markets(cEthAddress).call();
collateralFactor = (collateralFactor / 1e18) * 100;

console.log(`Fetching ${assetName} price from the price feed...`);
let underlyingPriceInUsd = await priceFeed.methods.price(assetName).call();
underlyingPriceInUsd = underlyingPriceInUsd / 1e6; // Price feed provides price in USD with 6 decimal places

console.log(`Fetching borrow rate per block for ${assetName} borrowing...`);
let borrowRate = await cToken.methods.borrowRatePerBlock().call();
borrowRate = borrowRate / Math.pow(10, underlyingDecimals);

console.log(`Now attempting to borrow ${amountOfDAIToBeBorrowed} ${assetName}...`);
const scaledUpBorrowAmount = (amountOfDAIToBeBorrowed * Math.pow(10, underlyingDecimals)).toString();
const trx = await cToken.methods.borrow(scaledUpBorrowAmount).send(fromMyWallet);
console.log('Borrow Transaction', trx);

console.log(`\nFetching ${assetName} borrow balance from c${assetName} contract...`);
let balance = await cToken.methods.borrowBalanceCurrent(myWalletAddress).call();
balance = balance / Math.pow(10, underlyingDecimals);
console.log(`Borrow balance is ${balance} ${assetName}`);

}


async function swapDAIToETH(): Promise<void> {
const dai = (await Fetcher.fetchTokenData(1, underlyingAddress))
const pair = await Fetcher.fetchPairData(dai, WETH[1])
const route = new Route([pair], dai)
const signer = new ethers.Wallet(process.env.PRIVATE_KEY)
const provider = ethers.getDefaultProvider('mainnet', { infura: `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}` })
const account = signer.connect(provider)

const daiSmartContract = new ethers.Contract(underlyingAddress, erc20Abi, account)
const balanceOfDaiOnAccount = await daiSmartContract.balanceOf(process.env.ACCOUNT)
console.log(balanceOfDaiOnAccount)

const trade = new Trade(route, new TokenAmount(dai, balanceOfDaiOnAccount), TradeType.EXACT_INPUT)
console.log(route.midPrice.toSignificant(6))
console.log(trade.executionPrice.toSignificant(6))

const slippageTolerance = new Percent('50', '10000')
const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw

console.log(amountOutMin.toString())

console.log(ethers.BigNumber.from("42"))

const path = [underlyingAddress, WETH[1].address]

const deadline = Math.floor(Date.now() / 1000) + 60 * 2

const uniswapSmartContract = new ethers.Contract('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', uniswapJSONInterface, account)

const tx = await uniswapSmartContract.swapExactTokensForETH(
balanceOfDaiOnAccount,
amountOutMin.toString(),
path,
process.env.ACCOUNT,
deadline
)

console.log(`Check your swap transaction at https://etherscan.io/tx/${tx.hash}`)
}


async function depositETHToCompound(amountToBeDeposited: number) {
await cEthContract.methods.mint().send({
from: myWalletAddress,
gasLimit: web3.utils.toHex(150000),
gasPrice: await web3.eth.getGasPrice(),
value: web3.utils.toHex(amountToBeDeposited * 1e18)
});

console.log(`deposited ${amountToBeDeposited} Ether to compound`)
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const uniswapJSONInterface = [{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Loading