hackathon-registration-dapp/server/main.js

196 lines
9.5 KiB
JavaScript

const Web3 = require('web3');
const EventEmitter = require('events');
const config = require('./config');
const express = require('express');
const bodyParser = require('body-parser');
const low = require('lowdb');
const _ = require('lodash');
const FileAsync = require('lowdb/adapters/FileAsync')
const adapter = new FileAsync('db.json');
const merkleData = require('./merkle');
const { sha3 } = require('ethereumjs-util');
const merkle = require('merkle-tree-solidity');
const min3 = 3*60*1000;
const abiDefinition = [ { "constant": false, "inputs": [ { "name": "_newController", "type": "address" } ], "name": "changeController", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0x3cebb823" }, { "constant": true, "inputs": [ { "name": "", "type": "bytes5" } ], "name": "codeUsed", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x723de5cd" }, { "constant": false, "inputs": [ { "name": "_proof", "type": "bytes32[]" }, { "name": "_code", "type": "bytes5" }, { "name": "_dest", "type": "address" } ], "name": "processRequest", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0x72d209f5" }, { "constant": true, "inputs": [], "name": "sntAmount", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x7f58fa14" }, { "constant": true, "inputs": [ { "name": "", "type": "address" } ], "name": "sentToAddress", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x81e8706d" }, { "constant": false, "inputs": [], "name": "boom", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0xa169ce09" }, { "constant": false, "inputs": [ { "name": "_ethAmount", "type": "uint256" }, { "name": "_sntAmount", "type": "uint256" }, { "name": "_root", "type": "bytes32" } ], "name": "updateSettings", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0xa4438334" }, { "constant": true, "inputs": [ { "name": "_proof", "type": "bytes32[]" }, { "name": "_code", "type": "bytes5" }, { "name": "_dest", "type": "address" } ], "name": "validRequest", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0xadb187bb" }, { "constant": true, "inputs": [], "name": "SNT", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0xc55a02a0" }, { "constant": true, "inputs": [], "name": "ethAmount", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0xc98166c0" }, { "constant": true, "inputs": [], "name": "root", "outputs": [ { "name": "", "type": "bytes32" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0xebf0c717" }, { "constant": true, "inputs": [], "name": "controller", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0xf77c4791" }, { "inputs": [ { "name": "_sntAddress", "type": "address" }, { "name": "_ethAmount", "type": "uint256" }, { "name": "_sntAmount", "type": "uint256" }, { "name": "_root", "type": "bytes32" } ], "payable": false, "stateMutability": "nonpayable", "type": "constructor", "signature": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "dest", "type": "address" }, { "indexed": false, "name": "code", "type": "bytes5" }, { "indexed": false, "name": "ethAmount", "type": "uint256" }, { "indexed": false, "name": "sntAmount", "type": "uint256" } ], "name": "AddressFunded", "type": "event", "signature": "0x0aa7ecfdc9fd3f39ab380a0b6413557f94ed0dfd05ed31c925521736fa750eac" } ] ;
const events = new EventEmitter();
let contract;
const web3 = new Web3(config.connectionURL);
const account = web3.eth.accounts.privateKeyToAccount(config.controllerPrivK);
web3.eth.net.isListening()
.then(async () => {
console.log("Connected to WEB3: '" + config.connectionURL + "'");
contract = new web3.eth.Contract(abiDefinition, config.contractAddress);
console.log("Using account: " + account.address);
web3.eth.accounts.wallet.add(account);
events.emit('web3:connected')
})
.catch(error => {
console.error(error);
process.exit();
});
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
const isProcessing = async(code) => {
const db = await low(adapter);
db.defaults({transactions: {}}).write();
const record = await db.get('transactions.' + code).value();
return record && record.transactionTimestamp > ((new Date()).getTime() - min3)? true : false;
};
const process = async (request) => {
const db = await low(adapter);
db.defaults({transactions: {}})
.write();
const record = await db.get('transactions.' + request.code).value();
const recordByAddress = _.filter(Object.values(await db.get('transactions').value()), {address: request.address});
const sentToAddress = await contract.methods.sentToAddress(request.address).call();
const codeUsed = await contract.methods.codeUsed('0x' + request.code).call();
if(sentToAddress || codeUsed){
const message = "Transaction already exists - sentToAddress: " + sentToAddress + " , codeUsed: " +codeUsed;
console.warn(message);
return {"error": true, message};
}
if(record && record.transactionTimestamp > ((new Date()).getTime() - min3) ) {
const message = "Transaction already exists for code: " + request.code;
console.warn(message);
return {"error": true, message};
}
if(recordByAddress.length && recordByAddress[0].transactionTimestamp > ((new Date()).getTime() - min3)){
const message = "Transaction already exists for address: " + request.address;
console.warn(message);
return {"error": true, message};
}
const merkleTree = new merkle.default(merkleData.elements);
const hashedCode = sha3('0x' + request.code);
let proof;
try {
proof = merkleTree.getProof(hashedCode).map(x => "0x" + x.toString('hex'));
} catch(error){
const message = "Invalid Request - " + request.address + " - 0x" + request.code;
console.warn(message);
return {"error": true, message};
}
const validRequest = await contract.methods.validRequest(proof, '0x' + request.code, request.address).call();
if(!validRequest){
const message = "Invalid Request - " + request.address + " - 0x" + request.code;
console.warn(message);
return {"error": true, message};
}
try {
// Add code to db to indicate that we're processing it
const trxRecord = {};
trxRecord[request.code] = request;
await db.get('transactions')
.assign(trxRecord)
.write();
const gasPrice = await web3.eth.getGasPrice();
// Execute the contract function
const toSend = contract.methods.processRequest(proof, '0x' + request.code, request.address);
const estimatedGas = await toSend.estimateGas({from: account.address});
const tx = {
gasPrice: parseInt(gasPrice),
gas: estimatedGas + 50000,
from: account.address,
to: config.contractAddress,
value: "0x00",
data: toSend.encodeABI()
}
web3.eth.sendTransaction(tx)
.on('transactionHash', async (hash) => {
trxRecord[request.code].transactionHash = hash;
trxRecord[request.code].transactionTimestamp = Math.floor(Date.now());
await db.get('transactions')
.assign(trxRecord)
.write();
});
const message = "Request Processed - " + request.address + " - 0x" + request.code;
console.log(message);
return {message};
} catch(err){
console.error(err);
// TODO: might be a good idea to notify someone?
await db.unset('transactions.' + request.code)
.write();
return {"error": true, message: "Error processing transaction - Please contract a Status Core Developer"};
}
}
events.on('web3:connected', () => {
const app = express();
const port = 3000;
const router = express.Router();
router.post('/requestFunds', asyncMiddleware(async (req, res) => {
const request = req.body;
if(request.code && request.address) {
const result = await process(request);
res.status(200)
.send(result);
} else {
res.status(501)
.send({error: "Invalid request"});
}
}));
router.get('/isProcessing/:code', asyncMiddleware(async (req, res) => {
const params = req.params;
const result = await isProcessing(params.code);
res.status(200).send({result});
}));
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use('/api/', router);
app.listen(port, () => {
console.log('Server listening on port ' + port);
});
});