1 | "use strict";
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4 | return new (P || (P = Promise))(function (resolve, reject) {
|
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9 | });
|
10 | };
|
11 | Object.defineProperty(exports, "__esModule", { value: true });
|
12 | const Constant_1 = require("./Constant");
|
13 | const Setting_1 = require("./Setting");
|
14 | const Transaction_1 = require("./transaction/Transaction");
|
15 | const txHelper = require("./utils/TxHelper");
|
16 | const await_to_js_1 = require("await-to-js");
|
17 | const Bn_1 = require("./utils/Bn");
|
18 |
|
19 |
|
20 |
|
21 | class Transactions {
|
22 | |
23 |
|
24 |
|
25 |
|
26 | constructor(rpc) {
|
27 | this.setting = Setting_1.Setting.getInstance();
|
28 | this._privateKey = this.setting.privateKey;
|
29 | this._chainRpcProvider = this.setting.chainRpcProvider;
|
30 | if (rpc) {
|
31 | this.chainRpcProvider = rpc;
|
32 | }
|
33 | }
|
34 | |
35 |
|
36 |
|
37 | get chainRpcProvider() {
|
38 | return this._chainRpcProvider;
|
39 | }
|
40 | |
41 |
|
42 |
|
43 | set chainRpcProvider(rpc) {
|
44 | this._chainRpcProvider = rpc;
|
45 | }
|
46 | |
47 |
|
48 |
|
49 | get privateKey() {
|
50 | return this._privateKey;
|
51 | }
|
52 | |
53 |
|
54 |
|
55 | set privateKey(pk) {
|
56 | this._privateKey = pk;
|
57 | }
|
58 | |
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | pickUtxos(amount, asset, address, page) {
|
67 | return __awaiter(this, void 0, void 0, function* () {
|
68 | let inputs = [];
|
69 | let total = new Bn_1.Bn(0);
|
70 | let pageCount = 1000;
|
71 | let [err, res] = yield await_to_js_1.to(this.chainRpcProvider.getUtxoInPage({
|
72 | address: address,
|
73 | asset: asset,
|
74 | from: page,
|
75 | count: pageCount
|
76 | }));
|
77 | if (err) {
|
78 | throw err;
|
79 | }
|
80 | let { utxos, count } = res;
|
81 | for (let i = 0, len = utxos.length; i < len; i++) {
|
82 | let u = utxos[i];
|
83 | if (amount.eq(0)) {
|
84 | total.add(u.amount);
|
85 | inputs.push(u);
|
86 | break;
|
87 | }
|
88 | if (total.lt(amount)) {
|
89 | total.add(u.amount);
|
90 | inputs.push(u);
|
91 | }
|
92 | }
|
93 | if (total.lt(amount) && inputs.length < count) {
|
94 | let res = yield this.pickUtxos(amount.sub(total), asset, address, page + 1);
|
95 | inputs = inputs.concat(res);
|
96 | }
|
97 | return inputs;
|
98 | });
|
99 | }
|
100 | |
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | pickVoteUtxos(voteValue, asset, address) {
|
108 | return __awaiter(this, void 0, void 0, function* () {
|
109 | let inputs = [];
|
110 | let page = 0;
|
111 | let pageCount = 1000;
|
112 | let [err, res] = yield await_to_js_1.to(this.chainRpcProvider.getUtxoInPage({
|
113 | address: address,
|
114 | asset: asset,
|
115 | from: page,
|
116 | count: pageCount
|
117 | }));
|
118 | if (err) {
|
119 | throw err;
|
120 | }
|
121 | let { utxos, count } = res;
|
122 | let [err1, res1] = yield await_to_js_1.to(this.chainRpcProvider.getUtxoInPage({
|
123 | address: address,
|
124 | asset: asset,
|
125 | from: 0,
|
126 | count: count
|
127 | }));
|
128 | if (err1) {
|
129 | throw err1;
|
130 | }
|
131 | inputs = res1.utxos;
|
132 | return inputs;
|
133 | });
|
134 | }
|
135 | |
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 | pickFeeUtxos(amount, asset, address) {
|
142 | return __awaiter(this, void 0, void 0, function* () {
|
143 | let total = new Bn_1.Bn(0);
|
144 | let feeInputs = yield this.pickUtxos(amount, asset, address, 0);
|
145 |
|
146 | if (!feeInputs.length) {
|
147 | throw new Error("There is not enough UTXO to set as transaction fee.");
|
148 | }
|
149 | feeInputs.forEach(i => {
|
150 | total.add(i.amount);
|
151 | });
|
152 | if (total.lt(amount)) {
|
153 | throw new Error("There is not enough UTXO to set as transaction fee.");
|
154 | }
|
155 | return feeInputs;
|
156 | });
|
157 | }
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | estimateFee(gasLimit, fee) {
|
168 | return __awaiter(this, void 0, void 0, function* () {
|
169 | if (!fee || !fee.amount || (fee.amount instanceof Bn_1.Bn && fee.amount.eq(0))) {
|
170 | let temp = {
|
171 | amount: 0,
|
172 | asset: Constant_1.DefaultAsset
|
173 | };
|
174 | let blockInfo = [];
|
175 | let [err, bestBlock] = yield await_to_js_1.to(this.chainRpcProvider.getBestBlock());
|
176 | let blockHash = bestBlock.hash;
|
177 | if (!err && blockHash) {
|
178 | [err, blockInfo] = yield await_to_js_1.to(this.chainRpcProvider.getBlock({
|
179 | blockHash: blockHash,
|
180 | verbose: true,
|
181 | verboseTx: true
|
182 | }));
|
183 | }
|
184 | let gasPrice = txHelper.estimateGasPrice(blockInfo['rawtx']);
|
185 | temp.amount = parseInt((gasPrice * gasLimit).toFixed(0));
|
186 | if (fee && !fee.asset) {
|
187 | temp.asset = Constant_1.DefaultAsset;
|
188 | }
|
189 | return temp;
|
190 | }
|
191 | else {
|
192 | fee.amount = new Bn_1.Bn(fee.amount);
|
193 | return fee;
|
194 | }
|
195 | });
|
196 | }
|
197 | estimateGas(tx, utxos) {
|
198 | return __awaiter(this, void 0, void 0, function* () {
|
199 | let gasLimit = 0;
|
200 | let [err, res] = yield await_to_js_1.to(this.chainRpcProvider.runTransaction({
|
201 | hex: tx.toHex(),
|
202 | utxos: utxos
|
203 | }));
|
204 | if (err) {
|
205 | gasLimit = Constant_1.MinGasLimit;
|
206 | }
|
207 | else {
|
208 | gasLimit = Math.max(Constant_1.MinGasLimit, res.gasUsed);
|
209 | }
|
210 | return gasLimit;
|
211 | });
|
212 | }
|
213 | generateChangeOutputs(inputs, changeAddress, toAmount, toAsset, fee = { asset: '', amount: 0 }) {
|
214 | let changeMap = {};
|
215 | let outputs = [];
|
216 |
|
217 | inputs.forEach(i => {
|
218 | if (changeMap[i.assets]) {
|
219 | changeMap[i.assets].add(i.amount);
|
220 | }
|
221 | else {
|
222 | changeMap[i.assets] = new Bn_1.Bn(i.amount);
|
223 | }
|
224 |
|
225 | });
|
226 | if (!toAmount.eq(0) && changeMap[toAsset] && changeMap[toAsset].lt(0)) {
|
227 | changeMap[toAsset].sub(toAmount);
|
228 | }
|
229 | if (fee && fee.amount && changeMap[fee.asset].lt(0)) {
|
230 | if (changeMap[fee.asset].lt(fee.amount)) {
|
231 | throw new Error("Not enough balance to pay the transaction fee.");
|
232 | }
|
233 | changeMap[fee.asset].sub(fee.amount);
|
234 | }
|
235 |
|
236 | for (let k in changeMap) {
|
237 | let v = changeMap[k];
|
238 | outputs.push({
|
239 | assets: k,
|
240 | amount: v,
|
241 | address: changeAddress
|
242 | });
|
243 | }
|
244 | return outputs;
|
245 | }
|
246 | |
247 |
|
248 |
|
249 |
|
250 |
|
251 | generateRawTransaction(params) {
|
252 | return __awaiter(this, void 0, void 0, function* () {
|
253 | let { from, to, asset = Constant_1.DefaultAsset, fee, gasLimit, contractType, data } = params;
|
254 | let amount = new Bn_1.Bn(params.amount);
|
255 | if (!from) {
|
256 | throw new Error("From address is not specified");
|
257 | }
|
258 | if (!to) {
|
259 | throw new Error("To address is not specified");
|
260 | }
|
261 | let total = new Bn_1.Bn(0);
|
262 | let totalAmount = new Bn_1.Bn(0);
|
263 | totalAmount.add(amount);
|
264 | let changeMap = {};
|
265 | let outputs = [{
|
266 | data: data,
|
267 | assets: asset,
|
268 | amount: amount,
|
269 | address: to,
|
270 | contractType: contractType
|
271 | }];
|
272 | let inputs = yield this.pickUtxos(totalAmount, asset, from, 0);
|
273 | if (!inputs.length) {
|
274 | throw new Error("There is not enough UTXOs to set as transaction inputs.");
|
275 | }
|
276 | inputs.forEach(i => {
|
277 | total.add(i.amount);
|
278 | });
|
279 | if (total.lt(totalAmount)) {
|
280 | throw new Error("There is not enough UTXOs to set as transaction inputs.");
|
281 | }
|
282 | let _outputs = outputs.concat(this.generateChangeOutputs(inputs, from, amount, asset));
|
283 |
|
284 | let tx = new Transaction_1.Transaction({
|
285 | inputs: inputs,
|
286 | outputs: _outputs,
|
287 | gasLimit: 0
|
288 | });
|
289 | if (!gasLimit) {
|
290 | gasLimit = yield this.estimateGas(tx, inputs);
|
291 | }
|
292 | fee = yield this.estimateFee(gasLimit, fee);
|
293 | let originInputsAmount = inputs.length;
|
294 | let originOutputsAmount = outputs.length;
|
295 | if (fee.asset == asset) {
|
296 | totalAmount.add(fee.amount);
|
297 |
|
298 |
|
299 | }
|
300 | inputs = yield this.pickUtxos(totalAmount, asset, from, 0);
|
301 | let feeAmount = new Bn_1.Bn(fee.amount);
|
302 | if (fee.asset !== asset && !feeAmount.eq(0)) {
|
303 | let feeInputs = yield this.pickFeeUtxos(feeAmount, fee.asset, from);
|
304 | inputs = inputs.concat(feeInputs);
|
305 | }
|
306 | outputs = outputs.concat(this.generateChangeOutputs(inputs, from, amount, asset, fee));
|
307 | gasLimit += txHelper.estimateIncreasedGas(inputs.length - originInputsAmount, outputs.length - originOutputsAmount);
|
308 | tx = new Transaction_1.Transaction({
|
309 | inputs: inputs,
|
310 | outputs: outputs,
|
311 | gasLimit: gasLimit
|
312 | });
|
313 | return tx;
|
314 | });
|
315 | }
|
316 | |
317 |
|
318 |
|
319 |
|
320 |
|
321 | generateVoteTransaction(params) {
|
322 | return __awaiter(this, void 0, void 0, function* () {
|
323 | let { from, to, asset = Constant_1.DefaultAsset, fee, gasLimit, contractType, data, voteId } = params;
|
324 | let amount = new Bn_1.Bn(params.amount);
|
325 | let voteValue = new Bn_1.Bn(params.voteValue);
|
326 | let total = new Bn_1.Bn(0);
|
327 | let changeMap = {};
|
328 | let outputs = [{
|
329 | data: data,
|
330 | assets: asset,
|
331 | amount: new Bn_1.Bn(0),
|
332 | address: to,
|
333 | contractType: contractType
|
334 | }];
|
335 | if (!from) {
|
336 | throw new Error("From address is not specified");
|
337 | }
|
338 | if (!to) {
|
339 | throw new Error("To address is not specified");
|
340 | }
|
341 | let inputs = [];
|
342 | inputs = yield this.pickVoteUtxos(voteValue, asset, from);
|
343 | let totalVote = new Bn_1.Bn(0);
|
344 | let temp = [];
|
345 | inputs.forEach(i => {
|
346 | let noVote = false;
|
347 | if (i.locks) {
|
348 | i.locks.forEach(lock => {
|
349 | let { lockAddress, id } = txHelper.parseLockId(lock.id);
|
350 | if (lockAddress == to && id == voteId) {
|
351 | if (lock.amount < i.amount) {
|
352 | totalVote.add(i.amount);
|
353 | totalVote.sub(lock.amount);
|
354 | temp.push(i);
|
355 | }
|
356 | else {
|
357 | noVote = true;
|
358 | }
|
359 | }
|
360 | });
|
361 | }
|
362 | if (!noVote) {
|
363 | totalVote.add(i.amount);
|
364 | temp.push(i);
|
365 | }
|
366 | });
|
367 | inputs = temp;
|
368 | if (!inputs.length) {
|
369 | throw new Error("There are not enough UTXOs to vote.");
|
370 | }
|
371 |
|
372 | let _outputs = outputs.concat(this.generateChangeOutputs(inputs, from, new Bn_1.Bn(0), asset));
|
373 |
|
374 | let tx = new Transaction_1.Transaction({
|
375 | inputs: inputs,
|
376 | outputs: _outputs,
|
377 | gasLimit: 0
|
378 | });
|
379 | if (!gasLimit) {
|
380 | gasLimit = yield this.estimateGas(tx, inputs);
|
381 | }
|
382 |
|
383 | fee = yield this.estimateFee(gasLimit, fee);
|
384 | let originInputsAmount = inputs.length;
|
385 | let originOutputsAmount = outputs.length;
|
386 | let feeAmount = new Bn_1.Bn(fee.amount);
|
387 | if (fee.asset !== asset && feeAmount.eq(0)) {
|
388 | let feeInputs = yield this.pickFeeUtxos(feeAmount, fee.asset, from);
|
389 | inputs = inputs.concat(feeInputs);
|
390 | }
|
391 | else if (totalVote.lte(fee.amount)) {
|
392 | throw new Error("There are not enough UTXOs to vote.");
|
393 | }
|
394 | outputs = outputs.concat(this.generateChangeOutputs(inputs, from, new Bn_1.Bn(0), asset, fee));
|
395 | gasLimit += txHelper.estimateIncreasedGas(inputs.length - originInputsAmount, outputs.length - originOutputsAmount);
|
396 | tx = new Transaction_1.Transaction({
|
397 | inputs: inputs,
|
398 | outputs: outputs,
|
399 | gasLimit: gasLimit
|
400 | });
|
401 | return tx;
|
402 | });
|
403 | }
|
404 | |
405 |
|
406 |
|
407 |
|
408 |
|
409 | send(params) {
|
410 | return __awaiter(this, void 0, void 0, function* () {
|
411 | let { address, amount, asset = Constant_1.DefaultAsset, feeValue, feeType } = params;
|
412 | if (!amount) {
|
413 | throw new Error("Can not transfer asset with 0 amount to " + address);
|
414 | }
|
415 | let fee = this.setting.fee;
|
416 | if (feeValue && feeType) {
|
417 | fee = {
|
418 | amount: feeValue,
|
419 | asset: feeType
|
420 | };
|
421 | }
|
422 | let from = txHelper.getAddressByPrivateKey(this.privateKey);
|
423 | let txParams = {
|
424 | from: from,
|
425 | to: address,
|
426 | amount: amount,
|
427 | asset: asset,
|
428 | fee: fee
|
429 | };
|
430 | let [err1, tx] = yield await_to_js_1.to(this.generateRawTransaction(txParams));
|
431 | if (err1) {
|
432 | throw err1;
|
433 | }
|
434 | let rawTx = tx.sign([this.privateKey]).toHex();
|
435 | let [err2, res] = yield await_to_js_1.to(this.chainRpcProvider.sendRawTransaction(rawTx));
|
436 | if (err2) {
|
437 | throw err2;
|
438 | }
|
439 | return res;
|
440 | });
|
441 | }
|
442 | |
443 |
|
444 |
|
445 |
|
446 |
|
447 | check(txId) {
|
448 | return __awaiter(this, void 0, void 0, function* () {
|
449 | let [err, res] = yield await_to_js_1.to(this.chainRpcProvider.getRawTransaction({
|
450 | txId: txId,
|
451 | verbose: true
|
452 | }));
|
453 | if (err) {
|
454 | throw err;
|
455 | }
|
456 | let { confirmations } = res;
|
457 | if (confirmations > 0) {
|
458 | return true;
|
459 | }
|
460 | else {
|
461 | return false;
|
462 | }
|
463 | });
|
464 | }
|
465 | |
466 |
|
467 |
|
468 |
|
469 |
|
470 | fetch(txId) {
|
471 | return __awaiter(this, void 0, void 0, function* () {
|
472 | let [err, res] = yield await_to_js_1.to(this.chainRpcProvider.getTransactionReceipt(txId));
|
473 | if (err) {
|
474 | throw err;
|
475 | }
|
476 | return res;
|
477 | });
|
478 | }
|
479 | }
|
480 | exports.Transactions = Transactions;
|
481 |
|
\ | No newline at end of file |