etoken-lib
Version:
Ambisafe Etoken Lib
1,318 lines (1,237 loc) • 53.1 kB
JavaScript
window.onload = function() {
if (window.opts && window.opts.gethUrl) {
return;
}
const match = window.location.href.match(/\?(https?.+)$/);
if (match) {
setRpcUrl(match[1]);
}
};
EToken = EToken.default;
var $logs = $('#logs');
$logs.css('word-wrap', 'break-word');
var web3 = EToken.web3;
var eth;
var ethAsync;
var address;
var sender;
var chainId;
var SIMULATION_BLOCK = window.opts && window.opts.simulationBlock || 'latest';
var nowSeconds = function(){return (Date.now() / 1000);};
var gasPrice = web3.toBigNumber(web3.toWei(20, 'gwei'));
var cb = function(err,ok){if (err) {console.log(err);} else {console.log(ok);}};
var cbval = function(err,ok){if (err) {console.log(err);} else {console.log(ok.valueOf());}};
var setPrivateKey = function(pk) {
if (pk === undefined) {
EToken.setPrivateKey(prompt('Enter your private key in here:'));
} else {
EToken.setPrivateKey(pk);
}
address = EToken.privateToAddress(('0x' + pk).slice(-64));
sender = address;
log('Your address(global variable `address` or `sender`) to send transactions: ' + address, $logs);
};
const setRpcUrl = async function(url, logger, doNotSend = false) {
if (url === undefined) {
EToken.setRpcUrl(prompt('Enter your Ethereum node RPC URL in here:'));
} else {
EToken.setRpcUrl(url, logger, doNotSend);
}
// Stop blocks polling.
EToken.web3.currentProvider.stop();
EToken.web3.currentProvider._ready.go();
eth = web3.eth;
ethAsync = Promise.promisifyAll(eth);
chainId = parseInt(await Promise.promisify(web3.version.getNetwork)());
}
var setGasPriceInGWei = function(gasPriceInGWei) {
gasPrice = web3.toBigNumber(gasPriceInGWei).mul(1000000000);
getGasPrice();
}
var getGasPrice = function() {
log('GasPrice is ' + gasPrice.div(1000000000).toFixed() + ' gwei.', $logs);
return gasPrice;
}
var help = function() {
log('Here is the list of functions you can use:', $logs);
log('setRpcUrl(rpcUrl);', $logs);
log('setPrivateKey(privateKey);', $logs);
log('getBalance([address]);', $logs);
log('getGasPrice();', $logs);
log('setGasPriceInGWei(gasPriceInGWei);', $logs);
log('safeTransaction(contract.method, paramsArray, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
log('safeSend(toAddress, valueInWei, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
log('safeSendAll(toAddress, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
log('safeTopup(toAddress, targetValueInWei, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
log('fastTopup(toAddress, targetValueInWei, sender, nonce[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
log('fastTopups(fastTopupFunctionArray, startingNonce[, testRun]);', $logs);
log('safeTransactions(safeFunctionsArray[, testRun[, fastRun]]);', $logs);
log('call(contract, {name: methodName, params: methodCallParamsArray, alias: toStore}Array, savingObjReference);', $logs);
log('assertCallFunction(contract, propertyNameOrObject, expectedValue, testRun); To be used as part of safeTransactions().', $logs);
log('asyncFunction(function(testRun) {}, testRun); To be used as part of safeTransactions().', $logs);
log('syncFunction(function(testRun) {}); To be used as part of safeTransactions().', $logs);
log('deployContractComplex(constructorArgs, byteCodeString, abiArray, sender[, globalNameToAssign, callback(contract), gas, nonce]);', $logs);
log('smartDeployContract({constructorArgs, bytecode, abi, sender, name, gas, nonce, waitReceipt, fastRun, deployedAddress});', $logs);
log('callNode(method, params = []);', $logs);
log('getPendingTransactions(address, tries = 40);', $logs);
log('speedUp(transactionHashes, gasPriceInGWei);', $logs);
log('getFutureTransactions(address, tries = 40);', $logs);
log('getAllFutureTransactionsByAddress(address, lastMinedNonce, lastSentNonce);', $logs);
log('getAllFutureTransactionsByAddressProbabalistic(address, tries = 4);', $logs);
log('rewrite(rawTransactions, gasPriceInGWei);', $logs);
log('readAndRewrite(transactionHashes, gasPriceInGWei);', $logs);
log('propagate(transactionHashes[, skipNotFound = true, concurrency = 50]);', $logs);
log('getTransactions(transactionHashes[, skipNotFound = false, concurrency = 50]);', $logs);
log('propagateRaws(rawTransactions[, concurrency = 50]);', $logs);
log('sumTxCost(transactionHashes);', $logs);
log('makeRequest(method, url);', $logs);
log('getEthplorerInfo(address, apiKey = freekey);', $logs);
log('getEthplorerInfos(addresses, concurrency = 1, apiKey = freekey);', $logs);
log('', $logs);
log('Some additional help available if you call the function without parameters.', $logs);
}
var _log = function(message, logger) {
logger.prepend(message);
}
var log = function(message, logger) {
console.log(message);
if (logger) {
_log('<p>' + message + '</p>', logger);
}
};
var logError = function(error, logger, dontThrow) {
if (logger) {
_log(`<p class="error">${error.message || error} ${error.data || ''}</p>`, logger);
}
if (dontThrow) {
console.error(error.message || error, error.data);
return;
}
throw error;
};
var logWarning = function(message, logger) {
if (logger) {
_log('<p class="warning">' + message + '</p>', logger);
}
console.log('Warning: ' + message);
};
var logSuccess = function(gas, result, params, logger) {
if (logger) {
var txHash = result.length === 66 ? '<a href="http://etherscan.io/tx/' + result + '" target="_blank">' + result + '</a>' : result;
_log('<p class="success">Success! Gas used: ' + gas + ' result: ' + txHash + (params ? ' params: ' + params : '') + '</p>', logger);
}
console.log('Success! Gas used: ' + gas + ' result: ' + result + (params ? ' params: ' + params : ''));
};
var logWaiting = function(logger) {
if (logger) {
$(logger.children()[0]).append('.');
}
};
var logFinish = function(logger) {
if (logger) {
_log('<hr/>', logger);
}
console.log('--------------------------------------------------------------------------------');
}
var flowControl = function() {
var STATES = { ready: 'ready', waiting: 'waiting', stopping: 'stopping' };
var state = STATES.ready;
return {
get ready () {
return state === STATES.ready;
},
get waiting () {
return state === STATES.waiting;
},
get stopping () {
return state === STATES.stopping;
},
get state () {
return state;
},
stop: function() {
if (state === STATES.waiting) {
state = STATES.stopping;
}
return state;
},
continue: function() {
if (state === STATES.waiting || state === STATES.stopping) {
state = STATES.ready;
}
return state;
},
__wait__: function() {
state = STATES.waiting;
return state;
}
};
}();
var safeTransaction = function(fun, params, sender, argsObject) {
if (arguments.length === 0) {
log('safeTransaction(contract.method, paramsArray, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
return;
}
return safeTransactionFunction(fun, params, sender, argsObject)().catch(function(err) {
logError(err, $logs, true);
}).tap(function() {
logFinish($logs);
});
};
var safeSend = function(to, value, sender, argsObject) {
if (arguments.length === 0) {
log('safeSend(toAddress, valueInWei, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
return;
}
return safeSendFunction(to, value, sender, argsObject)().tap(function() {
logFinish($logs);
});
};
var safeSendAll = function(to, sender, argsObject) {
if (arguments.length === 0) {
log('safeSendAll(toAddress, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
return;
}
return ethAsync.getBalanceAsync(sender, SIMULATION_BLOCK)
.then(balance => {
argsObject = argsObject || {};
argsObject.gas = web3.toBigNumber(argsObject && argsObject.gas || 21000);
argsObject.gasPrice = web3.toBigNumber(argsObject && argsObject.gasPrice || gasPrice);
var value = balance.sub(argsObject.gasPrice.mul(argsObject.gas));
return safeSend(to, value, sender, argsObject);
});
};
var safeTopup = function(to, targetValue, sender, argsObject) {
if (arguments.length === 0) {
log('safeTopup(toAddress, targetValueInWei, sender[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
return;
}
return safeTopupFunction(to, targetValue, sender, argsObject)().tap(function() {
log('Topup to ' + to + ' finished.', $logs);
logFinish($logs);
});
};
var safeTopupFunction = function(to, targetValue, sender, argsObject) {
if (arguments.length === 0) {
log('See safeTopup(). To be used as part of safeTransactions().', $logs);
return;
}
return asyncFunction(function(resolve, reject, testRun) {
ethAsync.getBalanceAsync(to, SIMULATION_BLOCK)
.then(balance => {
var value = web3.toBigNumber(targetValue).sub(balance);
if (value.lte(0)) {
log("Skipping: balance of " + to + " is " + web3.fromWei(balance, 'ether') + " ETH and it is more or equal to target value of " + web3.fromWei(targetValue, 'ether') + " ETH.", $logs);
return 0;
}
return safeSendFunction(to, value, sender, argsObject)(testRun);
})
.then(resolve)
.catch(reject);
});
};
var fastTopup = function(to, targetValue, sender, nonce, argsObject) {
if (arguments.length === 0) {
log('fastTopup(toAddress, targetValueInWei, sender, nonce[, {testRun: true, ignoreCallResponse: true, waitReceipt: true, transactionObjParams}]);', $logs);
return;
}
return fastTopupFunction(to, targetValue, sender, argsObject)(nonce).tap(function(nonce) {
log('Topup to ' + to + ' finished. Next nonce: ' + nonce, $logs);
});
};
var fastTopupFunction = function(to, targetValue, sender, argsObject) {
if (arguments.length === 0) {
log('See fastTopup(). To be used as part of fastTopups(), nonce will be passed automatically.', $logs);
return;
}
return function(nonce, _testRun) {
return asyncFunction(function(resolve, reject, testRun) {
ethAsync.getBalanceAsync(to, SIMULATION_BLOCK)
.then(balance => {
var value = web3.toBigNumber(targetValue).sub(balance);
if (value.lte(0)) {
log("Skipping: balance of " + to + " is " + web3.fromWei(balance, 'ether') + " ETH and it is more or equal to target value of " + web3.fromWei(targetValue, 'ether') + " ETH.", $logs);
return nonce;
}
if (argsObject) {
argsObject.nonce = nonce;
} else {
argsObject = {nonce: nonce};
}
safeSendFunction(to, value, sender, argsObject)(testRun).catch(error => {
console.error(to, value, error);
});
return nonce+1;
});
})(_testRun);
};
};
var fastTopups = function(txFunctions, nonce, testRun) {
if (arguments.length === 0) {
log('fastTopups(fastTopupFunctionArray, startingNonce[, testRun]);', $logs);
return;
}
if (txFunctions.length === 0) {
setTimeout(function() {
log('Done! Next nonce: ' + nonce + '.', $logs);
logFinish($logs);
}, 2000);
return true;
}
return txFunctions.shift()(nonce, testRun).then(nextNonce => fastTopups(txFunctions, nextNonce, testRun));
};
var safeSendFunction = function(to, value, sender, argsObject) {
if (arguments.length === 0) {
log('See safeSend(). To be used as part of safeTransactions().', $logs);
return;
}
argsObject = argsObject || {};
argsObject.value = argsObject.value || value;
argsObject.to = argsObject.to || to;
return safeTransactionFunction(eth.sendTransaction, [], sender, argsObject);
};
var safeTransactionFunction = function(fun, params, sender, argsObject) {
if (arguments.length === 0) {
log('See safeTransaction(). To be used as part of safeTransactions().', $logs);
return;
}
var merge = function(base, args) {
var target = ['nonce', 'value', 'gasPrice', 'to', 'data', 'chainId'];
if (args) {
while(target.length > 0) {
var arg = target.pop();
if (args[arg]) {
base[arg] = args[arg];
}
}
}
return base;
};
var processFunctionParams = function(paramsToProcess) {
for (var i = 0; i < paramsToProcess.length; i++) {
if (typeof paramsToProcess[i] === 'function') {
paramsToProcess[i] = paramsToProcess[i]();
}
}
};
var waitReceiptTimeoutSeconds = 120;
var gas = argsObject && argsObject.gas || 500000;
return function(testRun, fastRun) {
processFunctionParams(params);
return new Promise(function(resolve, reject) {
var _params = params.slice(0);
_params.push(merge({from: sender, gas: Math.max(3000000, gas), gasPrice: gasPrice}, argsObject));
// _params.push(SIMULATION_BLOCK); // https://github.com/ethereum/go-ethereum/issues/2586
_params.push(function(err, result) {
if (err) {
if (err.toString().toLowerCase().includes('execution error')) {
if (fastRun) {
resolve(gas);
return;
}
}
if (err.toString().startsWith('Error: no contract code at given address')) {
gas = argsObject && argsObject.gas || 21000;
resolve(gas);
return;
}
reject(err);
} else {
resolve(result);
}
});
if (typeof fun.call === "string") {
eth.estimateGas.apply(this, _params);
} else {
fun.estimateGas.apply(this, _params);
}
}).then(function(estimateGas) {
return new Promise(function(resolve, reject) {
var _params = params.slice(0);
if (estimateGas > gas) {
reject(new Error('Estimate gas is too big: ' + estimateGas));
} else if (typeof fun.call === "string" || fastRun || (argsObject && argsObject.ignoreCallResponse)) {
// simple eth.sendTransaction
resolve(estimateGas);
} else {
var repeater = function(tries, funcToCall, funcToCallArgs) {
var _repeat = function() {
if (tries-- === 0) {
return false;
}
logWaiting($logs);
setTimeout(() => funcToCall.apply(null, funcToCallArgs), 500);
return true;
};
return _repeat;
};
var retries = (testRun || (argsObject && argsObject.testRun)) ? 1 : 40;
var repeat = repeater(retries, fun.call, _params);
_params.push(merge({from: sender, gas: gas, gasPrice: gasPrice, chainId}, argsObject));
_params.push(SIMULATION_BLOCK);
_params.push(function(err, result) {
if (err) {
reject(err);
} else {
var success = typeof result.toNumber === 'function' ? result.toNumber() > 0 : result;
if (success) {
resolve(estimateGas);
} else {
if (!repeat()) {
reject(new Error('Call with gas: ' + gas + ' returned ' + result.toString() + ' ' + retries + ' times in a row.'));
}
}
}
});
repeat();
}
});
}).then(function(estimateGas) {
return new Promise(function(resolve, reject) {
var _params = params.slice(0);
_params.push(merge({from: sender, gas: gas, gasPrice: gasPrice, chainId}, argsObject));
_params.push(function(err, result) {
if (err) {
reject(err);
} else {
resolve([result, estimateGas]);
}
});
if (testRun || (argsObject && argsObject.testRun)) {
resolve(['OK', estimateGas]);
return;
}
fun.apply(this, _params);
});
}).then(function(result) {
var value = (argsObject && argsObject.value) ? " value: " + web3.fromWei(argsObject.value.valueOf(), 'ether') + " ETH." : "";
var to = (argsObject && argsObject.to) ? " to: " + argsObject.to : "";
var nonce = (argsObject && argsObject.nonce !== undefined) ? " nonce: " + argsObject.nonce : "";
logSuccess(result[1], result[0], params.join(', ') + to + value + nonce, $logs);
if (testRun || (argsObject && argsObject.testRun)) {
return [false, result[1]];
}
return new Promise(function(resolve, reject) {
if (argsObject && argsObject.waitReceipt) {
log('Waiting receipt for ' + result[0], $logs);
flowControl.__wait__();
var startTime = nowSeconds();
var timeoutTime = startTime + waitReceiptTimeoutSeconds;
var waitReceipt = function(txHash) {
web3.eth.getTransactionReceipt(txHash, function(err, receipt) {
var secondsPassed = Math.round(nowSeconds() - startTime);
if ((receipt && receipt.blockNumber) || flowControl.ready) {
flowControl.continue();
resolve([secondsPassed, result[1]]);
} else {
var message = 'No transaction receipt after ' + secondsPassed + ' seconds.';
if (flowControl.stopping) {
flowControl.continue();
reject(message);
return;
}
if (nowSeconds() > timeoutTime) {
logWarning(message + " If you are sure that transaction is already mined do: flowControl.continue(); If you want to stop execution do: flowControl.stop();", $logs);
timeoutTime += 60;
}
logWaiting($logs);
setTimeout(function() { waitReceipt(txHash); }, 1000);
}
});
};
return waitReceipt(result[0]);
}
return resolve([false, result[1]]);
});
}).then(function(results) {
if (results[0]) {
log('Mined in ' + results[0] + ' seconds.', $logs);
}
return [results[1], argsObject && argsObject.value];
});
};
};
var safeTransactions = function(...args) {
var _safeTransactions = function(txFunctions, testRun, fastRun, cumulativeGasUsed, totalValueSpent) {
if (arguments.length === 0) {
log('safeTransactions(safeFunctionsArray[, testRun[, fastRun]]);', $logs);
return Promise.resolve();
}
cumulativeGasUsed = cumulativeGasUsed || 0;
totalValueSpent = totalValueSpent || 0;
if (txFunctions.length === 0) {
log('Done! Cumulative gas used: ' + cumulativeGasUsed + ', total value sent: ' + web3.fromWei(totalValueSpent, 'ether') + ' ETH.', $logs);
logFinish($logs);
return Promise.resolve();
}
return txFunctions.shift()(testRun, fastRun).then(function(gasUsedAndvalueSpent){
var gasUsed = gasUsedAndvalueSpent && gasUsedAndvalueSpent[0] || 0;
var valueSent = web3.toBigNumber(gasUsedAndvalueSpent && gasUsedAndvalueSpent[1] || 0);
return _safeTransactions(txFunctions, testRun, fastRun, cumulativeGasUsed + gasUsed, valueSent.add(totalValueSpent));
});
};
return _safeTransactions(...args)
.catch(function(err) {
logError(err, $logs, true);
logFinish($logs);
throw err;
});
};
var call = function(contract, properties, target) {
if (arguments.length === 0) {
log('call(contract, {name: methodName, params: methodCallParamsArray, alias: toStore}Array, savingObjReference);', $logs);
return;
}
return callFunction(contract, properties, target)().tap(function() {
logFinish($logs);
});
}
var callFunction = function(contract, properties, target) {
if (arguments.length === 0) {
log('See call(). To be used as part of safeTransactions().', $logs);
return;
}
var processResult = function(property, alias, done, reject) {
return function(error, result) {
if (error) {
reject(new Error("Error while calling property '" + property + "' with message: " + error));
} else {
target[alias] = result;
done();
}
};
};
return function() {
return new Promise(function(resolve, reject) {
var propertiesCount = properties.length;
var finished = function() {
if (--propertiesCount === 0) {
log("Properties collected!", $logs);
resolve();
}
};
if (propertiesCount === 0) {
reject(new Error('Properties array cannot be empty.'));
return;
}
properties.forEach(function(property) {
if (typeof property === 'object') {
var params = property.params || [];
var alias = property.alias || property.name;
var name = property.name;
} else {
var params = [];
var alias = property;
var name = property;
}
var callParams = params.slice(0);
params.push(SIMULATION_BLOCK);
params.push(processResult(name, alias, finished, reject));
if (contract.call === 'eth_getBalance') {
contract.apply(this, params);
} else {
contract[name].call.apply(this, params);
}
log("Calling for property: " + name + (callParams.length ? " params: " + callParams.join(', ') : ""), $logs);
});
});
};
};
var assertCallFunction = function(contract, propertyNameOrObject, expectedValue, testRun) {
if (arguments.length === 0) {
log('assertCallFunction(contract, propertyNameOrObject, expectedValue, testRun); To be used as part of safeTransactions().', $logs);
return;
}
return asyncFunction(function(resolve, reject, _testRun) {
var callObj = {};
var propertyName = propertyNameOrObject.alias || propertyNameOrObject.name || propertyNameOrObject;
callFunction(contract, [propertyNameOrObject], callObj)()
.then(function() {
if (callObj[propertyName].toString() !== expectedValue.toString()) {
if (_testRun) {
resolve();
}
reject(new Error(propertyName + ' is expected to be: "' + expectedValue + '" actual: "' + callObj[propertyName] + '"'));
return;
}
resolve();
}, testRun)
.catch(reject);
});
};
var asyncFunction = function(fun, testRun) {
if (arguments.length === 0) {
log('asyncFunction(function(testRun) {}, testRun); To be used as part of safeTransactions().', $logs);
return;
}
return function(_testRun) {
return new Promise(function(resolve, reject) {
fun(resolve, reject, _testRun || testRun);
});
};
};
var syncFunction = function(fun) {
if (arguments.length === 0) {
log('syncFunction(function(testRun) {}); To be used as part of safeTransactions().', $logs);
return;
}
return function(testRun) {
return new Promise(function(resolve, _) {
fun(testRun);
resolve();
});
};
};
var getBalance = function(sender) {
sender = sender || address;
return ethAsync.getBalanceAsync(sender, SIMULATION_BLOCK)
.tap(balance => {
log(sender + ' address balance is ' + web3.fromWei(balance, 'ether').toString() + ' ETH', $logs);
});
};
var getTransaction = function(txHash, tries = 40) {
if (tries === 0) {
return Promise.reject(new Error(`Transaction ${txHash} not found.`));
}
return ethAsync.getTransactionAsync(txHash)
.then(tx => {
if (tx) {
return tx;
}
return delay(500)
.then(() => getTransaction(txHash, tries - 1));
})
};
var delay = function(msec) {
return Promise.delay(msec);
};
var getBlock = function(block) {
return ethAsync.getBlockAsync(block || 'latest');
};
// Should not be used.
var waitTransactionEvaluation = function(txHash) {
return Promise.resolve(true);
};
var deployContractAsync = function(...args) {
return deployContractComplexAsync([], ...args);
};
var deployContractComplexAsync = function(constructorArgs, bytecode, abi, sender, name, gas, nonce) {
return deployContractComplex(constructorArgs, bytecode, abi, sender, name, resolve, gas, nonce);
};
var deployContract = function(...args) {
if (args.length === 0) {
log('deployContract(byteCodeString, abiArray, sender[, globalNameToAssign, callback(contract), gas, nonce]);', $logs);
return;
}
return deployContractComplex([], ...args);
};
var deployContractComplex = function(constructorArgs, bytecode, abi, sender, name, callback, gas, nonce) {
if (arguments.length === 0) {
log('deployContractComplex(constructorArgs, byteCodeString, abiArray, sender[, globalNameToAssign, callback(contract), gas, nonce]);', $logs);
return;
}
callback = typeof name === 'function' ? name : callback;
name = typeof name !== 'function' ? name : false;
return smartDeployContract({
constructorArgs,
bytecode,
abi,
sender,
name,
gas,
nonce,
waitReceipt: true,
}).then(contract => {
if (callback) {
callback(contract);
}
});
};
function waitForReceipt(txHash) {
return retry(
async () => {
const result = await eth.getTransactionReceiptAsync(txHash);
if (not(result) || not(result.blockNumber)) {
throw new Error('Not mined yet');
}
return result;
},
4000
);
}
var smartDeployContract = function(args) {
if (arguments.length === 0) {
log('smartDeployContract({constructorArgs, bytecode, abi, sender, name, gas, nonce, waitReceipt, fastRun, deployedAddress});', $logs);
return;
}
const constructorArgs = args.constructorArgs || [];
const bytecode = args.bytecode;
const abi = args.abi || [];
const sender = args.sender;
const name = args.name;
const gas = args.gas;
const nonce = args.nonce;
const waitReceipt = args.waitReceipt;
const fastRun = args.fastRun;
const deployedAddress = args.deployedAddress;
const params = {
from: sender,
data: bytecode[1] === 'x' ? bytecode : '0x' + bytecode,
gas: gas || 3900000, // leave some space for other transactions
gasPrice: gasPrice,
chainId,
};
if (nonce !== undefined) {
params.nonce = nonce;
}
let processed = false;
if (deployedAddress) {
const contract = eth.contract(abi).at(deployedAddress);
if (name) {
window[name] = contract;
log(`Deployed contract is accessible by '${name}' global variable.`, $logs);
}
return Promise.resolve(contract);
}
return new Promise((resolve, reject) => {
eth.contract(abi).new(
...constructorArgs,
params,
(e, contract) => {
if (e) {
return reject(e);
}
if (waitReceipt) {
if (typeof contract.address != 'undefined') {
log(`Contract mined! address: ${contract.address} transactionHash: ${contract.transactionHash}`, $logs);
return resolve(eth.contract(abi).at(contract.address));
} else {
log(`Contract deployment transaction: ${contract.transactionHash}. Waiting for receipt.`, $logs);
}
} else {
if (processed) {
return;
}
log(`Contract deployment transaction: ${contract.transactionHash}.`, $logs);
processed = true;
getTransaction(contract.transactionHash)
.then(tx => {
const result = eth.contract(abi).at(tx.creates);
return result;
}).then(resolve).catch(reject);
}
}
);
}).then(contract => {
if (name) {
window[name] = contract;
log(`Deployed contract is accessible by '${name}' global variable. Contract address: ${contract.address}`, $logs);
}
return contract;
}).catch(function(err) {
logError(err, $logs, false);
});
};
function callNode(method, params = []) {
if (arguments.length === 0) {
log('callNode(method, params = []);', $logs);
return;
}
return new Promise((resolve, reject) => {
web3.currentProvider.sendAsync({
jsonrpc: '2.0',
method: method,
id: new Date().getTime(),
params: params,
}, (err, result) => {
if (err) {
return reject(err);
}
return resolve(result.result);
});
});
}
function getPendingTransactions(addr, tries = 40) {
if (arguments.length === 0) {
log('getPendingTransactions(address, tries = 40);', $logs);
return;
}
return callNode('parity_pendingTransactions')
.then(pendings => pendings.filter(txDetails => txDetails.from == addr.toLowerCase()))
.then(filtered => filtered.length > 0 ? filtered : Promise.delay(500).then(() => getPendingTransactions(addr, tries - 1)));
}
function speedUp(transactions, gasPriceInGWei) {
if (arguments.length === 0) {
log('speedUp(transactionHashes, gasPriceInGWei);', $logs);
log('Resend all the specified transactions with specified (usually increased) gas price.', $logs);
log('Attention: reorders address\'s transactions by pushing specified ones to the beginning of the queue.', $logs);
log('Consider using readAndRewrite() instead.', $logs);
return;
}
const gasPriceLocal = web3.toWei(gasPriceInGWei, 'gwei');
const ethAsync = Promise.promisifyAll(eth);
let queue;
let nonce;
setPrivateKey(prompt('Please enter your private key'));
return Promise
.map(transactions, txHash => getTransaction(txHash))
.then(transactionsDetails => queue = transactionsDetails)
.then(() => console.log('Processing', JSON.stringify(queue)))
.then(() => ethAsync.getTransactionCountAsync(address, 'latest'))
.then(startingNonce => nonce = startingNonce)
.then(() => Promise.reduce(queue, (nextNonce, txDetails) => {
return ethAsync.sendTransactionAsync({
to: txDetails.to,
from: txDetails.from,
gas: txDetails.gas,
gasPrice: gasPriceLocal,
data: txDetails.input,
nonce: nextNonce,
value: 0,
chainId,
})
.then(txHash => {
console.log(txDetails.hash, 'resent with', txHash, 'and nonce', nextNonce);
return nextNonce + 1;
})
.catch(err => {
console.log(txDetails.hash, 'failed to be sent');
throw err;
});
}, nonce))
.then(() => console.log('Done!') || true);
}
function getFutureTransactions(addr, tries = 40) {
if (arguments.length === 0) {
log('getFutureTransactions(address, tries = 40);', $logs);
log('Retries till find atleast 1 tx.', $logs);
return;
}
return callNode('parity_futureTransactions')
.then(pendings => pendings.filter(txDetails => txDetails.from == addr.toLowerCase()))
.then(filtered => filtered.length > 0 ? filtered : Promise.delay(500).then(() => getFutureTransactions(addr, tries - 1)));
}
function getAllFutureTransactionsByAddress(addr, lastMinedNonce, lastSentNonce) {
if (arguments.length === 0) {
log('getAllFutureTransactionsByAddress(address, lastMinedNonce, lastSentNonce);', $logs);
log('Retries till find atleast lastSentNonce-lastMinedNonce transactions.', $logs);
return;
}
var uniques = function(list, idFunc) {
var uni = {};
return list.filter(el => uni[idFunc(el)] ? false : uni[idFunc(el)] = true);
};
var getFuture = function(accum = []) {
return getFutureTransactions(addr, 4)
.then(txs => uniques(accum.concat(txs), el => el.nonce))
.then(total => (total.length <= lastSentNonce - lastMinedNonce) ? console.log('Try more', total.length) || getFuture(total) : total);
};
return getFuture()
.then(total => total.sort((a, b) => web3.toDecimal(a.nonce) - web3.toDecimal(b.nonce)));
}
function getAllFutureTransactionsByAddressProbabalistic(addr, tries = 4) {
if (arguments.length === 0) {
log('getAllFutureTransactionsByAddressProbabalistic(address, tries = 4);', $logs);
log('Calls getFutureTransactions(address, 4) for tries number of times and returns all the unique results.', $logs);
return;
}
var trial = 0;
var uniques = function(list, idFunc) {
var uni = {};
return list.filter(el => uni[idFunc(el)] ? false : uni[idFunc(el)] = true);
};
var getFuture = function(accum = []) {
return getFutureTransactions(addr, 4)
.then(txs => uniques(accum.concat(txs), el => el.nonce))
.then(total => (trial++ <= tries) ? getFuture(total) : total);
};
return getFuture()
.then(total => total.sort((a, b) => web3.toDecimal(a.nonce) - web3.toDecimal(b.nonce)))
.tap(console.log);
}
function rewrite(transactions, gasPriceInGWei) {
if (arguments.length === 0) {
log('rewrite(rawTransactions, gasPriceInGWei);', $logs);
log('Resend all the raw transactions changing only the gas price.', $logs);
log('No side effects.', $logs);
return;
}
const gasPriceLocal = web3.toWei(gasPriceInGWei, 'gwei');
const result = [];
setPrivateKey(prompt('Please enter your private key'));
return Promise.each(transactions, txDetails => {
return ethAsync.sendTransactionAsync({
to: txDetails.to,
from: txDetails.from,
gas: txDetails.gas,
gasPrice: gasPriceLocal,
data: txDetails.input,
nonce: txDetails.nonce,
value: txDetails.value,
chainId,
})
.then(txHash => {
console.log(txDetails.hash, 'resent with', txHash);
result.push([txDetails.hash, txHash])
return true;
})
.catch(err => {
if (err.message.includes('nonce') || err.message.includes('imported')) {
return true;
}
console.log(txDetails.hash, 'failed to be sent');
// throw err;
});
})
.then(() => console.log(result) || result);
}
function readAndRewrite(transactionHashes, gasPriceInGWei) {
if (arguments.length === 0) {
log('readAndRewrite(transactionHashes, gasPriceInGWei);', $logs);
log('Gets transactions by hashes and resend them changing only the gas price.', $logs);
log('No side effects.', $logs);
return;
}
const gasPriceLocal = web3.toWei(gasPriceInGWei, 'gwei');
const result = [];
setPrivateKey(prompt('Please enter your private key'));
return Promise.map(transactionHashes, txHash => getTransaction(txHash, 10), {concurrency: 20})
.each(txDetails => {
return ethAsync.sendTransactionAsync({
to: txDetails.to,
from: txDetails.from,
gas: txDetails.gas,
gasPrice: gasPriceLocal,
data: txDetails.input,
nonce: txDetails.nonce,
value: txDetails.value,
chainId,
})
.then(txHash => {
console.log(txDetails.hash, 'resent with', txHash);
result.push([txDetails.hash, txHash])
return true;
})
.catch(err => {
if (err.message.includes('nonce') || err.message.includes('imported')) {
return true;
}
console.log(txDetails.hash, 'failed to be sent', err.message || err);
// throw err;
});
})
.then(() => console.log(result) || result);
}
function propagate(transactionHashes, skipNotFound = true, concurrency = 50) {
if (arguments.length === 0) {
log('propagate(transactionHashes[, skipNotFound = true, concurrency = 50]);', $logs);
log('Gets transactions by hashes and resends them for better propagation.', $logs);
log('Returns promise that resolves to a list of raw transactions.', $logs);
log('No side effects.', $logs);
return;
}
return getTransactions(transactionHashes, skipNotFound, concurrency)
.map(tx => tx.raw)
.then(raws => propagateRaws(raws, concurrency));
}
function getTransactions(transactionHashes, skipNotFound = false, concurrency = 50) {
if (arguments.length === 0) {
log('getTransactions(transactionHashes[, skipNotFound = false, concurrency = 50]);', $logs);
log('Gets transactions by hashes.', $logs);
log('Returns promise that resolves to a list of transaction objects.', $logs);
log('No side effects.', $logs);
return;
}
return Promise.map(transactionHashes, tx =>
getTransaction(tx, 5)
.catch(err => skipNotFound ? 'Skip' : Promise.reject(err))
)
.then(txs => txs.filter(el => el != 'Skip'))
.tap(console.log);
}
function propagateRaws(rawTransactions, concurrency = 50) {
if (arguments.length === 0) {
log('propagateRaws(rawTransactions[, concurrency = 50]);', $logs);
log('Resends raw transactions for better propagation.', $logs);
log('Returns promise that resolves to input rawTransactions.', $logs);
log('No side effects.', $logs);
return;
}
return Promise.map(rawTransactions, raw =>
ethAsync.sendRawTransactionAsync(raw)
.catch(err => err.message.includes('imported') ? true : console.log(err)),
{concurrency}
)
.then(() => rawTransactions)
.tap(() => console.log(rawTransactions.length, 'transactions propagated.'));
}
function sumTxCost(transactions) {
if (arguments.length === 0) {
log('sumTxCost(transactionHashes);', $logs);
log('Get total ETH spent for specified transactions. Returns BigNumber.', $logs);
return;
}
const retry = (fun, ...params) => {
return fun(...params).catch(err => {
console.log('Retrying');
return Promise.delay(100).then(() => retry(fun, ...params));
});
};
const ethAsync = Promise.promisifyAll(eth);
let sum = web3.toBigNumber(0);
return Promise.map(
transactions,
txHash => retry(ethAsync.getTransactionAsync, txHash)
.then(tx => retry(ethAsync.getTransactionReceiptAsync, txHash)
.then(receipt => sum = sum.add(web3.toBigNumber(tx.gasPrice).mul(receipt.gasUsed)))
),
{concurrency: 100}
)
.then(() => console.log(web3.fromWei(sum).toFixed() + ' ETH') || sum);
}
function makeRequest(method, url) {
if (arguments.length === 0) {
log('makeRequest(method, url);', $logs);
log('Promisified and simplified AJAX request.', $logs);
return;
}
return new Promise(function (resolve, reject) {
try {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.send();
} catch(e) {
reject({
status: 0,
statusText: e && e.message ? e.message : e
});
}
});
}
function retry(fun, delay, ...params) {
if (arguments.length === 0) {
log('retry(fun, delay, ...params);', $logs);
log('Retry promise returning function (fun) till it is resolved.', $logs);
return;
}
return fun(...params).catch(err => {
console.log('Retrying');
return Promise.delay(delay).then(() => retry(fun, delay, ...params));
});
};
function jQueryRequest(...params) {
if (arguments.length === 0) {
log('jQueryRequest(...params);', $logs);
log('Promisified version of $.ajax().', $logs);
return;
}
return new Promise((resolve, reject) => {
const query = $.ajax(...params);
query.then((...results) => resolve(results));
query.fail(fail => reject(fail));
});
}
function stringifyNumbers(json) {
if (arguments.length === 0) {
log('stringifyNumbers(json);', $logs);
log('Wrap all numbers in double quotes inside of the JSON to not lose precision.', $logs);
return;
}
return json.replace(/":([\d.e+-]+)/g, '":"$1"');
}
async function getEthplorerInfo(address, apiKey = 'freekey') {
if (arguments.length === 0) {
log('getEthplorerInfo(address, apiKey = freekey);', $logs);
log('Get info about all the address token balances and their value in USD.', $logs);
return;
}
const response = await retry(jQueryRequest, 5000, {url: `https://api.ethplorer.io/getAddressInfo/${address}?apiKey=${apiKey}`}).then(([_1, _2, res]) => res.responseText);
const info = JSON.parse(stringifyNumbers(response));
info.tokens = info.tokens || [];
for (let token of info.tokens) {
token.balanceHuman = web3.toBigNumber(token.balance).div(web3.toBigNumber(10).pow(token.tokenInfo.decimals));
token.value = web3.toBigNumber(0);
if (token.tokenInfo.price) {
token.value = token.balanceHuman.mul(token.tokenInfo.price.rate);
}
}
info.totalValue = info.tokens.reduce((prev, next) => prev.add(next.value.gt(0) && next.tokenInfo.price.currency == 'USD' ? next.value : 0), web3.toBigNumber(0));
return info;
}
async function getEthplorerInfos(addresses, concurrency = 1, apiKey = 'freekey') {
if (arguments.length === 0) {
log('getEthplorerInfos(addresses, concurrency = 1, apiKey = freekey);', $logs);
log('Get infos about all the addresses token balances and their values in USD. Only increase concurrency if using personal apiKey.', $logs);
return;
}
const results = await Promise.map(addresses, address => getEthplorerInfo(address, apiKey), {concurrency});
const totals = {};
for (let result of results) {
for (let token of result.tokens) {
const tokenAddress = token.tokenInfo.address;
log(`Address ${result.address} has ${token.balanceHuman.toFixed()} ${token.tokenInfo.name} which is ${token.value.toFormat(2)} USD`, $logs);
totals[tokenAddress] = totals[tokenAddress] || { sumTokens: web3.toBigNumber(0), sumValue: web3.toBigNumber(0), name: token.tokenInfo.name || '_unknown' };
totals[tokenAddress].sumTokens = totals[tokenAddress].sumTokens.add(token.balanceHuman);
totals[tokenAddress].sumValue = totals[tokenAddress].sumValue.add(token.value);
}
}
let totalValue = web3.toBigNumber(0);
for (let token in totals) {
log(`Total of ${totals[token].sumTokens.toFixed()} ${totals[token].name} which is ${totals[token].sumValue.toFormat(2)} USD`, $logs);
totalValue = totalValue.add(totals[token].sumValue);
}
log(`Total value is ${totalValue.toFormat(2)} USD`, $logs);
return totals;
}
function buildMultiTransferData(receiverAndAmount) {
logFinish($logs);
const unsafeMultiplexor = eth.contract([{"constant":false,"inputs":[{"name":"_address","type":"address[]"},{"name":"_amount","type":"uint256[]"}],"name":"multiTransfer","outputs":[{"name":"","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"}])
.at('0x4eC4142B862C798b3056F5cc32ab25803828C823');
if (!(receiverAndAmount instanceof Array)) {
logError(`Input is not an array.`, $logs, true);
return false;
}
const receivers = [];
const amounts = [];
let totalAmount = web3.toBigNumber(0);
receiverAndAmount.forEach(([receiver, amount]) => {
if (!web3.isAddress(receiver)) {
logError(`${receiver} is not a correct address.`, $logs, false);
}
const amountWei = web3.toBigNumber(web3.toWei(amount));
receivers.push(receiver);
amounts.push(amountWei);
totalAmount = totalAmount.add(amountWei);
log(`${receiver} ${web3.fromWei(amountWei).toFixed()} ETH`, $logs);
});
const data = unsafeMultiplexor.multiTransfer.getData(receivers, amounts);
log(`Additional data: ${data}`, $logs);
log(`Amount: ${web3.fromWei(totalAmount).toFixed()}`, $logs);
log(`To: ${unsafeMultiplexor.address}`, $logs);
logFinish($logs);
return true;
}
function pairsIntoBatches(pairsList, devidedListSize = 199, ignoreCheckSum = false) {
pairsList.forEach(([address, amount]) => {
if (!web3.isAddress(ignoreCheckSum ? address.toLowerCase() : address)) {
throw new Error(`Address ${address} is invalid or checksum is invalid.`);
}
web3.toBigNumber(amount); // Will fail if something is wrong with the amount.
});
let j = -1;
const allData = {};
allData.addresses = [];
allData.amounts = [];
const addressList = [];
const amountList = [];
for (let i = 0; i < pairsList.length; i++) {
addressList[i] = pairsList[i][0];
amountList[i] = pairsList[i][1];
}
for (let i = 0; i < pairsList.length; i++) {
if (i % devidedListSize == 0) {
allData.addresses.push([]);
allData.amounts.push([]);
j++;
}
allData.addresses[j].push(addressList[i]);
allData.amounts[j].push(amountList[i]);
}
return allData;
}
function preparePayoutListingRaws(nonce, payoutContract, listingPairs, baseUnit = 0, batchSize = 199, gas = 5500000) {
const multiplier = web3.toBigNumber(10).pow(baseUnit);
const preparedPairs = listingPairs.map(pair => [pair[0], multiplier.mul(pair[1]).floor().toFixed()]);
const listingData = pairsIntoBatches(preparedPairs, batchSize);
const raws = [];
const builder = EToken.buildRawTransaction(payoutContract, 'setUsersList');
for (let i = 0; i < listingData.addresses.length; i++) {
raws.push(builder(listingData.addresses[i], listingData.amounts[i], {from: address, gas: 5500000, nonce: nonce + i, gasPrice, value: 0}));
}
return raws;
}
function preparePayoutDistributionRaws(nonce, payoutContract, listingPairs) {
const listingData = pairsIntoBatches(listingPairs, 30);
const raws = [];
const builder = EToken.buildRawTransaction(payoutContract, 'distribute');
for (let i = 0; i < listingData.addresses.length; i++) {
raws.push(builder(listingData.addresses[i], {from: address, gas: 5000000, nonce: nonce + i, gasPrice, value: 0}));
}
return raws;
}
function stripHexPrefix(hex) {
return hex.startsWith('0x') ? hex.slice(2) : hex;
}
function addHexPrefix(hex) {
return '0x' + stripHexPrefix(hex);
}
function isHex(str) {
if (typeof str !== 'string') {
return false;
}
return !!str.match(/^0x[0-9a-f]*$/i);
}
function sha3(...args) {
let str = '';
for (const arg of args) {
const hex = (isHex(arg) ? arg : web3.toHex(arg)).substr(2);
str += hex.length % 2 !== 0 ? `0${hex}` : hex;
}
console.log(str);
return '0x' + web3.sha3(str, {encoding: 'hex'}).slice(-64);
}
function bytes32(stringOrNumber) {
const zeros = '000000000000000000000000000000000000000000000000000000000000000';
if (typeof stringOrNumber === 'string') {
return (web3.toHex(stringOrNumber) + zeros).substr(0, 66);
}
const hexNumber = stringOrNumber.toString(16);
return addHexPrefix((zeros + hexNumber).substring(hexNumber.length - 1));
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
return true;
}
function buildForwardOnBehalf(to, value, data, userContractAddress, nonce, msgSender, privateKey) {
assert(web3.isAddress(to), `to '${to}' is not an address.`);
const parsedTo = addHexPrefix(to);
assert(web3.isAddress(userContractAddress), `userContractAddress '${userContractAddress}' is not an address.`);
const parsedUserContractAddress = addHexPrefix(userContractAddress);
assert(web3.isAddress(msgSender), `msgSender '${msgSender}' is not an address.`);
const parsedMsgSender = addHexPrefix(msgSender);
const parsedValue = web3.toBigNumber(value);
assert(parsedValue.decimalPlaces() === 0, `value ${value} should be an integer.`);
const parsedData = addHexPrefix(data);
assert(isHex(parsedData), `data ${data} should be a hex.`);
const parsedNonce = web3.toBigNumber(nonce);
assert(parsedNonce.decimalPlaces() === 0, `nonce ${nonce} should be an integer.`);
const hash = sha3(parsedTo, bytes32(parsedValue), parsedData, parsedUserContractAddress, bytes32(parsedNonce), parsedMsgSender);
const parsedPrivateKey = privateKey || prompt(`Enter user contract's owner private key:`);
const {v, r, s} = EToken.sign(hash, parsedPrivateKey);
log(`forwardOnBehalf('${parsedTo}', '${value}', '${parsedData}', '${nonce}', ${v}, '${r}', '${s}');`, $logs);
const userContract = eth.contract([{"constant":false,"inputs":[{"name":"_destination","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"},{"name":"_nonce","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"forwardOnBehalf","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]).at('0x0');
return userContract.forwardOnBehalf.getData(parsedTo, value, parsedData, nonce, v, r, s);
}
function getCloneDeploymentData(prototypeAddress) {
return `0x602d600081600a8239f358368180378080368173${stripHexPrefix(prototypeAddress)}5af43d91908282803e602b57fd5bf3`;
}
async function transferTokenBalance(tokenAddress, to, params = undefined) {
const token = Promise.promisifyAll(eth.contract([{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"de