runethtx
Version:
Run Ethereum Transactions Helper
573 lines (535 loc) • 17.6 kB
JavaScript
const async = require("async");
module.exports = {
sendContractTx,
sendTx,
getBalance,
getTransactionReceipt,
getBlock,
deploy,
asyncfunc,
generateClass,
};
function deploy(web3, { $abi, $byteCode, ...opts }, _cb) {
return asyncfunc((cb) => {
const constructorAbi = $abi.find(({ type }) => (type === "constructor"));
let paramNames;
let contract;
let fromAccount;
let gas;
if (constructorAbi) {
paramNames = constructorAbi.inputs.map(({ name }) => {
if (name[ 0 ] === "_") {
return name.substring(1);
}
return name;
});
} else {
paramNames = [];
}
async.series([
(cb1) => {
if (opts.$from) {
fromAccount = opts.$from;
setTimeout(cb1, 1);
} else {
web3.eth.getAccounts((err, _accounts) => {
if (err) {
console.log("xxxxx -> " + err);
cb1(err);
return;
}
if (_accounts.length === 0) {
cb1(new Error("No account to deploy a contract"));
return;
}
fromAccount = _accounts[ 0 ];
cb1();
});
}
},
(cb2) => {
if (opts.$gas) {
gas = opts.$gas;
cb2();
return;
}
const params = paramNames.map(name => opts[ name ]);
params.push({
from: fromAccount,
value: opts.$value || 0,
data: $byteCode,
gas: 4700000,
});
if (opts.$verbose) {
console.log("constructor: " + JSON.stringify(params));
}
const data = web3.eth.contract($abi).new.getData(...params);
web3.eth.estimateGas({
from: fromAccount,
value: opts.$value || 0,
data,
gas: 4700000,
}, (err, _gas) => {
if (err) {
cb2(err);
} else if (_gas >= 4700000) {
cb2(new Error("throw"));
} else {
if (opts.$verbose) {
console.log("Gas: " + _gas);
}
gas = _gas;
gas += opts.$extraGas ? opts.$extraGas : 10000;
cb2();
}
});
},
(cb3) => {
const params = paramNames.map(name => opts[ name ]);
params.push({
from: fromAccount,
value: opts.$value || 0,
data: $byteCode,
gas,
});
params.push((err, _contract) => {
if (err) {
cb3(err);
return;
}
if (typeof _contract.address !== "undefined") {
contract = _contract;
cb3();
}
});
const ctr = web3.eth.contract($abi);
ctr.new(...params);
},
], (err2) => {
if (err2) {
cb(err2);
} else {
cb(null, contract);
}
});
}, _cb);
}
function asyncfunc(f, cb) {
const promise = new Promise((resolve, reject) => {
f((err, val) => {
if (err) {
reject(err);
} else {
resolve(val);
}
});
});
if (cb) {
promise.then(
(value) => {
cb(null, value);
},
(reason) => {
cb(reason);
});
} else {
return promise;
}
}
function getBalance(web3, address, _cb) {
return asyncfunc((cb) => {
web3.eth.getBalance(address, cb);
}, _cb);
}
function getTransactionReceipt(web3, txHash, _cb) {
return asyncfunc((cb) => {
web3.eth.getTransactionReceipt(txHash, cb);
}, _cb);
}
function getBlock(web3, blockNumber, _cb) {
return asyncfunc((cb) => {
web3.eth.getBlock(blockNumber, cb);
}, _cb);
}
function sendTx(web3, { data, from, value, gas, gasPrice, nonce, to, ...opts }, _cb) {
return asyncfunc((cb) => {
const txOpts = {};
let txHash;
if (!to) {
cb(new Error("to is required"));
return;
}
txOpts.to = to;
txOpts.value = value || 0;
if (gas) txOpts.gas = gas;
if (gasPrice) txOpts.gasPrice = gasPrice;
if (nonce) txOpts.nonce = nonce;
if (data) txOpts.data = data;
async.series([
(cb1) => {
if (from) {
txOpts.from = from;
setTimeout(cb1, 1);
} else {
// eslint-disable-next-line no-underscore-dangle
web3.eth.getAccounts((err, _accounts) => {
if (err) {
console.log("xxxxx -> " + err);
cb1(err);
return;
}
if (_accounts.length === 0) {
cb1(new Error("No account to deploy a contract"));
return;
}
txOpts.from = _accounts[ 0 ];
cb1();
});
}
},
(cb1) => {
if (opts.$gas) {
txOpts.gas = opts.$gas;
cb1();
return;
}
if (opts.$verbose) {
console.log("sendTx: " + JSON.stringify(txOpts));
}
txOpts.$gas = 4700000;
web3.eth.estimateGas(txOpts, (err, _gas) => {
if (err) {
cb1(err);
} else if (_gas >= 4700000) {
cb1(new Error("throw"));
} else {
if (opts.$verbose) {
console.log("Gas: " + _gas);
}
if (opts.$gas) {
txOpts.gas = opts.$gas;
} else {
txOpts.gas = _gas;
txOpts.gas += opts.$extraGas ? opts.$extraGas : 10000;
}
cb1();
}
});
},
(cb1) => {
web3.eth.sendTransaction(txOpts, (err, _txHash) => {
if (err) {
cb1(err);
} else {
txHash = _txHash;
cb1();
}
});
},
(cb1) => {
setTimeout(cb1, 100);
},
], (err) => {
cb(err, txHash);
});
}, _cb);
}
function sendContractTx(web3, contract, method, opts, _cb) {
return asyncfunc((cb) => {
if (!contract) {
cb(new Error("Contract not defined"));
return;
}
if (!method) {
// TODO send raw transaction to the contract.
cb(new Error("Method not defined"));
return;
}
let errRes;
const methodAbi = contract.abi.find(({ name, inputs }) => {
if (name !== method) return false;
const paramNames = inputs.map((param) => {
if (param.name[ 0 ] === "_") {
return param.name.substring(1);
}
return param.name;
});
for (let i = 0; i < paramNames.length; i += 1) {
if (typeof opts[ paramNames[ i ] ] === "undefined") {
errRes = new Error("Param " + paramNames[ i ] + " not found.");
return false;
}
}
return true;
});
if (errRes) {
cb(errRes);
return;
}
if (!methodAbi) {
cb(new Error("Invalid method"));
return;
}
const paramNames = methodAbi.inputs.map(({ name }) => {
if (name[ 0 ] === "_") {
return name.substring(1);
}
return name;
});
let fromAccount;
let gas;
let txHash;
async.series([
(cb1) => {
if (opts.$from) {
fromAccount = opts.$from;
setTimeout(cb1, 1);
} else {
web3.eth.getAccounts((err, _accounts) => {
if (err) {
console.log("xxxxx -> " + err);
cb1(err);
return;
}
if (_accounts.length === 0) {
cb1(new Error("No account to send the TX"));
return;
}
fromAccount = _accounts[ 0 ];
cb1();
});
}
},
(cb2) => {
if (opts.$noEstimateGas) {
gas = 4700000;
cb2();
return;
}
if (opts.$gas) {
gas = opts.$gas;
cb2();
return;
}
const params = paramNames.map(name => opts[ name ]);
if (opts.$verbose) console.log(method + ": " + JSON.stringify(params));
params.push({
from: fromAccount,
value: opts.$value,
gas: 4700000,
});
params.push((err, _gas) => {
if (err) {
cb2(err);
} else if (_gas >= 4700000) {
cb2(new Error("throw"));
} else {
if (opts.$verbose) {
console.log("Gas: " + _gas);
}
gas = _gas;
gas += opts.$extraGas ? opts.$extraGas : 10000;
cb2();
}
});
contract[ method ].estimateGas.apply(null, params);
},
(cb3) => {
const params = paramNames.map(name => opts[ name ]);
params.push({
from: fromAccount,
value: opts.$value,
gas: opts.$gas || gas,
});
params.push((err, _txHash) => {
if (err) {
cb3(err);
} else {
txHash = _txHash;
cb3();
}
});
contract[ method ].apply(null, params);
},
(cb4) => {
web3.eth.getTransactionReceipt(txHash, cb4);
},
], (err) => {
cb(err, txHash);
});
}, _cb);
}
function sendContractConstTx(web3, contract, method, opts, _cb) {
return asyncfunc((cb) => {
if (!contract) {
cb(new Error("Contract not defined"));
return;
}
if (!method) {
// TODO send raw transaction to the contract.
cb(new Error("Method not defined"));
return;
}
let errRes;
const methodAbi = contract.abi.find(({ name, inputs }) => {
if (name !== method) return false;
const paramNames = inputs.map((param) => {
if (param.name[ 0 ] === "_") {
return param.name.substring(1);
}
return param.name;
});
for (let i = 0; i < paramNames.length; i += 1) {
if (typeof opts[ paramNames[ i ] ] === "undefined") {
errRes = new Error("Param " + paramNames[ i ] + " not found.");
return false;
}
}
return true;
});
if (errRes) {
cb(errRes);
return;
}
if (!methodAbi) {
cb(new Error("Invalid method"));
return;
}
const paramNames = methodAbi.inputs.map(({ name }) => {
if (name[ 0 ] === "_") {
return name.substring(1);
}
return name;
});
const params = paramNames.map(name => opts[ name ]);
params.push(cb);
contract[ method ].apply(null, params);
}, _cb);
}
function sendAction(web3, action, contract, method, opts, _cb) {
return asyncfunc((cb) => {
if (action.type === "ACCOUNT") {
const data = getData(contract, method, opts);
} else if (action.type === "MULTISIG_START") {
} else if (action.type === "MULTISIG_CONFIRM") {
} else if (action.type === "MULTISIG_REVOKE") {
}
}, cb);
}
function args2opts(_args, inputs) {
const norm = (S) => {
return S[0] == '_' ? S.substring(1) : S;
}
const isObject = (o) => {
return ((typeof o == "object") && (Object.keys(o).sort().join('') != 'ces') && !(o instanceof Array));
}
let args = _args.slice();
let opts = {};
if (typeof args[args.length -1 ] === "function") {
opts.$cb = args.pop();
}
for (let i=0; args.length > 0 && !isObject(args[0]) && i<inputs.length; i++) {
opts[norm(inputs[i].name)] = args.shift();
}
while (args.length > 0) {
const o = args.shift();
if (!isObject(o)) return null;
let isOld = false;
if (o.from) {
opts.$from = o.from;
isOld = true;
}
if (o.to) {
opts.$to = o.to;
isOld = true;
}
if (o.gasPrice) {
opts.$gasPrice = o.gasPrice;
isOld = true;
}
if (o.gas) {
opts.$gas = o.gas;
isOld = true;
}
if (o.value) {
opts.$value = o.value;
isOld = true;
}
if (o.nonce) {
opts.$nonce = o.nonce;
isOld = true;
}
if (!isOld) {
opts = Object.assign(opts, o);
}
}
return opts;
}
function generateClass(abi, byteCode) {
let constructorInputs = [];
const C = function C(web3, address) {
this.$web3 = web3;
this.$address = address;
this.$contract = this.$web3.eth.contract(abi).at(address);
this.$abi = abi;
this.$byteCode = byteCode;
}
abi.forEach(({ constant, name, inputs, type }) => {
// TODO overloaded functions
if (type === "function") {
const c = function() {
const args = Array.prototype.slice.call(arguments);
const self = this;
var opts = args2opts(args, inputs);
return asyncfunc( (cb) => {
return sendContractConstTx(
self.$web3,
self.$contract,
name,
opts,
cb);
}, opts.$cb);
}
if (!constant) {
C.prototype[name] = function() {
const args = Array.prototype.slice.call(arguments);
const self = this;
var opts = args2opts(args, inputs);
return asyncfunc( (cb) => {
return sendContractTx(
self.$web3,
self.$contract,
name,
opts,
cb);
}, opts.$cb);
};
} else {
C.prototype[name] = c;
}
C.prototype[name].call = c;
} else if (type === "constructor") {
constructorInputs = inputs;
}
});
C.new = function (web3) {
const args = Array.prototype.slice.call(arguments, 1);
const self = this;
let nargs;
let cb;
var opts = args2opts(args, constructorInputs);
opts.$abi = abi;
opts.$byteCode = byteCode;
return asyncfunc( (cb) => {
return deploy(web3, opts, (err, _contract) => {
if (err) {
cb(err);
return;
}
const cls = new self(web3, _contract.address);
cb(null, cls);
});
}, opts.$cb);
};
return C;
}