196 lines
9.5 KiB
JavaScript
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);
|
|
});
|
|
});
|