digibyte-js
Version:
A pure and powerful JavaScript DigiByte library.
250 lines (214 loc) • 7.79 kB
JavaScript
const Transaction = require('../transaction/transaction');
const Precision = require('../encoding/precision');
const Address = require('../address');
const Price = require('../price');
const Vote = require('./vote');
/**
* @param {Rules} rules
* @param {Price} price
*/
function Transferor(rules, price) {
this._rules = rules || {};
this._price = price || {};
this._inputs = [];
this._gas = [];
this._outputs = [];
this._assetChange = null;
this._toBurn = 0;
this._burnExtra = false;
}
Transferor.prototype = {
get assetsIn() {
var assets = {};
var amountIn = 0;
for (var input of this._inputs) {
assets[input.assets[0].assetId] = true;
amountIn += input.assets[0].count;
}
if (Object.keys(assets) > 1)
throw "This library can't handle different asset inputs";
return amountIn;
},
get assetsOut() {
var amountOut = 0;
for (var output of this._outputs)
amountOut += output.count;
return amountOut;
},
get assetsBurned() {
if (this._burnExtra)
return this.assetsIn - this.assetsOut;
var deflation = 0;
if (this._rules.deflation)
deflation = this.isConsolidation ? 0 : this._rules.deflation;
return this._toBurn + deflation;
},
get assetsChange() {
if (this._burnExtra)
return 0;
return this.assetsIn - this.assetsOut - this.assetsBurned;
},
get changes() {
var changes = {};
for (var input of this._inputs)
changes[input.address] = (changes[input.address] || 0) - input.assets[0].count;
for (var output of this._outputs)
changes[output.address] = (changes[output.address] || 0) + output.count;
this._rules = { rules: this._rules };
var change = this.assetsChange;
this._rules = this._rules.rules;
if (change > 0) {
if (!Address.isValid(this._assetChange))
throw "Define change address or burn extra assets";
changes[this._assetChange] = (changes[this._assetChange] || 0) + change;
}
return changes;
},
get isConsolidation() {
for (var change of Object.values(this.changes))
if (change > 0) return false;
return true;
},
get inputs() {
return this._inputs.concat(this._gas);
},
get outputs() {
var result = [];
for (var output of this._outputs)
result.push({ address: output.address, satoshis: 600 });
if (!this.isConsolidation)
if ((this._rules.voting || {}).restricted)
for (var address of Object.keys(this.changes)) {
if (this.changes[address] <= 0) continue;
if (!Vote.isValidAddress(address)) throw "This asset can't be send to a non voting address";
}
if (this.assetsChange > 0) {
if (Address.isValid(this._assetChange))
result.push({ address: this._assetChange, satoshis: 600 });
else throw "Define change address or burn extra assets";
} else if (this.assetsChange < 0)
throw "More assets output than input";
return result;
},
get instructions() {
var data = new Precision();
if (this.assetsBurned > 0) data.addHex("44410325");
else data.addHex("44410315");
var n = 0;
for (var output of this._outputs) {
data.addInteger(0, 3);
data.addInteger(n++, 5);
data.addPrecision(output.count);
}
if (this.assetsChange > 0) {
if (Address.isValid(this._assetChange)) {
data.addInteger(0, 3);
data.addInteger(n++, 5);
data.addPrecision(this.assetsChange);
} else
throw "Define change address or burn extra assets";
} else if (this.assetsChange < 0)
throw "More assets output than input";
if (n >= 32) throw "To many asset outputs max. 31";
if (this.assetsBurned > 0) {
data.addInteger(0, 3);
data.addInteger(31, 5);
data.addPrecision(this.assetsBurned);
}
var result = data.toBuffer();
if (result.length > 80) throw "To many asset outputs";
return result;
},
get royalty() {
var result = [];
if (this.isConsolidation)
return [];
if (this._rules.royalty === undefined)
return [];
var exchange = 100000000;
if (this._rules.royalty.units !== undefined) {
if (!this._price) throw "Must provide a price object";
if (typeof this._rules.royalty.units == "string" && this._rules.royalty.units.name != "DGB")
this._rules.royalty.units = { ...Price.publishers.find(x => x.name === this._rules.royalty.units) };
if (typeof this._rules.royalty.units == "object")
exchange = Math.round((this._price.find(x => x.address === this._rules.royalty.units.address && x.index === this._rules.royalty.units.index) || {}).value);
}
if (typeof exchange != "number" || isNaN(exchange))
throw "Corrupted price array";
var addresses = Object.keys(this._rules.royalty.addresses);
for (var address of addresses) {
var satoshis = Price.Multiply(this._rules.royalty.addresses[address], exchange);
result.push({ address, satoshis });
}
return result;
}
}
/**
*
* @param {Object} utxo
* @param {string} utxo.txid - Previus Transaction ID
* @param {int} utxo.vout - Output vout
* @param {string} utxo.address - Output Script
* @param {int} utxo.satoshis - Output amount
*
* @param { { assetId, cid, count }[] } utxo.assets - Asset Array
*/
Transferor.prototype.addInputs = function (utxos) {
if (typeof utxos.length == 'undefined') utxos = [utxos];
for (var utxo of utxos) {
if (!utxo.address) utxo.address = Address.fromScript(utxo.script || utxo.scriptPubKey).toString();
if (utxo.assets) {
if (utxo.assets.length == 1) this._inputs.push(utxo);
else if (utxo.assets.length == 0) this._gas.push(utxo);
else throw "This library can't handle multi-asset inputs";
}
else this._gas.push(utxo);
}
return this;
}
/**
*
* @param {string} address
* @param {int} amount
*/
Transferor.prototype.addOutput = function (address, count) {
this._outputs.push({ address, count });
return this;
}
/**
*
* @param {string} address
*/
Transferor.prototype.setAssetChange = function (address) {
this._assetChange = address;
return this;
}
/**
* Burn certain amount of assets
* @param {number} amount
*/
Transferor.prototype.burnAssets = function (amount) {
this._toBurn += amount;
return this;
}
/**
* Burn assets not assigned
* @param {boolean} burn
*/
Transferor.prototype.burnExtra = function (burn) {
this._burnExtra = burn;
return this;
}
/**
* Build a Transaction object without setting fees or change address
* @returns {Transaction}
*/
Transferor.prototype.toTransaction = function () {
var transaction = new Transaction()
.from(this.inputs)
.to(this.outputs)
.addData(this.instructions)
.to(this.royalty);
return transaction;
}
module.exports = Transferor;