UNPKG

@thorwallet/xchain-polkadot

Version:

Custom Polkadot client and utilities used by XChainJS clients

426 lines 17.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = void 0; const tslib_1 = require("tslib"); const axios_1 = tslib_1.__importDefault(require("axios")); const xchain_util_1 = require("@thorwallet/xchain-util"); const xchainCrypto = tslib_1.__importStar(require("@thorwallet/xchain-crypto")); const api_1 = require("@polkadot/api"); const util_1 = require("@polkadot/util"); const types_1 = require("./types"); const util_2 = require("./util"); /** * Custom Polkadot client */ class Client { /** * Constructor * Client is initialised with network type and phrase (optional) * * @param {XChainClientParams} params */ constructor({ network = 'testnet', rootDerivationPaths = { mainnet: "44//354//0//0//0'", testnet: "44//354//0//0//0'", }, }) { this.phrase = ''; /** * Purge client. * * @returns {void} */ this.purgeClient = () => { this.phrase = ''; }; /** * Get the client url. * * @returns {string} The client url based on the network. */ this.getClientUrl = () => { return this.network === 'testnet' ? 'https://westend.subscan.io' : 'https://polkadot.subscan.io'; }; /** * Get the client WebSocket url. * * @returns {string} The client WebSocket url based on the network. */ this.getWsEndpoint = () => { return this.network === 'testnet' ? 'wss://westend-rpc.polkadot.io' : 'wss://rpc.polkadot.io'; }; /** * Get the explorer url. * * @returns {string} The explorer url based on the network. */ this.getExplorerUrl = () => { return this.network === 'testnet' ? 'https://westend.subscan.io' : 'https://polkadot.subscan.io'; }; /** * 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()}/account/${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()}/extrinsic/${txID}`; }; /** * Get the SS58 format to be used for Polkadot Keyring. * * @returns {number} The SS58 format based on the network. */ this.getSS58Format = () => { return this.network === 'testnet' ? 42 : 0; }; /** * Set/update a new phrase. * * @param {string} phrase A new phrase. * @returns {Address} The address from the given phrase * * @throws {"Invalid phrase"} * Thrown if the given phase is invalid. */ this.setPhrase = (phrase, walletIndex = 0) => { if (this.phrase !== phrase) { if (!xchainCrypto.validatePhrase(phrase)) { throw new Error('Invalid phrase'); } this.phrase = phrase; } return this.getAddress(walletIndex); }; /** * @private * Private function to get Keyring pair for polkadotjs provider. * @see https://polkadot.js.org/docs/api/start/keyring/#creating-a-keyring-instance * * @returns {KeyringPair} The keyring pair to be used to generate wallet address. * */ this.getKeyringPair = (index) => { const key = new api_1.Keyring({ ss58Format: this.getSS58Format(), type: 'ed25519' }); return key.createFromUri(`${this.phrase}//${this.getFullDerivationPath(index)}`); }; /** * @private * Private function to get the polkadotjs API provider. * * @see https://polkadot.js.org/docs/api/start/create#api-instance * * @returns {ApiPromise} The polkadotjs API provider based on the network. * */ this.getAPI = () => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const api = new api_1.ApiPromise({ provider: new api_1.WsProvider(this.getWsEndpoint()) }); yield api.isReady; if (!api.isConnected) { yield api.connect(); } return api; } catch (error) { return Promise.reject(error); } }); /** * Validate the given address. * @see https://polkadot.js.org/docs/util-crypto/examples/validate-address * * @param {Address} address * @returns {boolean} `true` or `false` */ this.validateAddress = (address) => { try { const key = new api_1.Keyring({ ss58Format: this.getSS58Format(), type: 'ed25519' }); return key.encodeAddress(util_1.isHex(address) ? util_1.hexToU8a(address) : key.decodeAddress(address)) === address; } catch (error) { return false; } }; /** * Get the current address. * * 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} The current address. * * @throws {"Address not defined"} Thrown if failed creating account from phrase. */ this.getAddress = (index = 0) => tslib_1.__awaiter(this, void 0, void 0, function* () { const address = yield Promise.resolve(this.getKeyringPair(index).address); return address; }); /** * Get the DOT balance of a given address. * * @param {Address} address By default, it will return the balance of the current wallet. (optional) * @returns {Array<Balance>} The DOT balance of the address. */ this.getBalance = (address, assets) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const response = yield axios_1.default .post(`${this.getClientUrl()}/api/open/account`, { address: address || this.getAddress() }) .then((res) => res.data); if (!util_2.isSuccess(response)) { throw new Error('Invalid address'); } const account = response.data; return account && (!assets || assets.filter((asset) => xchain_util_1.assetToString(types_1.AssetDOT) === xchain_util_1.assetToString(asset)).length) ? [ { asset: types_1.AssetDOT, amount: xchain_util_1.assetToBase(xchain_util_1.assetAmount(account.balance, util_2.getDecimal(this.network))), }, ] : []; } catch (error) { return Promise.reject(error); } }); /** * 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. */ this.getTransactions = (params) => tslib_1.__awaiter(this, void 0, void 0, function* () { var _a, _b; const limit = (_a = params === null || params === void 0 ? void 0 : params.limit) !== null && _a !== void 0 ? _a : 10; const offset = (_b = params === null || params === void 0 ? void 0 : params.offset) !== null && _b !== void 0 ? _b : 0; try { const response = yield axios_1.default .post(`${this.getClientUrl()}/api/scan/transfers`, { address: params === null || params === void 0 ? void 0 : params.address, row: limit, page: offset, }) .then((res) => res.data); if (!util_2.isSuccess(response) || !response.data) { throw new Error('Failed to get transactions'); } const transferResult = response.data; return { total: transferResult.count, txs: (transferResult.transfers || []).map((transfer) => ({ asset: types_1.AssetDOT, from: [ { from: transfer.from, amount: xchain_util_1.assetToBase(xchain_util_1.assetAmount(transfer.amount, util_2.getDecimal(this.network))), }, ], to: [ { to: transfer.to, amount: xchain_util_1.assetToBase(xchain_util_1.assetAmount(transfer.amount, util_2.getDecimal(this.network))), }, ], date: new Date(transfer.block_timestamp * 1000), type: 'transfer', hash: transfer.hash, binanceFee: null, confirmations: null, ethCumulativeGasUsed: null, ethGas: null, ethGasPrice: null, ethGasUsed: null, ethTokenName: null, ethTokenSymbol: null, memo: null, })), }; } 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. */ this.getTransactionData = (txId) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const response = yield axios_1.default .post(`${this.getClientUrl()}/api/scan/extrinsic`, { hash: txId, }) .then((res) => res.data); if (!util_2.isSuccess(response) || !response.data) { throw new Error('Failed to get transactions'); } const extrinsic = response.data; const transfer = extrinsic.transfer; return { asset: types_1.AssetDOT, from: [ { from: transfer.from, amount: xchain_util_1.assetToBase(xchain_util_1.assetAmount(transfer.amount, util_2.getDecimal(this.network))), }, ], to: [ { to: transfer.to, amount: xchain_util_1.assetToBase(xchain_util_1.assetAmount(transfer.amount, util_2.getDecimal(this.network))), }, ], date: new Date(extrinsic.block_timestamp * 1000), type: 'transfer', hash: extrinsic.extrinsic_hash, binanceFee: null, confirmations: null, ethCumulativeGasUsed: null, ethGas: null, ethGasPrice: null, ethGasUsed: null, ethTokenName: null, ethTokenSymbol: null, memo: null, }; } catch (error) { return Promise.reject(error); } }); /** * Transfer DOT. * * @param {TxParams} params The transfer options. * @returns {TxHash} The transaction hash. */ this.transfer = (params) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const api = yield this.getAPI(); let transaction = null; const walletIndex = params.walletIndex || 0; // Createing a transfer const transfer = api.tx.balances.transfer(params.recipient, params.amount.amount().toString()); if (!params.memo) { // Send a simple transfer transaction = transfer; } else { // Send a `utility.batch` with two Calls: i) Balance.Transfer ii) System.Remark // Creating a remark const remark = api.tx.system.remark(params.memo); // Send the Batch Transaction transaction = api.tx.utility.batch([transfer, remark]); } // Check balances const paymentInfo = yield transaction.paymentInfo(this.getKeyringPair(walletIndex)); const fee = xchain_util_1.baseAmount(paymentInfo.partialFee.toString(), util_2.getDecimal(this.network)); const balances = yield this.getBalance(yield this.getAddress(walletIndex), [types_1.AssetDOT]); if (!balances || params.amount.amount().plus(fee.amount()).isGreaterThan(balances[0].amount.amount())) { throw new Error('insufficient balance'); } const txHash = yield transaction.signAndSend(this.getKeyringPair(walletIndex)); yield api.disconnect(); return txHash.toString(); } catch (error) { return Promise.reject(error); } }); /** * Get the current fee with transfer options. * * @see https://polkadot.js.org/docs/api/cookbook/tx/#how-do-i-estimate-the-transaction-fees * * @param {TxParams} params The transfer options. * @returns {Fees} The estimated fees with the transfer options. */ this.estimateFees = (params) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { const walletIndex = params.walletIndex ? params.walletIndex : 0; const api = yield this.getAPI(); const info = yield api.tx.balances .transfer(params.recipient, params.amount.amount().toNumber()) .paymentInfo(this.getKeyringPair(walletIndex)); const fee = xchain_util_1.baseAmount(info.partialFee.toString(), util_2.getDecimal(this.network)); yield api.disconnect(); return { type: 'byte', average: fee, fast: fee, fastest: fee, }; } catch (error) { return Promise.reject(error); } }); /** * Get the current fee. * * @returns {Fees} The current fee. */ this.getFees = () => tslib_1.__awaiter(this, void 0, void 0, function* () { return yield this.estimateFees({ recipient: yield this.getAddress(), amount: xchain_util_1.baseAmount(0, util_2.getDecimal(this.network)), }); }); this.network = network; this.rootDerivationPaths = rootDerivationPaths; } /** * Get getFullDerivationPath * * @param {number} index the HD wallet index * @returns {string} The polkadot derivation path based on the network. */ getFullDerivationPath(index = 0) { // console.log(this.rootDerivationPaths[this.network]) if (index === 0) { // this should make the tests backwards compatible return this.rootDerivationPaths[this.network]; } else { return this.rootDerivationPaths[this.network] + `//${index}`; } } /** * Set/update the current network. * * @param {Network} network `mainnet` or `testnet`. * * @throws {"Network must be provided"} * Thrown if network has not been set before. */ setNetwork(network) { if (!network) { throw new Error('Network must be provided'); } else { if (network !== this.network) { this.network = network; } } } /** * Get the current network. * * @returns {Network} The current network. (`mainnet` or `testnet`) */ getNetwork() { return this.network; } } exports.Client = Client; //# sourceMappingURL=client.js.map