airgap-coin-lib
Version:
The airgap-coin-lib is a protocol agnostic library to prepare, sign and broadcast cryptocurrency transactions.
841 lines • 47.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
Object.defineProperty(exports, "__esModule", { value: true });
var bitcoinJSMessage = require("../../dependencies/src/bitcoinjs-message-2.1.1/index");
var index_1 = require("../../dependencies/src/axios-0.19.0/index");
var bignumber_1 = require("../../dependencies/src/bignumber.js-9.0.0/bignumber");
var index_2 = require("../../dependencies/src/bip39-2.5.0/index");
var ProtocolSymbols_1 = require("../../utils/ProtocolSymbols");
var BitcoinProtocolOptions_1 = require("./BitcoinProtocolOptions");
var BitcoinCryptoClient_1 = require("./BitcoinCryptoClient");
var DUST_AMOUNT = 50;
var BitcoinProtocol = /** @class */ (function () {
function BitcoinProtocol(options) {
if (options === void 0) { options = new BitcoinProtocolOptions_1.BitcoinProtocolOptions(); }
this.options = options;
this.symbol = 'BTC';
this.name = 'Bitcoin';
this.marketSymbol = 'btc';
this.feeSymbol = 'btc';
this.subProtocols = [];
this.feeDefaults = {
low: '0.00002',
medium: '0.00004',
high: '0.00005'
};
this.decimals = 8;
this.feeDecimals = 8;
this.identifier = ProtocolSymbols_1.MainProtocolSymbols.BTC;
this.units = [
{
unitSymbol: 'BTC',
factor: '1'
},
{
unitSymbol: 'mBTC',
factor: '0.0001'
},
{
unitSymbol: 'Satoshi',
factor: '0.00000001'
}
];
this.supportsHD = true;
this.standardDerivationPath = "m/44'/0'/0'";
this.addressIsCaseSensitive = true;
this.addressValidationPattern = '^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$';
this.addressPlaceholder = '1ABC...';
}
BitcoinProtocol.prototype.getBlockExplorerLinkForAddress = function (address) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.options.network.blockExplorer.getAddressLink(address)];
});
});
};
BitcoinProtocol.prototype.getBlockExplorerLinkForTxId = function (txId) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.options.network.blockExplorer.getTransactionLink(txId)];
});
});
};
BitcoinProtocol.prototype.getPublicKeyFromMnemonic = function (mnemonic, derivationPath, password) {
return __awaiter(this, void 0, void 0, function () {
var secret;
return __generator(this, function (_a) {
secret = index_2.mnemonicToSeed(mnemonic, password);
return [2 /*return*/, this.getPublicKeyFromHexSecret(secret, derivationPath)];
});
});
};
BitcoinProtocol.prototype.getPrivateKeyFromMnemonic = function (mnemonic, derivationPath, password) {
return __awaiter(this, void 0, void 0, function () {
var secret;
return __generator(this, function (_a) {
secret = index_2.mnemonicToSeed(mnemonic, password);
return [2 /*return*/, this.getPrivateKeyFromHexSecret(secret, derivationPath)];
});
});
};
BitcoinProtocol.prototype.getExtendedPrivateKeyFromMnemonic = function (mnemonic, derivationPath, password) {
return __awaiter(this, void 0, void 0, function () {
var secret;
return __generator(this, function (_a) {
secret = index_2.mnemonicToSeed(mnemonic, password);
return [2 /*return*/, this.getExtendedPrivateKeyFromHexSecret(secret, derivationPath)];
});
});
};
BitcoinProtocol.prototype.getPublicKeyFromHexSecret = function (secret, derivationPath) {
return __awaiter(this, void 0, void 0, function () {
var bitcoinNode;
return __generator(this, function (_a) {
bitcoinNode = this.options.config.bitcoinJSLib.HDNode.fromSeedHex(secret, this.options.network.extras.network);
return [2 /*return*/, bitcoinNode.derivePath(derivationPath).neutered().toBase58()];
});
});
};
BitcoinProtocol.prototype.getPrivateKeyFromHexSecret = function (secret, derivationPath) {
return __awaiter(this, void 0, void 0, function () {
var bitcoinNode;
return __generator(this, function (_a) {
bitcoinNode = this.options.config.bitcoinJSLib.HDNode.fromSeedHex(secret, this.options.network.extras.network);
return [2 /*return*/, bitcoinNode.derivePath(derivationPath).keyPair.d.toBuffer(32)];
});
});
};
BitcoinProtocol.prototype.getExtendedPrivateKeyFromHexSecret = function (secret, derivationPath) {
return __awaiter(this, void 0, void 0, function () {
var bitcoinNode;
return __generator(this, function (_a) {
bitcoinNode = this.options.config.bitcoinJSLib.HDNode.fromSeedHex(secret, this.options.network.extras.network);
return [2 /*return*/, bitcoinNode.derivePath(derivationPath).toBase58()];
});
});
};
BitcoinProtocol.prototype.getAddressFromPublicKey = function (publicKey) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
// broadcaster knows this (both broadcaster and signer)
return [2 /*return*/, this.options.config.bitcoinJSLib.HDNode.fromBase58(publicKey, this.options.network.extras.network).getAddress()];
});
});
};
BitcoinProtocol.prototype.getAddressesFromPublicKey = function (publicKey) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getAddressFromPublicKey(publicKey)];
case 1: return [2 /*return*/, [_a.sent()]];
}
});
});
};
BitcoinProtocol.prototype.getAddressFromExtendedPublicKey = function (extendedPublicKey, visibilityDerivationIndex, addressDerivationIndex) {
// broadcaster knows this (both broadcaster and signer)
return this.options.config.bitcoinJSLib.HDNode.fromBase58(extendedPublicKey, this.options.network.extras.network)
.derive(visibilityDerivationIndex)
.derive(addressDerivationIndex)
.getAddress();
};
BitcoinProtocol.prototype.getAddressesFromExtendedPublicKey = function (extendedPublicKey, visibilityDerivationIndex, addressCount, offset) {
// broadcaster knows this (both broadcaster and signer)
var node = this.options.config.bitcoinJSLib.HDNode.fromBase58(extendedPublicKey, this.options.network.extras.network);
var generatorArray = Array.from(new Array(addressCount), function (x, i) { return i + offset; });
return Promise.all(generatorArray.map(function (x) { return node.derive(visibilityDerivationIndex).derive(x).getAddress(); }));
};
BitcoinProtocol.prototype.signWithPrivateKey = function (privateKey, transaction) {
return __awaiter(this, void 0, void 0, function () {
var transactionBuilder, _i, _a, input, _b, _c, output, generatedChangeAddress, i;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
transactionBuilder = new this.options.config.bitcoinJSLib.TransactionBuilder(this.options.network.extras.network);
for (_i = 0, _a = transaction.ins; _i < _a.length; _i++) {
input = _a[_i];
transactionBuilder.addInput(input.txId, input.vout);
}
_b = 0, _c = transaction.outs;
_d.label = 1;
case 1:
if (!(_b < _c.length)) return [3 /*break*/, 5];
output = _c[_b];
if (!output.isChange) return [3 /*break*/, 3];
return [4 /*yield*/, this.getAddressFromPublicKey(privateKey.toString('hex'))];
case 2:
generatedChangeAddress = _d.sent();
if (generatedChangeAddress !== output.recipient) {
throw new Error('Change address could not be verified.');
}
_d.label = 3;
case 3:
transactionBuilder.addOutput(output.recipient, new bignumber_1.default(output.value).toNumber());
_d.label = 4;
case 4:
_b++;
return [3 /*break*/, 1];
case 5:
for (i = 0; i < transaction.ins.length; i++) {
transactionBuilder.sign(i, privateKey);
}
return [2 /*return*/, transactionBuilder.build().toHex()];
}
});
});
};
BitcoinProtocol.prototype.signWithExtendedPrivateKey = function (extendedPrivateKey, transaction) {
return __awaiter(this, void 0, void 0, function () {
var transactionBuilder, node, _i, _a, input, changeAddressBatchSize, changeAddressMaxAddresses, _b, _c, output, changeAddressIsValid, generatedChangeAddress, x, addresses, i;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
transactionBuilder = new this.options.config.bitcoinJSLib.TransactionBuilder(this.options.network.extras.network);
node = this.options.config.bitcoinJSLib.HDNode.fromBase58(extendedPrivateKey, this.options.network.extras.network);
for (_i = 0, _a = transaction.ins; _i < _a.length; _i++) {
input = _a[_i];
transactionBuilder.addInput(input.txId, input.vout);
}
changeAddressBatchSize = 10;
changeAddressMaxAddresses = 500;
_b = 0, _c = transaction.outs;
_d.label = 1;
case 1:
if (!(_b < _c.length)) return [3 /*break*/, 10];
output = _c[_b];
changeAddressIsValid = false;
if (!output.isChange) return [3 /*break*/, 8];
if (!output.derivationPath) return [3 /*break*/, 3];
return [4 /*yield*/, this.getAddressesFromExtendedPublicKey(extendedPrivateKey, 1, 1, parseInt(output.derivationPath, 10))];
case 2:
generatedChangeAddress = _d.sent();
changeAddressIsValid = generatedChangeAddress.includes(output.recipient);
return [3 /*break*/, 7];
case 3:
x = 0;
_d.label = 4;
case 4:
if (!(x < changeAddressMaxAddresses)) return [3 /*break*/, 7];
return [4 /*yield*/, this.getAddressesFromExtendedPublicKey(extendedPrivateKey, 1, changeAddressBatchSize, x)];
case 5:
addresses = _d.sent();
if (addresses.indexOf(output.recipient) >= 0) {
changeAddressIsValid = true;
x = changeAddressMaxAddresses;
}
_d.label = 6;
case 6:
x += changeAddressBatchSize;
return [3 /*break*/, 4];
case 7:
if (!changeAddressIsValid) {
throw new Error('Change address could not be verified.');
}
_d.label = 8;
case 8:
transactionBuilder.addOutput(output.recipient, new bignumber_1.default(output.value).toNumber());
_d.label = 9;
case 9:
_b++;
return [3 /*break*/, 1];
case 10:
for (i = 0; i < transaction.ins.length; i++) {
transactionBuilder.sign(i, node.derivePath(transaction.ins[i].derivationPath));
}
return [2 /*return*/, transactionBuilder.build().toHex()];
}
});
});
};
BitcoinProtocol.prototype.getTransactionDetails = function (unsignedTx) {
return __awaiter(this, void 0, void 0, function () {
var transaction, feeCalculator, _i, _a, txIn, _b, _c, txOut;
return __generator(this, function (_d) {
transaction = unsignedTx.transaction;
feeCalculator = new bignumber_1.default(0);
for (_i = 0, _a = transaction.ins; _i < _a.length; _i++) {
txIn = _a[_i];
feeCalculator = feeCalculator.plus(new bignumber_1.default(txIn.value));
}
for (_b = 0, _c = transaction.outs; _b < _c.length; _b++) {
txOut = _c[_b];
feeCalculator = feeCalculator.minus(new bignumber_1.default(txOut.value));
}
return [2 /*return*/, [
{
from: transaction.ins.map(function (obj) { return obj.address; }),
to: transaction.outs.filter(function (obj) { return !obj.isChange; }).map(function (obj) { return obj.recipient; }),
amount: transaction.outs
.filter(function (obj) { return !obj.isChange; })
.map(function (obj) { return new bignumber_1.default(obj.value); })
.reduce(function (accumulator, currentValue) { return accumulator.plus(currentValue); })
.toString(10),
fee: feeCalculator.toString(10),
protocolIdentifier: this.identifier,
network: this.options.network,
isInbound: false,
transactionDetails: unsignedTx.transaction
}
]];
});
});
};
BitcoinProtocol.prototype.getTransactionDetailsFromSigned = function (signedTx) {
return __awaiter(this, void 0, void 0, function () {
var tx, bitcoinTx;
var _this = this;
return __generator(this, function (_a) {
tx = {
to: [],
from: signedTx.from,
amount: signedTx.amount,
fee: signedTx.fee,
protocolIdentifier: this.identifier,
network: this.options.network,
isInbound: false,
transactionDetails: signedTx.transaction
};
bitcoinTx = this.options.config.bitcoinJSLib.Transaction.fromHex(signedTx.transaction);
bitcoinTx.outs.forEach(function (output) {
var address = _this.options.config.bitcoinJSLib.address.fromOutputScript(output.script, _this.options.network.extras.network);
// only works if one output is target and rest is change, but this way we can filter out change addresses
if (new bignumber_1.default(output.value).isEqualTo(signedTx.amount)) {
tx.to.push(address);
}
});
return [2 /*return*/, [tx]];
});
});
};
BitcoinProtocol.prototype.getBalanceOfAddresses = function (addresses) {
return __awaiter(this, void 0, void 0, function () {
var valueAccumulator, _i, addresses_1, address, data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
valueAccumulator = new bignumber_1.default(0);
_i = 0, addresses_1 = addresses;
_a.label = 1;
case 1:
if (!(_i < addresses_1.length)) return [3 /*break*/, 4];
address = addresses_1[_i];
return [4 /*yield*/, index_1.default.get(this.options.network.extras.indexerApi + "/api/v2/address/" + address + "?details=basic", {
responseType: 'json'
})];
case 2:
data = (_a.sent()).data;
valueAccumulator = valueAccumulator.plus(new bignumber_1.default(data.balance));
_a.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/, valueAccumulator.toString(10)];
}
});
});
};
BitcoinProtocol.prototype.getBalanceOfPublicKey = function (publicKey) {
return __awaiter(this, void 0, void 0, function () {
var address;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getAddressFromPublicKey(publicKey)];
case 1:
address = _a.sent();
return [2 /*return*/, this.getBalanceOfAddresses([address])];
}
});
});
};
BitcoinProtocol.prototype.getBalanceOfExtendedPublicKey = function (extendedPublicKey, offset) {
if (offset === void 0) { offset = 0; }
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, index_1.default.get(this.options.network.extras.indexerApi + "/api/v2/xpub/" + extendedPublicKey + "?pageSize=1", {
responseType: 'json'
})];
case 1:
data = (_a.sent()).data;
return [2 /*return*/, data.balance];
}
});
});
};
BitcoinProtocol.prototype.getBalanceOfPublicKeyForSubProtocols = function (publicKey, subProtocols) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
throw Promise.reject('get balance of sub protocols not supported');
});
});
};
BitcoinProtocol.prototype.getAvailableBalanceOfAddresses = function (addresses) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.getBalanceOfAddresses(addresses)];
});
});
};
BitcoinProtocol.prototype.estimateMaxTransactionValueFromExtendedPublicKey = function (extendedPublicKey, recipients, fee) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.getBalanceOfExtendedPublicKey(extendedPublicKey)];
});
});
};
BitcoinProtocol.prototype.estimateMaxTransactionValueFromPublicKey = function (publicKey, recipients, fee) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.getBalanceOfPublicKey(publicKey)];
});
});
};
BitcoinProtocol.prototype.estimateFeeDefaultsFromExtendedPublicKey = function (publicKey, recipients, values, data) {
return __awaiter(this, void 0, void 0, function () {
var result, estimatedFee, feeStepFactor, mediumFee, lowFee, highFee;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, index_1.default.get(this.options.network.extras.indexerApi + "/api/v2/estimatefee/5")];
case 1:
result = (_a.sent()).data.result;
estimatedFee = new bignumber_1.default(result).shiftedBy(this.feeDecimals);
if (estimatedFee.isZero()) {
return [2 /*return*/, this.feeDefaults];
}
feeStepFactor = new bignumber_1.default(0.5);
mediumFee = estimatedFee;
lowFee = mediumFee.minus(mediumFee.times(feeStepFactor)).integerValue(bignumber_1.default.ROUND_FLOOR);
highFee = mediumFee.plus(mediumFee.times(feeStepFactor)).integerValue(bignumber_1.default.ROUND_FLOOR);
return [2 /*return*/, {
low: lowFee.shiftedBy(-this.feeDecimals).toFixed(),
medium: mediumFee.shiftedBy(-this.feeDecimals).toFixed(),
high: highFee.shiftedBy(-this.feeDecimals).toFixed()
}];
}
});
});
};
BitcoinProtocol.prototype.estimateFeeDefaultsFromPublicKey = function (publicKey, recipients, values, data) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, Promise.reject('estimating fee defaults using non extended public key not implemented')];
});
});
};
BitcoinProtocol.prototype.prepareTransactionFromExtendedPublicKey = function (extendedPublicKey, offset, recipients, values, fee) {
return __awaiter(this, void 0, void 0, function () {
var wrappedValues, wrappedFee, transaction, utxos, totalRequiredBalance, valueAccumulator, getPathIndexes, _i, utxos_1, utxo, indexes, derivedAddress, i, lastUsedInternalAddress, changeValue, changeAddressIndex, derivedAddress;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
wrappedValues = values.map(function (value) { return new bignumber_1.default(value); });
wrappedFee = new bignumber_1.default(fee);
transaction = {
ins: [],
outs: []
};
if (recipients.length !== wrappedValues.length) {
throw new Error('recipients do not match values');
}
console.log(this.options.network.extras.indexerApi + "/api/v2/utxo/" + extendedPublicKey + "?confirmed=true");
return [4 /*yield*/, index_1.default.get(this.options.network.extras.indexerApi + "/api/v2/utxo/" + extendedPublicKey + "?confirmed=true", {
responseType: 'json'
})];
case 1:
utxos = (_a.sent()).data;
if (utxos.length <= 0) {
throw new Error('not enough balance'); // no transactions found on those addresses, probably won't find anything in the next ones
}
totalRequiredBalance = wrappedValues
.reduce(function (accumulator, currentValue) { return accumulator.plus(currentValue); })
.plus(wrappedFee);
valueAccumulator = new bignumber_1.default(0);
getPathIndexes = function (path) {
var result = path
.split('/')
.slice(-2)
.map(function (item) { return parseInt(item); })
.filter(function (item) { return !isNaN(item); });
if (result.length !== 2) {
throw new Error('Unexpected path format');
}
return [result[0], result[1]];
};
_i = 0, utxos_1 = utxos;
_a.label = 2;
case 2:
if (!(_i < utxos_1.length)) return [3 /*break*/, 5];
utxo = utxos_1[_i];
valueAccumulator = valueAccumulator.plus(utxo.value);
indexes = getPathIndexes(utxo.path);
return [4 /*yield*/, this.getAddressFromExtendedPublicKey(extendedPublicKey, indexes[0], indexes[1])];
case 3:
derivedAddress = _a.sent();
if (derivedAddress === utxo.address) {
transaction.ins.push({
txId: utxo.txid,
value: new bignumber_1.default(utxo.value).toString(10),
vout: utxo.vout,
address: utxo.address,
derivationPath: indexes.join('/')
});
}
else {
throw new Error('Invalid address returned from API');
}
if (valueAccumulator.isGreaterThanOrEqualTo(totalRequiredBalance)) {
return [3 /*break*/, 5];
}
_a.label = 4;
case 4:
_i++;
return [3 /*break*/, 2];
case 5:
if (valueAccumulator.isLessThan(totalRequiredBalance)) {
throw new Error('not enough balance');
}
for (i = 0; i < recipients.length; i++) {
transaction.outs.push({
recipient: recipients[i],
isChange: false,
value: wrappedValues[i].toString(10),
derivationPath: '' // TODO: Remove this as soon as our serializer supports optional properties
});
valueAccumulator = valueAccumulator.minus(wrappedValues[i]);
}
lastUsedInternalAddress = Math.max.apply(Math, __spreadArrays([-1], utxos
.map(function (utxo) { return getPathIndexes(utxo.path); })
.filter(function (indexes) { return indexes[0] === 1; })
.map(function (indexes) { return indexes[1]; })));
changeValue = valueAccumulator.minus(wrappedFee);
if (!changeValue.isGreaterThan(new bignumber_1.default(DUST_AMOUNT))) return [3 /*break*/, 7];
changeAddressIndex = lastUsedInternalAddress + 1;
return [4 /*yield*/, this.getAddressFromExtendedPublicKey(extendedPublicKey, 1, changeAddressIndex)];
case 6:
derivedAddress = _a.sent();
transaction.outs.push({
recipient: derivedAddress,
isChange: true,
value: changeValue.toString(10),
derivationPath: changeAddressIndex.toString()
});
_a.label = 7;
case 7: return [2 /*return*/, transaction];
}
});
});
};
BitcoinProtocol.prototype.prepareTransactionFromPublicKey = function (publicKey, recipients, values, fee) {
return __awaiter(this, void 0, void 0, function () {
var wrappedValues, wrappedFee, transaction, address, utxos, totalRequiredBalance, valueAccumulator, _i, utxos_2, utxo, i, changeValue;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
wrappedValues = values.map(function (value) { return new bignumber_1.default(value); });
wrappedFee = new bignumber_1.default(fee);
transaction = {
ins: [],
outs: []
};
if (recipients.length !== wrappedValues.length) {
throw new Error('Recipient and value length does not match.');
}
return [4 /*yield*/, this.getAddressFromPublicKey(publicKey)];
case 1:
address = _a.sent();
return [4 /*yield*/, index_1.default.get(this.options.network.extras.indexerApi + "/api/v2/utxo/" + address, {
responseType: 'json'
})];
case 2:
utxos = (_a.sent()).data;
totalRequiredBalance = wrappedValues
.reduce(function (accumulator, currentValue) { return accumulator.plus(currentValue); })
.plus(wrappedFee);
valueAccumulator = new bignumber_1.default(0);
for (_i = 0, utxos_2 = utxos; _i < utxos_2.length; _i++) {
utxo = utxos_2[_i];
valueAccumulator = valueAccumulator.plus(new bignumber_1.default(utxo.value));
transaction.ins.push({
txId: utxo.txid,
value: new bignumber_1.default(utxo.value).toString(10),
vout: utxo.vout,
address: address
});
if (valueAccumulator.isGreaterThanOrEqualTo(totalRequiredBalance)) {
break;
}
}
if (valueAccumulator.isLessThan(totalRequiredBalance)) {
throw new Error("not enough balance, having " + valueAccumulator.toFixed() + " of " + totalRequiredBalance.toFixed());
}
// tx.addInput(utxo.txid, utxo.vout)
for (i = 0; i < recipients.length; i++) {
transaction.outs.push({
recipient: recipients[i],
isChange: false,
value: wrappedValues[i].toString(10)
});
valueAccumulator = valueAccumulator.minus(wrappedValues[i]);
// tx.addOutput(recipients[i], values[i])
}
changeValue = valueAccumulator.minus(wrappedFee);
if (changeValue.isGreaterThan(new bignumber_1.default(DUST_AMOUNT))) {
transaction.outs.push({
recipient: address,
isChange: true,
value: changeValue.toString(10)
});
}
return [2 /*return*/, transaction];
}
});
});
};
BitcoinProtocol.prototype.broadcastTransaction = function (rawTransaction) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, index_1.default.post(this.options.network.extras.indexerApi + '/api/v2/sendtx/', rawTransaction)];
case 1:
data = (_a.sent()).data;
return [2 /*return*/, data.result];
}
});
});
};
BitcoinProtocol.prototype.getTransactionsFromExtendedPublicKey = function (extendedPublicKey, limit, cursor, addressOffset) {
if (addressOffset === void 0) { addressOffset = 0; }
var _a, _b;
return __awaiter(this, void 0, void 0, function () {
var page, data, ourAddresses, airGapTransactions, _i, _c, transaction, tempAirGapTransactionFrom, tempAirGapTransactionTo, tempAirGapTransactionIsInbound, amount, _d, _e, vin, _f, _g, vout, airGapTransaction;
return __generator(this, function (_h) {
switch (_h.label) {
case 0:
page = (_b = (_a = cursor) === null || _a === void 0 ? void 0 : _a.page, (_b !== null && _b !== void 0 ? _b : 1));
return [4 /*yield*/, index_1.default.get(this.options.network.extras.indexerApi + '/api/v2/xpub/' + extendedPublicKey + ("?details=txs&tokens=used&pageSize=" + limit + "&page=" + page), {
responseType: 'json'
})];
case 1:
data = (_h.sent()).data;
ourAddresses = (data.tokens || []).filter(function (token) { return token.type === 'XPUBAddress'; }).map(function (token) { return token.name; });
airGapTransactions = [];
if (data.page == page) {
for (_i = 0, _c = data.transactions || []; _i < _c.length; _i++) {
transaction = _c[_i];
tempAirGapTransactionFrom = [];
tempAirGapTransactionTo = [];
tempAirGapTransactionIsInbound = true;
amount = new bignumber_1.default(0);
for (_d = 0, _e = transaction.vin; _d < _e.length; _d++) {
vin = _e[_d];
if (this.containsSome(vin.addresses, ourAddresses)) {
tempAirGapTransactionIsInbound = false;
}
tempAirGapTransactionFrom.push.apply(tempAirGapTransactionFrom, vin.addresses);
amount = amount.plus(vin.value);
}
for (_f = 0, _g = transaction.vout; _f < _g.length; _f++) {
vout = _g[_f];
if (vout.addresses) {
tempAirGapTransactionTo.push.apply(tempAirGapTransactionTo, vout.addresses);
// If receiving address is our address, and transaction is outbound => our change
if (this.containsSome(vout.addresses, ourAddresses) && !tempAirGapTransactionIsInbound) {
// remove only if related to this address
amount = amount.minus(vout.value);
}
// If receiving address is not ours, and transaction isbound => senders change
if (!this.containsSome(vout.addresses, ourAddresses) && tempAirGapTransactionIsInbound) {
amount = amount.minus(vout.value);
}
}
}
// deduct fee from amount
amount = amount.minus(transaction.fees);
airGapTransaction = {
hash: transaction.txid,
from: tempAirGapTransactionFrom,
to: tempAirGapTransactionTo,
isInbound: tempAirGapTransactionIsInbound,
amount: amount.toString(10),
fee: new bignumber_1.default(transaction.fees).toString(10),
blockHeight: transaction.blockHeight.toString(),
protocolIdentifier: this.identifier,
network: this.options.network,
timestamp: transaction.blockTime
};
airGapTransactions.push(airGapTransaction);
}
}
return [2 /*return*/, {
transactions: airGapTransactions,
cursor: {
page: cursor ? cursor.page + 1 : 2
}
}];
}
});
});
};
BitcoinProtocol.prototype.getTransactionsFromPublicKey = function (publicKey, limit, cursor) {
return __awaiter(this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = this.getTransactionsFromAddresses;
return [4 /*yield*/, this.getAddressFromPublicKey(publicKey)];
case 1: return [2 /*return*/, _a.apply(this, [[_b.sent()], limit, cursor])];
}
});
});
};
BitcoinProtocol.prototype.getTransactionsFromAddresses = function (addresses, limit, cursor) {
var _a, _b;
return __awaiter(this, void 0, void 0, function () {
var airGapTransactions, page, url, data, _i, _c, transaction, tempAirGapTransactionFrom, tempAirGapTransactionTo, tempAirGapTransactionIsInbound, amount, _d, _e, vin, _f, _g, vout, airGapTransaction;
return __generator(this, function (_h) {
switch (_h.label) {
case 0:
airGapTransactions = [];
page = (_b = (_a = cursor) === null || _a === void 0 ? void 0 : _a.page, (_b !== null && _b !== void 0 ? _b : 1));
url = this.options.network.extras.indexerApi + "/api/v2/address/" + addresses[0] + "?page=" + page + "&pageSize=" + limit + "&details=txs";
return [4 /*yield*/, index_1.default.get(url, {
responseType: 'json'
})];
case 1:
data = (_h.sent()).data;
if (data.page == page) {
for (_i = 0, _c = data.transactions || []; _i < _c.length; _i++) {
transaction = _c[_i];
tempAirGapTransactionFrom = [];
tempAirGapTransactionTo = [];
tempAirGapTransactionIsInbound = true;
amount = new bignumber_1.default(0);
for (_d = 0, _e = transaction.vin; _d < _e.length; _d++) {
vin = _e[_d];
if (vin.addresses && this.containsSome(vin.addresses, addresses)) {
tempAirGapTransactionIsInbound = false;
}
tempAirGapTransactionFrom.push.apply(tempAirGapTransactionFrom, vin.addresses);
amount = vin.value ? amount.plus(vin.value) : amount;
}
for (_f = 0, _g = transaction.vout; _f < _g.length; _f++) {
vout = _g[_f];
if (vout.addresses) {
tempAirGapTransactionTo.push.apply(tempAirGapTransactionTo, vout.addresses);
// If receiving address is our address, and transaction is outbound => our change
if (this.containsSome(vout.addresses, addresses) && !tempAirGapTransactionIsInbound) {
// remove only if related to this address
amount = amount.minus(new bignumber_1.default(vout.value).shiftedBy(this.decimals));
}
// If receiving address is not ours, and transaction isbound => senders change
if (!this.containsSome(vout.addresses, addresses) && tempAirGapTransactionIsInbound) {
amount = amount.minus(new bignumber_1.default(vout.value).shiftedBy(this.decimals));
}
}
}
// deduct fee from amount
amount = amount.minus(new bignumber_1.default(transaction.fees).shiftedBy(this.feeDecimals));
airGapTransaction = {
hash: transaction.txid,
from: tempAirGapTransactionFrom,
to: tempAirGapTransactionTo,
isInbound: tempAirGapTransactionIsInbound,
amount: amount.toString(10),
fee: new bignumber_1.default(transaction.fees).shiftedBy(this.feeDecimals).toString(10),
blockHeight: transaction.blockHeight.toString(),
protocolIdentifier: this.identifier,
network: this.options.network,
timestamp: transaction.blockTime
};
airGapTransactions.push(airGapTransaction);
}
}
return [2 /*return*/, {
transactions: airGapTransactions,
cursor: {
page: cursor ? cursor.page + 1 : 2
}
}];
}
});
});
};
BitcoinProtocol.prototype.containsSome = function (needles, haystack) {
for (var _i = 0, needles_1 = needles; _i < needles_1.length; _i++) {
var needle = needles_1[_i];
if (haystack.indexOf(needle) > -1) {
return true;
}
}
return false;
};
BitcoinProtocol.prototype.signMessage = function (message, keypair) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, new BitcoinCryptoClient_1.BitcoinCryptoClient(this, bitcoinJSMessage).signMessage(message, keypair)];
});
});
};
BitcoinProtocol.prototype.verifyMessage = function (message, signature, publicKey) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, new BitcoinCryptoClient_1.BitcoinCryptoClient(this, bitcoinJSMessage).verifyMessage(message, signature, publicKey)];
});
});
};
BitcoinProtocol.prototype.getTransactionStatuses = function (transactionHashes) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, Promise.reject('Transaction status not implemented')];
});
});
};
return BitcoinProtocol;
}());
exports.BitcoinProtocol = BitcoinProtocol;
//# sourceMappingURL=BitcoinProtocol.js.map