Add ESLint and convert code to ES6
This commit is contained in:
parent
83df18e9d8
commit
434f8c9cf5
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "standard"
|
||||||
|
}
|
|
@ -2,54 +2,46 @@
|
||||||
|
|
||||||
const https = require('https')
|
const https = require('https')
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
|
const bot = require('../bot')
|
||||||
|
|
||||||
// Returns the url for getting the labels of a request (Github API v3)
|
// Returns the url for getting the labels of a request (Github API v3)
|
||||||
// req has req.issue.labels_url
|
// req has req.issue.labels_url
|
||||||
const getLabelsURL = function (req) {
|
function getLabelsURL (req) {
|
||||||
let url = req.body.issue.labels_url
|
let url = req.body.issue.labels_url
|
||||||
// Make the URL generic removing the name of the label
|
// Make the URL generic removing the name of the label
|
||||||
return url.replace('{/name}', '')
|
return url.replace('{/name}', '')
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the bounty labelNames of the request, only for testing motives
|
|
||||||
const getLabelsMock = function (req) {
|
|
||||||
return new Promise((resolve, reject) => { resolve(req.body.issue.labels) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns all the bounty labelNames of a given issue (Github API v3)
|
// Returns all the bounty labelNames of a given issue (Github API v3)
|
||||||
const getLabels = function (req) {
|
function getLabels (req) {
|
||||||
if (config.debug) {
|
const path = getLabelsURL(req).replace('https://api.github.com', '')
|
||||||
return getLabelsMock(req)
|
const options = {
|
||||||
} else {
|
hostname: 'api.github.com',
|
||||||
const path = getLabelsURL(req).replace('https://api.github.com', '')
|
path: path,
|
||||||
const options = {
|
headers: { 'User-Agent': config.githubUsername }
|
||||||
hostname: 'api.github.com',
|
}
|
||||||
path: path,
|
return new Promise((resolve, reject) => {
|
||||||
headers: { 'User-Agent': config.githubUsername }
|
const request = https.get(options, (response) => {
|
||||||
}
|
// handle http errors
|
||||||
return new Promise((resolve, reject) => {
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
||||||
const request = https.get(options, (response) => {
|
bot.error(response, `Failed to load page, status code: ${response.statusCode}`)
|
||||||
// handle http errors
|
reject(new Error(`Failed to load page, status code: ${response.statusCode}`))
|
||||||
if (response.statusCode < 200 || response.statusCode > 299) {
|
}
|
||||||
bot.error(response, 'Failed to load page, status code: ' + response.statusCode)
|
// temporary data holder
|
||||||
reject(new Error('Failed to load page, status code: ' + response.statusCode))
|
const body = []
|
||||||
}
|
// on every content chunk, push it to the data array
|
||||||
// temporary data holder
|
response.on('data', (chunk) => body.push(chunk))
|
||||||
const body = []
|
// we are done, resolve promise with those joined chunks
|
||||||
// on every content chunk, push it to the data array
|
response.on('end', () => {
|
||||||
response.on('data', (chunk) => body.push(chunk))
|
const labels = JSON.parse(body.join('')).map(labelObj => labelObj.name)
|
||||||
// we are done, resolve promise with those joined chunks
|
resolve(labels)
|
||||||
response.on('end', () => {
|
})
|
||||||
const labels = JSON.parse(body.join('')).map(labelObj => labelObj.name)
|
})
|
||||||
resolve(labels)
|
// handle connection errors of the request
|
||||||
})
|
request.on('error', (err) => reject(err))
|
||||||
})
|
})
|
||||||
// handle connection errors of the request
|
|
||||||
request.on('error', (err) => reject(err))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getLabels: getLabels
|
getLabels: getLabels
|
||||||
}
|
}
|
||||||
|
|
249
bot/index.js
249
bot/index.js
|
@ -4,180 +4,155 @@ const ethers = require('ethers')
|
||||||
const Wallet = ethers.Wallet
|
const Wallet = ethers.Wallet
|
||||||
const Contract = ethers.Contract
|
const Contract = ethers.Contract
|
||||||
const providers = ethers.providers
|
const providers = ethers.providers
|
||||||
const utils = ethers.utils
|
|
||||||
|
|
||||||
const prices = require('./prices')
|
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
|
const prices = require('./prices')
|
||||||
const github = require('./github')
|
const github = require('./github')
|
||||||
|
|
||||||
const contractAddressString = 'Contract address:'
|
const contractAddressString = 'Contract address:'
|
||||||
|
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
const logger = winston.createLogger({
|
||||||
level: 'info',
|
level: 'info',
|
||||||
format: winston.format.json(),
|
format: winston.format.json(),
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.File({ filename: './log/error.log', level: 'error' }),
|
new winston.transports.File({ filename: './log/error.log', level: 'error' }),
|
||||||
new winston.transports.File({ filename: './log/info.log', level: 'info' }),
|
new winston.transports.File({ filename: './log/info.log', level: 'info' })
|
||||||
// new winston.transports.File({ filename: './log/combined.log' })
|
// new winston.transports.File({ filename: './log/combined.log' })
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function needsFunding (req) {
|
||||||
|
if (req.body.action !== 'edited' || !req.body.hasOwnProperty('comment')) {
|
||||||
|
return false
|
||||||
|
} else if (req.body.comment.user.login !== config.githubUsername) {
|
||||||
|
return false
|
||||||
|
} else if (!hasAddress(req)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const needsFunding = function (req) {
|
function hasAddress (req) {
|
||||||
if (req.body.action !== 'edited' || !req.body.hasOwnProperty('comment')) {
|
const commentBody = req.body.comment.body
|
||||||
return false
|
if (commentBody.search(contractAddressString) === -1) {
|
||||||
} else if (req.body.comment.user.login !== config.githubUsername) {
|
return false
|
||||||
return false
|
} else {
|
||||||
} else if (!hasAddress(req)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasAddress = function (req) {
|
function getAddress (req) {
|
||||||
const commentBody = req.body.comment.body
|
const commentBody = req.body.comment.body
|
||||||
if (commentBody.search(contractAddressString) === -1) {
|
const index = commentBody.search(contractAddressString) + 19
|
||||||
return false
|
return commentBody.substring(index, index + 42)
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAddress = function (req) {
|
async function getLabel (req) {
|
||||||
const commentBody = req.body.comment.body
|
const labels = await github.getLabels(req)
|
||||||
const index = commentBody.search(contractAddressString) + 19
|
const bountyLabels = labels.filter(label => config.bountyLabels.hasOwnProperty(label.name))
|
||||||
return commentBody.substring(index, index + 42)
|
if (bountyLabels.length === 1) {
|
||||||
|
return bountyLabels[0]
|
||||||
|
} else {
|
||||||
|
throw new Error('Error getting bounty labels')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLabel = function (req) {
|
async function getAmount (req) {
|
||||||
return new Promise((resolve, reject) => {
|
const label = await getLabel(req)
|
||||||
github.getLabels(req)
|
const tokenPrice = await prices.getTokenPrice(config.token)
|
||||||
.then(labels => {
|
|
||||||
const bountyLabels = labels.filter(label => config.bountyLabels.hasOwnProperty(label.name))
|
const amountToPayDollar = config.priceHour * config.bountyLabels[label.name]
|
||||||
if (bountyLabels.length === 1) {
|
return (amountToPayDollar / tokenPrice)
|
||||||
resolve(bountyLabels[0])
|
|
||||||
} else {
|
|
||||||
reject('Error getting bounty labels')
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAmountMock = function (req) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
resolve(10)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAmount = function (req) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const labelPromise = getLabel(req)
|
|
||||||
const tokenPricePromise = prices.getTokenPrice(config.token)
|
|
||||||
|
|
||||||
Promise.all([labelPromise, tokenPricePromise])
|
|
||||||
.then(function (values) {
|
|
||||||
const label = values[0]
|
|
||||||
const tokenPrice = values[1]
|
|
||||||
const amountToPayDollar = config.priceHour * config.bountyLabels[label.name]
|
|
||||||
resolve(amountToPayDollar / tokenPrice)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Logging functions
|
// Logging functions
|
||||||
|
|
||||||
const logTransaction = function (tx) {
|
function logTransaction (tx) {
|
||||||
logger.info("[OK] Succesfully funded bounty with transaction " + tx.hash)
|
logger.info(`[OK] Succesfully funded bounty with transaction ${tx.hash}`)
|
||||||
logger.info(" * From: " + tx.from)
|
logger.info(` * From: ${tx.from}`)
|
||||||
logger.info(" * To: " + tx.to)
|
logger.info(` * To: ${tx.to}`)
|
||||||
logger.info(" * Amount: " + tx.value)
|
logger.info(` * Amount: ${tx.value}`)
|
||||||
logger.info(" * Gas Price: " + tx.gasPrice)
|
logger.info(` * Gas Price: ${tx.gasPrice}`)
|
||||||
logger.info("====================================================")
|
logger.info(`====================================================`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = function (msg) {
|
function info (msg) {
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = function (errorMessage) {
|
function error (errorMessage) {
|
||||||
logger.error("[ERROR] Request processing failed: " + errorMessage)
|
logger.error(`[ERROR] Request processing failed: ${errorMessage}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendTransaction (to, amount, gasPrice) {
|
||||||
|
let hash = null
|
||||||
|
let chainId = providers.Provider.chainId.ropsten
|
||||||
|
let chainName = providers.networks.ropsten
|
||||||
|
|
||||||
const sendTransaction = async function (to, amount, gasPrice) {
|
if (config.realTransaction) {
|
||||||
let chainId = providers.Provider.chainId.ropsten
|
chainId = providers.Provider.chainId.homestead
|
||||||
let chainName = providers.networks.ropsten
|
chainName = providers.networks.homestead
|
||||||
|
}
|
||||||
|
|
||||||
if (config.realTransaction) {
|
const wallet = new Wallet(config.privateKey)
|
||||||
chainId = providers.Provider.chainId.homestead
|
wallet.provider = ethers.providers.getDefaultProvider(chainName)
|
||||||
chainName = providers.networks.homestead
|
|
||||||
|
if (config.token === 'ETH') {
|
||||||
|
const transaction = {
|
||||||
|
gasLimit: config.gasLimit,
|
||||||
|
gasPrice: gasPrice,
|
||||||
|
to: to,
|
||||||
|
value: amount,
|
||||||
|
chainId: chainId
|
||||||
}
|
}
|
||||||
|
|
||||||
const wallet = new Wallet(config.privateKey)
|
hash = await wallet.sendTransaction(transaction)
|
||||||
const provider = ethers.providers.getDefaultProvider(chainName)
|
} else {
|
||||||
|
const customSigner = getCustomSigner(wallet, sendTransaction)
|
||||||
wallet.provider = provider
|
const tokenContract = config.tokenContracts[config.token]
|
||||||
if (config.token === 'ETH') {
|
const contractAddress = tokenContract.address
|
||||||
const transaction = {
|
const contract = new Contract(contractAddress, tokenContract.abi, customSigner)
|
||||||
gasLimit: config.gasLimit,
|
const bigNumberAmount = ethers.utils.bigNumberify(amount)
|
||||||
gasPrice: gasPrice,
|
|
||||||
to: to,
|
|
||||||
value: amount,
|
|
||||||
chainId: chainId
|
|
||||||
}
|
|
||||||
|
|
||||||
return await wallet.sendTransaction(transaction)
|
await contract.transfer(to, bigNumberAmount)
|
||||||
} else {
|
}
|
||||||
let hash = null
|
|
||||||
|
|
||||||
async function getAddress() { return wallet.address }
|
return hash
|
||||||
async function sign(transaction) { return wallet.sign(transaction) }
|
}
|
||||||
|
|
||||||
async function resolveName(addressOrName) { return await provider.resolveName(addressOrName) }
|
function getCustomSigner (wallet, sendTransaction) {
|
||||||
async function estimateGas(transaction) { return await provider.estimateGas(transaction) }
|
const provider = wallet.provider
|
||||||
async function getGasPrice() { return await provider.getGasPrice() }
|
|
||||||
async function getTransactionCount(blockTag) { return await provider.getTransactionCount(blockTag) }
|
|
||||||
async function sendTransaction(transaction) {
|
|
||||||
hash = await provider.sendTransaction(transaction)
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
const customSigner = {
|
|
||||||
getAddress: getAddress,
|
|
||||||
provider: {
|
|
||||||
resolveName: resolveName,
|
|
||||||
estimateGas: estimateGas,
|
|
||||||
getGasPrice: getGasPrice,
|
|
||||||
getTransactionCount: getTransactionCount,
|
|
||||||
sendTransaction: sendTransaction
|
|
||||||
},
|
|
||||||
sign: sign
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokenContract = config.tokenContracts[config.token]
|
async function getAddress () { return wallet.address }
|
||||||
const contractAddress = tokenContract.address
|
async function sign (transaction) { return wallet.sign(transaction) }
|
||||||
const contract = new Contract(contractAddress, tokenContract.abi, customSigner)
|
|
||||||
const bigNumberAmount = ethers.utils.bigNumberify(amount)
|
|
||||||
await contract.transfer(to, bigNumberAmount)
|
|
||||||
|
|
||||||
return hash
|
async function resolveName (addressOrName) { return provider.resolveName(addressOrName) }
|
||||||
}
|
async function estimateGas (transaction) { return provider.estimateGas(transaction) }
|
||||||
|
async function getGasPrice () { return provider.getGasPrice() }
|
||||||
|
async function getTransactionCount (blockTag) { return provider.getTransactionCount(blockTag) }
|
||||||
|
|
||||||
|
const customSigner = {
|
||||||
|
getAddress: getAddress,
|
||||||
|
provider: {
|
||||||
|
resolveName: resolveName,
|
||||||
|
estimateGas: estimateGas,
|
||||||
|
getGasPrice: getGasPrice,
|
||||||
|
getTransactionCount: getTransactionCount,
|
||||||
|
sendTransaction: sendTransaction
|
||||||
|
},
|
||||||
|
sign: sign
|
||||||
|
}
|
||||||
|
|
||||||
|
return customSigner
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
needsFunding: needsFunding,
|
needsFunding: needsFunding,
|
||||||
getAddress: getAddress,
|
getAddress: getAddress,
|
||||||
getAmount: getAmount,
|
getAmount: getAmount,
|
||||||
getGasPrice: prices.getGasPrice,
|
getGasPrice: prices.getGasPrice,
|
||||||
sendTransaction: sendTransaction,
|
sendTransaction: sendTransaction,
|
||||||
info: info,
|
info: info,
|
||||||
logTransaction: logTransaction,
|
logTransaction: logTransaction,
|
||||||
error: error
|
error: error
|
||||||
}
|
}
|
||||||
|
|
117
bot/prices.js
117
bot/prices.js
|
@ -1,73 +1,62 @@
|
||||||
"use strict"
|
'use strict'
|
||||||
|
|
||||||
const https = require("https")
|
function getGasPrice () {
|
||||||
const config = require("../config")
|
const url = 'https://ethgasstation.info/json/ethgasAPI.json'
|
||||||
|
// return new pending promise
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
const getGasPrice = function () {
|
// select http or https module, depending on reqested url
|
||||||
const url = 'https://ethgasstation.info/json/ethgasAPI.json'
|
const lib = url.startsWith('https') ? require('https') : require('http')
|
||||||
// return new pending promise
|
const request = lib.get(url, (response) => {
|
||||||
return new Promise((resolve, reject) => {
|
// handle http errors
|
||||||
// select http or https module, depending on reqested url
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
||||||
const lib = url.startsWith('https') ? require('https') : require('http')
|
reject(new Error(`Failed to load page, status code: ${response.statusCode}`))
|
||||||
const request = lib.get(url, (response) => {
|
}
|
||||||
// handle http errors
|
// temporary data holder
|
||||||
if (response.statusCode < 200 || response.statusCode > 299) {
|
const body = []
|
||||||
reject('Failed to load page, status code: ' + response.statusCode)
|
// on every content chunk, push it to the data array
|
||||||
}
|
response.on('data', (chunk) => body.push(chunk))
|
||||||
// temporary data holder
|
// we are done, resolve promise with those joined chunks
|
||||||
const body = []
|
response.on('end', () => {
|
||||||
// on every content chunk, push it to the data array
|
// safeLowWait returns GWei (10^10 Wei).
|
||||||
response.on('data', (chunk) => body.push(chunk))
|
const jsonBody = JSON.parse(body.join(''))
|
||||||
// we are done, resolve promise with those joined chunks
|
const gasPriceWei = parseInt(jsonBody['safeLowWait']) * Math.pow(10, 10)
|
||||||
response.on('end', () => {
|
resolve(gasPriceWei)
|
||||||
// safeLowWait returns GWei (10^10 Wei).
|
})
|
||||||
const jsonBody = JSON.parse(body.join(''))
|
|
||||||
const gasPriceWei = parseInt(jsonBody['safeLowWait']) * Math.pow(10, 10)
|
|
||||||
resolve(gasPriceWei)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// handle connection errors of the request
|
|
||||||
request.on('error', (err) => reject(err))
|
|
||||||
})
|
})
|
||||||
|
// handle connection errors of the request
|
||||||
|
request.on('error', (err) => reject(err))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTokenPriceMock = function () {
|
function getTokenPrice (token) {
|
||||||
return new Promise((resolve, reject) => resolve(0.35))
|
const currency = 'USD'
|
||||||
}
|
const url = `https://min-api.cryptocompare.com/data/price?fsym=${token}&tsyms=${currency}`
|
||||||
|
// return new pending promise
|
||||||
const getTokenPrice = function (token) {
|
return new Promise((resolve, reject) => {
|
||||||
if (config.debug) {
|
// select http or https module, depending on reqested url
|
||||||
return getTokenPriceMock()
|
const lib = url.startsWith('https') ? require('https') : require('http')
|
||||||
}
|
const request = lib.get(url, (response) => {
|
||||||
const currency = 'USD'
|
// handle http errors
|
||||||
const url = 'https://min-api.cryptocompare.com/data/price?fsym=' + token + '&tsyms=' + currency
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
||||||
// return new pending promise
|
reject(new Error(`Failed to load page, status code: ${response.statusCode}`))
|
||||||
return new Promise((resolve, reject) => {
|
}
|
||||||
// select http or https module, depending on reqested url
|
// temporary data holder
|
||||||
const lib = url.startsWith('https') ? require('https') : require('http')
|
const body = []
|
||||||
const request = lib.get(url, (response) => {
|
// on every content chunk, push it to the data array
|
||||||
// handle http errors
|
response.on('data', (chunk) => body.push(chunk))
|
||||||
if (response.statusCode < 200 || response.statusCode > 299) {
|
// we are done, resolve promise with those joined chunks
|
||||||
reject('Failed to load page, status code: ' + response.statusCode)
|
response.on('end', () => {
|
||||||
}
|
const jsonBody = JSON.parse(body.join(''))
|
||||||
// temporary data holder
|
const tokenPrice = parseFloat(jsonBody[currency])
|
||||||
const body = []
|
resolve(tokenPrice)
|
||||||
// on every content chunk, push it to the data array
|
})
|
||||||
response.on('data', (chunk) => body.push(chunk))
|
|
||||||
// we are done, resolve promise with those joined chunks
|
|
||||||
response.on('end', () => {
|
|
||||||
const jsonBody = JSON.parse(body.join(''))
|
|
||||||
const tokenPrice = parseFloat(jsonBody[currency])
|
|
||||||
resolve(tokenPrice)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// handle connection errors of the request
|
|
||||||
request.on('error', (err) => reject(err))
|
|
||||||
})
|
})
|
||||||
|
// handle connection errors of the request
|
||||||
|
request.on('error', (err) => reject(err))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getGasPrice: getGasPrice,
|
getGasPrice: getGasPrice,
|
||||||
getTokenPrice: getTokenPrice
|
getTokenPrice: getTokenPrice
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +1,85 @@
|
||||||
// Work hours per label
|
// Work hours per label
|
||||||
const BOUNTY_LABELS = {
|
const BOUNTY_LABELS = {
|
||||||
'bounty-xs': 1,
|
'bounty-xs': 1,
|
||||||
'bounty-s': 10,
|
'bounty-s': 10,
|
||||||
'bounty-m': 100,
|
'bounty-m': 100,
|
||||||
'bounty-l': 1000,
|
'bounty-l': 1000,
|
||||||
'bounty-xl': 10000
|
'bounty-xl': 10000
|
||||||
}
|
}
|
||||||
|
|
||||||
const ERC20_ABI = [
|
const ERC20_ABI = [
|
||||||
{
|
{
|
||||||
"constant": false,
|
'constant': false,
|
||||||
"inputs": [
|
'inputs': [
|
||||||
{
|
{
|
||||||
"name": "_to",
|
'name': '_to',
|
||||||
"type": "address"
|
'type': 'address'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "_amount",
|
'name': '_amount',
|
||||||
"type": "uint256"
|
'type': 'uint256'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "transfer",
|
'name': 'transfer',
|
||||||
"outputs": [
|
'outputs': [
|
||||||
{
|
{
|
||||||
"name": "success",
|
'name': 'success',
|
||||||
"type": "bool"
|
'type': 'bool'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"payable": false,
|
'payable': false,
|
||||||
"type": "function"
|
'type': 'function'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const TOKEN_CONTRACTS = {
|
const TOKEN_CONTRACTS = {
|
||||||
'STT': {
|
'STT': {
|
||||||
address: '0xc55cF4B03948D7EBc8b9E8BAD92643703811d162',
|
address: '0xc55cF4B03948D7EBc8b9E8BAD92643703811d162',
|
||||||
abi: ERC20_ABI
|
abi: ERC20_ABI
|
||||||
},
|
},
|
||||||
'SNT': {
|
'SNT': {
|
||||||
address: '0x744d70fdbe2ba4cf95131626614a1763df805b9e',
|
address: '0x744d70fdbe2ba4cf95131626614a1763df805b9e',
|
||||||
abi: ERC20_ABI
|
abi: ERC20_ABI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Debug mode for testing the bot
|
// Debug mode for testing the bot
|
||||||
debug: true,
|
debug: true,
|
||||||
|
|
||||||
// URL where the bot is listening (e.g. '/funding')
|
// URL where the bot is listening (e.g. '/funding')
|
||||||
urlEndpoint: '/',
|
urlEndpoint: '/',
|
||||||
|
|
||||||
// URL for the signer
|
// URL for the signer
|
||||||
signerPath: 'https://ropsten.infura.io',
|
signerPath: 'https://ropsten.infura.io',
|
||||||
|
|
||||||
// Address with the funding for the bounties
|
// Address with the funding for the bounties
|
||||||
sourceAddress: '0x26a4D114B98C4b0B0118426F10fCc1112AA2864d',
|
sourceAddress: '0x26a4D114B98C4b0B0118426F10fCc1112AA2864d',
|
||||||
|
|
||||||
// Private key for ether.js wallet
|
// Private key for ether.js wallet
|
||||||
privateKey: '',
|
privateKey: '',
|
||||||
|
|
||||||
// Token of the currency for fetching real time prices (e.g. 'SNT')
|
// Token of the currency for fetching real time prices (e.g. 'SNT')
|
||||||
token: 'SNT',
|
token: 'SNT',
|
||||||
|
|
||||||
// Limit for the gas used in a transaction (e.g. 92000)
|
// Limit for the gas used in a transaction (e.g. 92000)
|
||||||
gasLimit: 92000,
|
gasLimit: 92000,
|
||||||
|
|
||||||
// Price per hour you will pay in dolars (e.g. 35)
|
// Price per hour you will pay in dolars (e.g. 35)
|
||||||
priceHour: 1,
|
priceHour: 1,
|
||||||
|
|
||||||
// Delay before funding a bounty (e.g. 3600000)
|
// Delay before funding a bounty (e.g. 3600000)
|
||||||
delayInMiliSeconds: 10000,
|
delayInMiliSeconds: 10000,
|
||||||
|
|
||||||
// Bounty Labels for the issues and the correspondent hours (e.g. {'bounty-xs': 3})
|
// Bounty Labels for the issues and the correspondent hours (e.g. {'bounty-xs': 3})
|
||||||
bountyLabels: BOUNTY_LABELS,
|
bountyLabels: BOUNTY_LABELS,
|
||||||
|
|
||||||
// Contract info for the different supported tokens
|
// Contract info for the different supported tokens
|
||||||
tokenContracts: TOKEN_CONTRACTS,
|
tokenContracts: TOKEN_CONTRACTS,
|
||||||
|
|
||||||
// username for the bot which has to comment for starting the process (e.g. status-bounty-)
|
// username for the bot which has to comment for starting the process (e.g. status-bounty-)
|
||||||
githubUsername: 'status-open-bounty',
|
githubUsername: 'status-open-bounty',
|
||||||
|
|
||||||
// Activate real transactions
|
// Activate real transactions
|
||||||
realTransaction: false
|
realTransaction: false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const _ = require("lodash")
|
const _ = require('lodash')
|
||||||
const defaults = require("./default.js")
|
const defaults = require('./default.js')
|
||||||
const config = require("./" + (process.env.NODE_ENV || "default") + ".js")
|
const config = require('./' + (process.env.NODE_ENV || 'default') + '.js')
|
||||||
module.exports = _.merge({}, defaults, config)
|
module.exports = _.merge({}, defaults, config)
|
||||||
|
|
145
index.js
145
index.js
|
@ -11,110 +11,89 @@ const config = require('./config')
|
||||||
const bot = require('./bot')
|
const bot = require('./bot')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
const express = require('express')
|
||||||
const express = require('express'),
|
const cors = require('cors')
|
||||||
cors = require('cors'),
|
const helmet = require('helmet')
|
||||||
helmet = require('helmet'),
|
const app = express()
|
||||||
app = express(),
|
const bodyParser = require('body-parser')
|
||||||
bodyParser = require('body-parser'),
|
const jsonParser = bodyParser.json()
|
||||||
jsonParser = bodyParser.json()
|
|
||||||
|
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
app.use(helmet())
|
app.use(helmet())
|
||||||
|
|
||||||
// Receive a POST request at the url specified by an env. var.
|
// Receive a POST request at the url specified by an env. var.
|
||||||
app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) {
|
app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) {
|
||||||
|
if (!req.body || !req.body.action) {
|
||||||
if (!req.body || !req.body.action) {
|
return res.sendStatus(400)
|
||||||
return res.sendStatus(400)
|
} else if (!bot.needsFunding(req)) {
|
||||||
} else if (!bot.needsFunding(req)) {
|
return res.sendStatus(204)
|
||||||
return res.sendStatus(204)
|
}
|
||||||
}
|
|
||||||
validation = validateRequest(req)
|
|
||||||
|
|
||||||
if (validation.correct) {
|
const validation = validateRequest(req)
|
||||||
|
|
||||||
setTimeout(() => {
|
if (validation.correct) {
|
||||||
processRequest(req)
|
setTimeout(async () => {
|
||||||
.then(() => {
|
try {
|
||||||
bot.info('issue well funded: ' + req.body.issue.url)
|
await processRequest(req)
|
||||||
})
|
bot.info(`issue well funded: ${req.body.issue.url}`)
|
||||||
.catch((err) => {
|
} catch (err) {
|
||||||
bot.error('Error processing request: ' + req.body.issue.url)
|
bot.error(`Error processing request: ${req.body.issue.url}`)
|
||||||
bot.error('Error: ' + err)
|
bot.error(`Error: ${err}`)
|
||||||
bot.error('Dump: ', req.body)
|
bot.error(`Dump: ${req.body}`)
|
||||||
})
|
}
|
||||||
}, config.delayInMiliSeconds)
|
}, config.delayInMiliSeconds)
|
||||||
|
} else {
|
||||||
} else {
|
bot.error(`Error validating issue: ${req.body.issue.url}`)
|
||||||
bot.error('Error validating issue: ' + req.body.issue.url)
|
bot.error(`Error: ${validation.error}`)
|
||||||
bot.error('Error: ' + validation.error)
|
}
|
||||||
}
|
return res.sendStatus(200)
|
||||||
return res.sendStatus(200)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const validateRequest = function (req) {
|
function validateRequest (req) {
|
||||||
validation = {correct: false, error: ''}
|
const validation = { correct: false, error: '' }
|
||||||
webhookSecret = process.env.WEBHOOK_SECRET
|
const webhookSecret = process.env.WEBHOOK_SECRET
|
||||||
|
|
||||||
if(!webhookSecret) {
|
if (!webhookSecret) {
|
||||||
validation.error = 'Github Webhook Secret key not found. ' +
|
validation.error = 'Github Webhook Secret key not found. ' +
|
||||||
'Please set env variable WEBHOOK_SECRET to github\'s webhook secret value'
|
'Please set env variable WEBHOOK_SECRET to github\'s webhook secret value'
|
||||||
|
} else {
|
||||||
|
const blob = JSON.stringify(req.body)
|
||||||
|
const hmac = crypto.createHmac('sha1', webhookSecret)
|
||||||
|
const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`
|
||||||
|
|
||||||
|
const theirSignature = req.get('X-Hub-Signature')
|
||||||
|
|
||||||
|
const bufferA = Buffer.from(ourSignature, 'utf8')
|
||||||
|
const bufferB = Buffer.from(theirSignature, 'utf8')
|
||||||
|
|
||||||
|
const safe = crypto.timingSafeEqual(bufferA, bufferB)
|
||||||
|
|
||||||
|
if (safe) {
|
||||||
|
validation.correct = true
|
||||||
} else {
|
} else {
|
||||||
|
validation.error = 'Invalid signature. Check that WEBHOOK_SECRET ' +
|
||||||
const blob = JSON.stringify(req.body)
|
'env variable matches github\'s webhook secret value'
|
||||||
const hmac = crypto.createHmac('sha1', webhookSecret)
|
|
||||||
const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`
|
|
||||||
|
|
||||||
const theirSignature = req.get('X-Hub-Signature')
|
|
||||||
|
|
||||||
const bufferA = Buffer.from(ourSignature, 'utf8')
|
|
||||||
const bufferB = Buffer.from(theirSignature, 'utf8')
|
|
||||||
|
|
||||||
const safe = crypto.timingSafeEqual(bufferA, bufferB)
|
|
||||||
|
|
||||||
if (safe) {
|
|
||||||
validation.correct = true
|
|
||||||
} else {
|
|
||||||
validation.error = 'Invalid signature. Check that WEBHOOK_SECRET ' +
|
|
||||||
'env variable matches github\'s webhook secret value'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return validation
|
return validation
|
||||||
}
|
}
|
||||||
|
|
||||||
const processRequest = function (req) {
|
async function processRequest (req) {
|
||||||
// const wallet = bot.wallet
|
// const wallet = bot.wallet
|
||||||
|
|
||||||
const from = config.sourceAddress
|
const to = bot.getAddress(req)
|
||||||
const to = bot.getAddress(req)
|
|
||||||
|
|
||||||
// Asynchronous requests for Gas Price and Amount
|
// Asynchronous requests for Gas Price and Amount
|
||||||
const amountPromise = bot.getAmount(req)
|
const amount = await bot.getAmount(req)
|
||||||
const gasPricePromise = bot.getGasPrice()
|
const gasPrice = await bot.getGasPrice()
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Promise.all([amountPromise, gasPricePromise])
|
|
||||||
.then(function (results) {
|
|
||||||
const amount = results[0]
|
|
||||||
const gasPrice = results[1]
|
|
||||||
|
|
||||||
bot.sendTransaction(to, amount, gasPrice)
|
const hash = await bot.sendTransaction(to, amount, gasPrice)
|
||||||
.then(function (hash) {
|
|
||||||
bot.logTransaction(hash)
|
bot.logTransaction(hash)
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = process.env.PORT || 8181
|
const port = process.env.PORT || 8181
|
||||||
app.listen(port, function () {
|
app.listen(port, function () {
|
||||||
bot.info('Autobounty listening on port', port)
|
bot.info('Autobounty listening on port', port)
|
||||||
})
|
})
|
||||||
|
|
13
package.json
13
package.json
|
@ -12,9 +12,7 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.17.2",
|
"body-parser": "^1.17.2",
|
||||||
"chai": "^4.1.2",
|
|
||||||
"cors": "^2.8.1",
|
"cors": "^2.8.1",
|
||||||
"eslint": "^4.15.0",
|
|
||||||
"ethers": "^2.2.6",
|
"ethers": "^2.2.6",
|
||||||
"ethjs-provider-signer": "^0.1.4",
|
"ethjs-provider-signer": "^0.1.4",
|
||||||
"ethjs-query": "^0.2.4",
|
"ethjs-query": "^0.2.4",
|
||||||
|
@ -22,8 +20,17 @@
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
"helmet": "^3.9.0",
|
"helmet": "^3.9.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"mocha": "^5.0.0",
|
|
||||||
"web3": "^0.18.2",
|
"web3": "^0.18.2",
|
||||||
"winston": "^3.0.0-rc1"
|
"winston": "^3.0.0-rc1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chai": "^4.1.2",
|
||||||
|
"eslint": "^4.19.0",
|
||||||
|
"eslint-config-standard": "^11.0.0",
|
||||||
|
"eslint-plugin-import": "^2.9.0",
|
||||||
|
"eslint-plugin-node": "^6.0.1",
|
||||||
|
"eslint-plugin-promise": "^3.7.0",
|
||||||
|
"eslint-plugin-standard": "^3.0.1",
|
||||||
|
"mocha": "^5.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,52 +5,51 @@ const should = require('chai').should
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
const bot = require('../bot')
|
const bot = require('../bot')
|
||||||
|
|
||||||
|
|
||||||
// status-open-bounty comment from https://github.com/status-im/autobounty/issues/1
|
// status-open-bounty comment from https://github.com/status-im/autobounty/issues/1
|
||||||
const sob_comment = 'Current balance: 0.000000 ETH\nTokens: SNT: 2500.00 ANT: 25.00\nContract address: 0x3645fe42b1a744ad98cc032c22472388806f86f9\nNetwork: Mainnet\n To claim this bounty sign up at https://openbounty.status.im and make sure to update your Ethereum address in My Payment Details so that the bounty is correctly allocated.\nTo fund it, send ETH or ERC20/ERC223 tokens to the contract address.'
|
const sobComment = 'Current balance: 0.000000 ETH\nTokens: SNT: 2500.00 ANT: 25.00\nContract address: 0x3645fe42b1a744ad98cc032c22472388806f86f9\nNetwork: Mainnet\n To claim this bounty sign up at https://openbounty.status.im and make sure to update your Ethereum address in My Payment Details so that the bounty is correctly allocated.\nTo fund it, send ETH or ERC20/ERC223 tokens to the contract address.'
|
||||||
|
|
||||||
// Fake requests
|
// Fake requests
|
||||||
const requests = [
|
const requests = [
|
||||||
{ body: { action: 'created', comment: { body: 'Creating my first comment', user: { login: 'randomUser' } } } },
|
{ body: { action: 'created', comment: { body: 'Creating my first comment', user: { login: 'randomUser' } } } },
|
||||||
{ body: { action: 'edited', comment: { body: 'Editing my comment', user: { login: 'RandomUser' } } } },
|
{ body: { action: 'edited', comment: { body: 'Editing my comment', user: { login: 'RandomUser' } } } },
|
||||||
{ body: { action: 'edited', comment: { body: sob_comment, user: { login: 'status-open-bounty' } } } },
|
{ body: { action: 'edited', comment: { body: sobComment, user: { login: 'status-open-bounty' } } } },
|
||||||
{ body: { action: 'created', issue: { labels: ['bounty', 'bounty-s'] }, comment: { body: sob_comment, user: { login: 'status-open-bounty' } } } }
|
{ body: { action: 'created', issue: { labels: ['bounty', 'bounty-s'] }, comment: { body: sobComment, user: { login: 'status-open-bounty' } } } }
|
||||||
]
|
]
|
||||||
|
|
||||||
describe('Bot behavior', function () {
|
describe('Bot behavior', function () {
|
||||||
describe('#needsFunding()', function () {
|
describe('#needsFunding()', function () {
|
||||||
it('should return false because the comment is not from status-open-bounty', function () {
|
it('should return false because the comment is not from status-open-bounty', function () {
|
||||||
assert.isFalse(bot.needsFunding(requests[0]))
|
assert.isFalse(bot.needsFunding(requests[0]))
|
||||||
})
|
|
||||||
it('should return false because a user is editing a comment', function () {
|
|
||||||
assert.isFalse(bot.needsFunding(requests[1]))
|
|
||||||
})
|
|
||||||
it('should return false because status-open-bounty edited a comment', function () {
|
|
||||||
assert.isFalse(bot.needsFunding(requests[2]))
|
|
||||||
})
|
|
||||||
it('should return true, it is all right and we should fund', function () {
|
|
||||||
assert.isTrue(bot.needsFunding(requests[3]))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
it('should return false because a user is editing a comment', function () {
|
||||||
|
assert.isFalse(bot.needsFunding(requests[1]))
|
||||||
|
})
|
||||||
|
it('should return false because status-open-bounty edited a comment', function () {
|
||||||
|
assert.isFalse(bot.needsFunding(requests[2]))
|
||||||
|
})
|
||||||
|
it('should return true, it is all right and we should fund', function () {
|
||||||
|
assert.isTrue(bot.needsFunding(requests[3]))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('#getAddress', function () {
|
describe('#getAddress', function () {
|
||||||
it('should return the address from a status-open-bounty bot comment', function () {
|
it('should return the address from a status-open-bounty bot comment', function () {
|
||||||
assert.equal(bot.getAddress(requests[3]), '0x3645fe42b1a744ad98cc032c22472388806f86f9')
|
assert.equal(bot.getAddress(requests[3]), '0x3645fe42b1a744ad98cc032c22472388806f86f9')
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('#getAmount', function () {
|
describe('#getAmount', function () {
|
||||||
it('should return the amount for the issue given the price per hour and the bounty label for this issue', (done) => {
|
it('should return the amount for the issue given the price per hour and the bounty label for this issue', (done) => {
|
||||||
bot.getAmount(requests[3])
|
bot.getAmount(requests[3])
|
||||||
.then(function (amount) {
|
.then(function (amount) {
|
||||||
const label = 'bounty-s'
|
const label = 'bounty-s'
|
||||||
const tokenPrice = 0.35
|
const tokenPrice = 0.35
|
||||||
const priceInDollars = config.priceHour * config.bountyLabels[label]
|
const priceInDollars = config.priceHour * config.bountyLabels[label]
|
||||||
expected_amount = priceInDollars / tokenPrice
|
expected_amount = priceInDollars / tokenPrice
|
||||||
assert.equal(amount, expected_amount)
|
assert.equal(amount, expected_amount)
|
||||||
done()
|
done()
|
||||||
})
|
|
||||||
.catch(() => { console.log('error'), done() })
|
|
||||||
})
|
})
|
||||||
|
.catch(() => { console.log('error'), done() })
|
||||||
})
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue