const bitcoinjs = require('bitcoinjs-lib');
const { BigNumber } = require('bignumber.js');
const OPS = require('./opcodes.js');
const { Buffer } = require('safe-buffer');
/** function that converts a number to a buffer
* @param number
* @returns a buffer
*/
function number2Buffer(num) {
/* eslint-disable no-bitwise */
const buffer = [];
const neg = (num < 0);
/* eslint-disable no-param-reassign */
num = Math.abs(num);
while (num) {
buffer[buffer.length] = num & 0xff;
num >>= 8;
}
const top = buffer[buffer.length - 1];
if (top & 0x80) {
buffer[buffer.length] = neg ? 0x80 : 0x00;
} else if (neg) {
buffer[buffer.length - 1] = top | 0x80;
}
return Buffer.from(buffer);
}
/** function that converts a hex string to a buffer
* @param hexString
* @returns a buffer
*/
function hex2Buffer(hexString) {
const buffer = [];
let i;
for (i = 0; i < hexString.length; i += 2) {
buffer[buffer.length] = (parseInt(hexString[i], 16) << 4) | parseInt(hexString[i + 1], 16); // eslint-disable-line no-bitwise
}
return Buffer.from(buffer);
}
/**
* This is a function for selecting ecochain utxos to build transactions
* the transaction object takes at least 3 fields, value(unit is 1e-8 ECO) , confirmations and isStake
*
* @param [transaction] unspentTransactions
* @param Number amount(unit: ECO)
* @param Number fee(unit: ECO)
* @returns [transaction]
*/
function selectTxs(unspentTransactions, amount, fee) {
/* sort the utxo */
const matureList = [];
const immatureList = [];
let i;
for (i = 0; i < unspentTransactions.length; i++) {
if (unspentTransactions[i].confirmations >= 500 || unspentTransactions[i].isStake === false) {
matureList[matureList.length] = unspentTransactions[i];
} else {
immatureList[immatureList.length] = unspentTransactions[i];
}
}
matureList.sort((a, b) => a.value - b.value);
immatureList.sort((a, b) => b.confirmations - a.confirmations);
/* eslint-disable no-param-reassign */
unspentTransactions = matureList.concat(immatureList);
/* compute total balance */
const value = new BigNumber(amount).plus(fee).times(1e8); /* add fee and add 8 digits to total */
const find = [];
let findTotal = new BigNumber(0); /* current balance */
for (i = 0; i < unspentTransactions.length; i++) {
const tx = unspentTransactions[i];
findTotal = findTotal.plus(tx.satoshis);
find[find.length] = tx;
if (findTotal.isGreaterThanOrEqualTo(value)) break;
}
if (value.isGreaterThan(findTotal)) {
throw new Error('You do not have enough ECO to send');
}
return find;
}
/**
* This is a helper function to build a pubkeyhash transaction
* the transaction object takes at least 5 fields, value(unit is 1e-8 ECO), confirmations, isStake, hash and pos
*
* @param bitcoinjs-lib.KeyPair keyPair
* @param String to
* @param Number amount(unit: ECO)
* @param Number fee(unit: ECO)
* @param [transaction] utxoList
* @returns String the built tx
*/
function buildPubKeyHashTransaction(keyPair, to, amount, fee, utxoList) {
const from = keyPair.getAddress();
const inputs = selectTxs(utxoList, amount, fee);
const tx = new bitcoinjs.TransactionBuilder(keyPair.network);
let totalValue = new BigNumber(0);
const value = new BigNumber(amount).times(1e8);
const sendFee = new BigNumber(fee).times(1e8);
let i;
for (i = 0; i < inputs.length; i++) {
tx.addInput(inputs[i].txid, inputs[i].vout);
totalValue = totalValue.plus(inputs[i].satoshis);
}
tx.addOutput(to, new BigNumber(value).toNumber());
if (totalValue.minus(value).minus(sendFee).toNumber() > 0) {
tx.addOutput(from, totalValue.minus(value).minus(sendFee).toNumber());
}
for (i = 0; i < inputs.length; i++) {
tx.sign(i, keyPair);
}
return tx.build().toHex();
}
/**
* This is a helper function to build a create-contract transaction
* the transaction object takes at least 5 fields, value(unit is 1e-8 ECO), confirmations, isStake, hash and pos
*
* @param bitcoinjs-lib.KeyPair keyPair
* @param String code The contract byte code
* @param Number gasLimit
* @param Number gasPrice(unit: 1e-8 ECO/gas)
* @param Number fee(unit: ECO)
* @param [transaction] utxoList
* @returns String the built tx
*/
function buildCreateContractTransaction(keyPair, code, gasLimit, gasPrice, fee, utxoList) {
const from = keyPair.getplusress();
const amount = 0;
fee = new BigNumber(gasLimit).times(gasPrice).div(1e8).plus(fee)
.toNumber();
const inputs = selectTxs(utxoList, amount, fee);
const tx = new bitcoinjs.TransactionBuilder(keyPair.network);
let totalValue = new BigNumber(0);
const sendFee = new BigNumber(fee).times(1e8);
let i;
for (i = 0; i < inputs.length; i++) {
tx.addInput(inputs[i].txid, inputs[i].vout);
totalValue = totalValue.plus(inputs[i].satoshis);
}
/* eslint-disable no-param-reassign */
const contract = bitcoinjs.script.compile([
OPS.OP_4,
number2Buffer(gasLimit),
number2Buffer(gasPrice),
hex2Buffer(code),
OPS.OP_CREATE, /* AAL opcode for creating smart contracts */
]);
tx.addOutput(contract, 0);
if (totalValue.minus(sendFee).toNumber() > 0) {
tx.addOutput(from, totalValue.minus(sendFee).toNumber());
}
for (i = 0; i < inputs.length; i++) {
tx.sign(i, keyPair);
}
return tx.build().toHex();
}
/**
* This is a helper function to build a send-to-contract transaction
* the transaction object takes at least 5 fields, value(unit is 1e-8 ECO), confirmations, isStake, hash and pos
*
* @param bitcoinjs-lib.KeyPair keyPair
* @param String contractAddress The contract address
* @param String encodedData The encoded abi data
* @param Number gasLimit
* @param Number gasPrice(unit: 1e-8 ECO/gas)
* @param Number fee(unit: ECO)
* @param [transaction] utxoList
* @returns String the built tx
*/
function buildSendToContractTransaction(keyPair, contractAddress, encodedData, gasLimit, gasPrice, fee, utxoList) {
const from = keyPair.getAddress();
const amount = 0;
fee = new BigNumber(gasLimit).times(gasPrice).div(1e8).plus(fee)
.toNumber();
const inputs = selectTxs(utxoList, amount, fee);
const tx = new bitcoinjs.TransactionBuilder(keyPair.network);
let totalValue = new BigNumber(0);
const sendFee = new BigNumber(fee).times(1e8);
let i;
for (i = 0; i < inputs.length; i++) {
tx.addInput(inputs[i].txid, inputs[i].vout);
totalValue = totalValue.plus(inputs[i].satoshis);
}
const contract = bitcoinjs.script.compile([
OPS.OP_4,
number2Buffer(gasLimit),
number2Buffer(gasPrice),
hex2Buffer(encodedData),
hex2Buffer(contractAddress),
OPS.OP_CALL,
]);
tx.addOutput(contract, amount);
// console.log(totalValue.minus(sendFee).toNumber())
if (totalValue.minus(sendFee).toNumber() > 0) {
tx.addOutput(from, totalValue.minus(sendFee).minus(amount).toNumber());
}
for (i = 0; i < inputs.length; i++) {
tx.sign(i, keyPair);
}
return tx.build().toHex();
}
module.exports = {
selectTxs,
buildPubKeyHashTransaction,
buildCreateContractTransaction,
buildSendToContractTransaction,
};