@xchainjs/xchain-binance
Version:
Custom Binance client and utilities used by XChainJS clients
612 lines (595 loc) • 24.9 kB
JavaScript
'use strict';
var client = require('@binance-chain/javascript-sdk/lib/client');
var crypto = require('@binance-chain/javascript-sdk/lib/crypto');
var xchainClient = require('@xchainjs/xchain-client');
var xchainUtil = require('@xchainjs/xchain-util');
var axios = require('axios');
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 crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
var axios__default = /*#__PURE__*/_interopDefault(axios);
/******************************************************************************
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;
};
// Import necessary types from external package
/**
* Chain identifier for BNB.
* This constant represents the identifier for the Binance Chain.
*/
const BNBChain = 'BNB';
/**
* Base "chain" asset of Binance chain.
* This constant represents the base asset of the Binance Chain.
* It includes information about the chain, symbol, ticker, and whether it's synthetic or not.
*/
const AssetBNB = { chain: BNBChain, symbol: 'BNB', ticker: 'BNB', type: xchainUtil.AssetType.NATIVE };
/**
* Asset Decimal.
* This constant represents the decimal precision used for BNB.
*/
const BNB_DECIMAL = 8;
// Import necessary modules and types from external packages and files
/**
* Type guard function for 'Fee'
* @param {Fee|TransferFee|DexFees} v
* @returns {boolean} `true` or `false`.
*/
const isFee = (v) => !!(v === null || v === void 0 ? void 0 : v.msg_type) && (v === null || v === void 0 ? void 0 : v.fee) !== undefined && (v === null || v === void 0 ? void 0 : v.fee_for) !== undefined;
/**
* Type guard function for 'TransferFee'
* @param {Fee|TransferFee|DexFees} v
* @returns {boolean} `true` or `false`.
*/
const isTransferFee = (v) => isFee(v === null || v === void 0 ? void 0 : v.fixed_fee_params) && !!(v === null || v === void 0 ? void 0 : v.multi_transfer_fee);
/**
* Type guard function for 'Account'
* @param {unknown} v
* @returns {boolean} `true` or `false`.
*/
const isAccount = (v) => typeof v.account_number === 'number' &&
typeof v.address === 'string' &&
Array.isArray(v.balances) &&
Array.isArray(v.public_key) &&
typeof v.flags === 'number' &&
typeof v.sequence === 'number';
/**
* Function to determine the transaction type
* @param {BinanceTxType} t
* @returns {TxType} `transfer` or `unknown`.
*/
const getTxType = (t) => {
if (t === 'TRANSFER' || t === 'DEPOSIT')
return xchainClient.TxType.Transfer;
return xchainClient.TxType.Unknown;
};
/**
* Function to parse a transaction
* @param {BinanceTx} t The transaction to be parsed. (optional)
* @returns {Tx|null} The transaction parsed from the binance tx.
*/
const parseTx = (tx) => {
const asset = xchainUtil.assetFromString(`${BNBChain}.${tx.txAsset}`);
if (!asset)
return null;
return {
asset,
from: [
{
from: tx.fromAddr,
amount: xchainUtil.assetToBase(xchainUtil.assetAmount(tx.value, 8)),
},
],
to: [
{
to: tx.toAddr,
amount: xchainUtil.assetToBase(xchainUtil.assetAmount(tx.value, 8)),
},
],
date: new Date(tx.timeStamp),
type: getTxType(tx.txType),
hash: tx.txHash,
};
};
/**
* Function to get derivation path
* @param {number} index (optional)
* @returns {DerivePath} The binance derivation path by the index.
*/
const getDerivePath = (index = 0) => [44, 714, 0, 0, index];
/**
* Function to get default fees
* @returns {Fees} The default fee.
*/
const getDefaultFees = () => {
return xchainClient.singleFee(xchainClient.FeeType.FlatFee, xchainUtil.baseAmount(37500));
};
/**
* Function to get address prefix based on the network
* @param {Network} network
* @returns {string} The address prefix based on the network.
*
**/
const getPrefix = (network) => {
switch (network) {
case xchainClient.Network.Mainnet:
case xchainClient.Network.Stagenet:
return 'bnb';
case xchainClient.Network.Testnet:
return 'tbnb';
}
};
/**
* Custom Binance client
*/
class Client extends xchainClient.BaseXChainClient {
/**
* Constructor
* Client has to be initialised with network type and phrase.
* It will throw an error if an invalid phrase has been passed.
* @param {XChainClientParams} params
* @throws {"Invalid phrase"} Thrown if the given phase is invalid.
*/
constructor({ network = xchainClient.Network.Mainnet, phrase, rootDerivationPaths = {
[xchainClient.Network.Mainnet]: "44'/714'/0'/0/",
[xchainClient.Network.Stagenet]: "44'/714'/0'/0/",
[xchainClient.Network.Testnet]: "44'/714'/0'/0/",
}, }) {
super(BNBChain, { network, rootDerivationPaths, phrase });
this.bncClient = new client.BncClient(this.getClientUrl());
this.bncClient.chooseNetwork(this.getNetwork() === xchainClient.Network.Testnet ? xchainClient.Network.Testnet : xchainClient.Network.Mainnet);
}
/**
* Get the BncClient interface.
* @returns {BncClient} The BncClient from `@binance-chain/javascript-sdk`.
*/
getBncClient() {
return this.bncClient;
}
/**
* Set/update the current network.
* @param {Network} network
* @returns {void}
*
* @throws {"Network must be provided"}
* Thrown if network has not been set before.
*/
setNetwork(network) {
super.setNetwork(network);
this.bncClient = new client.BncClient(this.getClientUrl());
this.bncClient.chooseNetwork(this.getNetwork() === xchainClient.Network.Testnet ? xchainClient.Network.Testnet : xchainClient.Network.Mainnet);
}
/**
* Get the client URL based on the network.
* @returns {string} The client URL for binance chain based on the network.
*/
getClientUrl() {
switch (this.getNetwork()) {
case xchainClient.Network.Mainnet:
case xchainClient.Network.Stagenet:
return 'https://dex.binance.org';
case xchainClient.Network.Testnet:
return 'https://testnet-dex.binance.org';
}
}
/**
* Get the explorer URL based on the network.
*
* @returns {string} The explorer URL based on the network.
*/
getExplorerUrl() {
switch (this.getNetwork()) {
case xchainClient.Network.Mainnet:
case xchainClient.Network.Stagenet:
return 'https://explorer.binance.org';
case xchainClient.Network.Testnet:
return 'https://testnet-explorer.binance.org';
}
}
/**
* Get the explorer URL for a given address based on the network.
* @param {Address} address The address to generate the explorer URL for.
* @returns {string} The explorer URL for the given address.
*/
getExplorerAddressUrl(address) {
return `${this.getExplorerUrl()}/address/${address}`;
}
/**
* Get the explorer URL for a given transaction ID based on the network.
* @param {string} txID The transaction ID to generate the explorer URL for.
* @returns {string} The explorer URL for the given transaction ID.
*/
getExplorerTxUrl(txID) {
return `${this.getExplorerUrl()}/tx/${txID}`;
}
/**
* @private
* Get the private key for a given account index.
* @param {number} index The account index for the derivation path.
* @returns {PrivKey} The private key generated from the given phrase.
* @throws {"Phrase not set"} Thrown if the phrase has not been set befor
* Throws an error if phrase has not been set before
* */
getPrivateKey(index) {
if (!this.phrase)
throw new Error('Phrase not set');
return crypto__namespace.getPrivateKeyFromMnemonic(this.phrase, true, index);
}
/**
* Get the address for a given account index.
* @deprecated Use getAddressAsync instead.
*/
getAddress(index = 0) {
return crypto__namespace.getAddressFromPrivateKey(this.getPrivateKey(index), getPrefix(this.network));
}
/**
* Get the current address asynchronously for a given account index.
* @param {number} index The account index for the derivation path. (optional)
* @returns {Address} A promise resolving to the current address.
* @throws {Error} Thrown if the phrase has not been set before.
* A phrase is needed to create a wallet and to derive an address from it.
*/
getAddressAsync(index = 0) {
return __awaiter(this, void 0, void 0, function* () {
return this.getAddress(index);
});
}
/**
* Validate the given address.
* @param {Address} address The address to validate.
* @returns {boolean} `true` if the address is valid, `false` otherwise.
*/
validateAddress(address) {
return this.bncClient.checkAddress(address, getPrefix(this.network));
}
/**
* Get asset information.
* @returns Asset information.
*/
getAssetInfo() {
const assetInfo = {
asset: AssetBNB,
decimal: BNB_DECIMAL,
};
return assetInfo;
}
/**
* Get account data for a given address.
* @param {Address} address The address to get account data for. (optional)
* By default, it will return account data for the current wallet.
* @param {number} index The account index for the derivation path. (optional)
* @returns {Account} A promise resolving to the account details of the given address.
*/
getAccount(address, index = 0) {
return __awaiter(this, void 0, void 0, function* () {
const accountAddress = address || (yield this.getAddressAsync(index));
const response = yield this.bncClient.getAccount(accountAddress);
if (!response || !response.result || !isAccount(response.result))
return Promise.reject(Error(`Could not get account data for address ${accountAddress}`));
return response.result;
});
}
/**
* Get the balance of a given address.
* @param {Address} address The address to get the balance for. (optional)
* By default, it will return the balance of the current wallet.
* @param {(Asset | TokenAsset)[]} asset If not set, it will return all assets available. (optional)
* @returns {Balance[]} The balance of the address.
*/
getBalance(address, assets) {
return __awaiter(this, void 0, void 0, function* () {
const balances = yield this.bncClient.getBalance(address);
return balances
.map((balance) => {
return {
asset: xchainUtil.assetFromString(`${BNBChain}.${balance.symbol}`) || AssetBNB,
amount: xchainUtil.assetToBase(xchainUtil.assetAmount(balance.free, 8)),
};
})
.filter((balance) => !assets || assets.filter((asset) => xchainUtil.assetToString(balance.asset) === xchainUtil.assetToString(asset)).length);
});
}
/**
* @private
* Search transactions with parameters.
* @returns {Params} The parameters to be used for transaction search.
*/
searchTransactions(params) {
return __awaiter(this, void 0, void 0, function* () {
// Construct the URL for transaction search
const clientUrl = `${this.getClientUrl()}/api/v1/transactions`;
const url = new URL(clientUrl);
// Set default time range for transaction search
const endTime = Date.now();
const diffTime = 90 * 24 * 60 * 60 * 1000;
url.searchParams.set('endTime', endTime.toString());
url.searchParams.set('startTime', (endTime - diffTime).toString());
// Set additional parameters if provided
for (const key in params) {
const value = params[key];
if (value) {
url.searchParams.set(key, value);
// Adjust time range if only one time boundary is provided
if (key === 'startTime' && !params['endTime']) {
url.searchParams.set('endTime', (parseInt(value) + diffTime).toString());
}
if (key === 'endTime' && !params['startTime']) {
url.searchParams.set('startTime', (parseInt(value) - diffTime).toString());
}
}
}
// Fetch transaction history from the server
const txHistory = (yield axios__default.default.get(url.toString())).data;
return {
total: txHistory.total,
txs: txHistory.tx.map(parseTx).filter(Boolean),
};
});
}
/**
* 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.
*/
getTransactions(params) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
return yield this.searchTransactions({
address: params && params.address,
limit: params && ((_a = params.limit) === null || _a === void 0 ? void 0 : _a.toString()),
offset: params && ((_b = params.offset) === null || _b === void 0 ? void 0 : _b.toString()),
startTime: params && params.startTime && params.startTime.getTime().toString(),
txAsset: params && params.asset,
});
});
}
/**
* 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.
*/
getTransactionData(txId) {
return __awaiter(this, void 0, void 0, function* () {
// Fetch transaction data from the server
const txResult = (yield axios__default.default.get(`${this.getClientUrl()}/api/v1/tx/${txId}?format=json`)).data;
const blockHeight = txResult.height;
let address = '';
const msgs = txResult.tx.value.msg;
if (msgs.length) {
const msg = msgs[0].value;
if (msg.inputs && msg.inputs.length) {
address = msg.inputs[0].address;
}
else if (msg.outputs && msg.outputs.length) {
address = msg.outputs[0].address;
}
}
// Search for the transaction in the transaction history
const txHistory = yield this.searchTransactions({ address, blockHeight });
const [transaction] = txHistory.txs.filter((tx) => tx.hash === txId);
if (!transaction) {
throw new Error('transaction not found');
}
return transaction;
});
}
/**
* Broadcast multi-send transaction.
* @param {MultiSendParams} params The multi-send transfer options.
* @returns {TxHash} The transaction hash.
*/
multiSend({ walletIndex = 0, transactions, memo = '' }) {
return __awaiter(this, void 0, void 0, function* () {
// Get the derived address using the wallet index
const derivedAddress = this.getAddress(walletIndex);
// Execute the multi-send transaction
yield this.bncClient.initChain();
yield this.bncClient.setPrivateKey(this.getPrivateKey(walletIndex));
const transferResult = yield this.bncClient.multiSend(derivedAddress, transactions.map((transaction) => {
return {
to: transaction.to,
coins: transaction.coins.map((coin) => {
return {
denom: coin.asset.symbol,
amount: xchainUtil.baseToAsset(coin.amount).amount().toString(),
};
}),
};
}), memo);
// Extract and return the transaction hash
return transferResult.result.map((txResult) => { var _a; return (_a = txResult === null || txResult === void 0 ? void 0 : txResult.hash) !== null && _a !== void 0 ? _a : ''; })[0];
});
}
/**
* Transfer balances.
* @param {TxParams} params The transfer options.
* @returns {TxHash} The transaction hash.
*/
transfer({ walletIndex, asset, amount, recipient, memo }) {
return __awaiter(this, void 0, void 0, function* () {
// Initialize the Binance client and set the private key
yield this.bncClient.initChain();
yield this.bncClient.setPrivateKey(this.getPrivateKey(walletIndex || 0));
// Execute the transfer transaction
const transferResult = yield this.bncClient.transfer(yield this.getAddressAsync(walletIndex), recipient, xchainUtil.baseToAsset(amount).amount().toString(), asset ? asset.symbol : AssetBNB.symbol, memo);
// Extract and return the transaction hash
return transferResult.result.map((txResult) => { var _a; return (_a = txResult === null || txResult === void 0 ? void 0 : txResult.hash) !== null && _a !== void 0 ? _a : ''; })[0];
});
}
/**
* Broadcast a raw transaction.
* @param {string} txHex The hexadecimal representation of the raw transaction.
* @returns {string} The result of broadcasting the transaction.
*/
broadcastTx(txHex) {
return __awaiter(this, void 0, void 0, function* () {
const tx = yield this.bncClient.sendRawTransaction(txHex);
return tx.result;
});
}
/**
* Get the current transfer fee.
* @returns {TransferFee} The current transfer fee.
*/
getTransferFee() {
return __awaiter(this, void 0, void 0, function* () {
// Fetch transfer fees from the Binance API
const feesArray = (yield axios__default.default.get(`${this.getClientUrl()}/api/v1/fees`)).data;
// Find and return the transfer fee
const [transferFee] = feesArray.filter(isTransferFee);
if (!transferFee)
throw new Error('failed to get transfer fees');
return transferFee;
});
}
/**
* Get the current fee.
* If the fee rate cannot be obtained from Thorchain, it falls back to fetching transfer fees from the Binance API.
*
* @returns {Fees} The current fee.
*/
getFees() {
return __awaiter(this, void 0, void 0, function* () {
let singleTxFee = undefined;
// Attempt to fetch fee rate from Thorchain
try {
singleTxFee = xchainUtil.baseAmount(yield this.getFeeRateFromThorchain());
}
catch (error) {
console.log(error);
console.warn(`Error pulling rates from thorchain, will try alternate`);
}
// If fee rate is not available from Thorchain, fetch transfer fees from the Binance API
if (!singleTxFee) {
const transferFee = yield this.getTransferFee();
singleTxFee = xchainUtil.baseAmount(transferFee.fixed_fee_params.fee);
}
// Return the fee
return xchainClient.singleFee(xchainClient.FeeType.FlatFee, singleTxFee);
});
}
/**
* Get the current fee for multi-send transaction.
* @returns {Fees} The current fee for multi-send transaction.
*/
getMultiSendFees() {
return __awaiter(this, void 0, void 0, function* () {
// Fetch transfer fees from the Binance API
const transferFee = yield this.getTransferFee();
const multiTxFee = xchainUtil.baseAmount(transferFee.multi_transfer_fee);
// Return the fee object
return {
type: 'base',
average: multiTxFee,
fast: multiTxFee,
fastest: multiTxFee,
};
});
}
/**
* Get the current fee for both single and multi-send transaction.
*
* @returns {SingleAndMultiFees} The current fee for both single and multi-send transaction.
*/
getSingleAndMultiFees() {
return __awaiter(this, void 0, void 0, function* () {
// Fetch transfer fees from the Binance API
const transferFee = yield this.getTransferFee();
const singleTxFee = xchainUtil.baseAmount(transferFee.fixed_fee_params.fee);
const multiTxFee = xchainUtil.baseAmount(transferFee.multi_transfer_fee);
// Return the fee objects for both single and multi-send transactions
return {
single: {
type: 'base',
fast: singleTxFee,
fastest: singleTxFee,
average: singleTxFee,
},
multi: {
type: 'base',
average: multiTxFee,
fast: multiTxFee,
fastest: multiTxFee,
},
};
});
}
/**
* Prepare transfer.
* Currently not supported for Binance chain.
* @param {TxParams&Address} params The transfer options.
* @returns {PreparedTx} The unsigned transaction data.
*/
prepareTx() {
return __awaiter(this, void 0, void 0, function* () {
throw Error(' Function not suported for BNB chain');
});
}
}
/**
* Type definitions for data of Binance WebSocket Streams
* @see https://docs.binance.org/api-reference/dex-api/ws-streams.html
*
*/
/**
* Taker (as part of {@link Trade})
*/
var Taker;
(function (Taker) {
Taker[Taker["UNKNOWN"] = 0] = "UNKNOWN";
Taker[Taker["SELL_TAKER"] = 1] = "SELL_TAKER";
Taker[Taker["BUY_TAKER"] = 2] = "BUY_TAKER";
Taker[Taker["BUY_SURPLUS"] = 3] = "BUY_SURPLUS";
Taker[Taker["SELL_SURPLUS"] = 4] = "SELL_SURPLUS";
Taker[Taker["NEUTRAL"] = 5] = "NEUTRAL";
})(Taker || (Taker = {}));
var binanceWs = /*#__PURE__*/Object.freeze({
__proto__: null,
get Taker () { return Taker; }
});
exports.AssetBNB = AssetBNB;
exports.BNBChain = BNBChain;
exports.Client = Client;
exports.WS = binanceWs;
exports.getDefaultFees = getDefaultFees;
exports.getDerivePath = getDerivePath;
exports.getPrefix = getPrefix;