UNPKG

@thorwallet/xchain-bitcoincash

Version:

Custom bitcoincash client and utilities used by XChainJS clients

376 lines 14.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = void 0; const tslib_1 = require("tslib"); const bitcash = require('@psf/bitcoincashjs-lib'); const utils = tslib_1.__importStar(require("./utils")); const xchain_crypto_1 = require("@thorwallet/xchain-crypto"); const haskoin_api_1 = require("./haskoin-api"); const node_api_1 = require("./node-api"); const react_native_simple_crypto_1 = tslib_1.__importDefault(require("react-native-simple-crypto")); const bitcoinjs_lib_1 = require("bitcoinjs-lib"); const get_address_1 = require("./get-address"); const BigInteger = require('bigi'); const ENABLE_FAST = true; /** * Custom Bitcoin Cash client */ class Client { /** * Constructor * Client is initialised with network type * * @param {BitcoinCashClientParams} params */ constructor({ network = 'testnet', haskoinUrl = { testnet: 'https://api.haskoin.com/bchtest', mainnet: 'https://api.haskoin.com/bch', }, nodeUrl = { testnet: 'https://testnet.bch.thorchain.info', mainnet: 'https://bch.thorchain.info', }, nodeAuth = { username: 'thorchain', password: 'password', }, rootDerivationPaths = { mainnet: `m/44'/145'/0'/0/`, testnet: `m/44'/1'/0'/0/`, }, }) { this.phrase = ''; /** * Set/Update the haskoin url. * * @param {string} url The new haskoin url. * @returns {void} */ this.setHaskoinURL = (url) => { this.haskoinUrl = url; }; /** * Get the haskoin url. * * @returns {string} The haskoin url based on the current network. */ this.getHaskoinURL = () => { return this.haskoinUrl[this.getNetwork()]; }; /** * Set/Update the node url. * * @param {string} url The new node url. * @returns {void} */ this.setNodeURL = (url) => { this.nodeUrl = url; }; /** * Get the node url. * * @returns {string} The node url for thorchain based on the current network. */ this.getNodeURL = () => { return this.nodeUrl[this.getNetwork()]; }; /** * Set/update a new phrase. * * @param {string} phrase A new phrase. * @param {string} derivationPath bip44 derivation path * @returns {Address} The address from the given phrase * * @throws {"Invalid phrase"} * Thrown if the given phase is invalid. */ this.setPhrase = (phrase, walletIndex = 0) => tslib_1.__awaiter(this, void 0, void 0, function* () { if (xchain_crypto_1.validatePhrase(phrase)) { this.phrase = phrase; return get_address_1.getAddress({ network: this.network, phrase: this.phrase, index: walletIndex }); } else { throw new Error('Invalid phrase'); } }); /** * Purge client. * * @returns {void} */ this.purgeClient = () => { this.phrase = ''; }; /** * Set/update the current network. * * @param {Network} network `mainnet` or `testnet`. * @returns {void} * * @throws {"Network must be provided"} * Thrown if network has not been set before. */ this.setNetwork = (net) => { if (!net) { throw new Error('Network must be provided'); } this.network = net; }; /** * Get the current network. * * @returns {Network} The current network. (`mainnet` or `testnet`) */ this.getNetwork = () => { return this.network; }; /** * Get the explorer url. * * @returns {string} The explorer url based on the network. */ this.getExplorerUrl = () => { const networkPath = utils.isTestnet(this.network) ? 'bch-testnet' : 'bch'; return `https://www.blockchain.com/${networkPath}`; }; /** * Get the explorer url for the given address. * * @param {Address} address * @returns {string} The explorer url for the given address based on the network. */ this.getExplorerAddressUrl = (address) => { return `${this.getExplorerUrl()}/address/${address}`; }; /** * Get the explorer url for the given transaction id. * * @param {string} txID The transaction id * @returns {string} The explorer url for the given transaction id based on the network. */ this.getExplorerTxUrl = (txID) => { return `${this.getExplorerUrl()}/tx/${txID}`; }; /** * @private * Get private key. * * Private function to get keyPair from the this.phrase * * @param {string} phrase The phrase to be used for generating privkey * @param {string} derivationPath BIP44 derivation path * @returns {PrivateKey} The privkey generated from the given phrase * * @throws {"Invalid phrase"} Thrown if invalid phrase is provided. * */ this.getBCHKeys = (phrase, derivationPath) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const rootSeed = yield xchain_crypto_1.getSeed(phrase); if (ENABLE_FAST) { const master = yield (yield xchain_crypto_1.bip32.fromSeed(rootSeed, utils.bchNetwork(this.network))).derivePath(derivationPath); const d = BigInteger.fromBuffer(master.privateKey); const btcKeyPair = new bitcash.ECPair(d, null, { network: utils.bchNetwork(this.network), compressed: true, }); return btcKeyPair; } const masterHDNode = bitcash.HDNode.fromSeedBuffer(rootSeed, utils.bchNetwork(this.network)); const keyPair = yield masterHDNode.derivePath(derivationPath).keyPair; return keyPair; } catch (error) { throw new Error(`Getting key pair failed: ${(error === null || error === void 0 ? void 0 : error.message) || error.toString()}`); } }); /** * Validate the given address. * * @param {Address} address * @returns {boolean} `true` or `false` */ this.validateAddress = (address) => { return utils.validateAddress(address, this.network); }; /** * Get transaction history of a given address with pagination options. * By default it will return the transaction history of the current wallet. * * @param {TxHistoryParams} params The options to get transaction history. (optional) * @returns {TxsPage} The transaction history. * * @throws {"Invalid address"} Thrown if the given address is an invalid address. */ this.getTransactions = ({ address, offset, limit }) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { offset = offset || 0; limit = limit || 10; const account = yield haskoin_api_1.getAccount({ haskoinUrl: this.getHaskoinURL(), address }); const txs = yield haskoin_api_1.getTransactions({ haskoinUrl: this.getHaskoinURL(), address, params: { offset, limit }, }); if (!account || !txs) { throw new Error('Invalid address'); } return { total: account.txs, txs: txs.map(utils.parseTransaction), }; } catch (error) { return Promise.reject(error); } }); /** * Get the transaction details of a given transaction id. * * @param {string} txId The transaction id. * @returns {Tx} The transaction details of the given transaction id. * * @throws {"Invalid TxID"} Thrown if the given transaction id is an invalid one. */ this.getTransactionData = (txId) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const tx = yield haskoin_api_1.getTransaction({ haskoinUrl: this.getHaskoinURL(), txId }); if (!tx) { throw new Error('Invalid TxID'); } return utils.parseTransaction(tx); } catch (error) { return Promise.reject(error); } }); /** * Get the rates and fees. * * @param {string} memo The memo to be used for fee calculation (optional) * @returns {FeesWithRates} The fees and rates */ this.getFeesWithRates = (memo) => tslib_1.__awaiter(this, void 0, void 0, function* () { const nextBlockFeeRate = yield haskoin_api_1.getSuggestedFee(); const rates = { fastest: nextBlockFeeRate * 5, fast: nextBlockFeeRate * 1, average: nextBlockFeeRate * 0.5, }; const fees = { type: 'byte', fast: utils.calcFee(rates.fast, memo), average: utils.calcFee(rates.average, memo), fastest: utils.calcFee(rates.fastest, memo), }; return { fees, rates }; }); /** * Get the current fees. * * @returns {Fees} The fees without memo */ this.getFees = () => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const { fees } = yield this.getFeesWithRates(); return fees; } catch (error) { return Promise.reject(error); } }); /** * Get the fees for transactions with memo. * If you want to get `Fees` and `FeeRates` at once, use `getFeesAndRates` method * * @param {string} memo * @returns {Fees} The fees with memo */ this.getFeesWithMemo = (memo) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const { fees } = yield this.getFeesWithRates(memo); return fees; } catch (error) { return Promise.reject(error); } }); /** * Get the fee rates for transactions without a memo. * If you want to get `Fees` and `FeeRates` at once, use `getFeesAndRates` method * * @returns {FeeRates} The fee rate */ this.getFeeRates = () => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const { rates } = yield this.getFeesWithRates(); return rates; } catch (error) { return Promise.reject(error); } }); /** * Sign an arbitrary string message. * * * @returns {Signature} The current address. * * @throws {"Phrase must be provided"} Thrown if phrase has not been set before. */ this.signMessage = (msg, index = 0) => tslib_1.__awaiter(this, void 0, void 0, function* () { const msgHash = yield react_native_simple_crypto_1.default.SHA.sha256(msg); const msgBuffer = Buffer.from(msgHash, 'hex'); const keys = yield this.getBCHKeys(this.phrase, this.getFullDerivationPath(index)); const wif = keys.toWIF(); // use bitcoinjs-lib ECPair, because @psf/bitcoincashjs-lib library operates on lower cryptographic level const ecpair = bitcoinjs_lib_1.ECPair.fromWIF(wif, keys.getNetwork()); const signature = ecpair.sign(msgBuffer).toString('hex'); const pubKey = ecpair.publicKey.toString('hex'); return { signature, pubKey }; }); /** * Transfer BCH. * * @param {TxParams&FeeRate} params The transfer options. * @returns {TxHash} The transaction hash. */ this.transfer = (params) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const index = params.walletIndex || 0; const derivationPath = this.rootDerivationPaths[this.network] + `${index}`; const feeRate = params.feeRate || (yield this.getFeeRates()).fast; const { builder, inputUTXOs } = yield utils.buildTx(Object.assign(Object.assign({}, params), { feeRate, sender: yield get_address_1.getAddress({ network: this.network, phrase: this.phrase, index: 0 }), haskoinUrl: this.getHaskoinURL(), network: this.network })); const keyPair = yield this.getBCHKeys(this.phrase, derivationPath); inputUTXOs.forEach((utxo, index) => { builder.sign(index, keyPair, undefined, 0x41, utxo.witnessUtxo.value); }); const tx = builder.build(); const txHex = tx.toHex(); return yield node_api_1.broadcastTx({ network: this.network, txHex, nodeUrl: this.getNodeURL(), auth: this.nodeAuth, }); } catch (e) { return Promise.reject(e); } }); this.network = network; this.haskoinUrl = haskoinUrl; this.nodeUrl = nodeUrl; this.rootDerivationPaths = rootDerivationPaths; this.nodeAuth = // Leave possibility to send requests without auth info for user // by strictly passing nodeAuth as null value nodeAuth === null ? undefined : nodeAuth; } /** * Get getFullDerivationPath * * @param {number} index the HD wallet index * @returns {string} The derivation path based on the network. */ getFullDerivationPath(index) { return this.rootDerivationPaths[this.network] + `${index}`; } } exports.Client = Client; //# sourceMappingURL=client.js.map