Skip to content

Instantly share code, notes, and snippets.

@zmitton
Created December 27, 2019 22:02
Show Gist options
  • Save zmitton/299c73e609c9be0bead01bdd194d1f57 to your computer and use it in GitHub Desktop.
Save zmitton/299c73e609c9be0bead01bdd194d1f57 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
const axios = require('axios')
const chalk = require('chalk')
const program = require('commander')
const fs = require('fs')
const ethers = require('ethers')
const Web3 = require('web3')
const _ = require('lodash')
const _cliProgress = require('cli-progress')
let config = require('./config.json')
let abi = require('./gastoken.json')
const version = require('./package.json').version
const pipeline = async (funcs, bar) => {
bar.start(funcs.length, 0)
let counter = 0
return await funcs.reduce((promise, func) => {
return promise.then(result => {
bar.update(++counter)
return func().then(Array.prototype.concat.bind(result))
})
}, Promise.resolve([]))
}
function validateChain(chain) {
let allChains = ['ETC', 'ETH']
let valid = _.includes(allChains, chain)
if (!valid) {
console.error('Wrong chain', chain)
process.exit(1)
}
return chain
}
function validateTokenType(std) {
let allTypes = ['GST1', 'GST2']
let valid = _.includes(allTypes, std)
if (!valid) {
console.error('Wrong token type', std)
process.exit(1)
}
return std
}
function validateBatch(batch) {
let parsedbatch = parseInt(batch)
if (_.isNaN(parsedbatch)) {
console.error('Batch size must be an integer, i.e. 10. You supplied', chalk.red(batch))
process.exit(1)
}
return parsedbatch
}
function validateAmount(batch) {
let parsedbatch = parseInt(batch)
if (_.isNaN(parsedbatch)) {
console.error('Amount must be an integer, i.e. 80. You supplied', chalk.red(batch))
process.exit(1)
}
return parsedbatch
}
function getChainId(chain) {
if (chain === 'ETC') { return 61 }
if (chain === 'ETH') { return 1 }
console.log('Unknown chainId for chain', chain)
process.exit(1)
}
async function getGasPrice(chain) {
if (chain === 'ETC') { return 1000000 }
let gasApiUrl = 'https://www.ethgasstationapi.com/api/low'
return (await axios.get(gasApiUrl)).data * 1000000000
}
async function waitForTxToBeMined(hash, chain) {
let web3 = new Web3(new Web3.providers.HttpProvider(config[chain].rpcnode))
while (true) {
try {
const receipt = await web3.eth.getTransactionReceipt(hash)
if (receipt.status) { break }
} catch(e) {
await sleep(1000)
}
}
return true
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function confirmTransaction(hash) {
if (program.chain === 'ETC') {
await sleep(100)
return true
}
if (program.chain === 'ETH') {
await sleep(100)
return true
}
console.error('Unknown chain ', program.chain)
process.exit(1)
}
program
.version(version, '-v, --version')
.description('Miner for GasToken on both ETH and ETC chains. More details are available at ' + chalk.underline.green('https://forum.saturn.network/t/gastoken-tokenize-gas/2361'))
.option('-c, --chain <chain>', 'The chain on which you want to mine', validateChain, 'ETC')
.option('-t, --token <tokentype>', 'Must be either GST1 or GST2. Read more on ' + chalk.underline.green('https://gastoken.io'), validateTokenType, 'GST1')
.option('-b, --batch <batchsize>', 'How many mining transactions to send in one batch', validateBatch, 10)
.option('-a, --amount <amount>', 'How many tokens to mine per one call? ' + chalk.red('BE CAREFUL OF LARGE VALUES'), validateAmount, 80)
.option('-p, --pkey [pkey]', 'Private key of the mining address. Do not need to supply this if you supply a mnemonic')
.option('-m, --mnemonic [mnemonic]', 'Mnemonic (i.e. from Saturn Wallet) of the wallet with which you want to mine')
.option('-n, --rpcnode [rpcnode]', 'Optional url to the JSONRPC node to be used for mining')
.option('-g, --gasprice [gasprice]', 'Optional gas price (in wei) to be used for mining. Leave empty for automatic discovery')
.parse(process.argv)
if (!program.mnemonic && !program.pkey) {
console.error('At least one of [pkey], [mnemonic] must be supplied')
process.exit(1)
}
if (program.mnemonic && program.pkey) {
console.error('Only one of [pkey], [mnemonic] must be supplied')
process.exit(1)
}
let rpcnode = program.rpcnode || config[program.chain].rpcnode
let chainId = getChainId(program.chain)
let provider = new ethers.providers.JsonRpcProvider(rpcnode, { chainId: chainId, name: program.chain })
let wallet
if (program.mnemonic) {
wallet = ethers.Wallet.fromMnemonic(program.mnemonic).connect(provider)
} else {
wallet = new ethers.Wallet(program.pkey, provider)
}
let token = new ethers.Contract(config[program.chain][program.token], abi, wallet)
let mineToken = async function(token, amount, nonce, gasPrice) {
let tx = await token.mint(amount, { gasPrice: gasPrice, nonce: nonce })
await confirmTransaction(tx.hash)
return tx.hash
}
wallet.getTransactionCount().then(async (whatever) => {
let gasPrice = parseInt(program.gasprice) || await getGasPrice(program.chain)
console.log(chalk.black.bgWhite('Gastoken miner v'+version+' developed by Saturn Network'))
while (true) {
let nonce = await wallet.getTransactionCount()
let balance = await token.balanceOf(wallet.address)
console.log('💰 Current balance:', chalk.green(parseInt(balance.toString()) / 100), chalk.green(program.token))
console.log(chalk.yellow('⛏ Mining a batch of size', program.batch, 'with gas price', gasPrice))
let jobs = _.map(Array.from({length: program.batch}, (v, i) => i), function(offset) {
return async () => await mineToken(token, program.amount, nonce + offset, gasPrice)
})
const bar = new _cliProgress.Bar({}, _cliProgress.Presets.shades_classic)
let txs = await pipeline(jobs, bar).catch((err) => {
bar.stop()
console.error(chalk.red('Something went wrong! Please ask for help and provide this error message.'))
console.error(err)
process.exit(1)
})
bar.stop()
console.log(chalk.yellow('⏱ Awaiting confirmation'))
let confirmations = _.map(txs, function(hash) {
return async () => await waitForTxToBeMined(hash, program.chain)
})
await pipeline(confirmations, bar).catch((err) => {
bar.stop()
console.error(chalk.red('Something went wrong! Please ask for help and provide this error message.'))
console.error(err)
process.exit(1)
})
bar.stop()
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment