@thorwallet/xchain-bitcoincash
Version:
Custom bitcoincash client and utilities used by XChainJS clients
376 lines • 14.7 kB
JavaScript
;
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