UNPKG

cosmic-lib

Version:

A JavaScript implementation of the CosmicLink protocol for Stellar

352 lines (320 loc) 10.3 kB
"use strict" /** * Contains functions that probe the blockchain or federation servers to collect * datas. * * @exports resolve */ const resolve = exports const misc = require("@cosmic-plus/jsutils/es5/misc") const StellarSdk = require("@cosmic-plus/base/es5/stellar-sdk") const specs = require("./specs") const status = require("./status") /** * Returns the * [Server]{@link https://stellar.github.io/js-stellar-sdk/Server.html} object * for **horizon**, or for **network**, or for the current network. * * @param {string} [network] 'public', 'test' or a network passphrase * @param {string} [horizon] A horizon URL * @returns {Server} A StellarSdk Server object */ resolve.server = function ( conf, network = conf.network, horizon = conf.horizon ) { if (!horizon) horizon = resolve.horizon(conf, network) if (!horizon) throw new Error("No horizon node defined for selected network.") if (!conf.current.server[horizon]) { conf.current.server[horizon] = new StellarSdk.Server(horizon) } return conf.current.server[horizon] } /** * Switch to the current network, or to **network** if provided. * * @deprecated StellarSdk global `Network` setting is deprecated. * @param {string} [network] 'public', 'test' or a network passphrase * @returns {Server} A StellarSdk Server object */ resolve.useNetwork = function (conf, network = conf.network) { // DEPRECATED - to be removed when in sync with stellar-sdk 3.x. console.warn( "`.selectNetwork()`, `.useNetwork()`, as well as StellarSdk global `Network` setting are deprecated. Please use `cosmicLib.config.network` or pass parameter explicitely." ) const passphrase = resolve.networkPassphrase(conf, network) const currentPassphrase = resolve.networkPassphrase() if (passphrase !== currentPassphrase) { // eslint-disable-next-line no-console console.log("Switch to network: " + network) StellarSdk.Network.use(new StellarSdk.Network(passphrase)) } } /** * Returns the curent Horizon node URL, or the Horizon node URL for **network** * if provided. * * @param {string} [network] A network name or passphrase. */ resolve.horizon = function (conf, network = conf.network) { if (conf.horizon) { return conf.horizon } else { const passphrase = resolve.networkPassphrase(conf, network) if (conf.current && conf.current.horizon[passphrase]) { return conf.current.horizon[passphrase] } } } /** * Returns the current network passphrase, or the passphrase for **network** is * provided. */ resolve.networkPassphrase = function (conf = {}, network = conf.network) { if (network === undefined) { // DEPRECATED: To be removed in sync with stellar-sdk 3.x. const currentNetwork = StellarSdk.Network.current() if (currentNetwork) return currentNetwork.networkPassphrase() } else { return conf.current.passphrase[network] || network } } /** * Returns the network name for **network passphrase**, or `undefined`. * * @param {string} networkPassphrase * @return {string} */ resolve.networkName = function (conf = {}, networkPassphrase) { const index = Object.values(conf.current.passphrase).indexOf( networkPassphrase ) if (index === -1) return networkPassphrase else return Object.keys(conf.current.passphrase)[index] } /** * Returns the federation server * [Account]{@link https://stellar.github.io/js-stellar-sdk/Account.html} * for **address**. * * @async * @param {string} address A Stellar public key or a federated address * @return {} Resolve to federation server response */ resolve.address = function (conf, address) { const cache = conf.cache if (cache && cache.destination[address]) return cache.destination[address] const promise = addressResolver(conf, address) if (cache) cache.destination[address] = promise return promise } async function addressResolver (conf, address) { try { const account = await StellarSdk.FederationServer.resolve(address) const accountId = account.account_id if (!accountId) throw new Error("Unknow address") if (!account.memo_type) delete account.memo if (address !== accountId) account.address = address if (conf.aliases && conf.aliases[accountId]) { account.alias = conf.aliases[accountId] } return account } catch (error) { console.error(error) status.error(conf, "Can't resolve: " + misc.shorter(address)) status.fail(conf, "Unresolved address", "throw") } } /** * Returns the * [AccountResponse]{@link https://stellar.github.io/js-stellar-sdk/AccountResponse.html} * object for **address**. * * @param {string} address A public key or a federated address * @return {Object} The AccountResponse */ resolve.account = async function (conf, address, quietFlag) { const account = await resolve.address(conf, address) const accountId = account.account_id const cache = conf.cache if (cache && cache.account[accountId]) return cache.account[accountId] const promise = accountResolver(conf, accountId, quietFlag) if (cache) cache.account[accountId] = promise return promise } async function accountResolver (conf, accountId, quietFlag) { const server = resolve.server(conf) try { const accountResponse = await server.loadAccount(accountId) return accountResponse } catch (error) { if (quietFlag) { throw error } else { if (error.response) { status.error(conf, "Empty account: " + misc.shorter(accountId), "throw") } else { status.error( conf, "Invalid horizon node: " + resolve.horizon(conf), "throw" ) } } } } /** * Returns `true` if **address** account is empty, `false` otherwise. * * @async * @param {string} address Public key or federated address * @return {boolean} */ resolve.isAccountEmpty = function (conf, address) { return resolve .account(conf, address, true) .then(() => false) .catch(() => true) } /** * Returns the account object for transaction source **address`** with sequence * set at **sequence** if provided. If **address** is not provided, returns the * neutral account object instead (as in SEP-0007 specifications). * * @param {string} [address] * @param {string|numbre} [sequence] * @return {AccountResponse} */ resolve.txSourceAccount = async function (conf, address, sequence) { if (!address) { return makeAccountResponse(conf, specs.neutralAccountId, "-1") } else { const destination = await resolve.address(conf, address) if (destination.memo) status.error( conf, "Invalid transaction source address (requires a memo)", "throw" ) const account = await resolve.account(conf, destination.account_id) if (sequence) { const baseAccount = new StellarSdk.Account(account.id, sequence) baseAccount.sequence = baseAccount.sequence.sub(1) account._baseAccount = baseAccount } return account } } /** * Creates an AccountResponse object with signers set for an empty account. * * @param {string} publicKey * @param {string} sequence [description] * @return {AccountResponse} */ function makeAccountResponse (conf, publicKey, sequence) { const account = new StellarSdk.Account(publicKey, sequence) if (conf.cache) conf.cache.account[publicKey] = account account.id = publicKey account.signers = [ { public_key: publicKey, weight: 1, key: publicKey, type: "ed25519_public_key" } ] return account } /** * Returns the array of all source accounts ID involved in **transaction**. * * @param {Transaction} transaction * @return {Array} */ resolve.txSources = function (conf, transaction) { if (!transaction.source) throw new Error("No source for transaction") const extra = resolve.extra(conf, transaction) if (extra.cache.txSources) return extra.cache.txSources const array = extra.cache.txSources = [transaction.source] for (let index in transaction.operations) { const source = transaction.operations[index].source if (source && !array.find(a => a === source)) array.push(source) } return array } /** * Returns an object such as: * * ```js * { * $accountId: $accountSigners * ... * } * ``` * * @param {Transaction} transaction * @return {Object} */ resolve.txSigners = async function (conf, transaction) { const extra = resolve.extra(conf, transaction) if (extra.cache.txSigners) return extra.cache.txSigners const txSources = resolve.txSources(extra, transaction) const signers = extra.cache.txSigners = {} for (let index in txSources) { const source = txSources[index] const account = await resolveTxSource(extra, source) if (!signers[account.id]) { signers[account.id] = account.signers.filter(signer => { return signer.type !== "preauthTx" }) } } return signers } async function resolveTxSource (conf, address) { try { return await resolve.account(conf, address, "quiet") } catch (error) { return makeAccountResponse(conf, address, "0") } } /** * Returns an Array containing the keys for all legit signers of **transaction**. * * @param {Transaction} transaction * @return {Array} */ resolve.txSignersList = async function (conf, transaction) { const extra = resolve.extra(conf, transaction) if (!extra.cache.txSignersList) { const txSigners = await resolve.txSigners(extra, transaction) extra.cache.txSignersList = signersTableToSignersList(txSigners) } return extra.cache.txSignersList } function signersTableToSignersList (signersTable) { const array = [] for (let accountId in signersTable) { signersTable[accountId].forEach(signer => { if (!array.find(key => key === signer.key)) array.push(signer.key) }) } return array } /** * Add an extra field to **object** that embed cache and local configuration. * * @private */ resolve.extra = function (conf, object) { if (!object._cosmicplus) { misc.setHiddenProperty(object, "_cosmicplus", {}) if (conf.cache) object._cosmicplus.cache = conf.cache else object._cosmicplus.cache = { destination: {}, account: {} } object._cosmicplus.network = conf.network object._cosmicplus.current = conf.current } return object._cosmicplus }