UNPKG

18.1 kBJavaScriptView Raw
1"use strict";
2var __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};
11Object.defineProperty(exports, "__esModule", { value: true });
12const Constant_1 = require("./Constant");
13const Setting_1 = require("./Setting");
14const Transaction_1 = require("./transaction/Transaction");
15const txHelper = require("./utils/TxHelper");
16const await_to_js_1 = require("await-to-js");
17const Bn_1 = require("./utils/Bn");
18/**
19 * High level Transactions object to send transactions on Asimov blockchain
20 */
21class Transactions {
22 /**
23 * Constructor of Transactions object
24 * @param rpc ChainRPC provider
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 * getter of ChainRPC provider
36 */
37 get chainRpcProvider() {
38 return this._chainRpcProvider;
39 }
40 /**
41 * setter of ChainRPC provider
42 */
43 set chainRpcProvider(rpc) {
44 this._chainRpcProvider = rpc;
45 }
46 /**
47 * getter of private key.
48 */
49 get privateKey() {
50 return this._privateKey;
51 }
52 /**
53 * setter of private key. Private key is set when developing automation scripts.
54 */
55 set privateKey(pk) {
56 this._privateKey = pk;
57 }
58 /**
59 * pick UTXOs in page.
60 *
61 * @param amount total value of UTXOs.
62 * @param asset asset type, hex format without 0x, such as "000000000000000200000001".
63 * @param address the address to pick from.
64 * @param page page number.
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 * pick UTXOs to vote.
102 *
103 * @param voteValue value to vote.
104 * @param asset asset type, hex format without 0x, such as "000000000000000200000001".
105 * @param address the address to pick from.
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 * pick UTXOs as transaction fee
137 * @param amount value as transaction fee.
138 * @param asset asset type, hex format without 0x, such as "000000000000000200000001".
139 * @param address the address to pick from.
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 //let [err, feeInputs] = await to(temp)
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 * estimate transaction fee
160 * @param amount value to transfer in the transaction.
161 * @param asset asset type, hex format without 0x, such as "000000000000000200000001".
162 * @param address the address to initiate the transaction.
163 * @param outputs outputs of the transaction.
164 * @param gasLimit gas limit set to the transaction.
165 * @return transaction fee (value + type)
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 //TODO bignumber
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 //i.amount = i.amount.toString()
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 //TODO bignumber
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 * Construct raw transaction object.
248 * @param params Parameters to construct a raw transaction object.
249 * @return Transaction id
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 //estimate gas
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 //TODO bignumber
298 //totalAmount = totalAmount + fee.amount
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 * Construct vote transaction object.
318 * @param params Parameters to construct a vote transaction object.
319 * @return Transaction id
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 //concat change ouputs
372 let _outputs = outputs.concat(this.generateChangeOutputs(inputs, from, new Bn_1.Bn(0), asset));
373 //estimate gas
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 // estimate fee from gas limit
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 * Construct a normal transaction and send it on Asimov blockchain.
406 * @param params Parameters to send a transaction on Asimov blockchain.
407 * @return Transaction id
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 * Check whether a transaction is confirmed on chain.
444 * @param txId transaction id.
445 * @return true or false
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 * Fetch transaction details.
467 * @param txId transaction id.
468 * @return Transaction details.
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}
480exports.Transactions = Transactions;
481//# sourceMappingURL=Transactions.js.map
\No newline at end of file