-
Notifications
You must be signed in to change notification settings - Fork 67
/
Transactions.js
143 lines (126 loc) · 5.39 KB
/
Transactions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
const Tx = require('ethereumjs-tx');
const { txutils } = require('eth-lightwallet');
const Queue = require('better-queue');
const sleep = require('sleep-async')().Promise;
const BN = require('bn.js');
const Utilities = require('../../Utilities.js');
const { TransactionFailedError } = require('../../errors');
const logger = require('../../logger');
class Transactions {
/**
* Initialize Transaction object
* @param web3 Instance of the Web object
* @param wallet Blockchain wallet represented in hex string in 0x format
* @param walletKey Wallet's private in Hex string without 0x at beginning
* @param log Standard logger object.
*/
constructor(web3, wallet, walletKey, log = logger) {
this.web3 = web3;
this.privateKey = Buffer.from(walletKey, 'hex');
this.walletAddress = wallet;
this.logger = log;
this.queue = new Queue((async (args, cb) => {
const { transaction, future } = args;
let transactionHandled = false;
try {
for (let i = 0; i < 3; i += 1) {
try {
// eslint-disable-next-line no-await-in-loop
const result = await this._sendTransaction(transaction);
if (result.status === '0x0') {
future.reject(result);
transactionHandled = true;
break;
} else {
future.resolve(result);
transactionHandled = true;
break;
}
} catch (error) {
if (!error.toString().includes('nonce too low') && !error.toString().includes('underpriced') &&
// Ganache's version of nonce error.
error.name !== 'TXRejectedError' && !error.toString().includes('the tx doesn\'t have the correct nonce.')
) {
throw new Error(error);
}
this.logger.trace(`Nonce too low / underpriced detected. Retrying. ${error.toString()}`);
// eslint-disable-next-line no-await-in-loop
await sleep.sleep(2000);
}
}
} catch (e) {
future.reject(e);
cb();
return;
}
if (!transactionHandled) {
future.reject(new TransactionFailedError('Transaction failed', transaction));
}
cb();
}), { concurrent: 1 });
}
/**
* Send transaction to Ethereum blockchain
* @returns {PromiEvent<TransactionReceipt>}
* @param newTransaction
*/
async _sendTransaction(newTransaction) {
if (!Utilities.isHexStrict(newTransaction.options.gasPrice)) {
throw Error('Gas price has to be in hex format.');
}
if (!Utilities.isHexStrict(newTransaction.options.gasLimit)) {
throw Error('Gas limit has to be in hex format.');
}
await this.web3.eth.getTransactionCount(this.walletAddress).then((nonce) => {
newTransaction.options.nonce = nonce;
});
const rawTx = txutils.functionTx(
newTransaction.contractAbi,
newTransaction.method,
newTransaction.args,
newTransaction.options,
);
const transaction = new Tx(rawTx);
transaction.sign(this.privateKey);
const serializedTx = transaction.serialize().toString('hex');
const balance = await this.web3.eth.getBalance(this.walletAddress);
const currentBalance = new BN(balance, 10);
const requiredAmount =
new BN(300000)
.imul(new BN(Utilities.denormalizeHex(newTransaction.options.gasPrice), 16));
const totalPriceBN =
new BN(Utilities.denormalizeHex(newTransaction.options.gasPrice), 16)
.imul(new BN(Utilities.denormalizeHex(newTransaction.options.gasLimit), 16));
if (currentBalance.lt(totalPriceBN)) {
throw Error(`ETH balance lower (${currentBalance.toString()}) than transaction cost (${totalPriceBN.toString()}).`);
}
// If current balance not enough for 300000 gas notify low ETH balance.
if (currentBalance.lt(requiredAmount)) {
this.logger.warn(`ETH balance running low! Your balance: ${currentBalance.toString()} wei, while minimum required is: ${requiredAmount.toString()} wei`);
}
this.logger.trace(`Sending transaction to blockchain, nonce ${newTransaction.options.nonce}, balance is ${currentBalance.toString()}`);
return this.web3.eth.sendSignedTransaction(`0x${serializedTx}`);
}
/**
* Adding new transaction in transaction queue
* @param contractAbi
* @param method
* @param args
* @param options
* @returns {Promise<any>}
*/
queueTransaction(contractAbi, method, args, options) {
return new Promise((async (resolve, reject) => {
const transaction = {
contractAbi, method, args, options,
};
this.queue.push({
transaction,
future: {
resolve, reject,
},
});
}));
}
}
module.exports = Transactions;