lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
168 lines (167 loc) • 6.06 kB
JavaScript
import { Preconditions } from '../util/preconditions.js';
import { JSUtil } from '../util/js.js';
import { Script } from '../script.js';
import { Address } from '../address.js';
import { PublicKey } from '../publickey.js';
import { Unit } from '../unit.js';
export class UnspentOutput {
address;
txId;
outputIndex;
script;
satoshis;
internalPubKey;
merkleRoot;
keyAggContext;
mySignerIndex;
constructor(data) {
Preconditions.checkArgument(typeof data === 'object' && data !== null, 'Must provide an object from where to extract data');
const address = data.address ? new Address(data.address) : undefined;
const txId = data.txid || data.txId;
if (!txId || !JSUtil.isHexaString(txId) || txId.length > 64) {
throw new Error('Invalid TXID in object: ' + JSON.stringify(data));
}
const outputIndex = data.vout !== undefined ? data.vout : data.outputIndex;
if (typeof outputIndex !== 'number') {
throw new Error('Invalid outputIndex, received ' + outputIndex);
}
Preconditions.checkArgument(data.scriptPubKey !== undefined || data.script !== undefined, 'Must provide the scriptPubKey for that output!');
const script = new Script(data.scriptPubKey || data.script);
Preconditions.checkArgument(data.amount !== undefined || data.satoshis !== undefined, 'Must provide an amount for the output');
let amount;
if (data.amount !== undefined) {
amount = Number(Unit.fromXPI(data.amount).toSatoshis());
}
else if (data.satoshis !== undefined) {
amount =
typeof data.satoshis === 'bigint'
? Number(data.satoshis)
: data.satoshis;
}
else {
throw new Error('No amount provided');
}
Preconditions.checkArgument(typeof amount === 'number', 'Amount must be a number');
this.address = address;
this.txId = txId;
this.outputIndex = outputIndex;
this.script = script;
this.satoshis = amount;
if (data.internalPubKey) {
if (data.internalPubKey instanceof PublicKey) {
this.internalPubKey = data.internalPubKey;
}
else if (Buffer.isBuffer(data.internalPubKey)) {
this.internalPubKey = new PublicKey(data.internalPubKey);
}
else if (typeof data.internalPubKey === 'string') {
this.internalPubKey = new PublicKey(data.internalPubKey);
}
}
this.merkleRoot = data.merkleRoot;
this.keyAggContext = data.keyAggContext;
this.mySignerIndex = data.mySignerIndex;
}
getXPI() {
return this.satoshis / 1000000;
}
getUnit() {
return Unit.fromSatoshis(this.satoshis);
}
isValid() {
return (JSUtil.isHexaString(this.txId) &&
this.txId.length === 64 &&
this.outputIndex >= 0 &&
this.satoshis > 0 &&
this.script.isValid());
}
isDust(dustThreshold = 546) {
return this.satoshis < dustThreshold;
}
toObject() {
return {
address: this.address ? this.address.toString() : undefined,
txid: this.txId,
vout: this.outputIndex,
scriptPubKey: this.script.toBuffer().toString('hex'),
amount: Unit.fromSatoshis(this.satoshis).toXPI(),
};
}
toJSON = this.toObject;
inspect() {
return ('<UnspentOutput: ' +
this.txId +
':' +
this.outputIndex +
', satoshis: ' +
this.satoshis +
', address: ' +
this.address +
'>');
}
clone() {
return new UnspentOutput({
txId: this.txId,
outputIndex: this.outputIndex,
script: this.script.clone(),
satoshis: this.satoshis,
address: this.address,
internalPubKey: this.internalPubKey,
merkleRoot: this.merkleRoot,
keyAggContext: this.keyAggContext,
mySignerIndex: this.mySignerIndex,
});
}
toString() {
return this.txId + ':' + this.outputIndex;
}
isPayToPublicKeyHash() {
return this.script.isPayToPublicKeyHash();
}
isPayToScriptHash() {
return this.script.isPayToScriptHash();
}
getAddress() {
try {
const addressInfo = this.script.getAddressInfo();
return addressInfo ? addressInfo.toString() : null;
}
catch (e) {
return null;
}
}
static fromObject(obj) {
return new UnspentOutput(obj);
}
static fromObjects(objects) {
return objects.map(obj => new UnspentOutput(obj));
}
static filterByAddress(unspentOutputs, address) {
const addressStr = typeof address === 'string' ? address : address.toString();
return unspentOutputs.filter(utxo => {
const utxoAddress = utxo.getAddress();
return utxoAddress === addressStr;
});
}
static filterByMinAmount(unspentOutputs, minAmount) {
const minSatoshis = typeof minAmount === 'bigint' ? Number(minAmount) : minAmount;
return unspentOutputs.filter(utxo => utxo.satoshis >= minSatoshis);
}
static calculateTotal(unspentOutputs) {
return unspentOutputs.reduce((total, utxo) => total + utxo.satoshis, 0);
}
static selectForAmount(unspentOutputs, targetAmount) {
const targetSatoshis = typeof targetAmount === 'bigint' ? Number(targetAmount) : targetAmount;
const selected = [];
let total = 0;
const sorted = [...unspentOutputs].sort((a, b) => b.satoshis - a.satoshis);
for (const utxo of sorted) {
selected.push(utxo);
total += utxo.satoshis;
if (total >= targetSatoshis) {
break;
}
}
return selected;
}
}