@xchainjs/xchain-litecoin
Version:
Custom Litecoin client and utilities used by XChainJS clients
889 lines (871 loc) • 37 kB
JavaScript
;
var xchainClient = require('@xchainjs/xchain-client');
var xchainUtxo = require('@xchainjs/xchain-utxo');
var Litecoin = require('bitcoinjs-lib');
var accumulative = require('coinselect/accumulative.js');
var xchainUtil = require('@xchainjs/xchain-util');
var xchainUtxoProviders = require('@xchainjs/xchain-utxo-providers');
var axios = require('axios');
var ecc = require('@bitcoin-js/tiny-secp256k1-asmjs');
var xchainCrypto = require('@xchainjs/xchain-crypto');
var bip32 = require('@scure/bip32');
var ecpair = require('ecpair');
var AppBtc = require('@ledgerhq/hw-app-btc');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var Litecoin__namespace = /*#__PURE__*/_interopNamespace(Litecoin);
var accumulative__default = /*#__PURE__*/_interopDefault(accumulative);
var axios__default = /*#__PURE__*/_interopDefault(axios);
var ecc__namespace = /*#__PURE__*/_interopNamespace(ecc);
var AppBtc__default = /*#__PURE__*/_interopDefault(AppBtc);
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __awaiter(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());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* Minimum transaction fee in satoshis.
* 1000 satoshi/kB (similar to current `minrelaytxfee`)
* @see https://github.com/bitcoin/bitcoin/blob/db88db47278d2e7208c50d16ab10cb355067d071/src/validation.h#L56
*/
const MIN_TX_FEE = 1000;
/**
* Lower bound for transaction fee rate in satoshis per byte.
*/
const LOWER_FEE_BOUND = 0.5;
/**
* Upper bound for transaction fee rate in satoshis per byte.
*/
const UPPER_FEE_BOUND = 500;
/**
* Number of decimal places for Litecoin.
*/
const LTC_DECIMAL = 8;
/**
* Chain identifier for Litecoin.
*/
const LTCChain = 'LTC';
/**
* Base "chain" asset on Litecoin mainnet.
*
* Based on definition in Thorchain `common`.
* @see https://gitlab.com/thorchain/thornode/-/blob/master/common/asset.go#L12-24
*/
const AssetLTC = { chain: LTCChain, symbol: 'LTC', ticker: 'LTC', type: xchainUtil.AssetType.NATIVE };
const LTC_MAINNET_EXPLORER = new xchainClient.ExplorerProvider('https://blockchair.com/litecoin/', 'https://blockchair.com/litecoin/address/%%ADDRESS%%', 'https://blockchair.com/litecoin/transaction/%%TX_ID%%');
const LTC_TESTNET_EXPLORER = new xchainClient.ExplorerProvider('https://blockexplorer.one/litecoin/testnet/', 'https://blockexplorer.one/litecoin/testnet/address/%%ADDRESS%%', 'https://blockexplorer.one/litecoin/testnet/blockHash/%%TX_ID%%');
/**
* Explorer providers for Litecoin.
*/
const explorerProviders = {
[xchainClient.Network.Testnet]: LTC_TESTNET_EXPLORER,
[xchainClient.Network.Stagenet]: LTC_MAINNET_EXPLORER,
[xchainClient.Network.Mainnet]: LTC_MAINNET_EXPLORER,
};
//======================
// sochain
//======================
const testnetSochainProvider = new xchainUtxoProviders.SochainProvider('https://sochain.com/api/v3', process.env.SOCHAIN_API_KEY || '', LTCChain, AssetLTC, 8, xchainUtxoProviders.SochainNetwork.LTCTEST);
const mainnetSochainProvider = new xchainUtxoProviders.SochainProvider('https://sochain.com/api/v3', process.env.SOCHAIN_API_KEY || '', LTCChain, AssetLTC, 8, xchainUtxoProviders.SochainNetwork.LTC);
/**
* Sochain data providers for Litecoin.
*/
const sochainDataProviders = {
[xchainClient.Network.Testnet]: testnetSochainProvider,
[xchainClient.Network.Stagenet]: mainnetSochainProvider,
[xchainClient.Network.Mainnet]: mainnetSochainProvider,
};
//======================
// Blockcypher
//======================
const mainnetBlockcypherProvider = new xchainUtxoProviders.BlockcypherProvider('https://api.blockcypher.com/v1', LTCChain, AssetLTC, 8, xchainUtxoProviders.BlockcypherNetwork.LTC, process.env.BLOCKCYPHER_API_KEY || '');
/**
* Blockcypher data providers for Litecoin.
*/
const BlockcypherDataProviders = {
[xchainClient.Network.Testnet]: undefined,
[xchainClient.Network.Stagenet]: mainnetBlockcypherProvider,
[xchainClient.Network.Mainnet]: mainnetBlockcypherProvider,
};
/**
* Bitgo data providers for Litecoin.
*/
const mainnetBitgoProvider = new xchainUtxoProviders.BitgoProvider({
baseUrl: 'https://app.bitgo.com',
chain: LTCChain,
});
const BitgoProviders = {
[xchainClient.Network.Testnet]: undefined,
[xchainClient.Network.Stagenet]: mainnetBitgoProvider,
[xchainClient.Network.Mainnet]: mainnetBitgoProvider,
};
/**
* Broadcasts a transaction to the blockchain network.
*
* @see https://developer.bitcoin.org/reference/rpc/sendrawtransaction.html
* @param {BroadcastTxParams} params Parameters for broadcasting the transaction.
* @returns {Promise<string>} The transaction ID if successful.
*/
const broadcastTx$1 = (_a) => __awaiter(void 0, [_a], void 0, function* ({ txHex, auth, nodeUrl }) {
const uniqueId = new Date().getTime().toString(); // Generates a unique ID for the request
const postData = {
jsonrpc: '2.0',
method: 'sendrawtransaction',
params: [txHex],
id: uniqueId,
};
let response;
// Posts the transaction data to the specified node URL, optionally with authentication
if (auth) {
response = (yield axios__default.default.post(nodeUrl, postData, { auth })).data;
}
else {
response = (yield axios__default.default.post(nodeUrl, postData)).data;
}
// Throws an error if the response contains an error message
if (response.error) {
throw new Error(`Failed to broadcast the transaction: ${response.error}`);
}
// Returns the transaction ID from the response result
return response.result;
});
/**
* Size of an empty transaction in bytes.
*/
const TX_EMPTY_SIZE = 4 + 1 + 1 + 4; // 10
/**
* Base size of a transaction input in bytes.
*/
const TX_INPUT_BASE = 32 + 4 + 1 + 4; // 41
/**
* Size of a transaction input with a pubkey hash in bytes.
*/
const TX_INPUT_PUBKEYHASH = 107;
/**
* Base size of a transaction output in bytes.
*/
const TX_OUTPUT_BASE = 8 + 1; // 9
/**
* Size of a transaction output with a pubkey hash in bytes.
*/
const TX_OUTPUT_PUBKEYHASH = 25;
/**
* Calculate the size of a transaction input in bytes.
*
* @param {UTXO} input The UTXO.
* @returns {number} The size of the transaction input.
*/
function inputBytes(input) {
var _a;
return TX_INPUT_BASE + (((_a = input.witnessUtxo) === null || _a === void 0 ? void 0 : _a.script) ? input.witnessUtxo.script.length : TX_INPUT_PUBKEYHASH);
}
/**
* Get the Litecoin network to be used with bitcoinjs.
*
* @param {Network} network The network identifier.
* @returns {Litecoin.Network} The Litecoin network.
*/
const ltcNetwork = (network) => {
switch (network) {
case xchainClient.Network.Mainnet:
case xchainClient.Network.Stagenet:
return xchainUtxo.toBitcoinJS('litecoin', 'main');
case xchainClient.Network.Testnet:
return xchainUtxo.toBitcoinJS('litecoin', 'test');
}
};
/**
* Validate a Litecoin address.
*
* @param {Address} address The Litecoin address to validate.
* @param {Network} network The network identifier.
* @returns {boolean} `true` if the address is valid, `false` otherwise.
*/
const validateAddress = (address, network) => {
try {
Litecoin__namespace.address.toOutputScript(address, ltcNetwork(network));
return true;
}
catch (_error) {
return false;
}
};
/**
* Broadcast a transaction.
*
* @param {BroadcastTxParams} params The parameters for broadcasting the transaction.
* @returns {Promise<TxHash>} The hash of the broadcasted transaction.
*/
const broadcastTx = (params) => __awaiter(void 0, void 0, void 0, function* () {
return yield broadcastTx$1(params);
});
/**
* Get the address prefix based on the network.
*
* @param {Network} network The network identifier.
* @returns {string} The address prefix based on the network.
**/
const getPrefix = (network) => {
switch (network) {
case xchainClient.Network.Mainnet:
case xchainClient.Network.Stagenet:
return 'ltc1';
case xchainClient.Network.Testnet:
return 'tltc1';
}
};
/**
* Default parameters for the Litecoin client.
*/
const defaultLtcParams = {
network: xchainClient.Network.Mainnet,
phrase: '',
explorerProviders: explorerProviders,
dataProviders: [BitgoProviders, BlockcypherDataProviders],
rootDerivationPaths: {
[xchainClient.Network.Mainnet]: `m/84'/2'/0'/0/`,
[xchainClient.Network.Testnet]: `m/84'/1'/0'/0/`,
[xchainClient.Network.Stagenet]: `m/84'/2'/0'/0/`,
},
feeBounds: {
lower: LOWER_FEE_BOUND,
upper: UPPER_FEE_BOUND,
},
};
/**
* Custom Litecoin client.
*/
class Client extends xchainUtxo.Client {
/**
* Constructs a new `Client` with the provided parameters.
*
* @param {UtxoClientParams} params The parameters for initializing the client.
*/
constructor(params = defaultLtcParams) {
super(LTCChain, {
network: params.network,
rootDerivationPaths: params.rootDerivationPaths,
phrase: params.phrase,
feeBounds: params.feeBounds,
explorerProviders: params.explorerProviders,
dataProviders: params.dataProviders,
});
}
/**
* Returns information about the asset used by the client.
*
* @returns {AssetInfo} Information about the asset.
*/
getAssetInfo() {
const assetInfo = {
asset: AssetLTC,
decimal: LTC_DECIMAL,
};
return assetInfo;
}
/**
* Validates the given Litecoin address.
*
* @param {string} address The Litecoin address to validate.
* @returns {boolean} `true` if the address is valid, `false` otherwise.
*/
validateAddress(address) {
return validateAddress(address, this.network);
}
/**
* Builds a Litecoin (LTC) transaction.
*
* @param {BuildParams} params The transaction build options.
* @returns {Transaction} A promise that resolves to the PSBT (Partially Signed Bitcoin Transaction) and UTXOs (Unspent Transaction Outputs).
* @deprecated This function will eventually be removed. Use `prepareTx` instead.
*/
buildTx(_a) {
return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, }) {
if (!this.validateAddress(recipient))
throw new Error('Invalid address');
const utxos = yield this.scanUTXOs(sender, false);
if (utxos.length === 0)
throw new Error('No utxos to send');
const feeRateWhole = Number(feeRate.toFixed(0));
const compiledMemo = memo ? this.compileMemo(memo) : null;
const targetOutputs = [];
//1. add output amount and recipient to targets
targetOutputs.push({
address: recipient,
value: amount.amount().toNumber(),
});
//2. add output memo to targets (optional)
if (compiledMemo) {
targetOutputs.push({ script: compiledMemo, value: 0 });
}
const { inputs, outputs } = accumulative__default.default(utxos, targetOutputs, feeRateWhole);
// .inputs and .outputs will be undefined if no solution was found
if (!inputs || !outputs)
throw new Error('Insufficient Balance for transaction');
const psbt = new Litecoin__namespace.Psbt({ network: ltcNetwork(this.network) }); // Network-specific
// psbt add input from accumulative inputs
inputs.forEach((utxo) => psbt.addInput({
hash: utxo.hash,
index: utxo.index,
witnessUtxo: utxo.witnessUtxo,
}));
// Outputs
outputs.forEach((output) => {
if (!output.address) {
//an empty address means this is the change address
output.address = sender;
}
if (!output.script) {
psbt.addOutput(output);
}
else {
//we need to add the compiled memo this way to
//avoid dust error tx when accumulating memo output with 0 value
if (compiledMemo) {
psbt.addOutput({ script: compiledMemo, value: 0 });
}
}
});
return { psbt, utxos, inputs };
});
}
/**
* Prepares a Litecoin (LTC) transaction.
*
* @deprecated Use `prepareTxEnhanced` instead for better UTXO selection and error handling.
* @param {TxParams&Address&FeeRate&boolean} params The transfer options.
* @returns {PreparedTx} A promise that resolves to the raw unsigned transaction.
*/
prepareTx(_a) {
return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, feeRate, }) {
const { psbt, utxos, inputs } = yield this.buildTx({
sender,
recipient,
amount,
feeRate,
memo,
});
return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
});
}
/**
* Compile memo.
*
* @param {string} memo The memo to be compiled.
* @returns {Buffer} The compiled memo.
*/
compileMemo(memo) {
const data = Buffer.from(memo, 'utf8'); // converts MEMO to buffer
return Litecoin__namespace.script.compile([Litecoin__namespace.opcodes.OP_RETURN, data]); // Compile OP_RETURN script
}
/**
* Calculates the transaction fee based on the provided UTXOs, fee rate, and optional compiled memo.
*
* @param {UTXO[]} inputs The The UTXOs used as inputs in the transaction.
* @param {FeeRate} feeRate The fee rate.
* @param {Buffer} data The compiled memo (Optional).
* @returns {number} The calculated fee amount.
*/
getFeeFromUtxos(inputs, feeRate, data = null) {
// Calculate transaction size based on inputs and outputs
const inputSizeBasedOnInputs = inputs.length > 0
? inputs.reduce((a, x) => a + inputBytes(x), 0) + inputs.length // +1 byte for each input signature
: 0;
let sum = TX_EMPTY_SIZE +
inputSizeBasedOnInputs +
inputs.length + // +1 byte for each input signature
TX_OUTPUT_BASE +
TX_OUTPUT_PUBKEYHASH +
TX_OUTPUT_BASE +
TX_OUTPUT_PUBKEYHASH;
// Add additional output size if memo is provided
if (data) {
sum += TX_OUTPUT_BASE + data.length;
}
// Calculate fee
const fee = sum * feeRate;
return fee > MIN_TX_FEE ? fee : MIN_TX_FEE;
}
// ==================== Enhanced Transaction Methods ====================
/**
* Build transaction with enhanced UTXO selection
*/
buildTxEnhanced(_a) {
return __awaiter(this, arguments, void 0, function* ({ amount, recipient, memo, feeRate, sender, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
try {
// Validate inputs using base class method
this.validateTransactionInputs({
amount,
recipient,
memo,
sender,
feeRate,
});
// Use provided UTXOs (coin control) or fetch from chain
let utxos;
if (selectedUtxos && selectedUtxos.length > 0) {
xchainUtxo.UtxoTransactionValidator.validateUtxoSet(selectedUtxos);
utxos = selectedUtxos;
}
else {
const confirmedOnly = !spendPendingUTXO;
utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
}
const compiledMemo = memo ? this.compileMemo(memo) : null;
const targetValue = amount.amount().toNumber();
const extraOutputs = 1 + (compiledMemo ? 1 : 0);
// Use base class UTXO selection
const selectionResult = this.selectUtxosForTransaction(utxos, targetValue, Math.ceil(feeRate), extraOutputs, utxoSelectionPreferences);
const psbt = new Litecoin__namespace.Psbt({ network: ltcNetwork(this.network) });
// Add inputs
selectionResult.inputs.forEach((utxo) => psbt.addInput({
hash: utxo.hash,
index: utxo.index,
witnessUtxo: utxo.witnessUtxo,
}));
// Add recipient output
psbt.addOutput({
address: recipient,
value: targetValue,
});
// Add change output if needed
if (selectionResult.changeAmount > 0) {
psbt.addOutput({
address: sender,
value: selectionResult.changeAmount,
});
}
// Add memo output if present
if (compiledMemo) {
psbt.addOutput({ script: compiledMemo, value: 0 });
}
return { psbt, utxos, inputs: selectionResult.inputs };
}
catch (error) {
if (xchainUtxo.UtxoError.isUtxoError(error)) {
throw error;
}
throw xchainUtxo.UtxoError.fromUnknown(error, 'buildTxEnhanced');
}
});
}
/**
* Prepare transaction with enhanced UTXO selection
*/
prepareTxEnhanced(_a) {
return __awaiter(this, arguments, void 0, function* ({ sender, memo, amount, recipient, spendPendingUTXO = true, feeRate, utxoSelectionPreferences, selectedUtxos, }) {
try {
const { psbt, utxos, inputs } = yield this.buildTxEnhanced({
sender,
recipient,
amount,
feeRate,
memo,
spendPendingUTXO,
utxoSelectionPreferences,
selectedUtxos,
});
return { rawUnsignedTx: psbt.toBase64(), utxos, inputs };
}
catch (error) {
if (xchainUtxo.UtxoError.isUtxoError(error)) {
throw error;
}
throw xchainUtxo.UtxoError.fromUnknown(error, 'prepareTxEnhanced');
}
});
}
/**
* Send maximum possible amount (sweep)
*/
sendMax(_a) {
return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
try {
// Validate addresses
if (!this.validateAddress(recipient)) {
throw xchainUtxo.UtxoError.invalidAddress(recipient, this.network);
}
if (!this.validateAddress(sender)) {
throw xchainUtxo.UtxoError.invalidAddress(sender, this.network);
}
// Use provided UTXOs (coin control) or fetch from chain
let utxos;
if (selectedUtxos && selectedUtxos.length > 0) {
xchainUtxo.UtxoTransactionValidator.validateUtxoSet(selectedUtxos);
utxos = selectedUtxos;
}
else {
const confirmedOnly = !spendPendingUTXO;
utxos = yield this.getValidatedUtxos(sender, confirmedOnly);
}
// Calculate max using base class method
const maxCalc = this.calculateMaxSendableAmount(utxos, Math.ceil(feeRate), !!memo, utxoSelectionPreferences);
const compiledMemo = memo ? this.compileMemo(memo) : null;
const psbt = new Litecoin__namespace.Psbt({ network: ltcNetwork(this.network) });
// Add inputs
maxCalc.inputs.forEach((utxo) => psbt.addInput({
hash: utxo.hash,
index: utxo.index,
witnessUtxo: utxo.witnessUtxo,
}));
// Add recipient output (max amount - no change)
psbt.addOutput({
address: recipient,
value: maxCalc.amount,
});
// Add memo output if present
if (compiledMemo) {
psbt.addOutput({ script: compiledMemo, value: 0 });
}
return {
psbt,
utxos,
inputs: maxCalc.inputs,
maxAmount: maxCalc.amount,
fee: maxCalc.fee,
};
}
catch (error) {
if (xchainUtxo.UtxoError.isUtxoError(error)) {
throw error;
}
throw xchainUtxo.UtxoError.fromUnknown(error, 'sendMax');
}
});
}
/**
* Prepare max send transaction
*/
prepareMaxTx(_a) {
return __awaiter(this, arguments, void 0, function* ({ sender, recipient, memo, feeRate, spendPendingUTXO = true, utxoSelectionPreferences, selectedUtxos, }) {
try {
const { psbt, utxos, inputs, maxAmount, fee } = yield this.sendMax({
sender,
recipient,
memo,
feeRate,
spendPendingUTXO,
utxoSelectionPreferences,
selectedUtxos,
});
return {
rawUnsignedTx: psbt.toBase64(),
utxos,
inputs,
maxAmount,
fee,
};
}
catch (error) {
if (xchainUtxo.UtxoError.isUtxoError(error)) {
throw error;
}
throw xchainUtxo.UtxoError.fromUnknown(error, 'prepareMaxTx');
}
});
}
}
const ECPair = ecpair.ECPairFactory(ecc__namespace);
class ClientKeystore extends Client {
/**
* [DEPRECATED] Retrieves the address at the specified index.
*
* @deprecated Use `getAddressAsync` instead.
* @param {number} index The index of the address.
* @returns {Address} The address at the specified index.
* @throws {Error} Thrown when the index is less than zero.
* @throws {Error} Thrown when the phrase has not been set.
* @throws {Error} Thrown when the address cannot be defined.
*/
getAddress(index = 0) {
if (index < 0) {
throw new Error('index must be greater than zero');
}
if (this.phrase) {
const ltcNetwork$1 = ltcNetwork(this.network);
const ltcKeys = this.getLtcKeys(this.phrase, index);
const { address } = Litecoin__namespace.payments.p2wpkh({
pubkey: ltcKeys.publicKey,
network: ltcNetwork$1,
});
if (!address) {
throw new Error('Address not defined');
}
return address;
}
throw new Error('Phrase must be provided');
}
/**
* Retrieves the current address asynchronously.
*
* Generates a network-specific key-pair by first converting the buffer to a Wallet-Import-Format (WIF)
* The address is then decoded into type P2WPKH and returned.
*
* @returns {Address} A promise that resolves to the current address
*
* @throws {"Phrase must be provided"} Thrown if phrase has not been set before.
* @throws {"Address not defined"} Thrown if failed creating account from phrase.
*/
getAddressAsync() {
return __awaiter(this, arguments, void 0, function* (walletIndex = 0) {
return this.getAddress(walletIndex);
});
}
/**
* Transfers Litecoin (LTC) from one address to another.
*
* @param {Object} params The transfer options.
* @returns {Promise<TxHash>} The transaction hash.
*/
transfer(params) {
return __awaiter(this, void 0, void 0, function* () {
const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
xchainClient.checkFeeBounds(this.feeBounds, feeRate);
const fromAddressIndex = params.walletIndex || 0;
const sender = yield this.getAddressAsync(fromAddressIndex);
const ltcKeys = this.getLtcKeys(this.phrase, fromAddressIndex);
const mergedPreferences = Object.assign({ minimizeFee: true, avoidDust: true, minimizeInputs: false }, params.utxoSelectionPreferences);
const { rawUnsignedTx } = yield this.prepareTxEnhanced(Object.assign(Object.assign({}, params), { feeRate,
sender, utxoSelectionPreferences: mergedPreferences, selectedUtxos: params.selectedUtxos }));
const psbt = Litecoin__namespace.Psbt.fromBase64(rawUnsignedTx);
psbt.signAllInputs(ltcKeys);
psbt.finalizeAllInputs();
const txHex = psbt.extractTransaction().toHex();
return yield this.broadcastTx(txHex);
});
}
/**
* Transfer the maximum amount of Litecoin (sweep).
*
* Calculates the maximum sendable amount after fees, signs, and broadcasts the transaction.
* @param {Object} params The transfer parameters.
* @param {string} params.recipient The recipient address.
* @param {string} [params.memo] Optional memo for the transaction.
* @param {FeeRate} [params.feeRate] Optional fee rate. Defaults to 'fast' rate.
* @param {number} [params.walletIndex] Optional wallet index. Defaults to 0.
* @param {UtxoSelectionPreferences} [params.utxoSelectionPreferences] Optional UTXO selection preferences.
* @returns {Promise<{ hash: TxHash; maxAmount: number; fee: number }>} The transaction hash, amount sent, and fee.
*/
transferMax(params) {
return __awaiter(this, void 0, void 0, function* () {
const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
xchainClient.checkFeeBounds(this.feeBounds, feeRate);
const fromAddressIndex = params.walletIndex || 0;
const sender = yield this.getAddressAsync(fromAddressIndex);
const { psbt, maxAmount, fee } = yield this.sendMax({
sender,
recipient: params.recipient,
memo: params.memo,
feeRate,
utxoSelectionPreferences: params.utxoSelectionPreferences,
selectedUtxos: params.selectedUtxos,
});
const ltcKeys = this.getLtcKeys(this.phrase, fromAddressIndex);
psbt.signAllInputs(ltcKeys);
psbt.finalizeAllInputs();
const txHex = psbt.extractTransaction().toHex();
const hash = yield this.broadcastTx(txHex);
return { hash, maxAmount, fee };
});
}
/**
* @private
* [PRIVATE] Retrieves the private key.
*
* Private function to get keyPair from the this.phrase
*
* @param {string} phrase The phrase used to generate the private key.
* @returns {ECPairInterface} The privkey generated from the given phrase
*
* @throws {"Could not get private key from phrase"} Throws an error if failed creating LTC keys from the given phrase
* */
getLtcKeys(phrase, index = 0) {
const ltcNetwork$1 = ltcNetwork(this.network);
const seed = xchainCrypto.getSeed(phrase);
const master = bip32.HDKey.fromMasterSeed(Uint8Array.from(seed), ltcNetwork$1.bip32).derive(this.getFullDerivationPath(index));
if (!master.privateKey) {
throw new Error('Could not get private key from phrase');
}
return ECPair.fromPrivateKey(Buffer.from(master.privateKey), { network: ltcNetwork$1 });
}
}
/**
* Custom Ledger Litecoin client
*/
class ClientLedger extends Client {
// Constructor
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(params) {
super(params);
this.transport = params.transport;
}
// Get the Ledger BTC application instance
getApp() {
return __awaiter(this, void 0, void 0, function* () {
if (this.app) {
return this.app;
}
this.app = new AppBtc__default.default({ transport: this.transport, currency: 'litecoin' });
return this.app;
});
}
// Get the current address synchronously
getAddress() {
throw Error('Sync method not supported for Ledger');
}
// Get the current address asynchronously
getAddressAsync() {
return __awaiter(this, arguments, void 0, function* (index = 0, verify = false) {
const app = yield this.getApp();
const result = yield app.getWalletPublicKey(this.getFullDerivationPath(index), {
format: 'bech32',
verify,
});
return result.bitcoinAddress;
});
}
// Transfer LTC from Ledger
transfer(params) {
return __awaiter(this, void 0, void 0, function* () {
const app = yield this.getApp();
const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
// Get fee rate
const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
xchainClient.checkFeeBounds(this.feeBounds, feeRate);
// Get sender address
const sender = yield this.getAddressAsync(fromAddressIndex);
// Prepare transaction
const { rawUnsignedTx, inputs } = yield this.prepareTx(Object.assign(Object.assign({}, params), { sender, feeRate }));
const psbt = Litecoin__namespace.Psbt.fromBase64(rawUnsignedTx);
// Prepare Ledger inputs
const ledgerInputs = inputs.map(({ txHex, hash, index }) => {
if (!txHex) {
throw Error(`Missing 'txHex' for UTXO (txHash ${hash})`);
}
const utxoTx = Litecoin__namespace.Transaction.fromHex(txHex);
const splittedTx = app.splitTransaction(txHex, utxoTx.hasWitnesses());
return [splittedTx, index, null, null];
});
// Prepare associated keysets
const associatedKeysets = ledgerInputs.map(() => this.getFullDerivationPath(fromAddressIndex));
// Serialize unsigned transaction
const unsignedHex = psbt.data.globalMap.unsignedTx.toBuffer().toString('hex');
const newTx = app.splitTransaction(unsignedHex, true);
const outputScriptHex = app.serializeTransactionOutputs(newTx).toString('hex');
// Create payment transaction
const txHex = yield app.createPaymentTransaction({
inputs: ledgerInputs,
associatedKeysets,
outputScriptHex,
segwit: true,
useTrustedInputForSegwit: true,
additionals: ['bech32'],
});
// Broadcast transaction
const txHash = yield this.broadcastTx(txHex);
// Throw error if no transaction hash is received
if (!txHash) {
throw Error('No Tx hash');
}
return txHash;
});
}
// Transfer max LTC from Ledger (sweep transaction)
transferMax(params) {
return __awaiter(this, void 0, void 0, function* () {
const app = yield this.getApp();
const fromAddressIndex = (params === null || params === void 0 ? void 0 : params.walletIndex) || 0;
const feeRate = params.feeRate || (yield this.getFeeRates())[xchainClient.FeeOption.Fast];
xchainClient.checkFeeBounds(this.feeBounds, feeRate);
const sender = yield this.getAddressAsync(fromAddressIndex);
const { rawUnsignedTx, inputs, maxAmount, fee } = yield this.prepareMaxTx({
sender,
recipient: params.recipient,
memo: params.memo,
feeRate,
utxoSelectionPreferences: params.utxoSelectionPreferences,
selectedUtxos: params.selectedUtxos,
});
const psbt = Litecoin__namespace.Psbt.fromBase64(rawUnsignedTx);
const ledgerInputs = inputs.map(({ txHex, hash, index }) => {
if (!txHex) {
throw Error(`Missing 'txHex' for UTXO (txHash ${hash})`);
}
const utxoTx = Litecoin__namespace.Transaction.fromHex(txHex);
const splittedTx = app.splitTransaction(txHex, utxoTx.hasWitnesses());
return [splittedTx, index, null, null];
});
const associatedKeysets = ledgerInputs.map(() => this.getFullDerivationPath(fromAddressIndex));
const unsignedHex = psbt.data.globalMap.unsignedTx.toBuffer().toString('hex');
const newTx = app.splitTransaction(unsignedHex, true);
const outputScriptHex = app.serializeTransactionOutputs(newTx).toString('hex');
const txHex = yield app.createPaymentTransaction({
inputs: ledgerInputs,
associatedKeysets,
outputScriptHex,
segwit: true,
useTrustedInputForSegwit: true,
additionals: ['bech32'],
});
const hash = yield this.broadcastTx(txHex);
if (!hash) {
throw Error('No Tx hash');
}
return { hash, maxAmount, fee };
});
}
}
exports.AssetLTC = AssetLTC;
exports.BitgoProviders = BitgoProviders;
exports.BlockcypherDataProviders = BlockcypherDataProviders;
exports.Client = ClientKeystore;
exports.ClientKeystore = ClientKeystore;
exports.ClientLedger = ClientLedger;
exports.LOWER_FEE_BOUND = LOWER_FEE_BOUND;
exports.LTCChain = LTCChain;
exports.LTC_DECIMAL = LTC_DECIMAL;
exports.MIN_TX_FEE = MIN_TX_FEE;
exports.UPPER_FEE_BOUND = UPPER_FEE_BOUND;
exports.broadcastTx = broadcastTx;
exports.defaultLtcParams = defaultLtcParams;
exports.explorerProviders = explorerProviders;
exports.getPrefix = getPrefix;
exports.sochainDataProviders = sochainDataProviders;
exports.validateAddress = validateAddress;